Coverage for /builds/ase/ase/ase/calculators/lammps/inputwriter.py: 76.00%

100 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-08-02 00:12 +0000

1# fmt: off 

2 

3""" 

4Stream input commands to lammps to perform desired simulations 

5""" 

6from ase.calculators.lammps.unitconvert import convert 

7from ase.parallel import paropen 

8 

9# "End mark" used to indicate that the calculation is done 

10CALCULATION_END_MARK = "__end_of_ase_invoked_calculation__" 

11 

12 

13def lammps_create_atoms(fileobj, parameters, atoms, prismobj): 

14 """Create atoms in lammps with 'create_box' and 'create_atoms' 

15 

16 :param fileobj: open stream for lammps input 

17 :param parameters: dict of all lammps parameters 

18 :type parameters: dict 

19 :param atoms: Atoms object 

20 :type atoms: Atoms 

21 :param prismobj: coordinate transformation between ase and lammps 

22 :type prismobj: Prism 

23 

24 """ 

25 if parameters["verbose"]: 

26 fileobj.write("## Original ase cell\n") 

27 fileobj.write( 

28 "".join( 

29 [ 

30 "# {:.16} {:.16} {:.16}\n".format(*x) 

31 for x in atoms.get_cell() 

32 ] 

33 ) 

34 ) 

35 

36 fileobj.write("lattice sc 1.0\n") 

37 

38 # Get cell parameters and convert from ASE units to LAMMPS units 

39 xhi, yhi, zhi, xy, xz, yz = convert(prismobj.get_lammps_prism(), 

40 "distance", "ASE", parameters.units) 

41 

42 if parameters["always_triclinic"] or prismobj.is_skewed(): 

43 fileobj.write( 

44 "region asecell prism 0.0 {} 0.0 {} 0.0 {} ".format( 

45 xhi, yhi, zhi 

46 ) 

47 ) 

48 fileobj.write( 

49 f"{xy} {xz} {yz} side in units box\n" 

50 ) 

51 else: 

52 fileobj.write( 

53 "region asecell block 0.0 {} 0.0 {} 0.0 {} " 

54 "side in units box\n".format(xhi, yhi, zhi) 

55 ) 

56 

57 symbols = atoms.get_chemical_symbols() 

58 try: 

59 # By request, specific atom type ordering 

60 species = parameters["specorder"] 

61 except AttributeError: 

62 # By default, atom types in alphabetic order 

63 species = sorted(set(symbols)) 

64 

65 species_i = {s: i + 1 for i, s in enumerate(species)} 

66 

67 fileobj.write( 

68 "create_box {} asecell\n" "".format(len(species)) 

69 ) 

70 for sym, pos in zip(symbols, atoms.get_positions()): 

71 # Convert position from ASE units to LAMMPS units 

72 pos = convert(pos, "distance", "ASE", parameters.units) 

73 if parameters["verbose"]: 

74 fileobj.write( 

75 "# atom pos in ase cell: {:.16} {:.16} {:.16}\n" 

76 "".format(*tuple(pos)) 

77 ) 

78 fileobj.write( 

79 "create_atoms {} single {} {} {} remap yes units box\n".format( 

80 *((species_i[sym],) + tuple(prismobj.vector_to_lammps(pos))) 

81 ) 

82 ) 

83 

84 

85def write_lammps_in(lammps_in, parameters, atoms, prismobj, 

86 lammps_trj=None, lammps_data=None): 

87 """Write a LAMMPS in_ file with run parameters and settings.""" 

88 

89 def write_model_post_and_masses(fileobj, parameters): 

90 # write additional lines needed for some LAMMPS potentials 

91 if 'model_post' in parameters: 

92 mlines = parameters['model_post'] 

93 for ii in range(len(mlines)): 

94 fileobj.write(mlines[ii]) 

95 

96 if "masses" in parameters: 

97 for mass in parameters["masses"]: 

98 # Note that the variable mass is a string containing 

99 # the type number and value of mass separated by a space 

100 fileobj.write(f"mass {mass} \n") 

101 

102 if isinstance(lammps_in, str): 

103 fileobj = paropen(lammps_in, "w") 

104 close_in_file = True 

105 else: 

106 # Expect lammps_in to be a file-like object 

107 fileobj = lammps_in 

108 close_in_file = False 

109 

110 if parameters["verbose"]: 

111 fileobj.write("# (written by ASE)\n") 

112 

113 # Write variables 

114 fileobj.write( 

115 ( 

116 "clear\n" 

117 'variable dump_file string "{}"\n' 

118 'variable data_file string "{}"\n' 

119 ).format(lammps_trj, lammps_data) 

120 ) 

121 

122 if "package" in parameters: 

123 fileobj.write( 

124 "\n".join( 

125 [f"package {p}" for p in parameters["package"]] 

126 ) 

127 + "\n" 

128 ) 

129 

