calphy 2.1.1__tar.gz → 2.3.0__tar.gz
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.
- {calphy-2.1.1 → calphy-2.3.0}/PKG-INFO +4 -2
- {calphy-2.1.1 → calphy-2.3.0}/README.md +1 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/__init__.py +1 -1
- {calphy-2.1.1 → calphy-2.3.0}/calphy/alchemy.py +163 -245
- {calphy-2.1.1 → calphy-2.3.0}/calphy/composition_transformation.py +15 -21
- calphy-2.3.0/calphy/dcci.py +702 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/helpers.py +104 -58
- {calphy-2.1.1 → calphy-2.3.0}/calphy/input.py +195 -78
- {calphy-2.1.1 → calphy-2.3.0}/calphy/integrators.py +3 -2
- {calphy-2.1.1 → calphy-2.3.0}/calphy/phase.py +82 -67
- {calphy-2.1.1 → calphy-2.3.0}/calphy/postprocessing.py +68 -1
- {calphy-2.1.1 → calphy-2.3.0}/calphy/queuekernel.py +15 -2
- {calphy-2.1.1 → calphy-2.3.0}/calphy/routines.py +101 -34
- {calphy-2.1.1 → calphy-2.3.0}/calphy/runner.py +2 -2
- calphy-2.3.0/calphy/structures.py +146 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy.egg-info/PKG-INFO +4 -2
- {calphy-2.1.1 → calphy-2.3.0}/calphy.egg-info/SOURCES.txt +11 -1
- {calphy-2.1.1 → calphy-2.3.0}/calphy.egg-info/requires.txt +2 -1
- {calphy-2.1.1 → calphy-2.3.0}/pyproject.toml +3 -2
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_command_vocabulary.py +1 -1
- calphy-2.3.0/tests/test_composition_scaling_potential.py +76 -0
- calphy-2.3.0/tests/test_dcci_driver.py +308 -0
- calphy-2.3.0/tests/test_dcci_input.py +117 -0
- calphy-2.3.0/tests/test_dcci_integrator.py +62 -0
- calphy-2.3.0/tests/test_dcci_lammps_e2e.py +109 -0
- calphy-2.3.0/tests/test_dcci_lammps_semantics.py +220 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_golden_streams.py +48 -9
- calphy-2.3.0/tests/test_helpers.py +157 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_input_validation.py +60 -0
- calphy-2.3.0/tests/test_integrators.py +29 -0
- calphy-2.3.0/tests/test_structures.py +175 -0
- calphy-2.3.0/tests/test_sweep_barostat_targets.py +142 -0
- calphy-2.1.1/tests/test_helpers.py +0 -86
- calphy-2.1.1/tests/test_integrators.py +0 -10
- {calphy-2.1.1 → calphy-2.3.0}/LICENSE +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/clitools.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/errors.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/kernel.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/library_runner.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/liquid.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/phase_diagram.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/range_scan.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/scheduler.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/solid.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/splines.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy/utils.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy.egg-info/dependency_links.txt +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy.egg-info/entry_points.txt +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/calphy.egg-info/top_level.txt +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/setup.cfg +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_composition_transformation_bug.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_dissipation_check.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_element_ordering.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_ex07.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_find_tm_crossing_error.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_fix_paths_user_expansion.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_gather_results_sweep_data.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_integration_lammps.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_library_runner.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_meam_pair_coeff.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_melting_temperature_n_iterations.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_options.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_phase_detection_defaults.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_phase_diagram.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_phase_diagram_validation.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_postprocessing.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_preflight.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_pressure_convergence_pv_fit.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_pressure_coupling.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_qtb_input.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_runner.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_scheduler.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_seed.py +0 -0
- {calphy-2.1.1 → calphy-2.3.0}/tests/test_solid_methods.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: calphy
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: free energy calculation for python
|
|
5
5
|
Author: Yury Lysogorskiy, Ralf Drautz
|
|
6
6
|
Author-email: Sarath Menon <sarath.menon@ruhr-uni-bochum.de>
|
|
@@ -26,7 +26,8 @@ Requires-Dist: mendeleev
|
|
|
26
26
|
Requires-Dist: tqdm
|
|
27
27
|
Requires-Dist: scipy
|
|
28
28
|
Requires-Dist: pydantic>=2.3.0
|
|
29
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: ase
|
|
30
|
+
Requires-Dist: pyscal3>=4.0.0
|
|
30
31
|
Provides-Extra: test
|
|
31
32
|
Requires-Dist: pytest>=7; extra == "test"
|
|
32
33
|
Provides-Extra: materials-project
|
|
@@ -42,6 +43,7 @@ Dynamic: license-file
|
|
|
42
43
|
- <img src="https://render.githubusercontent.com/render/math?math=F(V_i,T_i)"> and <img src="https://render.githubusercontent.com/render/math?math=G(P_i,T_i)"> for both solid and liquid phases at the given thermodynamic conditions using [non-equilibrium Hamiltonian interpolation](https://linkinghub.elsevier.com/retrieve/pii/S0927025615007089).
|
|
43
44
|
- <img src="https://render.githubusercontent.com/render/math?math=F(T_i \to T_f)"> and <img src="https://render.githubusercontent.com/render/math?math=G(T_i \to T_f)">, temperature dependence of Gibbs and Helmholtz free energies using the [reversible scaling](https://link.aps.org/doi/10.1103/PhysRevLett.83.3973) approach.
|
|
44
45
|
- Calculation of solid-solid or solid-liquid phase transition temperatures.
|
|
46
|
+
- Calculation of solid-liquid coexistence lines <img src="https://render.githubusercontent.com/render/math?math=P_{coex}(T)"> over a wide pressure range from a single known coexistence point using [dynamic Clausius-Clapeyron integration](https://doi.org/10.1063/1.1420486).
|
|
45
47
|
- Calculation of specific heat <img src="https://render.githubusercontent.com/render/math?math=c_P(T)"> as a function of temperature.
|
|
46
48
|
- Calculation of <img src="https://render.githubusercontent.com/render/math?math=F(x, T)"> and <img src="https://render.githubusercontent.com/render/math?math=G(x, T)"> for multicomponent systems using [alchemical transformations](https://journals.aps.org/prmaterials/abstract/10.1103/PhysRevMaterials.5.103801).
|
|
47
49
|
- [Upsampling calculations](https://journals.aps.org/prmaterials/abstract/10.1103/PhysRevMaterials.5.103801).
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
- <img src="https://render.githubusercontent.com/render/math?math=F(V_i,T_i)"> and <img src="https://render.githubusercontent.com/render/math?math=G(P_i,T_i)"> for both solid and liquid phases at the given thermodynamic conditions using [non-equilibrium Hamiltonian interpolation](https://linkinghub.elsevier.com/retrieve/pii/S0927025615007089).
|
|
6
6
|
- <img src="https://render.githubusercontent.com/render/math?math=F(T_i \to T_f)"> and <img src="https://render.githubusercontent.com/render/math?math=G(T_i \to T_f)">, temperature dependence of Gibbs and Helmholtz free energies using the [reversible scaling](https://link.aps.org/doi/10.1103/PhysRevLett.83.3973) approach.
|
|
7
7
|
- Calculation of solid-solid or solid-liquid phase transition temperatures.
|
|
8
|
+
- Calculation of solid-liquid coexistence lines <img src="https://render.githubusercontent.com/render/math?math=P_{coex}(T)"> over a wide pressure range from a single known coexistence point using [dynamic Clausius-Clapeyron integration](https://doi.org/10.1063/1.1420486).
|
|
8
9
|
- Calculation of specific heat <img src="https://render.githubusercontent.com/render/math?math=c_P(T)"> as a function of temperature.
|
|
9
10
|
- Calculation of <img src="https://render.githubusercontent.com/render/math?math=F(x, T)"> and <img src="https://render.githubusercontent.com/render/math?math=G(x, T)"> for multicomponent systems using [alchemical transformations](https://journals.aps.org/prmaterials/abstract/10.1103/PhysRevMaterials.5.103801).
|
|
10
11
|
- [Upsampling calculations](https://journals.aps.org/prmaterials/abstract/10.1103/PhysRevMaterials.5.103801).
|
|
@@ -58,6 +58,91 @@ class Alchemy(cph.Phase):
|
|
|
58
58
|
calculation=calculation, simfolder=simfolder, log_to_screen=log_to_screen,
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
+
def _end_states(self):
|
|
62
|
+
"""
|
|
63
|
+
The initial and final potential of the alchemical switch.
|
|
64
|
+
|
|
65
|
+
The pair lists hold both end states back to back: the first half
|
|
66
|
+
describes the initial potential, the second half the final one. Each
|
|
67
|
+
half is one physical potential made of one component (a plain pair
|
|
68
|
+
style) or several (``pair_mode: overlay``, or the component-wise
|
|
69
|
+
rewrite done by composition scaling). A component is the tuple
|
|
70
|
+
(style with options, style name, pair_coeff).
|
|
71
|
+
"""
|
|
72
|
+
styles = self.calc._pair_style_with_options
|
|
73
|
+
names = self.calc._pair_style_names
|
|
74
|
+
coeffs = self.calc.pair_coeff
|
|
75
|
+
if len(styles) % 2 or len(styles) != len(coeffs):
|
|
76
|
+
raise ValueError(
|
|
77
|
+
"alchemical switching needs pair_style and pair_coeff lists of "
|
|
78
|
+
"equal, even length (initial components followed by final "
|
|
79
|
+
"components); got %d pair styles and %d pair coeffs"
|
|
80
|
+
% (len(styles), len(coeffs))
|
|
81
|
+
)
|
|
82
|
+
half = len(styles) // 2
|
|
83
|
+
components = list(zip(styles, names, coeffs))
|
|
84
|
+
return components[:half], components[half:]
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _pure_pair_style_command(components):
|
|
88
|
+
"""``pair_style`` for one potential on its own: the plain style for a
|
|
89
|
+
single component, ``hybrid/overlay`` of the components otherwise."""
|
|
90
|
+
if len(components) == 1:
|
|
91
|
+
return "pair_style %s" % components[0][0]
|
|
92
|
+
return "pair_style hybrid/overlay %s" % " ".join(
|
|
93
|
+
style for style, _, _ in components
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def _pure_pair_coeff_commands(components):
|
|
98
|
+
"""``pair_coeff`` lines matching :meth:`_pure_pair_style_command`."""
|
|
99
|
+
if len(components) == 1:
|
|
100
|
+
return ["pair_coeff %s" % components[0][2]]
|
|
101
|
+
return ph.hybrid_pair_coeff_commands_for(
|
|
102
|
+
[name for _, name, _ in components], [coeff for _, _, coeff in components]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def _scaled_pair_commands(self, initial, final, initial_scale, final_scale):
|
|
106
|
+
"""
|
|
107
|
+
Install ``hybrid/scaled`` with every initial component scaled by
|
|
108
|
+
``initial_scale`` and every final component by ``final_scale``,
|
|
109
|
+
then define computes ``c1``/``c2`` (or ``c1_1, c1_2, ...`` for
|
|
110
|
+
multi-component ends) holding the two potential energies and the
|
|
111
|
+
per-atom variables ``dU1``/``dU2`` recorded during switching.
|
|
112
|
+
|
|
113
|
+
Returns the commands and the compute ids to release afterwards.
|
|
114
|
+
"""
|
|
115
|
+
terms = ["%s %s" % (initial_scale, style) for style, _, _ in initial]
|
|
116
|
+
terms += ["%s %s" % (final_scale, style) for style, _, _ in final]
|
|
117
|
+
commands = ["pair_style hybrid/scaled %s" % " ".join(terms)]
|
|
118
|
+
|
|
119
|
+
components = initial + final
|
|
120
|
+
names = [name for _, name, _ in components]
|
|
121
|
+
commands += ph.hybrid_pair_coeff_commands_for(
|
|
122
|
+
names, [coeff for _, _, coeff in components]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
tags = ph.hybrid_component_tags(names)
|
|
126
|
+
compute_ids, energies = [], []
|
|
127
|
+
for label, part, part_tags in (
|
|
128
|
+
("c1", initial, tags[: len(initial)]),
|
|
129
|
+
("c2", final, tags[len(initial) :]),
|
|
130
|
+
):
|
|
131
|
+
ids = (
|
|
132
|
+
[label]
|
|
133
|
+
if len(part) == 1
|
|
134
|
+
else ["%s_%d" % (label, i) for i in range(1, len(part) + 1)]
|
|
135
|
+
)
|
|
136
|
+
compute_commands, energy = ph.hybrid_pair_compute_commands(ids, part_tags)
|
|
137
|
+
commands += compute_commands
|
|
138
|
+
compute_ids += ids
|
|
139
|
+
energies.append(energy if len(ids) == 1 else "(%s)" % energy)
|
|
140
|
+
|
|
141
|
+
commands.append("variable step equal step")
|
|
142
|
+
commands.append("variable dU1 equal %s/atoms" % energies[0])
|
|
143
|
+
commands.append("variable dU2 equal %s/atoms" % energies[1])
|
|
144
|
+
return commands, compute_ids
|
|
145
|
+
|
|
61
146
|
def _coupling_pair(self, lmp, ramp="0.0", tag=None):
|
|
62
147
|
"""Coupling mode: base components (all but last) at constant scale
|
|
63
148
|
1.0, the LAST component scaled by `ramp` (a constant or v_name).
|
|
@@ -180,13 +265,16 @@ class Alchemy(cph.Phase):
|
|
|
180
265
|
lmp = ph.create_structure(lmp, self.calc)
|
|
181
266
|
self._coupling_pair(lmp, ramp="0.0")
|
|
182
267
|
else:
|
|
183
|
-
|
|
268
|
+
# equilibrate with the initial potential on its own
|
|
269
|
+
initial, _ = self._end_states()
|
|
270
|
+
lmp.command(self._pure_pair_style_command(initial))
|
|
184
271
|
|
|
185
272
|
# set up structure
|
|
186
273
|
lmp = ph.create_structure(lmp, self.calc)
|
|
187
274
|
|
|
188
275
|
# set up potential
|
|
189
|
-
|
|
276
|
+
for command in self._pure_pair_coeff_commands(initial):
|
|
277
|
+
lmp.command(command)
|
|
190
278
|
lmp = ph.set_mass(lmp, self.calc)
|
|
191
279
|
|
|
192
280
|
# add some computes
|
|
@@ -254,6 +342,8 @@ class Alchemy(cph.Phase):
|
|
|
254
342
|
if self.calc.alchemy_coupling:
|
|
255
343
|
return self._run_integration_coupling(iteration=iteration)
|
|
256
344
|
|
|
345
|
+
initial, final = self._end_states()
|
|
346
|
+
|
|
257
347
|
# create lammps object
|
|
258
348
|
lmp = ph.create_object(self.calc, self.simfolder)
|
|
259
349
|
|
|
@@ -261,22 +351,16 @@ class Alchemy(cph.Phase):
|
|
|
261
351
|
lmp.command("variable li equal 1.0")
|
|
262
352
|
lmp.command("variable lf equal 0.0")
|
|
263
353
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
# read dump file
|
|
267
|
-
# conf = os.path.join(self.simfolder, "conf.equilibration.dump")
|
|
354
|
+
# the equilibrated configuration is read in with the initial potential
|
|
355
|
+
lmp.command(self._pure_pair_style_command(initial))
|
|
268
356
|
conf = os.path.join(self.simfolder, "conf.equilibration.data")
|
|
269
357
|
lmp = ph.read_data(lmp, conf)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
# here we only need to set one potential
|
|
273
|
-
lmp.command(f"pair_coeff {self.calc.pair_coeff[0]}")
|
|
358
|
+
for command in self._pure_pair_coeff_commands(initial):
|
|
359
|
+
lmp.command(command)
|
|
274
360
|
lmp = ph.set_mass(lmp, self.calc)
|
|
275
361
|
|
|
276
|
-
# NEW ADDED
|
|
277
362
|
lmp.command("group g1 type 1")
|
|
278
363
|
lmp.command("group g2 type 2")
|
|
279
|
-
# lmp = ph.set_double_hybrid_potential(lmp, self.options, self.calc._pressureair_style, self.calc._pressureair_coeff)
|
|
280
364
|
|
|
281
365
|
# remap the box to get the correct pressure
|
|
282
366
|
lmp = ph.remap_box(lmp, self.lx, self.ly, self.lz)
|
|
@@ -316,143 +400,20 @@ class Alchemy(cph.Phase):
|
|
|
316
400
|
# equilibration run is over
|
|
317
401
|
|
|
318
402
|
# ---------------------------------------------------------------
|
|
319
|
-
# FWD cycle
|
|
403
|
+
# FWD cycle: initial potential scaled 1 -> 0, final potential 0 -> 1
|
|
320
404
|
# ---------------------------------------------------------------
|
|
321
405
|
lmp.command("variable flambda equal ramp(${li},${lf})")
|
|
322
406
|
lmp.command("variable blambda equal ramp(${lf},${li})")
|
|
323
407
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
# Compute pair definitions
|
|
327
|
-
if self.calc.pair_style[0] == self.calc.pair_style[1]:
|
|
328
|
-
pc = self.calc.pair_coeff[0]
|
|
329
|
-
pcraw = pc.split()
|
|
330
|
-
pc1 = " ".join(
|
|
331
|
-
[
|
|
332
|
-
*pcraw[:2],
|
|
333
|
-
*[
|
|
334
|
-
self.calc._pair_style_names[0],
|
|
335
|
-
],
|
|
336
|
-
"1",
|
|
337
|
-
*pcraw[2:],
|
|
338
|
-
]
|
|
339
|
-
)
|
|
340
|
-
pc = self.calc.pair_coeff[1]
|
|
341
|
-
pcraw = pc.split()
|
|
342
|
-
pc2 = " ".join(
|
|
343
|
-
[
|
|
344
|
-
*pcraw[:2],
|
|
345
|
-
*[
|
|
346
|
-
self.calc._pair_style_names[1],
|
|
347
|
-
],
|
|
348
|
-
"2",
|
|
349
|
-
*pcraw[2:],
|
|
350
|
-
]
|
|
351
|
-
)
|
|
352
|
-
else:
|
|
353
|
-
pc = self.calc.pair_coeff[0]
|
|
354
|
-
pcraw = pc.split()
|
|
355
|
-
pc1 = " ".join(
|
|
356
|
-
[
|
|
357
|
-
*pcraw[:2],
|
|
358
|
-
*[
|
|
359
|
-
self.calc._pair_style_names[0],
|
|
360
|
-
],
|
|
361
|
-
*pcraw[2:],
|
|
362
|
-
]
|
|
363
|
-
)
|
|
364
|
-
pc = self.calc.pair_coeff[1]
|
|
365
|
-
pcraw = pc.split()
|
|
366
|
-
pc2 = " ".join(
|
|
367
|
-
[
|
|
368
|
-
*pcraw[:2],
|
|
369
|
-
*[
|
|
370
|
-
self.calc._pair_style_names[1],
|
|
371
|
-
],
|
|
372
|
-
*pcraw[2:],
|
|
373
|
-
]
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
lmp.command(
|
|
377
|
-
"pair_style hybrid/scaled v_flambda %s v_blambda %s"
|
|
378
|
-
% (
|
|
379
|
-
self.calc._pair_style_with_options[0],
|
|
380
|
-
self.calc._pair_style_with_options[1],
|
|
381
|
-
)
|
|
408
|
+
commands, compute_ids = self._scaled_pair_commands(
|
|
409
|
+
initial, final, "v_flambda", "v_blambda"
|
|
382
410
|
)
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
# apply pair force commands
|
|
387
|
-
if self.calc._pair_style_names[0] == self.calc._pair_style_names[1]:
|
|
388
|
-
lmp.command(
|
|
389
|
-
"compute c1 all pair %s 1" % self.calc._pair_style_names[0]
|
|
390
|
-
)
|
|
391
|
-
lmp.command(
|
|
392
|
-
"compute c2 all pair %s 2" % self.calc._pair_style_names[1]
|
|
393
|
-
)
|
|
394
|
-
else:
|
|
395
|
-
lmp.command(
|
|
396
|
-
"compute c1 all pair %s" % self.calc._pair_style_names[0]
|
|
397
|
-
)
|
|
398
|
-
lmp.command(
|
|
399
|
-
"compute c2 all pair %s" % self.calc._pair_style_names[1]
|
|
400
|
-
)
|
|
411
|
+
for command in commands:
|
|
412
|
+
lmp.command(command)
|
|
401
413
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
"variable dU1 equal c_c1/atoms"
|
|
406
|
-
) # Driving-force obtained from NEHI procedure.
|
|
407
|
-
lmp.command("variable dU2 equal c_c2/atoms")
|
|
408
|
-
|
|
409
|
-
# add swaps if n_swap is > 0 - forward pass
|
|
410
|
-
if (
|
|
411
|
-
self.calc.monte_carlo.n_swaps > 0
|
|
412
|
-
and len(self.calc.monte_carlo.forward_swap_types) >= 2
|
|
413
|
-
):
|
|
414
|
-
swap_types = self.calc.monte_carlo.forward_swap_types
|
|
415
|
-
swap_combos = list(itertools.combinations(swap_types, 2))
|
|
416
|
-
self.logger.info(
|
|
417
|
-
f"Forward pass: {self.calc.monte_carlo.n_swaps} swap moves per combo, {len(swap_combos)} combinations every {self.calc.monte_carlo.n_steps}"
|
|
418
|
-
)
|
|
419
|
-
for combo in swap_combos:
|
|
420
|
-
self.logger.info(f" Swapping types: {combo[0]} <-> {combo[1]}")
|
|
421
|
-
|
|
422
|
-
for idx, (type1, type2) in enumerate(swap_combos):
|
|
423
|
-
swap_str = f"{type1} {type2}"
|
|
424
|
-
if self.calc.monte_carlo.use_custom_lammps:
|
|
425
|
-
lmp.command(
|
|
426
|
-
"fix swap%d all atom/swap %d %d %d %f ke no types %s noforce yes localE yes"
|
|
427
|
-
% (
|
|
428
|
-
idx,
|
|
429
|
-
self.calc.monte_carlo.n_steps,
|
|
430
|
-
self.calc.monte_carlo.n_swaps,
|
|
431
|
-
np.random.randint(1, 10000),
|
|
432
|
-
self.calc._temperature,
|
|
433
|
-
swap_str,
|
|
434
|
-
)
|
|
435
|
-
)
|
|
436
|
-
else:
|
|
437
|
-
lmp.command(
|
|
438
|
-
"fix swap%d all atom/swap %d %d %d %f ke no types %s"
|
|
439
|
-
% (
|
|
440
|
-
idx,
|
|
441
|
-
self.calc.monte_carlo.n_steps,
|
|
442
|
-
self.calc.monte_carlo.n_swaps,
|
|
443
|
-
np.random.randint(1, 10000),
|
|
444
|
-
self.calc._temperature,
|
|
445
|
-
swap_str,
|
|
446
|
-
)
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
# Use the first swap fix for output tracking
|
|
450
|
-
# lmp.command("variable a equal f_swap0[1]")
|
|
451
|
-
# lmp.command("variable b equal f_swap0[2]")
|
|
452
|
-
# lmp.command(
|
|
453
|
-
# 'fix swap_print all print 1 "${a} ${b} ${flambda}" screen no file swap.forward_%d.dat'
|
|
454
|
-
# % iteration
|
|
455
|
-
# )
|
|
414
|
+
swap_fixes = self._add_swap_fixes(
|
|
415
|
+
lmp, self.calc.monte_carlo.forward_swap_types, "Forward"
|
|
416
|
+
)
|
|
456
417
|
|
|
457
418
|
# Thermo output.
|
|
458
419
|
lmp.command("thermo_style custom step v_dU1 v_dU2")
|
|
@@ -467,21 +428,16 @@ class Alchemy(cph.Phase):
|
|
|
467
428
|
)
|
|
468
429
|
lmp.command("run %d" % self.calc._n_switching_steps)
|
|
469
430
|
|
|
470
|
-
# now equilibrate at the second potential
|
|
471
431
|
lmp.command("unfix f2")
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if self.calc.monte_carlo.n_swaps > 0:
|
|
477
|
-
swap_types = self.calc.monte_carlo.forward_swap_types
|
|
478
|
-
swap_combos = list(itertools.combinations(swap_types, 2))
|
|
479
|
-
for idx in range(len(swap_combos)):
|
|
480
|
-
lmp.command(f"unfix swap{idx}")
|
|
481
|
-
# lmp.command("unfix swap_print")
|
|
432
|
+
for compute_id in compute_ids:
|
|
433
|
+
lmp.command("uncompute %s" % compute_id)
|
|
434
|
+
for swap_fix in swap_fixes:
|
|
435
|
+
lmp.command("unfix %s" % swap_fix)
|
|
482
436
|
|
|
483
|
-
|
|
484
|
-
lmp.command(
|
|
437
|
+
# now equilibrate with the final potential on its own
|
|
438
|
+
lmp.command(self._pure_pair_style_command(final))
|
|
439
|
+
for command in self._pure_pair_coeff_commands(final):
|
|
440
|
+
lmp.command(command)
|
|
485
441
|
|
|
486
442
|
# Thermo output.
|
|
487
443
|
lmp.command("thermo_style custom step pe")
|
|
@@ -490,90 +446,21 @@ class Alchemy(cph.Phase):
|
|
|
490
446
|
# run eqbrm run
|
|
491
447
|
lmp.command("run %d" % self.calc.n_equilibration_steps)
|
|
492
448
|
|
|
493
|
-
#
|
|
449
|
+
# ---------------------------------------------------------------
|
|
450
|
+
# BKD cycle: initial potential scaled 0 -> 1, final potential 1 -> 0
|
|
451
|
+
# ---------------------------------------------------------------
|
|
494
452
|
lmp.command("variable flambda equal ramp(${lf},${li})")
|
|
495
453
|
lmp.command("variable blambda equal ramp(${li},${lf})")
|
|
496
454
|
|
|
497
|
-
|
|
498
|
-
"
|
|
499
|
-
% (
|
|
500
|
-
self.calc._pair_style_with_options[0],
|
|
501
|
-
self.calc._pair_style_with_options[1],
|
|
502
|
-
)
|
|
455
|
+
commands, compute_ids = self._scaled_pair_commands(
|
|
456
|
+
initial, final, "v_flambda", "v_blambda"
|
|
503
457
|
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
# apply pair force commands
|
|
508
|
-
if self.calc._pair_style_names[0] == self.calc._pair_style_names[1]:
|
|
509
|
-
lmp.command(
|
|
510
|
-
"compute c1 all pair %s 1" % self.calc._pair_style_names[0]
|
|
511
|
-
)
|
|
512
|
-
lmp.command(
|
|
513
|
-
"compute c2 all pair %s 2" % self.calc._pair_style_names[1]
|
|
514
|
-
)
|
|
515
|
-
else:
|
|
516
|
-
lmp.command(
|
|
517
|
-
"compute c1 all pair %s" % self.calc._pair_style_names[0]
|
|
518
|
-
)
|
|
519
|
-
lmp.command(
|
|
520
|
-
"compute c2 all pair %s" % self.calc._pair_style_names[1]
|
|
521
|
-
)
|
|
458
|
+
for command in commands:
|
|
459
|
+
lmp.command(command)
|
|
522
460
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
"variable dU1 equal c_c1/atoms"
|
|
527
|
-
) # Driving-force obtained from NEHI procedure.
|
|
528
|
-
lmp.command("variable dU2 equal c_c2/atoms")
|
|
529
|
-
|
|
530
|
-
# add swaps if n_swap is > 0 - reverse pass
|
|
531
|
-
if (
|
|
532
|
-
self.calc.monte_carlo.n_swaps > 0
|
|
533
|
-
and len(self.calc.monte_carlo.reverse_swap_types) >= 2
|
|
534
|
-
):
|
|
535
|
-
swap_types = self.calc.monte_carlo.reverse_swap_types
|
|
536
|
-
swap_combos = list(itertools.combinations(swap_types, 2))
|
|
537
|
-
self.logger.info(
|
|
538
|
-
f"Reverse pass: {self.calc.monte_carlo.n_swaps} swap moves per combo, {len(swap_combos)} combinations every {self.calc.monte_carlo.n_steps}"
|
|
539
|
-
)
|
|
540
|
-
for combo in swap_combos:
|
|
541
|
-
self.logger.info(f" Swapping types: {combo[0]} <-> {combo[1]}")
|
|
542
|
-
|
|
543
|
-
for idx, (type1, type2) in enumerate(swap_combos):
|
|
544
|
-
swap_str = f"{type1} {type2}"
|
|
545
|
-
if self.calc.monte_carlo.use_custom_lammps:
|
|
546
|
-
lmp.command(
|
|
547
|
-
"fix swap%d all atom/swap %d %d %d %f ke no types %s noforce yes localE yes"
|
|
548
|
-
% (
|
|
549
|
-
idx,
|
|
550
|
-
self.calc.monte_carlo.n_steps,
|
|
551
|
-
self.calc.monte_carlo.n_swaps,
|
|
552
|
-
np.random.randint(1, 10000),
|
|
553
|
-
self.calc._temperature,
|
|
554
|
-
swap_str,
|
|
555
|
-
)
|
|
556
|
-
)
|
|
557
|
-
else:
|
|
558
|
-
lmp.command(
|
|
559
|
-
"fix swap%d all atom/swap %d %d %d %f ke no types %s"
|
|
560
|
-
% (
|
|
561
|
-
idx,
|
|
562
|
-
self.calc.monte_carlo.n_steps,
|
|
563
|
-
self.calc.monte_carlo.n_swaps,
|
|
564
|
-
np.random.randint(1, 10000),
|
|
565
|
-
self.calc._temperature,
|
|
566
|
-
swap_str,
|
|
567
|
-
)
|
|
568
|
-
)
|
|
569
|
-
|
|
570
|
-
# Use the first swap fix for output tracking
|
|
571
|
-
# lmp.command("variable a equal f_swap0[1]")
|
|
572
|
-
# lmp.command("variable b equal f_swap0[2]")
|
|
573
|
-
# lmp.command(
|
|
574
|
-
#'fix swap_print all print 1 "${a} ${b} ${blambda}" screen no file swap.backward_%d.dat'
|
|
575
|
-
# % iteration
|
|
576
|
-
# )
|
|
461
|
+
swap_fixes = self._add_swap_fixes(
|
|
462
|
+
lmp, self.calc.monte_carlo.reverse_swap_types, "Reverse"
|
|
463
|
+
)
|
|
577
464
|
|
|
578
465
|
# Thermo output.
|
|
579
466
|
lmp.command("thermo_style custom step v_dU1 v_dU2")
|
|
@@ -588,21 +475,52 @@ class Alchemy(cph.Phase):
|
|
|
588
475
|
)
|
|
589
476
|
lmp.command("run %d" % self.calc._n_switching_steps)
|
|
590
477
|
|
|
591
|
-
# now equilibrate at the second potential
|
|
592
478
|
lmp.command("unfix f2")
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
swap_types = self.calc.monte_carlo.reverse_swap_types
|
|
598
|
-
swap_combos = list(itertools.combinations(swap_types, 2))
|
|
599
|
-
for idx in range(len(swap_combos)):
|
|
600
|
-
lmp.command(f"unfix swap{idx}")
|
|
601
|
-
# lmp.command("unfix swap_print")
|
|
479
|
+
for compute_id in compute_ids:
|
|
480
|
+
lmp.command("uncompute %s" % compute_id)
|
|
481
|
+
for swap_fix in swap_fixes:
|
|
482
|
+
lmp.command("unfix %s" % swap_fix)
|
|
602
483
|
|
|
603
484
|
self.lammps_close(lmp=lmp)
|
|
604
485
|
lmp.rotate_logs("integration")
|
|
605
486
|
|
|
487
|
+
def _add_swap_fixes(self, lmp, swap_types, pass_name):
|
|
488
|
+
"""
|
|
489
|
+
Add ``fix atom/swap`` moves between every pair of ``swap_types`` when
|
|
490
|
+
Monte Carlo swaps are requested; returns the fix ids that were added.
|
|
491
|
+
"""
|
|
492
|
+
mc = self.calc.monte_carlo
|
|
493
|
+
if not (mc.n_swaps > 0 and len(swap_types) >= 2):
|
|
494
|
+
return []
|
|
495
|
+
|
|
496
|
+
swap_combos = list(itertools.combinations(swap_types, 2))
|
|
497
|
+
self.logger.info(
|
|
498
|
+
f"{pass_name} pass: {mc.n_swaps} swap moves per combo, "
|
|
499
|
+
f"{len(swap_combos)} combinations every {mc.n_steps}"
|
|
500
|
+
)
|
|
501
|
+
for combo in swap_combos:
|
|
502
|
+
self.logger.info(f" Swapping types: {combo[0]} <-> {combo[1]}")
|
|
503
|
+
|
|
504
|
+
fix_ids = []
|
|
505
|
+
for idx, (type1, type2) in enumerate(swap_combos):
|
|
506
|
+
fix_id = "swap%d" % idx
|
|
507
|
+
extra = " noforce yes localE yes" if mc.use_custom_lammps else ""
|
|
508
|
+
lmp.command(
|
|
509
|
+
"fix %s all atom/swap %d %d %d %f ke no types %s %s%s"
|
|
510
|
+
% (
|
|
511
|
+
fix_id,
|
|
512
|
+
mc.n_steps,
|
|
513
|
+
mc.n_swaps,
|
|
514
|
+
np.random.randint(1, 10000),
|
|
515
|
+
self.calc._temperature,
|
|
516
|
+
type1,
|
|
517
|
+
type2,
|
|
518
|
+
extra,
|
|
519
|
+
)
|
|
520
|
+
)
|
|
521
|
+
fix_ids.append(fix_id)
|
|
522
|
+
return fix_ids
|
|
523
|
+
|
|
606
524
|
def thermodynamic_integration(self):
|
|
607
525
|
"""
|
|
608
526
|
Calculate free energy after integration step
|
|
@@ -27,11 +27,9 @@ import re
|
|
|
27
27
|
import numpy as np
|
|
28
28
|
import os
|
|
29
29
|
import random
|
|
30
|
-
import pyscal3.core as pc
|
|
31
30
|
from mendeleev import element
|
|
32
31
|
from ase.io import read, write
|
|
33
32
|
from ase.atoms import Atoms
|
|
34
|
-
from pyscal3.core import element_dict
|
|
35
33
|
from calphy.integrators import kb
|
|
36
34
|
|
|
37
35
|
|
|
@@ -163,9 +161,9 @@ class CompositionTransformation:
|
|
|
163
161
|
entropy_term = kb * np.sum(ents)
|
|
164
162
|
return entropy_term
|
|
165
163
|
|
|
166
|
-
def
|
|
164
|
+
def read_structure(self):
|
|
167
165
|
"""
|
|
168
|
-
|
|
166
|
+
Read the input structure and set up the element <-> type mappings
|
|
169
167
|
"""
|
|
170
168
|
# Create Z_of_type mapping to properly read LAMMPS data files
|
|
171
169
|
# This ensures atoms are correctly identified by their element
|
|
@@ -178,20 +176,19 @@ class CompositionTransformation:
|
|
|
178
176
|
aseobj = read(
|
|
179
177
|
self.calc.lattice, format="lammps-data", style="atomic", Z_of_type=Z_of_type
|
|
180
178
|
)
|
|
181
|
-
pstruct = pc.System(aseobj, format="ase")
|
|
182
179
|
|
|
183
180
|
# here we have to validate the input composition dict; and map it
|
|
184
|
-
typelist =
|
|
181
|
+
typelist = aseobj.get_chemical_symbols()
|
|
185
182
|
types, typecounts = np.unique(typelist, return_counts=True)
|
|
186
183
|
composition = {types[x]: typecounts[x] for x in range(len(types))}
|
|
187
184
|
|
|
188
185
|
atomsymbols = self.calc.element
|
|
189
186
|
atomtypes = [x + 1 for x in range(len(self.calc.element))]
|
|
190
187
|
|
|
191
|
-
self.
|
|
188
|
+
self.structure = aseobj
|
|
192
189
|
self.typedict = dict(zip(atomsymbols, atomtypes))
|
|
193
190
|
self.reversetypedict = dict(zip(atomtypes, atomsymbols))
|
|
194
|
-
self.natoms = self.
|
|
191
|
+
self.natoms = len(self.structure)
|
|
195
192
|
|
|
196
193
|
# Count of actual unique atom types present in the structure
|
|
197
194
|
# This matches what's declared in the LAMMPS data file header
|
|
@@ -239,8 +236,9 @@ class CompositionTransformation:
|
|
|
239
236
|
self.atom_mark.append(False)
|
|
240
237
|
|
|
241
238
|
# Use species (element symbols) instead of numeric types
|
|
242
|
-
self.atom_species = self.
|
|
243
|
-
|
|
239
|
+
self.atom_species = self.structure.get_chemical_symbols()
|
|
240
|
+
# placeholder types; update_types assigns the mapping type of every atom
|
|
241
|
+
self.atom_type = [1] * self.natoms
|
|
244
242
|
self.mappings = [f"{x}-{x}" for x in self.atom_species]
|
|
245
243
|
|
|
246
244
|
def update_mark_atoms(self):
|
|
@@ -378,9 +376,6 @@ class CompositionTransformation:
|
|
|
378
376
|
for x in range(len(self.atom_type)):
|
|
379
377
|
self.atom_type[x] = self.mappingdict[self.mappings[x]]
|
|
380
378
|
|
|
381
|
-
# Update pyscal structure types
|
|
382
|
-
self.pyscal_structure.atoms.types = self.atom_type
|
|
383
|
-
|
|
384
379
|
def iselement(self, symbol):
|
|
385
380
|
try:
|
|
386
381
|
_ = element(symbol)
|
|
@@ -582,9 +577,8 @@ class CompositionTransformation:
|
|
|
582
577
|
from ase.io import write as ase_write
|
|
583
578
|
from ase import Atoms as ASEAtoms
|
|
584
579
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
cell = self.pyscal_structure.box
|
|
580
|
+
positions = self.structure.positions
|
|
581
|
+
cell = self.structure.cell
|
|
588
582
|
|
|
589
583
|
# Build per-atom type numbers and chemical symbols.
|
|
590
584
|
# for_fe_mode: remap each atom to its TARGET element's typedict entry so
|
|
@@ -598,8 +592,8 @@ class CompositionTransformation:
|
|
|
598
592
|
invert_typedict = {v: k for k, v in self.typedict.items()}
|
|
599
593
|
symbols = [invert_typedict[t] for t in fe_types]
|
|
600
594
|
else:
|
|
601
|
-
fe_types = None # will use
|
|
602
|
-
symbols = [self.reversetypedict[t] for t in self.
|
|
595
|
+
fe_types = None # will use the mapping types below
|
|
596
|
+
symbols = [self.reversetypedict[t] for t in self.atom_type]
|
|
603
597
|
|
|
604
598
|
ase_atoms = ASEAtoms(symbols=symbols, positions=positions, cell=cell, pbc=True)
|
|
605
599
|
|
|
@@ -611,7 +605,7 @@ class CompositionTransformation:
|
|
|
611
605
|
lines = f.readlines()
|
|
612
606
|
|
|
613
607
|
# Choose which per-atom type list to use
|
|
614
|
-
atom_types_to_write = fe_types if for_fe_mode else list(self.
|
|
608
|
+
atom_types_to_write = fe_types if for_fe_mode else list(self.atom_type)
|
|
615
609
|
|
|
616
610
|
# Find the Atoms section and replace type numbers
|
|
617
611
|
# Support different ASE formats: "Atoms # atomic", "Atoms # full", or just "Atoms"
|
|
@@ -635,7 +629,7 @@ class CompositionTransformation:
|
|
|
635
629
|
break
|
|
636
630
|
|
|
637
631
|
# Verify all atoms were updated
|
|
638
|
-
expected_atoms = len(self.
|
|
632
|
+
expected_atoms = len(self.atom_type)
|
|
639
633
|
if atom_idx != expected_atoms:
|
|
640
634
|
raise RuntimeError(
|
|
641
635
|
f"Failed to update all atoms in {outfilename}. "
|
|
@@ -666,7 +660,7 @@ class CompositionTransformation:
|
|
|
666
660
|
self.unique_mappings = []
|
|
667
661
|
|
|
668
662
|
self.get_composition_transformation()
|
|
669
|
-
self.
|
|
663
|
+
self.read_structure()
|
|
670
664
|
|
|
671
665
|
self.mark_atoms()
|
|
672
666
|
self.update_mark_atoms()
|