calphy 2.1.1__tar.gz → 2.1.2__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.1.2}/PKG-INFO +1 -1
- {calphy-2.1.1 → calphy-2.1.2}/calphy/__init__.py +1 -1
- {calphy-2.1.1 → calphy-2.1.2}/calphy/alchemy.py +163 -245
- {calphy-2.1.1 → calphy-2.1.2}/calphy/helpers.py +75 -46
- {calphy-2.1.1 → calphy-2.1.2}/calphy/input.py +29 -7
- {calphy-2.1.1 → calphy-2.1.2}/calphy/routines.py +52 -13
- {calphy-2.1.1 → calphy-2.1.2}/calphy.egg-info/PKG-INFO +1 -1
- {calphy-2.1.1 → calphy-2.1.2}/calphy.egg-info/SOURCES.txt +1 -0
- {calphy-2.1.1 → calphy-2.1.2}/pyproject.toml +1 -1
- calphy-2.1.2/tests/test_composition_scaling_potential.py +76 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_golden_streams.py +47 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_helpers.py +54 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_input_validation.py +55 -0
- {calphy-2.1.1 → calphy-2.1.2}/LICENSE +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/README.md +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/clitools.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/composition_transformation.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/errors.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/integrators.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/kernel.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/library_runner.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/liquid.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/phase.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/phase_diagram.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/postprocessing.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/queuekernel.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/range_scan.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/runner.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/scheduler.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/solid.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/splines.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy/utils.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy.egg-info/dependency_links.txt +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy.egg-info/entry_points.txt +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy.egg-info/requires.txt +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/calphy.egg-info/top_level.txt +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/setup.cfg +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_command_vocabulary.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_composition_transformation_bug.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_dissipation_check.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_element_ordering.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_ex07.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_find_tm_crossing_error.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_fix_paths_user_expansion.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_gather_results_sweep_data.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_integration_lammps.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_integrators.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_library_runner.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_meam_pair_coeff.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_melting_temperature_n_iterations.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_options.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_phase_detection_defaults.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_phase_diagram.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_phase_diagram_validation.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_postprocessing.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_preflight.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_pressure_convergence_pv_fit.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_pressure_coupling.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_qtb_input.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_runner.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_scheduler.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_seed.py +0 -0
- {calphy-2.1.1 → calphy-2.1.2}/tests/test_solid_methods.py +0 -0
|
@@ -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
|
|
@@ -208,26 +208,78 @@ def _component_pair_coeffs(options):
|
|
|
208
208
|
return components
|
|
209
209
|
|
|
210
210
|
|
|
211
|
-
def
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
211
|
+
def hybrid_component_tags(style_names):
|
|
212
|
+
"""
|
|
213
|
+
Sub-style tags of the components of a hybrid pair_style.
|
|
214
|
+
|
|
215
|
+
LAMMPS addresses a sub-style of a hybrid pair_style by its name, followed
|
|
216
|
+
by a 1-based occurrence index when the same style appears more than once
|
|
217
|
+
(``pair_coeff * * eam/alloy 2 ...``, ``compute c all pair eam/alloy 2``).
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
style_names : list of str
|
|
222
|
+
Style names in the order they appear in the hybrid pair_style.
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
list of (str, int or None)
|
|
227
|
+
One (name, index) tag per component; the index is None for a style
|
|
228
|
+
that occurs once.
|
|
229
|
+
"""
|
|
230
|
+
counts = Counter(style_names)
|
|
217
231
|
seen = defaultdict(int)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
232
|
+
tags = []
|
|
233
|
+
for name in style_names:
|
|
234
|
+
seen[name] += 1
|
|
235
|
+
tags.append((name, seen[name] if counts[name] > 1 else None))
|
|
236
|
+
return tags
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def hybrid_pair_coeff_commands_for(style_names, pair_coeffs):
|
|
240
|
+
"""
|
|
241
|
+
``pair_coeff`` commands for the components of a hybrid pair_style.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
style_names : list of str
|
|
246
|
+
Style names in hybrid order, one per pair_coeff.
|
|
247
|
+
pair_coeffs : list of str
|
|
248
|
+
The pair_coeff arguments, with or without the style name already
|
|
249
|
+
included after the type pair.
|
|
250
|
+
"""
|
|
251
|
+
return [
|
|
252
|
+
"pair_coeff %s" % _with_hybrid_pair_coeff_style(pair_coeff, name, index)
|
|
253
|
+
for (name, index), pair_coeff in zip(hybrid_component_tags(style_names), pair_coeffs)
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def hybrid_pair_compute_commands(compute_ids, tags):
|
|
258
|
+
"""
|
|
259
|
+
``compute pair`` commands, one per hybrid sub-style tag.
|
|
221
260
|
|
|
261
|
+
Returns the commands and the LAMMPS expression summing the computes.
|
|
262
|
+
"""
|
|
222
263
|
commands = []
|
|
223
|
-
for
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return commands
|
|
264
|
+
for compute_id, (name, index) in zip(compute_ids, tags):
|
|
265
|
+
if index is None:
|
|
266
|
+
commands.append("compute %s all pair %s" % (compute_id, name))
|
|
267
|
+
else:
|
|
268
|
+
commands.append(
|
|
269
|
+
"compute %s all pair %s %d" % (compute_id, name, index)
|
|
270
|
+
)
|
|
271
|
+
return commands, "+".join("c_%s" % compute_id for compute_id in compute_ids)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def hybrid_pair_coeff_commands(options, repeat_index=0, total_repeats=1):
|
|
275
|
+
components = _component_pair_coeffs(options)
|
|
276
|
+
n = len(components)
|
|
277
|
+
style_names = [style_name for style_name, _ in components] * total_repeats
|
|
278
|
+
tags = hybrid_component_tags(style_names)[repeat_index * n : (repeat_index + 1) * n]
|
|
279
|
+
return [
|
|
280
|
+
"pair_coeff %s" % _with_hybrid_pair_coeff_style(pair_coeff, name, index)
|
|
281
|
+
for (name, index), (_, pair_coeff) in zip(tags, components)
|
|
282
|
+
]
|
|
231
283
|
|
|
232
284
|
|
|
233
285
|
def set_pair_coeff(lmp, options):
|
|
@@ -253,35 +305,12 @@ def real_pair_compute_commands(
|
|
|
253
305
|
options, prefix="c_real", total_repeats=1, repeat_index=0
|
|
254
306
|
):
|
|
255
307
|
components = _component_pair_coeffs(options)
|
|
256
|
-
|
|
257
|
-
for _ in
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
for style_name, _ in components:
|
|
263
|
-
seen[style_name] += 1
|
|
264
|
-
|
|
265
|
-
commands = []
|
|
266
|
-
terms = []
|
|
267
|
-
for idx, (style_name, _) in enumerate(components, start=1):
|
|
268
|
-
seen[style_name] += 1
|
|
269
|
-
compute_id = "%s%d" % (prefix, idx)
|
|
270
|
-
if total_counts[style_name] > 1:
|
|
271
|
-
commands.append(
|
|
272
|
-
"compute %s all pair %s %d"
|
|
273
|
-
% (compute_id, style_name, seen[style_name])
|
|
274
|
-
)
|
|
275
|
-
else:
|
|
276
|
-
commands.append(
|
|
277
|
-
"compute %s all pair %s" % (compute_id, style_name)
|
|
278
|
-
)
|
|
279
|
-
terms.append("c_%s" % compute_id)
|
|
280
|
-
return (
|
|
281
|
-
commands,
|
|
282
|
-
"+".join(terms),
|
|
283
|
-
["%s%d" % (prefix, idx) for idx in range(1, len(components) + 1)],
|
|
284
|
-
)
|
|
308
|
+
n = len(components)
|
|
309
|
+
style_names = [style_name for style_name, _ in components] * total_repeats
|
|
310
|
+
tags = hybrid_component_tags(style_names)[repeat_index * n : (repeat_index + 1) * n]
|
|
311
|
+
compute_ids = ["%s%d" % (prefix, idx) for idx in range(1, n + 1)]
|
|
312
|
+
commands, energy = hybrid_pair_compute_commands(compute_ids, tags)
|
|
313
|
+
return commands, energy, compute_ids
|
|
285
314
|
|
|
286
315
|
|
|
287
316
|
def set_potential(lmp, options):
|
|
@@ -53,7 +53,7 @@ from pyscal3.core import structure_dict, element_dict, _make_crystal
|
|
|
53
53
|
from ase.io import read, write
|
|
54
54
|
import shutil
|
|
55
55
|
|
|
56
|
-
__version__ = "2.1.
|
|
56
|
+
__version__ = "2.1.2"
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def _check_equal(val):
|
|
@@ -669,16 +669,38 @@ class Calculation(_StrictInput, title="Main input class"):
|
|
|
669
669
|
raise ValueError(
|
|
670
670
|
"pair_mode overlay expects component pair styles, not a hybrid pair_style"
|
|
671
671
|
)
|
|
672
|
+
if self.mode == "alchemy" and not self.alchemy_coupling:
|
|
673
|
+
raise ValueError(
|
|
674
|
+
"pair_mode overlay is not supported for mode alchemy: the two "
|
|
675
|
+
"pair_style/pair_coeff entries are the potential switched from "
|
|
676
|
+
"and the potential switched to. To change the composition of an "
|
|
677
|
+
"overlay potential use mode composition_scaling."
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
# the components that make up the physical potential: every entry for
|
|
681
|
+
# an overlay, otherwise only the first one is used
|
|
682
|
+
if self.pair_coeff is not None and self.pair_mode == "overlay":
|
|
683
|
+
potential_pair_coeffs = list(self.pair_coeff)
|
|
684
|
+
elif self.pair_coeff is not None and len(self.pair_coeff) > 0:
|
|
685
|
+
potential_pair_coeffs = [self.pair_coeff[0]]
|
|
686
|
+
else:
|
|
687
|
+
potential_pair_coeffs = []
|
|
688
|
+
|
|
689
|
+
if self.mode == "composition_scaling":
|
|
690
|
+
# composition scaling renumbers the atom types, so a pair_coeff
|
|
691
|
+
# tied to explicit type numbers would address the wrong atoms
|
|
692
|
+
for pc in potential_pair_coeffs:
|
|
693
|
+
if pc.split()[:2] != ["*", "*"]:
|
|
694
|
+
raise ValueError(
|
|
695
|
+
"mode composition_scaling needs every pair_coeff to apply to "
|
|
696
|
+
f"all atom types ('* * ...'); got: {pc}"
|
|
697
|
+
)
|
|
672
698
|
|
|
673
699
|
# Validate element/mass/pair_coeff ordering consistency
|
|
674
700
|
# This is critical for multi-element systems where LAMMPS type numbers
|
|
675
701
|
# are assigned based on element order: element[0]=Type1, element[1]=Type2, etc.
|
|
676
|
-
if (
|
|
677
|
-
|
|
678
|
-
and self.pair_coeff is not None
|
|
679
|
-
and len(self.pair_coeff) > 0
|
|
680
|
-
):
|
|
681
|
-
extracted_elements = _extract_elements_from_pair_coeff(self.pair_coeff[0])
|
|
702
|
+
for potential_pair_coeff in potential_pair_coeffs if len(self.element) > 1 else []:
|
|
703
|
+
extracted_elements = _extract_elements_from_pair_coeff(potential_pair_coeff)
|
|
682
704
|
|
|
683
705
|
if extracted_elements is not None:
|
|
684
706
|
# pair_coeff specifies elements - check ordering
|
|
@@ -602,6 +602,48 @@ def routine_alchemy(job):
|
|
|
602
602
|
return job
|
|
603
603
|
|
|
604
604
|
|
|
605
|
+
def set_composition_scaling_potential(calc, comp):
|
|
606
|
+
"""
|
|
607
|
+
Rewrite the pair lists of a composition_scaling calculation into the two
|
|
608
|
+
end states the alchemy driver switches between.
|
|
609
|
+
|
|
610
|
+
A plain potential contributes its single component; with
|
|
611
|
+
``pair_mode: overlay`` every component of the overlay is carried along.
|
|
612
|
+
The pair_coeff of each component is mapped once onto the input types
|
|
613
|
+
(the element list before the transformation) and once onto the output
|
|
614
|
+
types, and the lists become ``[initial_1 .. initial_N, final_1 ..
|
|
615
|
+
final_N]`` with the pair styles repeated to match.
|
|
616
|
+
|
|
617
|
+
Parameters
|
|
618
|
+
----------
|
|
619
|
+
calc : Calculation
|
|
620
|
+
the calculation, modified in place
|
|
621
|
+
comp : CompositionTransformation
|
|
622
|
+
the transformation providing ``update_pair_coeff``
|
|
623
|
+
|
|
624
|
+
Returns
|
|
625
|
+
-------
|
|
626
|
+
n_components : int
|
|
627
|
+
number of components in each end state
|
|
628
|
+
"""
|
|
629
|
+
n_components = len(calc.pair_style) if ph.is_overlay_potential(calc) else 1
|
|
630
|
+
styles = list(calc.pair_style[:n_components])
|
|
631
|
+
styles_with_options = list(calc._pair_style_with_options[:n_components])
|
|
632
|
+
names = list(calc._pair_style_names[:n_components])
|
|
633
|
+
|
|
634
|
+
initial, final = [], []
|
|
635
|
+
for pair_coeff in calc.pair_coeff[:n_components]:
|
|
636
|
+
pc_initial, pc_final = comp.update_pair_coeff(pair_coeff)
|
|
637
|
+
initial.append(pc_initial)
|
|
638
|
+
final.append(pc_final)
|
|
639
|
+
|
|
640
|
+
calc.pair_style = styles + styles
|
|
641
|
+
calc._pair_style_with_options = styles_with_options + styles_with_options
|
|
642
|
+
calc._pair_style_names = names + names
|
|
643
|
+
calc.pair_coeff = initial + final
|
|
644
|
+
return n_components
|
|
645
|
+
|
|
646
|
+
|
|
605
647
|
def routine_composition_scaling(job):
|
|
606
648
|
"""
|
|
607
649
|
Perform a compositional scaling routine
|
|
@@ -617,19 +659,16 @@ def routine_composition_scaling(job):
|
|
|
617
659
|
job.logger.info(f"Forward swap types: {forward_swap_types}")
|
|
618
660
|
job.logger.info(f"Reverse swap types: {reverse_swap_types}")
|
|
619
661
|
|
|
620
|
-
#
|
|
621
|
-
|
|
622
|
-
job.
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
job.logger.info("Update pair styles")
|
|
631
|
-
job.logger.info(f"pair style 1: {job.calc._pair_style_names[0]}")
|
|
632
|
-
job.logger.info(f"pair style 2: {job.calc._pair_style_names[1]}")
|
|
662
|
+
# rewrite the potential into its initial and final composition
|
|
663
|
+
n_components = set_composition_scaling_potential(job.calc, comp)
|
|
664
|
+
job.logger.info("Update pair styles and coefficients")
|
|
665
|
+
for idx in range(2 * n_components):
|
|
666
|
+
end = "initial" if idx < n_components else "final"
|
|
667
|
+
job.logger.info(
|
|
668
|
+
f"{end} component {idx % n_components + 1}: "
|
|
669
|
+
f"pair_style {job.calc._pair_style_with_options[idx]} | "
|
|
670
|
+
f"pair_coeff {job.calc.pair_coeff[idx]}"
|
|
671
|
+
)
|
|
633
672
|
|
|
634
673
|
backup_element = job.calc.element.copy()
|
|
635
674
|
job.calc.element = comp.pair_list_old
|
|
@@ -30,6 +30,7 @@ calphy.egg-info/entry_points.txt
|
|
|
30
30
|
calphy.egg-info/requires.txt
|
|
31
31
|
calphy.egg-info/top_level.txt
|
|
32
32
|
tests/test_command_vocabulary.py
|
|
33
|
+
tests/test_composition_scaling_potential.py
|
|
33
34
|
tests/test_composition_transformation_bug.py
|
|
34
35
|
tests/test_dissipation_check.py
|
|
35
36
|
tests/test_element_ordering.py
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""set_composition_scaling_potential: the pair lists of a composition_scaling
|
|
2
|
+
calculation become the initial and final potential of the alchemical switch,
|
|
3
|
+
component by component."""
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from calphy.input import Calculation
|
|
9
|
+
from calphy.composition_transformation import CompositionTransformation
|
|
10
|
+
from calphy.routines import set_composition_scaling_potential
|
|
11
|
+
|
|
12
|
+
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
13
|
+
ZRCU = os.path.join(REPO, "examples", "example_10", "ZrCu.data")
|
|
14
|
+
POT = os.path.join(REPO, "examples", "potentials", "ZrCu.eam.fs")
|
|
15
|
+
|
|
16
|
+
BASE = dict(
|
|
17
|
+
element=["Zr", "Cu"], mass=[91.224, 63.546], lattice=ZRCU, lattice_constant=0.0,
|
|
18
|
+
mode="composition_scaling",
|
|
19
|
+
composition_scaling={"output_chemical_composition": {"Cu": 532, "Zr": 492}},
|
|
20
|
+
temperature=800, pressure=0, reference_phase="solid",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def in_tmp(tmp_path, monkeypatch):
|
|
26
|
+
monkeypatch.chdir(tmp_path)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _prepare(**extra):
|
|
30
|
+
calc = Calculation(**{**BASE, **extra})
|
|
31
|
+
comp = CompositionTransformation(calc)
|
|
32
|
+
n = set_composition_scaling_potential(calc, comp)
|
|
33
|
+
return calc, comp, n
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_plain_potential_becomes_two_copies(in_tmp):
|
|
37
|
+
calc, comp, n = _prepare(pair_style="eam/fs", pair_coeff="* * %s Zr Cu" % POT)
|
|
38
|
+
assert n == 1
|
|
39
|
+
assert calc.pair_style == ["eam/fs", "eam/fs"]
|
|
40
|
+
assert calc._pair_style_names == ["eam/fs", "eam/fs"]
|
|
41
|
+
old, new = comp.update_pair_coeff("* * %s Zr Cu" % POT)
|
|
42
|
+
assert calc.pair_coeff == [old, new]
|
|
43
|
+
# the elements follow the per-type mappings (Cu-Cu, Zr-Cu, Zr-Zr): the
|
|
44
|
+
# initial mapping keeps the 20 transformed atoms Zr, the final names them Cu
|
|
45
|
+
assert old.split()[3:] == comp.pair_list_old == ["Cu", "Zr", "Zr"]
|
|
46
|
+
assert new.split()[3:] == comp.pair_list_new == ["Cu", "Cu", "Zr"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_overlay_components_are_rewritten_pairwise(in_tmp):
|
|
50
|
+
calc, comp, n = _prepare(
|
|
51
|
+
pair_mode="overlay",
|
|
52
|
+
pair_style=["eam/fs", "zero 5.0"],
|
|
53
|
+
pair_coeff=["* * eam/fs %s Zr Cu" % POT, "* * zero"],
|
|
54
|
+
)
|
|
55
|
+
assert n == 2
|
|
56
|
+
assert calc.pair_style == ["eam/fs", "zero 5.0", "eam/fs", "zero 5.0"]
|
|
57
|
+
assert calc._pair_style_with_options == calc.pair_style
|
|
58
|
+
assert calc._pair_style_names == ["eam/fs", "zero", "eam/fs", "zero"]
|
|
59
|
+
assert calc.pair_coeff == [
|
|
60
|
+
"* * eam/fs %s Cu Zr Zr" % POT,
|
|
61
|
+
"* * zero",
|
|
62
|
+
"* * eam/fs %s Cu Cu Zr" % POT,
|
|
63
|
+
"* * zero",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_extra_entries_of_a_plain_potential_are_dropped(in_tmp):
|
|
68
|
+
"""Without pair_mode overlay only the first pair_style/pair_coeff describe
|
|
69
|
+
the potential; a stray second entry must not leak into the end states."""
|
|
70
|
+
calc, comp, n = _prepare(
|
|
71
|
+
pair_style=["eam/fs", "eam/alloy"],
|
|
72
|
+
pair_coeff=["* * %s Zr Cu" % POT, "* * other.eam.alloy Zr Cu"],
|
|
73
|
+
)
|
|
74
|
+
assert n == 1
|
|
75
|
+
assert calc.pair_style == ["eam/fs", "eam/fs"]
|
|
76
|
+
assert len(calc.pair_coeff) == 2
|
|
@@ -284,3 +284,50 @@ def test_iterations_draw_fresh_seeds(make_calc, recorded_job, tmp_path, JobClass
|
|
|
284
284
|
vel, therm = zip(*seen)
|
|
285
285
|
assert len(set(vel)) == 3, "velocity seeds repeat across iterations: %s" % (vel,)
|
|
286
286
|
assert len(set(therm)) == 3, "thermostat seeds repeat across iterations: %s" % (therm,)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# --------------------------------------------------------------------------- #
|
|
290
|
+
# composition_scaling: the potential is rewritten into an initial and a final
|
|
291
|
+
# composition; with pair_mode overlay every component is carried along.
|
|
292
|
+
# --------------------------------------------------------------------------- #
|
|
293
|
+
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
294
|
+
ZRCU = os.path.join(REPO, "examples", "example_10", "ZrCu.data")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _composition_scaling_job(make_calc, recorded_job, fixtures=(), **overrides):
|
|
298
|
+
from calphy.composition_transformation import CompositionTransformation
|
|
299
|
+
from calphy.routines import set_composition_scaling_potential
|
|
300
|
+
|
|
301
|
+
# the transformation reads the real structure, so pin the sentinel afterwards
|
|
302
|
+
calc = make_calc("B13", lattice_sentinel=None, lattice=ZRCU, **LOOSE_TOL, **overrides)
|
|
303
|
+
comp = CompositionTransformation(calc)
|
|
304
|
+
set_composition_scaling_potential(calc, comp)
|
|
305
|
+
calc.element = comp.pair_list_old
|
|
306
|
+
calc.lattice = "structure.data"
|
|
307
|
+
return recorded_job(Alchemy, calc, fixtures=fixtures)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def test_composition_scaling_overlay_averaging(make_calc, recorded_job):
|
|
311
|
+
job, rec = _composition_scaling_job(make_calc, recorded_job, fixtures=["avg.dat"])
|
|
312
|
+
job.run_averaging()
|
|
313
|
+
assert_golden(rec.commands, "composition_scaling_overlay_averaging")
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def test_composition_scaling_overlay_integration(make_calc, recorded_job):
|
|
317
|
+
job, rec = _composition_scaling_job(make_calc, recorded_job)
|
|
318
|
+
_set_state(job)
|
|
319
|
+
job.run_integration(iteration=1)
|
|
320
|
+
assert_golden(rec.commands, "composition_scaling_overlay_integration")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def test_composition_scaling_integration(make_calc, recorded_job):
|
|
324
|
+
"""Plain (single component) potential: the pre-overlay command stream."""
|
|
325
|
+
job, rec = _composition_scaling_job(
|
|
326
|
+
make_calc, recorded_job,
|
|
327
|
+
pair_mode=None,
|
|
328
|
+
pair_style="eam/fs",
|
|
329
|
+
pair_coeff="* * examples/potentials/ZrCu.eam.fs Zr Cu",
|
|
330
|
+
)
|
|
331
|
+
_set_state(job)
|
|
332
|
+
job.run_integration(iteration=1)
|
|
333
|
+
assert_golden(rec.commands, "composition_scaling_integration")
|
|
@@ -84,3 +84,57 @@ def test_prepare_log_no_cross_contamination():
|
|
|
84
84
|
assert "message from calc2" not in content1, "calc1.log must NOT contain calc2 message"
|
|
85
85
|
assert "message from calc2" in content2, "calc2.log should contain calc2 message"
|
|
86
86
|
assert "message from calc1" not in content2, "calc2.log must NOT contain calc1 message"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_hybrid_component_tags_index_only_repeated_styles():
|
|
90
|
+
tags = ch.hybrid_component_tags(["eam/fs", "zero", "eam/fs", "zero"])
|
|
91
|
+
assert tags == [("eam/fs", 1), ("zero", 1), ("eam/fs", 2), ("zero", 2)]
|
|
92
|
+
assert ch.hybrid_component_tags(["eam/alloy", "eam/fs"]) == [
|
|
93
|
+
("eam/alloy", None),
|
|
94
|
+
("eam/fs", None),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_hybrid_pair_coeff_commands_for_keeps_native_style_token():
|
|
99
|
+
cmds = ch.hybrid_pair_coeff_commands_for(
|
|
100
|
+
["eam/fs", "zero", "eam/fs", "zero"],
|
|
101
|
+
["* * eam/fs a.eam.fs Zr Zr", "* * zero", "* * a.eam.fs Zr Cu", "* * zero"],
|
|
102
|
+
)
|
|
103
|
+
assert [" ".join(c.split()) for c in cmds] == [
|
|
104
|
+
"pair_coeff * * eam/fs 1 a.eam.fs Zr Zr",
|
|
105
|
+
"pair_coeff * * zero 1",
|
|
106
|
+
"pair_coeff * * eam/fs 2 a.eam.fs Zr Cu",
|
|
107
|
+
"pair_coeff * * zero 2",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_hybrid_pair_compute_commands_sum_expression():
|
|
112
|
+
cmds, energy = ch.hybrid_pair_compute_commands(
|
|
113
|
+
["c1_1", "c1_2"], [("eam/fs", 1), ("zero", None)]
|
|
114
|
+
)
|
|
115
|
+
assert [" ".join(c.split()) for c in cmds] == [
|
|
116
|
+
"compute c1_1 all pair eam/fs 1",
|
|
117
|
+
"compute c1_2 all pair zero",
|
|
118
|
+
]
|
|
119
|
+
assert energy == "c_c1_1+c_c1_2"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_hybrid_pair_coeff_commands_repeat_index_matches_full_hybrid():
|
|
123
|
+
class Options:
|
|
124
|
+
_pair_style_names = ["eam/alloy", "eam/alloy"]
|
|
125
|
+
pair_coeff = ["* * potA Cu", "* * potB Cu"]
|
|
126
|
+
|
|
127
|
+
first = ch.hybrid_pair_coeff_commands(Options, repeat_index=0, total_repeats=2)
|
|
128
|
+
second = ch.hybrid_pair_coeff_commands(Options, repeat_index=1, total_repeats=2)
|
|
129
|
+
assert [" ".join(c.split()) for c in first + second] == [
|
|
130
|
+
"pair_coeff * * eam/alloy 1 potA Cu",
|
|
131
|
+
"pair_coeff * * eam/alloy 2 potB Cu",
|
|
132
|
+
"pair_coeff * * eam/alloy 3 potA Cu",
|
|
133
|
+
"pair_coeff * * eam/alloy 4 potB Cu",
|
|
134
|
+
]
|
|
135
|
+
cmds, energy, ids = ch.real_pair_compute_commands(Options, total_repeats=2, repeat_index=1)
|
|
136
|
+
assert [" ".join(c.split()) for c in cmds] == [
|
|
137
|
+
"compute c_real1 all pair eam/alloy 3",
|
|
138
|
+
"compute c_real2 all pair eam/alloy 4",
|
|
139
|
+
]
|
|
140
|
+
assert energy == "c_c_real1+c_c_real2" and ids == ["c_real1", "c_real2"]
|
|
@@ -151,3 +151,58 @@ def test_repo_example_inputs_are_key_clean():
|
|
|
151
151
|
if "unknown input key" in str(exc) or "removed in calphy" in str(exc):
|
|
152
152
|
bad.append((f, str(exc)))
|
|
153
153
|
assert not bad, bad
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# --------------------------------------------------------------------------- #
|
|
157
|
+
# overlay potentials: alchemy vs composition_scaling
|
|
158
|
+
# --------------------------------------------------------------------------- #
|
|
159
|
+
OVERLAY = dict(
|
|
160
|
+
pair_mode="overlay",
|
|
161
|
+
pair_style=["eam/alloy", "zero 5.0"],
|
|
162
|
+
pair_coeff=["* * eam/alloy Cu.eam.alloy Cu", "* * zero"],
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_overlay_rejected_for_mode_alchemy():
|
|
167
|
+
msg = error_of(mode="alchemy", **OVERLAY)
|
|
168
|
+
assert "pair_mode overlay is not supported for mode alchemy" in msg
|
|
169
|
+
assert "composition_scaling" in msg
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_overlay_allowed_for_alchemy_coupling():
|
|
173
|
+
build(mode="alchemy", alchemy_coupling=True, **OVERLAY)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
ZRCU = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
177
|
+
"examples", "example_10", "ZrCu.data")
|
|
178
|
+
CSCALE = dict(
|
|
179
|
+
element=["Zr", "Cu"], mass=[91.224, 63.546], lattice=ZRCU, lattice_constant=0.0,
|
|
180
|
+
mode="composition_scaling",
|
|
181
|
+
composition_scaling={"output_chemical_composition": {"Cu": 532, "Zr": 492}},
|
|
182
|
+
temperature=800, pressure=0, reference_phase="solid",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_composition_scaling_accepts_overlay(tmp_path, monkeypatch):
|
|
187
|
+
monkeypatch.chdir(tmp_path)
|
|
188
|
+
calc = build(**CSCALE, pair_mode="overlay", pair_style=["eam/fs", "zero 5.0"],
|
|
189
|
+
pair_coeff=["* * eam/fs ZrCu.eam.fs Zr Cu", "* * zero"])
|
|
190
|
+
assert calc._pair_style_names == ["eam/fs", "zero"]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_composition_scaling_needs_wildcard_types(tmp_path, monkeypatch):
|
|
194
|
+
monkeypatch.chdir(tmp_path)
|
|
195
|
+
msg = error_of(**CSCALE, pair_style="eam/fs", pair_coeff="1 2 ZrCu.eam.fs Zr Cu")
|
|
196
|
+
assert "all atom types" in msg
|
|
197
|
+
# the wildcard rule covers every overlay component, not only the first
|
|
198
|
+
msg = error_of(**CSCALE, pair_mode="overlay", pair_style=["eam/fs", "zero 5.0"],
|
|
199
|
+
pair_coeff=["* * eam/fs ZrCu.eam.fs Zr Cu", "1 1 zero"])
|
|
200
|
+
assert "1 1 zero" in msg
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_overlay_element_order_checked_on_every_component(tmp_path, monkeypatch):
|
|
204
|
+
monkeypatch.chdir(tmp_path)
|
|
205
|
+
# elements listed only on the second component, in the wrong order
|
|
206
|
+
msg = error_of(**CSCALE, pair_mode="overlay", pair_style=["zero 5.0", "eam/fs"],
|
|
207
|
+
pair_coeff=["* * zero", "* * eam/fs ZrCu.eam.fs Cu Zr"])
|
|
208
|
+
assert "Element ordering mismatch" in msg
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|