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
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3"""
4Stream input commands to lammps to perform desired simulations
5"""
6from ase.calculators.lammps.unitconvert import convert
7from ase.parallel import paropen
9# "End mark" used to indicate that the calculation is done
10CALCULATION_END_MARK = "__end_of_ase_invoked_calculation__"
13def lammps_create_atoms(fileobj, parameters, atoms, prismobj):
14 """Create atoms in lammps with 'create_box' and 'create_atoms'
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
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 )
36 fileobj.write("lattice sc 1.0\n")
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)
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 )
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))
65 species_i = {s: i + 1 for i, s in enumerate(species)}
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 )
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."""
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])
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")
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
110 if parameters["verbose"]:
111 fileobj.write("# (written by ASE)\n")
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 )
122 if "package" in parameters:
123 fileobj.write(
124 "\n".join(
125 [f"package {p}" for p in parameters["package"]]
126 )
127 + "\n"
128 )
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]))
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])
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')
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")
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")
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)
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)
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 )
212 if "group" in parameters:
213 fileobj.write(
214 "\n".join([f"group {p}" for p in parameters["group"]])
215 + "\n"
216 )
218 fileobj.write("\n### run\n" "fix fix_nve all nve\n")
220 if "fix" in parameters:
221 fileobj.write(
222 "\n".join([f"fix {p}" for p in parameters["fix"]])
223 + "\n"
224 )
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 )
237 if "timestep" in parameters:
238 fileobj.write(
239 "timestep {}\n".format(parameters["timestep"])
240 )
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")
251 fileobj.write(
252 f'print "{CALCULATION_END_MARK}" \n'
253 )
254 # Force LAMMPS to flush log
255 fileobj.write("log /dev/stdout\n")
257 fileobj.flush()
258 if close_in_file:
259 fileobj.close()