interfacemethod 2.0__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.
- interfacemethod/__init__.py +29 -0
- interfacemethod/_version.py +24 -0
- interfacemethod/helper.py +88 -0
- interfacemethod/lammps.py +379 -0
- interfacemethod/plot.py +310 -0
- interfacemethod/result.py +16 -0
- interfacemethod/structure.py +115 -0
- interfacemethod/workflow.py +205 -0
- interfacemethod-2.0.dist-info/METADATA +183 -0
- interfacemethod-2.0.dist-info/RECORD +12 -0
- interfacemethod-2.0.dist-info/WHEEL +4 -0
- interfacemethod-2.0.dist-info/licenses/LICENSE +29 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from interfacemethod.helper import (
|
|
2
|
+
initialise_iterators,
|
|
3
|
+
round_temperature_next,
|
|
4
|
+
get_strain_lst,
|
|
5
|
+
)
|
|
6
|
+
from interfacemethod.lammps import (
|
|
7
|
+
minimize_structure_positions,
|
|
8
|
+
minimize_structure_volume,
|
|
9
|
+
npt_solid,
|
|
10
|
+
npt_liquid,
|
|
11
|
+
run_npt_step,
|
|
12
|
+
run_strain_point,
|
|
13
|
+
)
|
|
14
|
+
from interfacemethod.plot import (
|
|
15
|
+
check_for_holes,
|
|
16
|
+
plot_solid_liquid_ratio,
|
|
17
|
+
plot_equilibration,
|
|
18
|
+
plot_melting_point_prediction,
|
|
19
|
+
ratio_selection,
|
|
20
|
+
)
|
|
21
|
+
from interfacemethod.structure import (
|
|
22
|
+
check_diamond,
|
|
23
|
+
analyse_minimized_structure,
|
|
24
|
+
remove_selective_dynamics,
|
|
25
|
+
)
|
|
26
|
+
from interfacemethod.workflow import (
|
|
27
|
+
bisection_step,
|
|
28
|
+
validate_convergence,
|
|
29
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from ase.atoms import Atoms
|
|
2
|
+
from ase.constraints import FixAtoms
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def initialise_iterators(project_parameter: dict):
|
|
7
|
+
return (
|
|
8
|
+
iter(project_parameter["timestep_lst"]),
|
|
9
|
+
iter(project_parameter["fit_range_lst"]),
|
|
10
|
+
iter(project_parameter["nve_run_time_steps_lst"]),
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def freeze_one_half(basis: Atoms) -> FixAtoms:
|
|
15
|
+
"""
|
|
16
|
+
Split the structure into two parts along the z-axis and then freeze the position of the atoms
|
|
17
|
+
of the upper part (z>0.5) by attaching an ASE FixAtoms constraint to them.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
basis (ase.atoms.Atoms): Atomistic structure object
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
ase.constraints.FixAtoms: Constraint fixing the upper half of the structure
|
|
24
|
+
"""
|
|
25
|
+
basis = basis.copy()
|
|
26
|
+
z = basis.get_scaled_positions()[:, 2]
|
|
27
|
+
return FixAtoms(indices=np.where(z >= 0.5)[0])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def round_temperature_next(temperature_next: float) -> float:
|
|
31
|
+
"""
|
|
32
|
+
Round temperature to the last two dicits
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
temperature_next (float): Temperature
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
float: rounded temperature
|
|
39
|
+
"""
|
|
40
|
+
return np.round(temperature_next, 2)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_nve_job_name(
|
|
44
|
+
temperature_next: float,
|
|
45
|
+
strain: float,
|
|
46
|
+
steps_lst: list[int],
|
|
47
|
+
nve_run_time_steps: int,
|
|
48
|
+
):
|
|
49
|
+
temperature_next = round_temperature_next(temperature_next)
|
|
50
|
+
temp_str = str(temperature_next).replace(".", "_")
|
|
51
|
+
strain_str = str(strain).replace(".", "_")
|
|
52
|
+
steps_str = str(steps_lst.index(nve_run_time_steps))
|
|
53
|
+
return "ham_nve_" + strain_str + "_" + temp_str + "_" + steps_str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_center_point(strain_result_lst=None, pressure_result_lst=None, center=None):
|
|
57
|
+
if (
|
|
58
|
+
strain_result_lst is not None
|
|
59
|
+
and len(strain_result_lst) != 0
|
|
60
|
+
and pressure_result_lst is not None
|
|
61
|
+
and len(pressure_result_lst) != 0
|
|
62
|
+
):
|
|
63
|
+
center_point = np.round(
|
|
64
|
+
np.roots(np.polyfit(strain_result_lst, pressure_result_lst, 1))[0], 2
|
|
65
|
+
)
|
|
66
|
+
elif center is not None:
|
|
67
|
+
center_point = center
|
|
68
|
+
else:
|
|
69
|
+
center_point = 1.0
|
|
70
|
+
return center_point
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_strain_lst(
|
|
74
|
+
fit_range=0.02,
|
|
75
|
+
points=21,
|
|
76
|
+
strain_result_lst=None,
|
|
77
|
+
pressure_result_lst=None,
|
|
78
|
+
center=None,
|
|
79
|
+
):
|
|
80
|
+
center_point = get_center_point(
|
|
81
|
+
strain_result_lst=strain_result_lst,
|
|
82
|
+
pressure_result_lst=pressure_result_lst,
|
|
83
|
+
center=center,
|
|
84
|
+
)
|
|
85
|
+
return [
|
|
86
|
+
np.round(s, 3)
|
|
87
|
+
for s in np.linspace(center_point - fit_range, center_point + fit_range, points)
|
|
88
|
+
]
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from ase.atoms import Atoms
|
|
3
|
+
from lammpsparser import lammps_file_interface_function
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas
|
|
6
|
+
|
|
7
|
+
from interfacemethod.helper import (
|
|
8
|
+
freeze_one_half,
|
|
9
|
+
round_temperature_next,
|
|
10
|
+
get_nve_job_name,
|
|
11
|
+
)
|
|
12
|
+
from interfacemethod.result import StrainPointResult
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def structure_from_parsed_output(
|
|
16
|
+
initial_structure: Atoms, parsed_output: dict, *, wrap: bool = False
|
|
17
|
+
) -> Atoms:
|
|
18
|
+
"""Construct an `Atoms` object from parsed output data.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
initial_structure: The initial atomic structure to use as a template.
|
|
22
|
+
parsed_output: Parsed output containing atomic positions, cell, and indices.
|
|
23
|
+
wrap: Whether to wrap the atomic positions to the simulation cell (default is False).
|
|
24
|
+
Keeping the unwrapped positions is more beneficial if structures are passed between
|
|
25
|
+
different LAMMPS simulations in one workflow to ensure continuity.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
An `Atoms` object with updated positions and cell.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> new_atoms = structure_from_parsed_output(atoms, lammps_output)
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
atoms_copy = initial_structure.copy()
|
|
35
|
+
atoms_copy.set_array("indices", parsed_output["generic"]["indices"][-1])
|
|
36
|
+
atoms_copy.set_cell(parsed_output["generic"]["cells"][-1], scale_atoms=True)
|
|
37
|
+
atoms_copy.set_positions(parsed_output["generic"]["positions"][-1])
|
|
38
|
+
atoms_copy.set_velocities(parsed_output["generic"]["velocities"][-1])
|
|
39
|
+
atoms_copy.set_pbc(True)
|
|
40
|
+
if wrap:
|
|
41
|
+
atoms_copy.wrap()
|
|
42
|
+
|
|
43
|
+
return atoms_copy
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def minimize_structure_positions(
|
|
47
|
+
structure: Atoms,
|
|
48
|
+
potential: pandas.DataFrame,
|
|
49
|
+
project_path: str,
|
|
50
|
+
max_iter: int = 1000,
|
|
51
|
+
lmp_command: str = "lmp -in lmp.in",
|
|
52
|
+
) -> Atoms:
|
|
53
|
+
"""Relax the atomic positions of a structure at fixed cell shape."""
|
|
54
|
+
_, parsed_output, _ = lammps_file_interface_function(
|
|
55
|
+
working_directory=os.path.join(project_path, "minimize_pos"),
|
|
56
|
+
structure=structure,
|
|
57
|
+
potential=potential,
|
|
58
|
+
calc_mode="minimize",
|
|
59
|
+
calc_kwargs={
|
|
60
|
+
"max_iter": max_iter,
|
|
61
|
+
"ionic_energy_tolerance": 1.0e-9,
|
|
62
|
+
"ionic_force_tolerance": 1.0e-8,
|
|
63
|
+
"n_print": max_iter,
|
|
64
|
+
},
|
|
65
|
+
lmp_command=lmp_command,
|
|
66
|
+
)
|
|
67
|
+
return structure_from_parsed_output(structure, parsed_output, wrap=True)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def minimize_structure_volume(
|
|
71
|
+
structure: Atoms,
|
|
72
|
+
potential: pandas.DataFrame,
|
|
73
|
+
project_path: str,
|
|
74
|
+
max_iter: int = 1000,
|
|
75
|
+
lmp_command: str = "lmp -in lmp.in",
|
|
76
|
+
) -> Atoms:
|
|
77
|
+
"""Relax both atomic positions and cell volume of a structure at zero pressure."""
|
|
78
|
+
_, parsed_output, _ = lammps_file_interface_function(
|
|
79
|
+
working_directory=os.path.join(project_path, "minimize_vol"),
|
|
80
|
+
structure=structure,
|
|
81
|
+
potential=potential,
|
|
82
|
+
calc_mode="minimize",
|
|
83
|
+
calc_kwargs={
|
|
84
|
+
"max_iter": max_iter,
|
|
85
|
+
"ionic_energy_tolerance": 1.0e-9,
|
|
86
|
+
"ionic_force_tolerance": 1.0e-8,
|
|
87
|
+
"n_print": max_iter,
|
|
88
|
+
"pressure": 0.0,
|
|
89
|
+
},
|
|
90
|
+
input_control_file={"fix": "ensemble all box/relax iso 0.0 vmax 0.001"},
|
|
91
|
+
lmp_command=lmp_command,
|
|
92
|
+
)
|
|
93
|
+
return structure_from_parsed_output(structure, parsed_output, wrap=True)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def run_npt_step(
|
|
97
|
+
structure: Atoms,
|
|
98
|
+
potential: pandas.DataFrame,
|
|
99
|
+
temperature: float,
|
|
100
|
+
seed: int,
|
|
101
|
+
project_path: str,
|
|
102
|
+
run_time_steps: int = 10000,
|
|
103
|
+
lmp_command: str = "lmp -in lmp.in",
|
|
104
|
+
):
|
|
105
|
+
"""
|
|
106
|
+
Calculate NPT ensemble at a given temperature using the job defined in the project parameters:
|
|
107
|
+
- job_type: Type of Simulation code to be used
|
|
108
|
+
- project: Project object used to create the job
|
|
109
|
+
- potential: Interatomic Potential
|
|
110
|
+
- queue (optional): HPC Job queue to be used
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
structure (ase.atoms.Atoms): Atomistic Structure object to be set to the job as input sturcture
|
|
114
|
+
temperature (float): Temperature of the Molecular dynamics calculation
|
|
115
|
+
run_time_steps (int): Number of Molecular dynamics steps
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Final Atomistic Structure object
|
|
119
|
+
"""
|
|
120
|
+
_, parsed_output, _ = lammps_file_interface_function(
|
|
121
|
+
working_directory=os.path.join(
|
|
122
|
+
project_path, "temp_heating", str(temperature).replace(".", "_")
|
|
123
|
+
),
|
|
124
|
+
structure=structure,
|
|
125
|
+
potential=potential,
|
|
126
|
+
calc_mode="md",
|
|
127
|
+
calc_kwargs={
|
|
128
|
+
"temperature": temperature,
|
|
129
|
+
"initial_temperature": temperature,
|
|
130
|
+
"temperature_damping_timescale": 100.0,
|
|
131
|
+
"pressure": 0.0,
|
|
132
|
+
"pressure_damping_timescale": 1000.0,
|
|
133
|
+
"n_print": run_time_steps,
|
|
134
|
+
"n_ionic_steps": run_time_steps,
|
|
135
|
+
"seed": seed,
|
|
136
|
+
},
|
|
137
|
+
input_control_file={
|
|
138
|
+
"fix": f"ensemble all npt temp {temperature} {temperature} 0.1 iso 0.0 0.0 1.0 couple xyz"
|
|
139
|
+
},
|
|
140
|
+
lmp_command=lmp_command,
|
|
141
|
+
)
|
|
142
|
+
return structure_from_parsed_output(structure, parsed_output, wrap=True)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def npt_solid(
|
|
146
|
+
temperature: float,
|
|
147
|
+
basis: Atoms,
|
|
148
|
+
project_parameter: dict,
|
|
149
|
+
project_path: str,
|
|
150
|
+
timestep: float = 1.0,
|
|
151
|
+
lmp_command: str = "lmp -in lmp.in",
|
|
152
|
+
) -> Atoms:
|
|
153
|
+
"""
|
|
154
|
+
Calculate NPT ensemble at a given temperature using lammps_file_interface_function.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
temperature (float): Temperature of the Molecular dynamics calculation
|
|
158
|
+
basis (ase.atoms.Atoms): Atomistic Structure object to be used as input structure
|
|
159
|
+
project_parameter (dict): Dictionary with the project parameters
|
|
160
|
+
project_path (str): Working directory the calculation is executed in
|
|
161
|
+
timestep (float): Molecular dynamics time step
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Atoms: Final Atomistic Structure object
|
|
165
|
+
"""
|
|
166
|
+
_, parsed_output, _ = lammps_file_interface_function(
|
|
167
|
+
working_directory=os.path.join(
|
|
168
|
+
project_path, "npt_solid", str(temperature).replace(".", "_")
|
|
169
|
+
),
|
|
170
|
+
structure=basis,
|
|
171
|
+
potential=project_parameter["potential"],
|
|
172
|
+
calc_mode="md",
|
|
173
|
+
calc_kwargs={
|
|
174
|
+
"temperature": temperature,
|
|
175
|
+
"initial_temperature": temperature,
|
|
176
|
+
"temperature_damping_timescale": 100.0,
|
|
177
|
+
"time_step": timestep,
|
|
178
|
+
"pressure": 0.0,
|
|
179
|
+
"pressure_damping_timescale": 1000.0,
|
|
180
|
+
"n_print": project_parameter["run_time_steps"],
|
|
181
|
+
"n_ionic_steps": project_parameter["run_time_steps"],
|
|
182
|
+
"seed": project_parameter["seed"],
|
|
183
|
+
},
|
|
184
|
+
input_control_file={
|
|
185
|
+
"fix": f"ensemble all npt temp {temperature} {temperature} 0.1 iso 0.0 0.0 1.0 couple xyz"
|
|
186
|
+
},
|
|
187
|
+
lmp_command=lmp_command,
|
|
188
|
+
)
|
|
189
|
+
return structure_from_parsed_output(basis, parsed_output, wrap=True)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def setup_liquid_job(
|
|
193
|
+
job_name: str,
|
|
194
|
+
basis: Atoms,
|
|
195
|
+
temperature: float,
|
|
196
|
+
project_parameter: dict,
|
|
197
|
+
project_path: str,
|
|
198
|
+
timestep: float = 1.0,
|
|
199
|
+
lmp_command: str = "lmp -in lmp.in",
|
|
200
|
+
):
|
|
201
|
+
"""
|
|
202
|
+
Calculate NPT ensemble at a given temperature while freezing the position of the atoms
|
|
203
|
+
of the upper part (z>0.5) using lammps_file_interface_function. Only the z-component of
|
|
204
|
+
the pressure is coupled to a barostat, matching the previous fix_z_dir behaviour.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
job_name (str): Name used for the working directory of the calculation
|
|
208
|
+
basis (ase.atoms.Atoms): Atomistic Structure object to be used as input structure
|
|
209
|
+
temperature (float): Temperature of the Molecular dynamics calculation
|
|
210
|
+
project_parameter (dict): Dictionary with the project parameters
|
|
211
|
+
project_path (str): Working directory the calculation is executed in
|
|
212
|
+
timestep (float): Molecular dynamics time step
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Atoms: Final Atomistic Structure object
|
|
216
|
+
"""
|
|
217
|
+
_, parsed_output, _ = lammps_file_interface_function(
|
|
218
|
+
working_directory=os.path.join(project_path, "liquid", job_name),
|
|
219
|
+
structure=basis,
|
|
220
|
+
potential=project_parameter["potential"],
|
|
221
|
+
calc_mode="md",
|
|
222
|
+
calc_kwargs={
|
|
223
|
+
"temperature": temperature,
|
|
224
|
+
"initial_temperature": temperature,
|
|
225
|
+
"temperature_damping_timescale": 100.0,
|
|
226
|
+
"time_step": timestep,
|
|
227
|
+
"pressure": [None, None, 0.0],
|
|
228
|
+
"pressure_damping_timescale": 1000.0,
|
|
229
|
+
"n_print": project_parameter["run_time_steps"],
|
|
230
|
+
"n_ionic_steps": project_parameter["run_time_steps"],
|
|
231
|
+
"seed": project_parameter["seed"],
|
|
232
|
+
},
|
|
233
|
+
lmp_command=lmp_command,
|
|
234
|
+
)
|
|
235
|
+
return structure_from_parsed_output(basis, parsed_output, wrap=True)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def npt_liquid(
|
|
239
|
+
temperature_solid: float,
|
|
240
|
+
temperature_liquid: float,
|
|
241
|
+
basis: Atoms,
|
|
242
|
+
project_parameter: dict,
|
|
243
|
+
project_path: str,
|
|
244
|
+
lmp_command: str = "lmp -in lmp.in",
|
|
245
|
+
timestep: float = 1.0,
|
|
246
|
+
):
|
|
247
|
+
"""
|
|
248
|
+
Calculate NPT ensemble at a given temperature while initially freezing the position of the atoms
|
|
249
|
+
of the upper part (z>0.5) and afterwards calculating the full sample at a lower temperature.
|
|
250
|
+
These steps are used to construct the solid liquid interface as part of the coexistence approach.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
temperature_solid (float): Temperature to simulate the whole structure
|
|
254
|
+
temperature_liquid (float): Temperature to simulate the upper half of the structure
|
|
255
|
+
basis (ase.atoms.Atoms): Atomistic Structure object to be used as input structure
|
|
256
|
+
project_parameter (dict): Dictionary with the project parameters
|
|
257
|
+
project_path (str): Working directory the calculation is executed in
|
|
258
|
+
timestep (float): Molecular dynamics time step
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Atoms: Final Atomistic Structure object
|
|
262
|
+
"""
|
|
263
|
+
constraint = freeze_one_half(basis)
|
|
264
|
+
basis.set_constraint(constraint)
|
|
265
|
+
structure_liquid_high = setup_liquid_job(
|
|
266
|
+
job_name="high_" + str(temperature_liquid).replace(".", "_"),
|
|
267
|
+
basis=basis,
|
|
268
|
+
temperature=temperature_liquid,
|
|
269
|
+
project_parameter=project_parameter,
|
|
270
|
+
project_path=project_path,
|
|
271
|
+
timestep=timestep,
|
|
272
|
+
lmp_command=lmp_command,
|
|
273
|
+
)
|
|
274
|
+
structure_liquid_high.set_constraint(constraint)
|
|
275
|
+
structure_liquid_low = setup_liquid_job(
|
|
276
|
+
job_name="low_" + str(temperature_solid).replace(".", "_"),
|
|
277
|
+
basis=structure_liquid_high,
|
|
278
|
+
temperature=temperature_solid,
|
|
279
|
+
project_parameter=project_parameter,
|
|
280
|
+
project_path=project_path,
|
|
281
|
+
timestep=timestep,
|
|
282
|
+
lmp_command=lmp_command,
|
|
283
|
+
)
|
|
284
|
+
return structure_liquid_low
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def get_press(parsed_output, step: int = 20):
|
|
288
|
+
"""
|
|
289
|
+
Args:
|
|
290
|
+
parsed_output (dict): Output parsed from a LAMMPS MD calculation via
|
|
291
|
+
lammps_file_interface_function
|
|
292
|
+
step (int): Number of steps counted from the end of the trajectory to average over
|
|
293
|
+
"""
|
|
294
|
+
return np.mean(
|
|
295
|
+
parsed_output["generic"]["pressures"][step:, :, :].diagonal(0, 2), axis=1
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def run_strain_point(
|
|
300
|
+
strain: float,
|
|
301
|
+
basis_relative: Atoms,
|
|
302
|
+
temperature_next: float,
|
|
303
|
+
nve_run_time_steps: int,
|
|
304
|
+
project_parameter: dict,
|
|
305
|
+
project_path: str,
|
|
306
|
+
timestep: float = 1.0,
|
|
307
|
+
lmp_command: str = "lmp -in lmp.in",
|
|
308
|
+
) -> StrainPointResult:
|
|
309
|
+
"""
|
|
310
|
+
Apply one strain to the interface structure along z and measure the resulting pressure and
|
|
311
|
+
temperature via a short NVT equilibration followed by an NVE production run.
|
|
312
|
+
|
|
313
|
+
Every strain point is independent of every other, so this is the unit of work for the strain
|
|
314
|
+
scan: a `for strain in strain_lst: run_strain_point(strain, ...)` loop can be replaced with
|
|
315
|
+
`executor.map(...)` on a `concurrent.futures.ProcessPoolExecutor` or an `executorlib.Executor`
|
|
316
|
+
to run the scan in parallel.
|
|
317
|
+
"""
|
|
318
|
+
temperature_next = round_temperature_next(temperature_next)
|
|
319
|
+
job_name = get_nve_job_name(
|
|
320
|
+
temperature_next=temperature_next,
|
|
321
|
+
strain=strain,
|
|
322
|
+
steps_lst=project_parameter["nve_run_time_steps_lst"],
|
|
323
|
+
nve_run_time_steps=nve_run_time_steps,
|
|
324
|
+
)
|
|
325
|
+
nvt_working_directory = os.path.join(
|
|
326
|
+
project_path, "strain_circle", job_name.replace("nve", "nvt")
|
|
327
|
+
)
|
|
328
|
+
nve_working_directory = os.path.join(project_path, "strain_circle", job_name)
|
|
329
|
+
basis_strain = basis_relative.copy()
|
|
330
|
+
cell = basis_strain.cell.copy()
|
|
331
|
+
cell[2, 2] *= strain
|
|
332
|
+
basis_strain.set_cell(cell=cell, scale_atoms=True)
|
|
333
|
+
lammps_file_interface_function(
|
|
334
|
+
working_directory=nvt_working_directory,
|
|
335
|
+
structure=basis_strain,
|
|
336
|
+
potential=project_parameter["potential"],
|
|
337
|
+
calc_mode="md",
|
|
338
|
+
calc_kwargs={
|
|
339
|
+
"temperature": temperature_next,
|
|
340
|
+
"initial_temperature": temperature_next,
|
|
341
|
+
"time_step": timestep,
|
|
342
|
+
"temperature_damping_timescale": 100.0,
|
|
343
|
+
"n_print": project_parameter["nvt_run_time_steps"],
|
|
344
|
+
"n_ionic_steps": project_parameter["nvt_run_time_steps"],
|
|
345
|
+
"seed": project_parameter["seed"],
|
|
346
|
+
},
|
|
347
|
+
input_control_file={
|
|
348
|
+
"fix": f"ensemble all nvt temp {temperature_next} {temperature_next} 0.1 drag 1"
|
|
349
|
+
},
|
|
350
|
+
write_restart_file=True,
|
|
351
|
+
lmp_command=lmp_command,
|
|
352
|
+
)
|
|
353
|
+
restart_file_path = os.path.join(nvt_working_directory, "restart.out")
|
|
354
|
+
_, parsed_output, _ = lammps_file_interface_function(
|
|
355
|
+
working_directory=nve_working_directory,
|
|
356
|
+
structure=basis_strain,
|
|
357
|
+
potential=project_parameter["potential"],
|
|
358
|
+
calc_mode="md",
|
|
359
|
+
calc_kwargs={
|
|
360
|
+
"time_step": timestep,
|
|
361
|
+
"n_print": max(1, int(nve_run_time_steps / 100)),
|
|
362
|
+
"n_ionic_steps": nve_run_time_steps,
|
|
363
|
+
"seed": project_parameter["seed"],
|
|
364
|
+
},
|
|
365
|
+
read_restart_file=True,
|
|
366
|
+
restart_file=restart_file_path,
|
|
367
|
+
dump_final_structure=True,
|
|
368
|
+
lmp_command=lmp_command,
|
|
369
|
+
)
|
|
370
|
+
structure_nve = structure_from_parsed_output(basis_strain, parsed_output, wrap=True)
|
|
371
|
+
return StrainPointResult(
|
|
372
|
+
strain=strain,
|
|
373
|
+
pressure=np.mean(get_press(parsed_output=parsed_output, step=-20)),
|
|
374
|
+
pressure_std=np.std(get_press(parsed_output=parsed_output, step=-20)),
|
|
375
|
+
temperature=np.mean(parsed_output["generic"]["temperature"][-20:]),
|
|
376
|
+
temperature_std=np.std(parsed_output["generic"]["temperature"][-20:]),
|
|
377
|
+
structure=structure_nve,
|
|
378
|
+
parsed_output=parsed_output,
|
|
379
|
+
)
|