lammpsparser 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lammpsparser/__init__.py +38 -0
- lammpsparser/_version.py +34 -0
- lammpsparser/compatibility/__init__.py +0 -0
- lammpsparser/compatibility/calculate.py +484 -0
- lammpsparser/compatibility/constraints.py +106 -0
- lammpsparser/compatibility/file.py +285 -0
- lammpsparser/compatibility/structure.py +336 -0
- lammpsparser/output.py +340 -0
- lammpsparser/output_raw.py +251 -0
- lammpsparser/potential.py +380 -0
- lammpsparser/structure.py +536 -0
- lammpsparser/units.py +246 -0
- lammpsparser-0.0.1.dist-info/METADATA +146 -0
- lammpsparser-0.0.1.dist-info/RECORD +16 -0
- lammpsparser-0.0.1.dist-info/WHEEL +4 -0
- lammpsparser-0.0.1.dist-info/licenses/LICENSE +29 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import pandas
|
|
7
|
+
from ase.atoms import Atoms
|
|
8
|
+
|
|
9
|
+
from lammpsparser.compatibility.calculate import (
|
|
10
|
+
calc_md,
|
|
11
|
+
calc_minimize,
|
|
12
|
+
calc_static,
|
|
13
|
+
)
|
|
14
|
+
from lammpsparser.compatibility.constraints import set_selective_dynamics
|
|
15
|
+
from lammpsparser.output import parse_lammps_output
|
|
16
|
+
from lammpsparser.potential import get_potential_by_name
|
|
17
|
+
from lammpsparser.structure import write_lammps_datafile
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def lammps_file_interface_function(
|
|
21
|
+
working_directory: str,
|
|
22
|
+
structure: Atoms,
|
|
23
|
+
potential: str,
|
|
24
|
+
calc_mode: str = "static",
|
|
25
|
+
calc_kwargs: Optional[dict] = None,
|
|
26
|
+
units: str = "metal",
|
|
27
|
+
lmp_command: str = "mpiexec -n 1 --oversubscribe lmp_mpi -in lmp.in",
|
|
28
|
+
resource_path: Optional[str] = None,
|
|
29
|
+
input_control_file: Optional[dict] = None,
|
|
30
|
+
write_restart_file: bool = False,
|
|
31
|
+
read_restart_file: bool = False,
|
|
32
|
+
restart_file: str = "restart.out",
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
A single function to execute a LAMMPS calculation based on the LAMMPS job implemented in pyiron
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
|
|
39
|
+
>>> import os
|
|
40
|
+
>>> from ase.build import bulk
|
|
41
|
+
>>> from pyiron_atomistics.lammps.lammps import lammps_function
|
|
42
|
+
>>>
|
|
43
|
+
>>> shell_output, parsed_output, job_crashed = lammps_function(
|
|
44
|
+
... working_directory=os.path.abspath("lmp_working_directory"),
|
|
45
|
+
... structure=bulk("Al", cubic=True),
|
|
46
|
+
... potential='2009--Mendelev-M-I--Al-Mg--LAMMPS--ipr1',
|
|
47
|
+
... calc_mode="md",
|
|
48
|
+
... calc_kwargs={"temperature": 500.0, "pressure": 0.0, "n_ionic_steps": 1000, "n_print": 100},
|
|
49
|
+
... cutoff_radius=None,
|
|
50
|
+
... units="metal",
|
|
51
|
+
... bonds_kwargs={},
|
|
52
|
+
... enable_h5md=False,
|
|
53
|
+
... )
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
working_directory (str): directory in which the LAMMPS calculation is executed
|
|
57
|
+
structure (Atoms): ase.atoms.Atoms - atomistic structure
|
|
58
|
+
potential (str): Name of the LAMMPS potential based on the NIST database and the OpenKIM database
|
|
59
|
+
calc_mode (str): select mode of calculation ["static", "md", "minimize", "vcsgc"]
|
|
60
|
+
calc_kwargs (dict): key-word arguments for the calculate function, the input parameters depend on the calc_mode:
|
|
61
|
+
"static": No parameters
|
|
62
|
+
"md": "temperature", "pressure", "n_ionic_steps", "time_step", "n_print", "temperature_damping_timescale",
|
|
63
|
+
"pressure_damping_timescale", "seed", "tloop", "initial_temperature", "langevin", "delta_temp",
|
|
64
|
+
"delta_press", job_name", "rotation_matrix"
|
|
65
|
+
"minimize": "ionic_energy_tolerance", "ionic_force_tolerance", "max_iter", "pressure", "n_print", "style",
|
|
66
|
+
"rotation_matrix"
|
|
67
|
+
"vcsgc": "mu", "ordered_element_list", "target_concentration", "kappa", "mc_step_interval", "swap_fraction",
|
|
68
|
+
"temperature_mc", "window_size", "window_moves", "temperature", "pressure", "n_ionic_steps",
|
|
69
|
+
"time_step", "n_print", "temperature_damping_timescale", "pressure_damping_timescale", "seed",
|
|
70
|
+
"initial_temperature", "langevin", "job_name", "rotation_matrix"
|
|
71
|
+
cutoff_radius (float): cut-off radius for the interatomic potential
|
|
72
|
+
units (str): Units for LAMMPS
|
|
73
|
+
bonds_kwargs (dict): key-word arguments to create atomistic bonds:
|
|
74
|
+
"species", "element_list", "cutoff_list", "max_bond_list", "bond_type_list", "angle_type_list",
|
|
75
|
+
server_kwargs (dict): key-word arguments to create server object - the available parameters are:
|
|
76
|
+
"user", "host", "run_mode", "queue", "qid", "cores", "threads", "new_h5", "structure_id", "run_time",
|
|
77
|
+
"memory_limit", "accept_crash", "additional_arguments", "gpus", "conda_environment_name",
|
|
78
|
+
"conda_environment_path"
|
|
79
|
+
enable_h5md (bool): activate h5md mode for LAMMPS
|
|
80
|
+
write_restart_file (bool): enable writing the LAMMPS restart file
|
|
81
|
+
read_restart_file (bool): enable loading the LAMMPS restart file
|
|
82
|
+
restart_file (str): file name of the LAMMPS restart file to copy
|
|
83
|
+
executable_version (str): LAMMPS version to for the execution
|
|
84
|
+
executable_path (str): path to the LAMMPS executable
|
|
85
|
+
input_control_file (str|list|dict): Option to modify the LAMMPS input file directly
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
str, dict, bool: Tuple consisting of the shell output (str), the parsed output (dict) and a boolean flag if
|
|
89
|
+
the execution raised an accepted error.
|
|
90
|
+
"""
|
|
91
|
+
if calc_kwargs is None:
|
|
92
|
+
calc_kwargs = {}
|
|
93
|
+
else:
|
|
94
|
+
calc_kwargs = calc_kwargs.copy()
|
|
95
|
+
|
|
96
|
+
os.makedirs(working_directory, exist_ok=True)
|
|
97
|
+
potential_lst, potential_replace, species = _get_potential(
|
|
98
|
+
potential=potential, resource_path=resource_path
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
lmp_str_lst = []
|
|
102
|
+
atom_type = "atomic"
|
|
103
|
+
for l in lammps_file_initialization(
|
|
104
|
+
structure=structure,
|
|
105
|
+
units=units,
|
|
106
|
+
read_restart_file=read_restart_file,
|
|
107
|
+
restart_file=restart_file,
|
|
108
|
+
):
|
|
109
|
+
if l.strip().startswith("units") and "units" in potential_replace:
|
|
110
|
+
lmp_str_lst.append(potential_replace["units"])
|
|
111
|
+
elif l.strip().startswith("atom_style") and "atom_style" in potential_replace:
|
|
112
|
+
lmp_str_lst.append(potential_replace["atom_style"])
|
|
113
|
+
atom_type = potential_replace["atom_style"].split()[-1]
|
|
114
|
+
elif l.strip().startswith("dimension") and "dimension" in potential_replace:
|
|
115
|
+
lmp_str_lst.append(potential_replace["dimension"])
|
|
116
|
+
else:
|
|
117
|
+
lmp_str_lst.append(l)
|
|
118
|
+
|
|
119
|
+
lmp_str_lst += potential_lst
|
|
120
|
+
lmp_str_lst += ["variable dumptime equal {} ".format(calc_kwargs.get("n_print", 1))]
|
|
121
|
+
lmp_str_lst += [
|
|
122
|
+
"dump 1 all custom ${dumptime} dump.out id type xsu ysu zsu fx fy fz vx vy vz",
|
|
123
|
+
'dump_modify 1 sort id format line "%d %d %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g %20.15g"',
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
if calc_mode == "static":
|
|
127
|
+
lmp_str_lst += [
|
|
128
|
+
k + " " + v
|
|
129
|
+
for k, v in set_selective_dynamics(
|
|
130
|
+
structure=structure, calc_md=False
|
|
131
|
+
).items()
|
|
132
|
+
]
|
|
133
|
+
lmp_str_tmp_lst = calc_static()
|
|
134
|
+
lmp_str_lst = _modify_input_dict(
|
|
135
|
+
input_control_file=input_control_file,
|
|
136
|
+
lmp_str_lst=lmp_str_lst + lmp_str_tmp_lst[:-1],
|
|
137
|
+
)
|
|
138
|
+
lmp_str_lst.append(lmp_str_tmp_lst[-1])
|
|
139
|
+
elif calc_mode == "md":
|
|
140
|
+
lmp_str_lst += [
|
|
141
|
+
k + " " + v
|
|
142
|
+
for k, v in set_selective_dynamics(
|
|
143
|
+
structure=structure, calc_md=True
|
|
144
|
+
).items()
|
|
145
|
+
]
|
|
146
|
+
if "n_ionic_steps" in calc_kwargs.keys():
|
|
147
|
+
n_ionic_steps = int(calc_kwargs.pop("n_ionic_steps"))
|
|
148
|
+
else:
|
|
149
|
+
n_ionic_steps = 1
|
|
150
|
+
if read_restart_file:
|
|
151
|
+
calc_kwargs["initial_temperature"] = 0.0
|
|
152
|
+
calc_kwargs["units"] = units
|
|
153
|
+
lmp_str_lst += calc_md(**calc_kwargs)
|
|
154
|
+
lmp_str_lst = _modify_input_dict(
|
|
155
|
+
input_control_file=input_control_file,
|
|
156
|
+
lmp_str_lst=lmp_str_lst,
|
|
157
|
+
)
|
|
158
|
+
if read_restart_file:
|
|
159
|
+
lmp_str_lst += ["reset_timestep 0"]
|
|
160
|
+
lmp_str_lst += ["run {} ".format(n_ionic_steps)]
|
|
161
|
+
elif calc_mode == "minimize":
|
|
162
|
+
calc_kwargs["units"] = units
|
|
163
|
+
lmp_str_lst += [
|
|
164
|
+
k + " " + v
|
|
165
|
+
for k, v in set_selective_dynamics(
|
|
166
|
+
structure=structure, calc_md=False
|
|
167
|
+
).items()
|
|
168
|
+
]
|
|
169
|
+
lmp_str_tmp_lst, structure = calc_minimize(structure=structure, **calc_kwargs)
|
|
170
|
+
lmp_str_lst = _modify_input_dict(
|
|
171
|
+
input_control_file=input_control_file,
|
|
172
|
+
lmp_str_lst=lmp_str_lst + lmp_str_tmp_lst[:-1],
|
|
173
|
+
)
|
|
174
|
+
lmp_str_lst.append(lmp_str_tmp_lst[-1])
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"calc_mode must be one of: static, md or minimize, not {calc_mode}"
|
|
178
|
+
)
|
|
179
|
+
if read_restart_file:
|
|
180
|
+
shutil.copyfile(
|
|
181
|
+
os.path.abspath(restart_file),
|
|
182
|
+
os.path.join(working_directory, os.path.basename(restart_file)),
|
|
183
|
+
)
|
|
184
|
+
if write_restart_file:
|
|
185
|
+
lmp_str_lst.append(f"write_restart {os.path.basename(restart_file)}")
|
|
186
|
+
|
|
187
|
+
with open(os.path.join(working_directory, "lmp.in"), "w") as f:
|
|
188
|
+
f.writelines([l + "\n" for l in lmp_str_lst])
|
|
189
|
+
|
|
190
|
+
write_lammps_datafile(
|
|
191
|
+
structure=structure,
|
|
192
|
+
potential_elements=species,
|
|
193
|
+
bond_dict=None,
|
|
194
|
+
units=units,
|
|
195
|
+
file_name="lammps.data",
|
|
196
|
+
working_directory=working_directory,
|
|
197
|
+
atom_type=atom_type,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
shell = subprocess.check_output(
|
|
201
|
+
lmp_command,
|
|
202
|
+
cwd=working_directory,
|
|
203
|
+
shell=True,
|
|
204
|
+
universal_newlines=True,
|
|
205
|
+
env=os.environ.copy(),
|
|
206
|
+
)
|
|
207
|
+
output = parse_lammps_output(
|
|
208
|
+
working_directory=working_directory,
|
|
209
|
+
structure=structure,
|
|
210
|
+
potential_elements=species,
|
|
211
|
+
units=units,
|
|
212
|
+
prism=None,
|
|
213
|
+
dump_h5_file_name="dump.h5",
|
|
214
|
+
dump_out_file_name="dump.out",
|
|
215
|
+
log_lammps_file_name="log.lammps",
|
|
216
|
+
)
|
|
217
|
+
return shell, output, False
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def lammps_file_initialization(
|
|
221
|
+
structure, dimension=3, units="metal", read_restart_file=False, restart_file=None
|
|
222
|
+
):
|
|
223
|
+
init_commands = ["units " + units]
|
|
224
|
+
boundary = " ".join(["p" if coord else "f" for coord in structure.pbc])
|
|
225
|
+
if read_restart_file:
|
|
226
|
+
init_commands.append(f"read_restart {os.path.basename(restart_file)}")
|
|
227
|
+
else:
|
|
228
|
+
init_commands += [
|
|
229
|
+
"dimension " + str(dimension),
|
|
230
|
+
"boundary " + boundary + "",
|
|
231
|
+
"atom_style atomic",
|
|
232
|
+
"read_data lammps.data",
|
|
233
|
+
]
|
|
234
|
+
return init_commands
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _modify_input_dict(
|
|
238
|
+
input_control_file: Optional[dict] = None,
|
|
239
|
+
lmp_str_lst: list[str] = [],
|
|
240
|
+
):
|
|
241
|
+
if input_control_file is not None:
|
|
242
|
+
lmp_tmp_lst, keys_used = [], []
|
|
243
|
+
for l in lmp_str_lst:
|
|
244
|
+
ls = l.split()
|
|
245
|
+
if len(ls) >= 1: # Remove empty lines
|
|
246
|
+
key = ls[0]
|
|
247
|
+
if key in input_control_file.keys():
|
|
248
|
+
lmp_tmp_lst.append(key + " " + input_control_file[key])
|
|
249
|
+
keys_used.append(key)
|
|
250
|
+
else:
|
|
251
|
+
lmp_tmp_lst.append(l)
|
|
252
|
+
for k, v in input_control_file.items():
|
|
253
|
+
if k not in keys_used:
|
|
254
|
+
lmp_tmp_lst.append(k + " " + v)
|
|
255
|
+
|
|
256
|
+
return lmp_tmp_lst
|
|
257
|
+
else:
|
|
258
|
+
return lmp_str_lst
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _get_potential(potential, resource_path: Optional[str] = None):
|
|
262
|
+
if isinstance(potential, str):
|
|
263
|
+
potential_dataframe = get_potential_by_name(
|
|
264
|
+
potential_name=potential, resource_path=resource_path
|
|
265
|
+
)
|
|
266
|
+
elif isinstance(potential, pandas.DataFrame):
|
|
267
|
+
potential_dataframe = potential.iloc[0]
|
|
268
|
+
elif isinstance(potential, pandas.Series):
|
|
269
|
+
potential_dataframe = potential
|
|
270
|
+
else:
|
|
271
|
+
raise TypeError()
|
|
272
|
+
|
|
273
|
+
potential_replace = {}
|
|
274
|
+
potential_lst = []
|
|
275
|
+
for l in potential_dataframe["Config"]:
|
|
276
|
+
if l.startswith("units"):
|
|
277
|
+
potential_replace["units"] = l
|
|
278
|
+
elif l.startswith("atom_style"):
|
|
279
|
+
potential_replace["atom_style"] = l
|
|
280
|
+
elif l.startswith("dimension"):
|
|
281
|
+
potential_replace["dimension"] = l
|
|
282
|
+
else:
|
|
283
|
+
potential_lst.append(l)
|
|
284
|
+
|
|
285
|
+
return potential_lst, potential_replace, potential_dataframe["Species"]
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
from typing import Dict, Optional
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from ase.atoms import Atoms
|
|
6
|
+
from structuretoolkit.analyse import get_neighbors
|
|
7
|
+
from structuretoolkit.common import select_index
|
|
8
|
+
|
|
9
|
+
from lammpsparser.structure import LammpsStructure
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LammpsStructureCompatibility(LammpsStructure):
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
bond_dict: Optional[Dict] = None,
|
|
16
|
+
units: str = "metal",
|
|
17
|
+
atom_type: str = "atomic",
|
|
18
|
+
):
|
|
19
|
+
super().__init__(bond_dict=bond_dict, units=units, atom_type=atom_type)
|
|
20
|
+
self._molecule_ids = []
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def structure(self) -> Optional[Atoms]:
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
return self._structure
|
|
30
|
+
|
|
31
|
+
@structure.setter
|
|
32
|
+
def structure(self, structure):
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
structure:
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
self._structure = structure
|
|
42
|
+
if self.atom_type == "full":
|
|
43
|
+
input_str = self.structure_full()
|
|
44
|
+
elif self.atom_type == "bond":
|
|
45
|
+
input_str = self.structure_bond()
|
|
46
|
+
elif self.atom_type == "charge":
|
|
47
|
+
input_str = self.structure_charge()
|
|
48
|
+
else: # self.atom_type == 'atomic'
|
|
49
|
+
input_str = self.structure_atomic()
|
|
50
|
+
|
|
51
|
+
self._string_input = input_str + self._get_velocities_input_string()
|
|
52
|
+
|
|
53
|
+
def structure_bond(self):
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
species_lammps_id_dict = self.get_lammps_id_dict(self.el_eam_lst)
|
|
60
|
+
self.molecule_ids = None
|
|
61
|
+
# analyze structure to get molecule_ids, bonds, angles etc
|
|
62
|
+
coords = self.rotate_positions(self._structure)
|
|
63
|
+
|
|
64
|
+
elements = self._structure.get_chemical_symbols()
|
|
65
|
+
|
|
66
|
+
## Standard atoms stuff
|
|
67
|
+
atoms = "Atoms \n\n"
|
|
68
|
+
# atom_style bond
|
|
69
|
+
# format: atom-ID, molecule-ID, atom_type, x, y, z
|
|
70
|
+
format_str = "{0:d} {1:d} {2:d} {3:f} {4:f} {5:f} "
|
|
71
|
+
if len(self._structure.positions[0]) == 3:
|
|
72
|
+
for id_atom, (x, y, z) in enumerate(coords):
|
|
73
|
+
id_mol = self.molecule_ids[id_atom]
|
|
74
|
+
atoms += (
|
|
75
|
+
format_str.format(
|
|
76
|
+
id_atom + 1,
|
|
77
|
+
id_mol,
|
|
78
|
+
species_lammps_id_dict[elements[id_atom]],
|
|
79
|
+
x,
|
|
80
|
+
y,
|
|
81
|
+
z,
|
|
82
|
+
)
|
|
83
|
+
+ "\n"
|
|
84
|
+
)
|
|
85
|
+
elif len(self._structure.positions[0]) == 2:
|
|
86
|
+
for id_atom, (x, y) in enumerate(coords):
|
|
87
|
+
id_mol = self.molecule_ids[id_atom]
|
|
88
|
+
atoms += (
|
|
89
|
+
format_str.format(
|
|
90
|
+
id_atom + 1,
|
|
91
|
+
id_mol,
|
|
92
|
+
species_lammps_id_dict[elements[id_atom]],
|
|
93
|
+
x,
|
|
94
|
+
y,
|
|
95
|
+
0.0,
|
|
96
|
+
)
|
|
97
|
+
+ "\n"
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
raise ValueError("dimension 1 not yet implemented")
|
|
101
|
+
|
|
102
|
+
## Bond related.
|
|
103
|
+
# This seems independent from the lammps atom type ids, because bonds only use atom ids
|
|
104
|
+
el_list = set(self._structure.get_chemical_symbols())
|
|
105
|
+
el_dict = OrderedDict()
|
|
106
|
+
for object_id, el in enumerate(el_list):
|
|
107
|
+
el_dict[el] = object_id
|
|
108
|
+
|
|
109
|
+
n_s = len(el_list)
|
|
110
|
+
bond_type = np.ones([n_s, n_s], dtype=int)
|
|
111
|
+
count = 0
|
|
112
|
+
for i in range(n_s):
|
|
113
|
+
for j in range(i, n_s):
|
|
114
|
+
count += 1
|
|
115
|
+
bond_type[i, j] = count
|
|
116
|
+
bond_type[j, i] = count
|
|
117
|
+
|
|
118
|
+
if self.structure.bonds is None:
|
|
119
|
+
if self.cutoff_radius is None:
|
|
120
|
+
bonds_lst = get_bonds(structure=self.structure, max_shells=1)
|
|
121
|
+
else:
|
|
122
|
+
bonds_lst = get_bonds(
|
|
123
|
+
structure=self.structure, radius=self.cutoff_radius
|
|
124
|
+
)
|
|
125
|
+
bonds = []
|
|
126
|
+
|
|
127
|
+
for ia, i_bonds in enumerate(bonds_lst):
|
|
128
|
+
el_i = el_dict[elements[ia]]
|
|
129
|
+
for el_j, b_lst in i_bonds.items():
|
|
130
|
+
b_type = bond_type[el_i][el_dict[el_j]]
|
|
131
|
+
for i_shell, ib_shell_lst in enumerate(b_lst):
|
|
132
|
+
for ib in np.unique(ib_shell_lst):
|
|
133
|
+
if ia < ib: # avoid double counting of bonds
|
|
134
|
+
bonds.append([ia + 1, ib + 1, b_type])
|
|
135
|
+
|
|
136
|
+
self.structure.bonds = np.array(bonds)
|
|
137
|
+
bonds = self.structure.bonds
|
|
138
|
+
|
|
139
|
+
bonds_str = "Bonds \n\n"
|
|
140
|
+
for i_bond, (i_a, i_b, b_type) in enumerate(bonds):
|
|
141
|
+
bonds_str += (
|
|
142
|
+
"{0:d} {1:d} {2:d} {3:d}".format(i_bond + 1, b_type, i_a, i_b) + "\n"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
self.lammps_header(
|
|
147
|
+
structure=self.structure,
|
|
148
|
+
cell_dimensions=self.simulation_cell(),
|
|
149
|
+
species_lammps_id_dict=species_lammps_id_dict,
|
|
150
|
+
nbonds=len(bonds),
|
|
151
|
+
nbond_types=np.max(np.array(bonds)[:, 2]),
|
|
152
|
+
)
|
|
153
|
+
+ "\n"
|
|
154
|
+
+ atoms
|
|
155
|
+
+ "\n"
|
|
156
|
+
+ bonds_str
|
|
157
|
+
+ "\n"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def structure_full(self):
|
|
161
|
+
"""
|
|
162
|
+
Write routine to create atom structure static file for atom_type='full' that can be loaded by LAMMPS
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
species_lammps_id_dict = self.get_lammps_id_dict(self.el_eam_lst)
|
|
168
|
+
self.molecule_ids = None
|
|
169
|
+
coords = self.rotate_positions(self._structure)
|
|
170
|
+
|
|
171
|
+
# extract electric charges from potential file
|
|
172
|
+
q_dict = {
|
|
173
|
+
species_name: self.potential.get_charge(species_name)
|
|
174
|
+
for species_name in set(self.structure.get_chemical_symbols())
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
bonds_lst, angles_lst = [], []
|
|
178
|
+
bond_type_lst, angle_type_lst = [], []
|
|
179
|
+
# Using a cutoff distance to draw the bonds instead of the number of neighbors
|
|
180
|
+
# Only if any bonds are defined
|
|
181
|
+
if len(self._bond_dict.keys()) > 0:
|
|
182
|
+
cutoff_list = list()
|
|
183
|
+
for val in self._bond_dict.values():
|
|
184
|
+
cutoff_list.append(np.max(val["cutoff_list"]))
|
|
185
|
+
max_cutoff = np.max(cutoff_list)
|
|
186
|
+
# Calculate neighbors only once
|
|
187
|
+
neighbors = get_neighbors(
|
|
188
|
+
structure=self.structure, cutoff_radius=max_cutoff
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Draw bonds between atoms is defined in self._bond_dict
|
|
192
|
+
# Go through all elements for which bonds are defined
|
|
193
|
+
for element, val in self._bond_dict.items():
|
|
194
|
+
el_1_list = select_index(structure=self._structure, element=element)
|
|
195
|
+
if el_1_list is not None:
|
|
196
|
+
if len(el_1_list) > 0:
|
|
197
|
+
for i, v in enumerate(val["element_list"]):
|
|
198
|
+
el_2_list = select_index(
|
|
199
|
+
structure=self._structure, element=v
|
|
200
|
+
)
|
|
201
|
+
cutoff_dist = val["cutoff_list"][i]
|
|
202
|
+
for j, ind in enumerate(
|
|
203
|
+
np.array(neighbors.indices, dtype=object)[el_1_list]
|
|
204
|
+
):
|
|
205
|
+
# Only chose those indices within the cutoff distance and which belong
|
|
206
|
+
# to the species defined in the element_list
|
|
207
|
+
# i is the index of each bond type, and j is the element index
|
|
208
|
+
id_el = el_1_list[j]
|
|
209
|
+
bool_1 = (
|
|
210
|
+
np.array(neighbors.distances, dtype=object)[id_el]
|
|
211
|
+
<= cutoff_dist
|
|
212
|
+
)
|
|
213
|
+
act_ind = ind[bool_1]
|
|
214
|
+
bool_2 = np.in1d(act_ind, el_2_list)
|
|
215
|
+
final_ind = act_ind[bool_2]
|
|
216
|
+
# Get the bond and angle type
|
|
217
|
+
bond_type = val["bond_type_list"][i]
|
|
218
|
+
angle_type = val["angle_type_list"][i]
|
|
219
|
+
# Draw only maximum allowed bonds
|
|
220
|
+
final_ind = final_ind[: val["max_bond_list"][i]]
|
|
221
|
+
for fi in final_ind:
|
|
222
|
+
bonds_lst.append([id_el + 1, fi + 1])
|
|
223
|
+
bond_type_lst.append(bond_type)
|
|
224
|
+
# Draw angles if at least 2 bonds are present and if an angle type is defined for this
|
|
225
|
+
# particular set of bonds
|
|
226
|
+
if (
|
|
227
|
+
len(final_ind) >= 2
|
|
228
|
+
and val["angle_type_list"][i] is not None
|
|
229
|
+
):
|
|
230
|
+
angles_lst.append(
|
|
231
|
+
[final_ind[0] + 1, id_el + 1, final_ind[1] + 1]
|
|
232
|
+
)
|
|
233
|
+
angle_type_lst.append(angle_type)
|
|
234
|
+
|
|
235
|
+
if len(bond_type_lst) == 0:
|
|
236
|
+
num_bond_types = 0
|
|
237
|
+
else:
|
|
238
|
+
num_bond_types = int(np.max(bond_type_lst))
|
|
239
|
+
if len(angle_type_lst) == 0:
|
|
240
|
+
num_angle_types = 0
|
|
241
|
+
else:
|
|
242
|
+
num_angle_types = int(np.max(angle_type_lst))
|
|
243
|
+
|
|
244
|
+
atoms = "Atoms \n\n"
|
|
245
|
+
|
|
246
|
+
# format: atom-ID, molecule-ID, atom_type, q, x, y, z
|
|
247
|
+
format_str = "{0:d} {1:d} {2:d} {3:f} {4:f} {5:f} {6:f}"
|
|
248
|
+
el_lst = self.structure.get_chemical_symbols()
|
|
249
|
+
for id_atom, (el, coord) in enumerate(zip(el_lst, coords)):
|
|
250
|
+
atoms += (
|
|
251
|
+
format_str.format(
|
|
252
|
+
id_atom + 1,
|
|
253
|
+
self.molecule_ids[id_atom],
|
|
254
|
+
species_lammps_id_dict[el],
|
|
255
|
+
q_dict[el],
|
|
256
|
+
coord[0],
|
|
257
|
+
coord[1],
|
|
258
|
+
coord[2],
|
|
259
|
+
)
|
|
260
|
+
+ "\n"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if len(bonds_lst) > 0:
|
|
264
|
+
bonds_str = "Bonds \n\n"
|
|
265
|
+
for i_bond, id_vec in enumerate(bonds_lst):
|
|
266
|
+
bonds_str += (
|
|
267
|
+
"{0:d} {1:d} {2:d} {3:d}".format(
|
|
268
|
+
i_bond + 1, bond_type_lst[i_bond], id_vec[0], id_vec[1]
|
|
269
|
+
)
|
|
270
|
+
+ "\n"
|
|
271
|
+
)
|
|
272
|
+
else:
|
|
273
|
+
bonds_str = "\n"
|
|
274
|
+
|
|
275
|
+
if len(angles_lst) > 0:
|
|
276
|
+
angles_str = "Angles \n\n"
|
|
277
|
+
for i_angle, id_vec in enumerate(angles_lst):
|
|
278
|
+
angles_str += (
|
|
279
|
+
"{0:d} {1:d} {2:d} {3:d} {4:d}".format(
|
|
280
|
+
i_angle + 1,
|
|
281
|
+
angle_type_lst[i_angle],
|
|
282
|
+
id_vec[0],
|
|
283
|
+
id_vec[1],
|
|
284
|
+
id_vec[2],
|
|
285
|
+
)
|
|
286
|
+
+ "\n"
|
|
287
|
+
)
|
|
288
|
+
else:
|
|
289
|
+
angles_str = "\n"
|
|
290
|
+
return (
|
|
291
|
+
self.lammps_header(
|
|
292
|
+
structure=self.structure,
|
|
293
|
+
cell_dimensions=self.simulation_cell(),
|
|
294
|
+
species_lammps_id_dict=species_lammps_id_dict,
|
|
295
|
+
nbonds=len(bonds_lst),
|
|
296
|
+
nangles=len(angles_lst),
|
|
297
|
+
nbond_types=num_bond_types,
|
|
298
|
+
nangle_types=num_angle_types,
|
|
299
|
+
)
|
|
300
|
+
+ " \n"
|
|
301
|
+
+ atoms
|
|
302
|
+
+ "\n"
|
|
303
|
+
+ bonds_str
|
|
304
|
+
+ "\n"
|
|
305
|
+
+ angles_str
|
|
306
|
+
+ "\n"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def get_bonds(
|
|
311
|
+
structure: Atoms, radius=np.inf, max_shells=None, prec=0.1, num_neighbors=20
|
|
312
|
+
):
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
structure (ase.Atoms):
|
|
317
|
+
radius (float):
|
|
318
|
+
max_shells:
|
|
319
|
+
prec: minimum distance between any two clusters (if smaller considered to be single cluster)
|
|
320
|
+
num_neighbors:
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
|
|
324
|
+
"""
|
|
325
|
+
neighbors = get_neighbors(
|
|
326
|
+
structure=structure,
|
|
327
|
+
cutoff_radius=radius,
|
|
328
|
+
num_neighbors=num_neighbors,
|
|
329
|
+
tolerance=2,
|
|
330
|
+
id_list=None,
|
|
331
|
+
width_buffer=1.2,
|
|
332
|
+
allow_ragged=None,
|
|
333
|
+
mode="ragged",
|
|
334
|
+
norm_order=2,
|
|
335
|
+
)
|
|
336
|
+
return neighbors.get_bonds(radius=radius, max_shells=max_shells, prec=prec)
|