Skip to content

Commit 5da794f

Browse files
authored
Merge pull request #165 from ICAMS/add_swap
Add swap
2 parents f944cfc + be16f16 commit 5da794f

File tree

13 files changed

+978
-62
lines changed

13 files changed

+978
-62
lines changed

calphy/alchemy.py

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
2020
For more information contact:
2121
22+
23+
Notes
24+
-----
25+
- swapping is strictly only performed between types 1 and 2 at the moment; this needs to be refined further
2226
"""
2327

2428
import numpy as np
@@ -159,6 +163,9 @@ def run_integration(self, iteration=1):
159163
lmp.command(f'pair_coeff {self.calc.pair_coeff[0]}')
160164
lmp = ph.set_mass(lmp, self.calc)
161165

166+
#NEW ADDED
167+
lmp.command("group g1 type 1")
168+
lmp.command("group g2 type 2")
162169
#lmp = ph.set_double_hybrid_potential(lmp, self.options, self.calc._pressureair_style, self.calc._pressureair_coeff)
163170

164171
#remap the box to get the correct pressure
@@ -173,9 +180,12 @@ def run_integration(self, iteration=1):
173180
lmp.command("fix f1 all nvt temp %f %f %f"%(self.calc._temperature, self.calc._temperature,
174181
self.calc.md.thermostat_damping[1]))
175182

183+
176184
lmp.command("thermo_style custom step pe")
177185
lmp.command("thermo 1000")
178186
lmp.command("run %d"%self.calc.n_equilibration_steps)
187+
188+
179189
#equilibration run is over
180190

181191
#---------------------------------------------------------------
@@ -226,29 +236,49 @@ def run_integration(self, iteration=1):
226236
lmp.command("variable dU1 equal c_c1/atoms") # Driving-force obtained from NEHI procedure.
227237
lmp.command("variable dU2 equal c_c2/atoms")
228238

239+
#add swaps if n_swap is > 0
240+
if self.calc.monte_carlo.n_swaps > 0:
241+
self.logger.info(f'{self.calc.monte_carlo.n_swaps} swap moves are performed between {self.calc.monte_carlo.swap_types[0]} and {self.calc.monte_carlo.swap_types[1]} every {self.calc.monte_carlo.n_steps}')
242+
lmp.command("fix swap all atom/swap %d %d %d %f ke no types %d %d"%(self.calc.monte_carlo.n_steps,
243+
self.calc.monte_carlo.n_swaps,
244+
np.random.randint(1, 10000),
245+
self.calc._temperature,
246+
self.calc.monte_carlo.swap_types[0],
247+
self.calc.monte_carlo.swap_types[1]))
248+
lmp.command("variable a equal f_swap[1]")
249+
lmp.command("variable b equal f_swap[2]")
250+
lmp.command("fix swap2 all print 1 \"${a} ${b} ${flambda}\" screen no file swap.forward_%d.dat"%iteration)
251+
229252
# Thermo output.
230-
lmp.command("thermo_style custom step v_dU1 v_dU2")
253+
if self.calc.monte_carlo.n_swaps > 0:
254+
lmp.command("thermo_style custom step v_dU1 v_dU2 v_a v_b")
255+
else:
256+
lmp.command("thermo_style custom step v_dU1 v_dU2")
231257
lmp.command("thermo 1000")
232258

233-
259+
234260
#save the necessary items to a file: first step
235261
lmp.command("fix f2 all print 1 \"${dU1} ${dU2} ${flambda}\" screen no file forward_%d.dat"%iteration)
236-
lmp.command("run %d"%self.calc._n_switching_steps)
237-
262+
lmp.command("run %d"%self.calc._n_switching_steps)
238263

239264
#now equilibrate at the second potential
240265
lmp.command("unfix f2")
241266
lmp.command("uncompute c1")
242267
lmp.command("uncompute c2")
243268

269+
#NEW SWAP
270+
if self.calc.monte_carlo.n_swaps > 0:
271+
lmp.command("unfix swap")
272+
lmp.command("unfix swap2")
273+
244274

245275
lmp.command("pair_style %s"%self.calc._pair_style_with_options[1])
246276
lmp.command("pair_coeff %s"%self.calc.pair_coeff[1])
247277

248278
# Thermo output.
249279
lmp.command("thermo_style custom step pe")
250280
lmp.command("thermo 1000")
251-
281+
252282
#run eqbrm run
253283
lmp.command("run %d"%self.calc.n_equilibration_steps)
254284

@@ -278,11 +308,39 @@ def run_integration(self, iteration=1):
278308
lmp.command("variable dU1 equal c_c1/atoms") # Driving-force obtained from NEHI procedure.
279309
lmp.command("variable dU2 equal c_c2/atoms")
280310

311+
#add swaps if n_swap is > 0
312+
if self.calc.monte_carlo.n_swaps > 0:
313+
if self.calc.monte_carlo.reverse_swap:
314+
self.logger.info(f'{self.calc.monte_carlo.n_swaps} swap moves are performed between {self.calc.monte_carlo.swap_types[1]} and {self.calc.monte_carlo.swap_types[0]} every {self.calc.monte_carlo.n_steps}')
315+
lmp.command("fix swap all atom/swap %d %d %d %f ke no types %d %d"%(self.calc.monte_carlo.n_steps,
316+
self.calc.monte_carlo.n_swaps,
317+
np.random.randint(1, 10000),
318+
self.calc._temperature,
319+
self.calc.monte_carlo.swap_types[1],
320+
self.calc.monte_carlo.swap_types[0]))
321+
else:
322+
self.logger.info(f'{self.calc.monte_carlo.n_swaps} swap moves are performed between {self.calc.monte_carlo.swap_types[0]} and {self.calc.monte_carlo.swap_types[1]} every {self.calc.monte_carlo.n_steps}')
323+
self.logger.info('note that swaps are not reversed')
324+
lmp.command("fix swap all atom/swap %d %d %d %f ke no types %d %d"%(self.calc.monte_carlo.n_steps,
325+
self.calc.monte_carlo.n_swaps,
326+
np.random.randint(1, 10000),
327+
self.calc._temperature,
328+
self.calc.monte_carlo.swap_types[0],
329+
self.calc.monte_carlo.swap_types[1]))
330+
331+
lmp.command("variable a equal f_swap[1]")
332+
lmp.command("variable b equal f_swap[2]")
333+
lmp.command("fix swap2 all print 1 \"${a} ${b} ${blambda}\" screen no file swap.backward_%d.dat"%iteration)
334+
281335
# Thermo output.
282-
lmp.command("thermo_style custom step v_dU1 v_dU2")
336+
if self.calc.monte_carlo.n_swaps > 0:
337+
lmp.command("thermo_style custom step v_dU1 v_dU2 v_a v_b")
338+
else:
339+
lmp.command("thermo_style custom step v_dU1 v_dU2")
283340
lmp.command("thermo 1000")
284341

285342

343+
286344
#save the necessary items to a file: first step
287345
lmp.command("fix f2 all print 1 \"${dU1} ${dU2} ${flambda}\" screen no file backward_%d.dat"%iteration)
288346
lmp.command("run %d"%self.calc._n_switching_steps)
@@ -293,6 +351,8 @@ def run_integration(self, iteration=1):
293351
lmp.command("uncompute c1")
294352
lmp.command("uncompute c2")
295353

354+
if self.calc.monte_carlo.n_swaps > 0:
355+
lmp.command("unfix swap")
296356
lmp.close()
297357

298358

@@ -329,8 +389,6 @@ def thermodynamic_integration(self):
329389
return flambda_arr, w_arr, q_arr, qerr_arr
330390

331391

332-
333-
334392
def mass_integration(self, flambda, ref_mass, target_masses, target_counts):
335393
mcorarr, mcorsum = integrate_mass(flambda, ref_mass, target_masses, target_counts,
336394
self.calc._temperature, self.natoms)

calphy/clitools.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from calphy.liquid import Liquid
1212
from calphy.solid import Solid
1313
from calphy.alchemy import Alchemy
14+
from calphy.phase_diagram import prepare_inputs_for_phase_diagram
1415

1516
def _generate_job(calc, simfolder):
1617
if calc.mode == "alchemy" or calc.mode == "composition_scaling":
@@ -120,3 +121,8 @@ def convert_legacy_inputfile():
120121
yaml.safe_dump(data, fout)
121122

122123

124+
def phase_diagram():
125+
arg = ap.ArgumentParser()
126+
arg.add_argument("-i", "--input", required=True, type=str,
127+
help="name of the input file")
128+
prepare_inputs_for_phase_diagram(args['input'])

calphy/composition_transformation.py

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ase.io import read, write
3131
from ase.atoms import Atoms
3232
from pyscal3.core import element_dict
33+
from calphy.integrators import kb
3334

3435
class CompositionTransformation:
3536
"""
@@ -124,6 +125,7 @@ def __init__(self, calc):
124125
self.atom_species = None
125126
self.mappings = None
126127
self.unique_mappings = None
128+
self.mappingdict = None
127129
self.prepare_mappings()
128130