130 # setup styles except 'pair_style' 

131 for style_type in ("atom", "bond", "angle", 

132 "dihedral", "improper", "kspace"): 

133 style = style_type + "_style" 

134 if style in parameters: 

135 fileobj.write( 

136 '{} {} \n'.format( 

137 style, 

138 parameters[style])) 

139 

140 # write initialization lines needed for some LAMMPS potentials 

141 if 'model_init' in parameters: 

142 mlines = parameters['model_init'] 

143 for ii in range(len(mlines)): 

144 fileobj.write(mlines[ii]) 

145 

146 # write units 

147 if 'units' in parameters: 

148 units_line = 'units ' + parameters['units'] + '\n' 

149 fileobj.write(units_line) 

150 else: 

151 fileobj.write('units metal\n') 

152 

153 pbc = atoms.get_pbc() 

154 if "boundary" in parameters: 

155 fileobj.write( 

156 "boundary {} \n".format(parameters["boundary"]) 

157 ) 

158 else: 

159 fileobj.write( 

160 "boundary {} {} {} \n".format( 

161 *tuple("sp"[int(x)] for x in pbc) 

162 ) 

163 ) 

164 # Prior to version 22Dec2022, `box tilt large` is necessary to run systems 

165 # with large tilts. Since version 22Dec2022, this command is ignored, and 

166 # systems with large tilts can be run by default. 

167 # https://docs.lammps.org/Commands_removed.html#box-command 

168 # This command does not affect the efficiency for systems with small tilts 

169 # and therefore worth written always. 

170 fileobj.write("box tilt large \n") 

171 fileobj.write("atom_modify sort 0 0.0 \n") 

172 for key in ("neighbor", "newton"): 

173 if key in parameters: 

174 fileobj.write( 

175 f"{key} {parameters[key]} \n" 

176 ) 

177 fileobj.write("\n") 

178 

179 # write the simulation box and the atoms 

180 if not lammps_data: 

181 lammps_create_atoms(fileobj, parameters, atoms, prismobj) 

182 # or simply refer to the data-file 

183 else: 

184 fileobj.write(f"read_data {lammps_data}\n") 

185 

186 # Write interaction stuff 

187 fileobj.write("\n### interactions\n") 

188 if "kim_interactions" in parameters: 

189 fileobj.write( 

190 "{}\n".format( 

191 parameters["kim_interactions"])) 

192 write_model_post_and_masses(fileobj, parameters) 

193 

194 elif ("pair_style" in parameters) and ("pair_coeff" in parameters): 

195 pair_style = parameters["pair_style"] 

196 fileobj.write(f"pair_style {pair_style} \n") 

197 for pair_coeff in parameters["pair_coeff"]: 

198 fileobj.write( 

199 "pair_coeff {} \n" "".format(pair_coeff) 

200 ) 

201 write_model_post_and_masses(fileobj, parameters) 

202 

203 else: 

204 # simple default parameters 

205 # that should always make the LAMMPS calculation run 

206 fileobj.write( 

207 "pair_style lj/cut 2.5 \n" 

208 "pair_coeff * * 1 1 \n" 

209 "mass * 1.0 \n" 

210 ) 

211 

212 if "group" in parameters: 

213 fileobj.write( 

214 "\n".join([f"group {p}" for p in parameters["group"]]) 

215 + "\n" 

216 ) 

217 

218 fileobj.write("\n### run\n" "fix fix_nve all nve\n") 

219 

220 if "fix" in parameters: 

221 fileobj.write( 

222 "\n".join([f"fix {p}" for p in parameters["fix"]]) 

223 + "\n" 

224 ) 

225 

226 fileobj.write( 

227 "dump dump_all all custom {1} {0} id type x y z vx vy vz " 

228 "fx fy fz\n" 

229 "".format(lammps_trj, parameters["dump_period"]) 

230 ) 

231 fileobj.write( 

232 "thermo_style custom {}\n" 

233 "thermo_modify flush yes format float %23.16g\n" 

234 "thermo 1\n".format(" ".join(parameters["thermo_args"])) 

235 ) 

236 

237 if "timestep" in parameters: 

238 fileobj.write( 

239 "timestep {}\n".format(parameters["timestep"]) 

240 ) 

241 

242 if "minimize" in parameters: 

243 fileobj.write( 

244 "minimize {}\n".format(parameters["minimize"]) 

245 ) 

246 if "run" in parameters: 

247 fileobj.write("run {}\n".format(parameters["run"])) 

248 if "minimize" not in parameters and "run" not in parameters: 

249 fileobj.write("run 0\n") 

250 

251 fileobj.write( 

252 f'print "{CALCULATION_END_MARK}" \n' 

253 ) 

254 # Force LAMMPS to flush log 

255 fileobj.write("log /dev/stdout\n") 

256 

257 fileobj.flush() 

258 if close_in_file: 

259 fileobj.close()