129131
def dict_to_string(self, inputdict):
@@ -132,8 +134,32 @@ def dict_to_string(self, inputdict):
132134
strlst.append(str(key))
133135
strlst.append(str(val))
134136
return "".join(strlst)
135-
136-
137+
138+
@property
139+
def entropy_contribution(self):
140+
"""
141+
Find the entropy entribution of the transformation. To get
142+
free energies, multiply by -T.
143+
"""
144+
def _log(val):
145+
if val == 0:
146+
return 0
147+
else:
148+
return np.log(val)
149+
ents = []
150+
for key, val in self.output_chemical_composition.items():
151+
if key in self.input_chemical_composition.keys():
152+
t1 = self.input_chemical_composition[key]/self.natoms
153+
t2 = self.output_chemical_composition[key]/self.natoms
154+
cont = t2*_log(t2) - t1*_log(t1)
155+
else:
156+
t1 = 0
157+
t2 = self.output_chemical_composition[key]/self.natoms
158+
cont = t2*_log(t2) - 0
159+
ents.append(cont)
160+
entropy_term = kb*np.sum(ents)
161+
return entropy_term
162+
137163
def convert_to_pyscal(self):
138164
"""
139165
Convert a given system to pyscal and give a dict of type mappings
@@ -159,7 +185,6 @@ def convert_to_pyscal(self):
159185
self.maxtype = self.actual_species + 1 #+ self.new_species
160186
#print(self.typedict)
161187

162-
163188
def get_composition_transformation(self):
164189
"""
165190
From the two given composition transformation, find the transformation dict
@@ -192,9 +217,10 @@ def get_random_index_of_species(self, species):
192217
def mark_atoms(self):
193218
for i in range(self.natoms):
194219
self.atom_mark.append(False)
195-
self.atom_type = self.pyscal_structure.atoms.types
196-
self.mappings = [f"{x}-{x}" for x in self.atom_type]
197-
220+
221+
self.atom_type = self.pyscal_structure.atoms.types
222+
self.mappings = [f"{x}-{x}" for x in self.atom_type]
223+
198224
def update_mark_atoms(self):
199225
self.marked_atoms = []
200226
for key, val in self.to_remove.items():
@@ -281,7 +307,7 @@ def get_mappings(self):
281307
self.update_typedicts()
282308
self.compute_possible_mappings()
283309
self.update_mappings()
284-
310+
285311
def prepare_pair_lists(self):
286312
self.pair_list_old = []
287313
self.pair_list_new = []
@@ -300,8 +326,12 @@ def prepare_pair_lists(self):
300326
def update_types(self):
301327
for x in range(len(self.atom_type)):
302328
self.atom_type[x] = self.mappingdict[self.mappings[x]]
303-
for count in range(len(self.pyscal_structure.atoms.types)):
304-
self.pyscal_structure.atoms.types[count] = self.atom_type[count]
329+
330+
#smartify these loops
331+
#npyscal = len(self.pyscal_structure.atoms.types)
332+
self.pyscal_structure.atoms.types = self.atom_type
333+
#for count in range(npyscal)):
334+
# self.pyscal_structure.atoms.types[count] = self.atom_type[count]
305335

306336
def iselement(self, symbol):
307337
try:
@@ -331,6 +361,30 @@ def update_pair_coeff(self, pair_coeff):
331361
pc_old = " ".join([*pc_before, *self.pair_list_old, *pc_after])
332362
pc_new = " ".join([*pc_before, *self.pair_list_new, *pc_after])
333363
return pc_old, pc_new
364+
365+
def get_swap_types(self):
366+
"""
367+
Get swapping types
368+
"""
369+
swap_list = []
370+
for mapping in self.unique_mappings:
371+
map_split = mapping.split("-")
372+
#conserved atom
373+
if (map_split[0]==map_split[1]):
374+
pass
375+
else:
376+
first_type = map_split[0]
377+
second_type = map_split[1]
378+
first_map = f'{first_type}-{first_type}'
379+
second_map = mapping
380+
381+
#get the numbers from dict
382+
first_swap_type = self.mappingdict[first_map]
383+
second_swap_type = self.mappingdict[second_map]
384+
385+
swap_list.append([first_swap_type, second_swap_type])
386+
return swap_list[0]
387+
334388

335389
def write_structure(self, outfilename):
336390
#create some species dict

calphy/helpers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,12 @@ def create_structure(lmp, calc):
121121

122122

123123
def set_mass(lmp, options):
124-
for i in range(options.n_elements):
125-
lmp.command(f'mass {i+1} {options.mass[i]}')
124+
if options.mode == 'composition_scaling':
125+
lmp.command(f'mass * {options.mass[-1]}')
126+
127+
else:
128+
for i in range(options.n_elements):
129+
lmp.command(f'mass {i+1} {options.mass[i]}')
126130
return lmp
127131

128132

calphy/input.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from pydantic.functional_validators import AfterValidator, BeforeValidator
2828
from annotated_types import Len
2929
import mendeleev
30+
from tqdm import tqdm
3031

3132
import yaml
3233
import numpy as np
@@ -72,7 +73,14 @@ def _to_float(val):
7273
return float(val)
7374
else:
7475
return [float(x) for x in val]
75-
76+
77+
class MonteCarlo(BaseModel, title='Options for Monte Carlo moves during particle swap moves'):
78+
n_steps: Annotated[int, Field(default=1, gt=0, description='perform swap moves every n_step')]
79+
n_swaps: Annotated[int, Field(default=0, ge=0, description='number of swap moves to perform')]
80+
reverse_swap: Annotated[ bool, Field(default=True)]
81+
swap_types: Annotated[conlist(int, min_length=2, max_length=2), Field(default=[1,2],
82+
description='which atoms to swap between')]
83+
7684
class CompositionScaling(BaseModel, title='Composition scaling input options'):
7785
_input_chemical_composition: PrivateAttr(default=None)
7886
output_chemical_composition: Annotated[dict, Field(default={}, required=False)]
@@ -125,6 +133,7 @@ class MeltingTemperature(BaseModel, title='Input options for melting temperature
125133
attempts: Annotated[int, Field(default=5, ge=1)]
126134

127135
class Calculation(BaseModel, title='Main input class'):
136+
monte_carlo: Optional[MonteCarlo] = MonteCarlo()
128137
composition_scaling: Optional[CompositionScaling] = CompositionScaling()
129138
md: Optional[MD] = MD()
130139
nose_hoover: Optional[NoseHoover] = NoseHoover()
@@ -197,9 +206,16 @@ class Calculation(BaseModel, title='Main input class'):
197206
#add second level options; for example spring constants
198207
spring_constants: Annotated[Union[List[float],None], Field(default = None)]
199208

209+
#some input keywords that will be used for the phase diagram mode
210+
phase_name: Annotated[str, Field(default="")]
211+
reference_composition: Annotated[float, Field(default=0.00)]
212+
200213
#structure items
201214
_structure: Any = PrivateAttr(default=None)
202215

216+
#just check for nlements in compscale
217+
_totalelements = PrivateAttr(default=0)
218+
203219
@model_validator(mode='after')
204220
def _validate_all(self) -> 'Input':
205221

@@ -326,6 +342,7 @@ def _validate_all(self) -> 'Input':
326342

327343
#generate temporary filename if needed
328344
write_structure_file = False
345+
rename_structure_file = False
329346

330347
if self.lattice == "":
331348
#fetch from dict
@@ -399,6 +416,7 @@ def _validate_all(self) -> 'Input':
399416
Z_of_type = dict([(count+1, self._element_dict[element]['atomic_number']) for count, element in enumerate(self.element)])
400417
structure = read(self.lattice, format='lammps-data', style='atomic', Z_of_type=Z_of_type)
401418
#structure = System(aseobj, format='ase')
419+
rename_structure_file = True
402420
else:
403421
raise TypeError('Only lammps-data files are supported!')
404422

@@ -420,6 +438,12 @@ def _validate_all(self) -> 'Input':
420438
write(structure_filename, structure, format='lammps-data')
421439
self.lattice = structure_filename
422440

441+
if rename_structure_file:
442+
structure_filename = ".".join([self.create_identifier(), str(self.kernel), "data"])
443+
structure_filename = os.path.join(os.getcwd(), structure_filename)
444+
shutil.copy(self.lattice, structure_filename)
445+
self.lattice = structure_filename
446+
423447
if self.mode == 'composition_scaling':
424448
#we also should check if actual contents are present
425449
input_chem_comp = {}
@@ -548,7 +572,7 @@ def _read_inputfile(file):
548572
with open(file, 'r') as fin:
549573
data = yaml.safe_load(fin)
550574
calculations = []
551-
for count, calc in enumerate(data['calculations']):
575+
for count, calc in enumerate(tqdm(data['calculations'])):
552576
calc['kernel'] = count
553577
calc['inputfile'] = file
554578
if 'pressure' in calc.keys():

0 commit comments

Comments
 (0)