calphy 1.4.6__py3-none-any.whl → 1.4.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -172,7 +172,17 @@ class CompositionTransformation:
172
172
  """
173
173
  Convert a given system to pyscal and give a dict of type mappings
174
174
  """
175
- aseobj = read(self.calc.lattice, format="lammps-data", style="atomic")
175
+ # Create Z_of_type mapping to properly read LAMMPS data files
176
+ # This ensures atoms are correctly identified by their element
177
+ Z_of_type = dict(
178
+ [
179
+ (count + 1, element(el).atomic_number)
180
+ for count, el in enumerate(self.calc.element)
181
+ ]
182
+ )
183
+ aseobj = read(
184
+ self.calc.lattice, format="lammps-data", style="atomic", Z_of_type=Z_of_type
185
+ )
176
186
  pstruct = pc.System(aseobj, format="ase")
177
187
 
178
188
  # here we have to validate the input composition dict; and map it
@@ -188,10 +198,17 @@ class CompositionTransformation:
188
198
  self.reversetypedict = dict(zip(atomtypes, atomsymbols))
189
199
  self.natoms = self.pyscal_structure.natoms
190
200
 
191
- self.actual_species = len(self.typedict)
192
- self.new_species = len(self.output_chemical_composition) - len(self.typedict)
201
+ # Count of actual unique atom types present in the structure
202
+ # This matches what's declared in the LAMMPS data file header
203
+ self.actual_species_in_structure = len(types)
204
+ # Count from calc.element (may include types with 0 atoms)
205
+ self.calc_element_count = len(self.calc.element)
206
+
207
+ # Use actual structure types for pair_coeff consistency
208
+ # pair_coeff must match the number declared in the data file header
209
+ self.actual_species = self.actual_species_in_structure
210
+ self.new_species = len(self.output_chemical_composition) - len(types)
193
211
  self.maxtype = self.actual_species + 1 # + self.new_species
194
- # print(self.typedict)
195
212
 
196
213
  def get_composition_transformation(self):
197
214
  """
@@ -215,26 +232,28 @@ class CompositionTransformation:
215
232
  self.to_remove = to_remove
216
233
  self.to_add = to_add
217
234
 
218
- def get_random_index_of_species(self, species):
235
+ def get_random_index_of_species(self, species_name):
219
236
  """
220
- Get a random index of a given species
237
+ Get a random index of a given species by element name
221
238
  """
222
- ids = [count for count, x in enumerate(self.atom_type) if x == species]
239
+ ids = [count for count, x in enumerate(self.atom_species) if x == species_name]
223
240
  return ids[np.random.randint(0, len(ids))]
224
241
 
225
242
  def mark_atoms(self):
226
243
  for i in range(self.natoms):
227
244
  self.atom_mark.append(False)
228
245
 
246
+ # Use species (element symbols) instead of numeric types
247
+ self.atom_species = self.pyscal_structure.atoms.species
229
248
  self.atom_type = self.pyscal_structure.atoms.types
230
- self.mappings = [f"{x}-{x}" for x in self.atom_type]
249
+ self.mappings = [f"{x}-{x}" for x in self.atom_species]
231
250
 
232
251
  def update_mark_atoms(self):
233
252
  self.marked_atoms = []
234
253
  for key, val in self.to_remove.items():
235
- # print(f"Element {key}, count {val}")
254
+ # key is the element name (e.g., "Mg")
236
255
  for i in range(100000):
237
- rint = self.get_random_index_of_species(self.typedict[key])
256
+ rint = self.get_random_index_of_species(key)
238
257
  if rint not in self.marked_atoms:
239
258
  self.atom_mark[rint] = True
240
259
  self.marked_atoms.append(rint)
@@ -257,14 +276,12 @@ class CompositionTransformation:
257
276
 
258
277
  def compute_possible_mappings(self):
259
278
  self.possible_mappings = []
260
- # Now make a list of possible mappings
279
+ # Now make a list of possible mappings using element names
261
280
  for key1, val1 in self.to_remove.items():
262
281
  for key2, val2 in self.to_add.items():
263
282
  mapping = f"{key1}-{key2}"
264
283
  if mapping not in self.restrictions:
265
- self.possible_mappings.append(
266
- f"{self.typedict[key1]}-{self.typedict[key2]}"
267
- )
284
+ self.possible_mappings.append(mapping)
268
285
 
269
286
  def update_mappings(self):
270
287
  marked_atoms = self.marked_atoms.copy()
@@ -272,7 +289,7 @@ class CompositionTransformation:
272
289
  # now get all
273
290
 
274
291
  # we to see if we can get val number of atoms from marked ones
275
- if val < len(marked_atoms):
292
+ if val > len(marked_atoms):
276
293
  raise ValueError(
277
294
  f"Not enough atoms to choose {val} from {len(marked_atoms)} not possible"
278
295
  )
@@ -284,8 +301,8 @@ class CompositionTransformation:
284
301
  to_del = []
285
302
  for x in range(len(self.marked_atoms)):
286
303
  random_choice = np.random.choice(marked_atoms)
287
- # find corresponding mappiong
288
- mapping = f"{self.atom_type[random_choice]}-{self.typedict[key]}"
304
+ # find corresponding mapping using species name
305
+ mapping = f"{self.atom_species[random_choice]}-{key}"
289
306
  if mapping in self.possible_mappings:
290
307
  # this is a valid choice
291
308
  self.mappings[random_choice] = mapping
@@ -314,12 +331,8 @@ class CompositionTransformation:
314
331
  mapsplit = mapping.split("-")
315
332
  if not mapsplit[0] == mapsplit[1]:
316
333
  transformation_dict = {}
317
- transformation_dict["primary_element"] = self.reversetypedict[
318
- int(mapsplit[0])
319
- ]
320
- transformation_dict["secondary_element"] = self.reversetypedict[
321
- int(mapsplit[1])
322
- ]
334
+ transformation_dict["primary_element"] = mapsplit[0]
335
+ transformation_dict["secondary_element"] = mapsplit[1]
323
336
  transformation_dict["count"] = self.unique_mapping_counts[count]
324
337
  self.transformation_list.append(transformation_dict)
325
338
 
@@ -333,25 +346,45 @@ class CompositionTransformation:
333
346
  self.pair_list_new = []
334
347
  for mapping in self.unique_mappings:
335
348
  map_split = mapping.split("-")
336
- # conserved atom
349
+ # conserved atom - mappings now use element names directly
337
350
  if map_split[0] == map_split[1]:
338
- self.pair_list_old.append(self.reversetypedict[int(map_split[0])])
339
- self.pair_list_new.append(self.reversetypedict[int(map_split[0])])
351
+ self.pair_list_old.append(map_split[0])
352
+ self.pair_list_new.append(map_split[0])
340
353
  else:
341
- self.pair_list_old.append(self.reversetypedict[int(map_split[0])])
342
- self.pair_list_new.append(self.reversetypedict[int(map_split[1])])
343
- self.new_atomtype = np.array(range(len(self.unique_mappings))) + 1
344
- self.mappingdict = dict(zip(self.unique_mappings, self.new_atomtype))
354
+ self.pair_list_old.append(map_split[0])
355
+ self.pair_list_new.append(map_split[1])
356
+
357
+ # Special case: 100% transformation with only 1 mapping
358
+ # LAMMPS requires pair_coeff to map ALL atom types declared in data file
359
+ # Example: Pure Al→Mg with 2 types declared → need ['Al', 'Al'] and ['Mg', 'Mg']
360
+ # This ensures consistency between data file type count and pair_coeff mappings
361
+ if len(self.unique_mappings) == 1 and self.actual_species > 1:
362
+ # Duplicate the single mapping to match number of declared atom types
363
+ for _ in range(self.actual_species - 1):
364
+ self.pair_list_old.append(self.pair_list_old[0])
365
+ self.pair_list_new.append(self.pair_list_new[0])
366
+
367
+ # Create mapping from transformation strings to UNIQUE type numbers
368
+ # Each unique transformation mapping needs its own type for LAMMPS swapping
369
+ # Example: Al-Al, Mg-Al, Mg-Mg should map to types 1, 2, 3 respectively
370
+ self.mappingdict = {}
371
+ for idx, mapping in enumerate(self.unique_mappings, start=1):
372
+ self.mappingdict[mapping] = idx
373
+
374
+ # Update reversetypedict - map each type to its source element
375
+ # We'll handle species naming in write_structure to keep types separate
376
+ self.reversetypedict = {}
377
+ for mapping, type_num in self.mappingdict.items():
378
+ source_element = mapping.split("-")[0]
379
+ self.reversetypedict[type_num] = source_element
345
380
 
346
381
  def update_types(self):
382
+ # Update atom_type based on mapping to new types
347
383
  for x in range(len(self.atom_type)):
348
384
  self.atom_type[x] = self.mappingdict[self.mappings[x]]
349
385
 
350
- # smartify these loops
351
- # npyscal = len(self.pyscal_structure.atoms.types)
386
+ # Update pyscal structure types
352
387
  self.pyscal_structure.atoms.types = self.atom_type
353
- # for count in range(npyscal)):
354
- # self.pyscal_structure.atoms.types[count] = self.atom_type[count]
355
388
 
356
389
  def iselement(self, symbol):
357
390
  try:
@@ -392,7 +425,7 @@ class CompositionTransformation:
392
425
  # If element_group matches our pair_list_old, replace with pair_list_new
393
426
  # Otherwise replace with pair_list_old (for the old/reference command)
394
427
  if element_group == self.pair_list_old or set(element_group) == set(
395
- self.element
428
+ self.calc.element
396
429
  ):
397
430
  # This needs special handling - we'll mark position for later
398
431
  result_parts.append("__ELEMENTS__")
@@ -432,12 +465,22 @@ class CompositionTransformation:
432
465
 
433
466
  def get_swap_types(self):
434
467
  """
435
- Get swapping types
468
+ Get swapping types for configurational entropy calculation.
469
+
470
+ Returns types that share the same initial element but have different
471
+ transformation paths (e.g., Al→Al vs Al→Mg).
472
+
473
+ The order matters for reversibility:
474
+ - Forward pass (e.g., Mg→Al enrichment): swap between Mg types
475
+ - Backward pass (e.g., Al→Mg depletion): swap between Al types
476
+
477
+ Returns list ordered as: [conserved_type, transforming_type]
478
+ where conserved_type is X→X and transforming_type is X→Y
436
479
  """
437
480
  swap_list = []
438
481
  for mapping in self.unique_mappings:
439
482
  map_split = mapping.split("-")
440
- # conserved atom
483
+ # conserved atom - skip
441
484
  if map_split[0] == map_split[1]:
442
485
  pass
443
486
  else:
@@ -446,26 +489,77 @@ class CompositionTransformation:
446
489
  first_map = f"{first_type}-{first_type}"
447
490
  second_map = mapping
448
491
 
449
- # get the numbers from dict
450
- first_swap_type = self.mappingdict[first_map]
451
- second_swap_type = self.mappingdict[second_map]
492
+ # Check if conserved mapping exists
493
+ if first_map in self.mappingdict:
494
+ # get the numbers from dict
495
+ first_swap_type = self.mappingdict[first_map]
496
+ second_swap_type = self.mappingdict[second_map]
497
+ # Order: [transforming_type, conserved_type]
498
+ # This represents: atoms that transform vs atoms that don't
499
+ swap_list.append([second_swap_type, first_swap_type])
500
+ else:
501
+ # 100% transformation case - no conserved atoms of this type
502
+ # Only the transforming type exists
503
+ second_swap_type = self.mappingdict[second_map]
504
+ swap_list.append([second_swap_type])
452
505
 
453
- swap_list.append([first_swap_type, second_swap_type])
454
- return swap_list[0]
506
+ return swap_list[0] if swap_list else []
455
507
 
456
508
  def write_structure(self, outfilename):
457
- # create some species dict
458
- # just to trick ase to write
459
- utypes = np.unique(self.pyscal_structure.atoms["types"])
460
- element_list = list(element_dict.keys())
461
- element_type_dict = {
462
- str(u): element_list[count] for count, u in enumerate(utypes)
463
- }
464
- species = [
465
- element_type_dict[str(x)] for x in self.pyscal_structure.atoms["types"]
466
- ]
467
- self.pyscal_structure.atoms["species"] = species
468
- self.pyscal_structure.write.file(outfilename, format="lammps-data")
509
+ """Write structure to LAMMPS data file with proper type declarations.
510
+
511
+ Writes using ASE directly with custom atom types to preserve distinct
512
+ type numbers for each transformation mapping.
513
+ """
514
+ from ase.io import write as ase_write
515
+ from ase import Atoms as ASEAtoms
516
+
517
+ # Get positions and cell from pyscal structure
518
+ positions = self.pyscal_structure.atoms.positions
519
+ cell = self.pyscal_structure.box
520
+
521
+ # Create ASE Atoms object with chemical symbols from reversetypedict
522
+ # All atoms get their source element symbol
523
+ symbols = [self.reversetypedict[t] for t in self.pyscal_structure.atoms.types]
524
+
525
+ ase_atoms = ASEAtoms(symbols=symbols, positions=positions, cell=cell, pbc=True)
526
+
527
+ # Write using ASE with atom_style
528
+ ase_write(outfilename, ase_atoms, format="lammps-data", atom_style="atomic")
529
+
530
+ # Post-process to fix the type column with our custom types
531
+ with open(outfilename, "r") as f:
532
+ lines = f.readlines()
533
+
534
+ # Find the Atoms section and replace type numbers
535
+ in_atoms_section = False
536
+ atom_idx = 0
537
+ for i, line in enumerate(lines):
538
+ if "Atoms" in line and "#" in line:
539
+ in_atoms_section = True
540
+ continue
541
+
542
+ if in_atoms_section and line.strip():
543
+ parts = line.split()
544
+ if len(parts) >= 5: # atom_id type x y z
545
+ # Replace the type (column 1, 0-indexed) with our custom type
546
+ custom_type = self.pyscal_structure.atoms.types[atom_idx]
547
+ parts[1] = str(custom_type)
548
+ lines[i] = " " + " ".join(parts) + "\n"
549
+ atom_idx += 1
550
+ if atom_idx >= len(self.pyscal_structure.atoms.types):
551
+ break
552
+
553
+ # Update the number of atom types in the header
554
+ required_ntypes = len(self.pair_list_old)
555
+ for i, line in enumerate(lines):
556
+ if "atom types" in line:
557
+ lines[i] = f"{required_ntypes} atom types\n"
558
+ break
559
+
560
+ # Write the corrected file
561
+ with open(outfilename, "w") as f:
562
+ f.writelines(lines)
469
563
 
470
564
  def prepare_mappings(self):
471
565
  self.atom_mark = []
calphy/helpers.py CHANGED
@@ -74,6 +74,8 @@ def create_object(
74
74
  else:
75
75
  if cmdargs == "":
76
76
  cmdargs = None
77
+ elif isinstance(cmdargs, str):
78
+ cmdargs = cmdargs.split()
77
79
  lmp = LammpsLibrary(cores=cores, working_directory=directory, cmdargs=cmdargs)
78
80
 
79
81
  commands = [
@@ -129,7 +131,7 @@ def set_mass(lmp, options):
129
131
 
130
132
  else:
131
133
  for i in range(options.n_elements):
132
- lmp.command(f"mass {i+1} {options.mass[i]}")
134
+ lmp.command(f"mass {i + 1} {options.mass[i]}")
133
135
  return lmp
134
136
 
135
137
 
calphy/input.py CHANGED
@@ -49,7 +49,7 @@ from pyscal3.core import structure_dict, element_dict, _make_crystal
49
49
  from ase.io import read, write
50
50
  import shutil
51
51
 
52
- __version__ = "1.4.6"
52
+ __version__ = "1.4.12"
53
53
 
54
54
 
55
55
  def _check_equal(val):
@@ -350,7 +350,6 @@ class Calculation(BaseModel, title="Main input class"):
350
350
 
351
351
  @model_validator(mode="after")
352
352
  def _validate_all(self) -> "Input":
353
-
354
353
  if not (len(self.element) == len(self.mass)):
355
354
  raise ValueError("mass and elements should have same length")
356
355
 
calphy/liquid.py CHANGED
@@ -23,6 +23,7 @@ sarath.menon@ruhr-uni-bochum.de/yury.lysogorskiy@icams.rub.de
23
23
 
24
24
  import numpy as np
25
25
  import yaml
26
+ import os
26
27
 
27
28
  from calphy.integrators import *
28
29
  import calphy.helpers as ph
@@ -100,6 +101,10 @@ class Liquid(cph.Phase):
100
101
  # if melting cycle is over and still not melted, raise error
101
102
  if not melted:
102
103
  lmp.close()
104
+ # Preserve log file
105
+ logfile = os.path.join(self.simfolder, "log.lammps")
106
+ if os.path.exists(logfile):
107
+ os.rename(logfile, os.path.join(self.simfolder, "melting.log.lammps"))
103
108
  raise SolidifiedError(
104
109
  "Liquid system did not melt, maybe try a higher thigh temperature."
105
110
  )
@@ -175,6 +180,10 @@ class Liquid(cph.Phase):
175
180
  self.dump_current_snapshot(lmp, "traj.equilibration_stage2.dat")
176
181
  lmp = ph.write_data(lmp, "conf.equilibration.data")
177
182
  lmp.close()
183
+ # Preserve log file
184
+ logfile = os.path.join(self.simfolder, "log.lammps")
185
+ if os.path.exists(logfile):
186
+ os.rename(logfile, os.path.join(self.simfolder, "averaging.log.lammps"))
178
187
 
179
188
  def run_integration(self, iteration=1):
180
189
  """
@@ -390,6 +399,10 @@ class Liquid(cph.Phase):
390
399
 
391
400
  # close object
392
401
  lmp.close()
402
+ # Preserve log file
403
+ logfile = os.path.join(self.simfolder, "log.lammps")
404
+ if os.path.exists(logfile):
405
+ os.rename(logfile, os.path.join(self.simfolder, "integration.log.lammps"))
393
406
 
394
407
  def thermodynamic_integration(self):
395
408
  """
calphy/phase.py CHANGED
@@ -278,6 +278,12 @@ class Phase:
278
278
  solids = ph.find_solid_fraction(os.path.join(self.simfolder, filename))
279
279
  if solids / lmp.natoms < self.calc.tolerance.solid_fraction:
280
280
  lmp.close()
281
+ # Preserve log file on error
282
+ logfile = os.path.join(self.simfolder, "log.lammps")
283
+ if os.path.exists(logfile):
284
+ os.rename(
285
+ logfile, os.path.join(self.simfolder, "melted_error.log.lammps")
286
+ )
281
287
  raise MeltedError(
282
288
  "System melted, increase size or reduce temp!\n Solid detection algorithm only works with BCC/FCC/HCP/SC/DIA. Detection algorithm can be turned off by setting:\n tolerance.solid_fraction: 0"
283
289
  )
@@ -287,6 +293,12 @@ class Phase:
287
293
  solids = ph.find_solid_fraction(os.path.join(self.simfolder, filename))
288
294
  if solids / lmp.natoms > self.calc.tolerance.liquid_fraction:
289
295
  lmp.close()
296
+ # Preserve log file on error
297
+ logfile = os.path.join(self.simfolder, "log.lammps")
298
+ if os.path.exists(logfile):
299
+ os.rename(
300
+ logfile, os.path.join(self.simfolder, "solidified_error.log.lammps")
301
+ )
290
302
  raise SolidifiedError("System solidified, increase temperature")
291
303
 
292
304
  def fix_nose_hoover(
@@ -594,6 +606,15 @@ class Phase:
594
606
 
595
607
  if not converged:
596
608
  lmp.close()
609
+ # Preserve log file on error
610
+ logfile = os.path.join(self.simfolder, "log.lammps")
611
+ if os.path.exists(logfile):
612
+ os.rename(
613
+ logfile,
614
+ os.path.join(
615
+ self.simfolder, "pressure_convergence_error.log.lammps"
616
+ ),
617
+ )
597
618
  raise ValueError(
598
619
  "Pressure did not converge after MD runs, maybe change lattice_constant and try?"
599
620
  )
@@ -708,6 +729,15 @@ class Phase:
708
729
 
709
730
  if not converged:
710
731
  lmp.close()
732
+ # Preserve log file on error
733
+ logfile = os.path.join(self.simfolder, "log.lammps")
734
+ if os.path.exists(logfile):
735
+ os.rename(
736
+ logfile,
737
+ os.path.join(
738
+ self.simfolder, "constrained_pressure_error.log.lammps"
739
+ ),
740
+ )
711
741
  raise ValueError("pressure did not converge")
712
742
 
713
743
  def process_pressure(
@@ -1180,6 +1210,12 @@ class Phase:
1180
1210
 
1181
1211
  # close the object
1182
1212
  lmp.close()
1213
+ # Preserve log file
1214
+ logfile = os.path.join(self.simfolder, "log.lammps")
1215
+ if os.path.exists(logfile):
1216
+ os.rename(
1217
+ logfile, os.path.join(self.simfolder, "reversible_scaling.log.lammps")
1218
+ )
1183
1219
 
1184
1220
  self.logger.info("Please cite the following publications:")
1185
1221
  if self.calc.mode == "mts":
@@ -1217,10 +1253,13 @@ class Phase:
1217
1253
  return_values=return_values,
1218
1254
  )
1219
1255
 
1220
- self.logger.info(f'Maximum energy dissipation along the temperature scaling part: {ediss} eV/atom')
1221
- if np.abs(ediss) > 1E-4:
1222
- self.logger.warning(f'Found max energy dissipation of {ediss} along the temperature scaling path. Please ensure there are no structural changes!')
1223
-
1256
+ self.logger.info(
1257
+ f"Maximum energy dissipation along the temperature scaling part: {ediss} eV/atom"
1258
+ )
1259
+ if np.abs(ediss) > 1e-4:
1260
+ self.logger.warning(
1261
+ f"Found max energy dissipation of {ediss} along the temperature scaling path. Please ensure there are no structural changes!"
1262
+ )
1224
1263
 
1225
1264
  if return_values:
1226
1265
  return res
@@ -1369,6 +1408,12 @@ class Phase:
1369
1408
  lmp.command("run %d" % self.calc._n_sweep_steps)
1370
1409
 
1371
1410
  lmp.close()
1411
+ # Preserve log file
1412
+ logfile = os.path.join(self.simfolder, "log.lammps")
1413
+ if os.path.exists(logfile):
1414
+ os.rename(
1415
+ logfile, os.path.join(self.simfolder, "temperature_scaling.log.lammps")
1416
+ )
1372
1417
 
1373
1418
  def pressure_scaling(self, iteration=1):
1374
1419
  """
@@ -1499,6 +1544,12 @@ class Phase:
1499
1544
  lmp.command("run %d" % self.calc._n_sweep_steps)
1500
1545
 
1501
1546
  lmp.close()
1547
+ # Preserve log file
1548
+ logfile = os.path.join(self.simfolder, "log.lammps")
1549
+ if os.path.exists(logfile):
1550
+ os.rename(
1551
+ logfile, os.path.join(self.simfolder, "pressure_scaling.log.lammps")
1552
+ )
1502
1553
 
1503
1554
  self.logger.info("Please cite the following publications:")
1504
1555
  self.logger.info("- 10.1016/j.commatsci.2022.111275")
calphy/phase_diagram.py CHANGED
@@ -332,7 +332,8 @@ def _create_composition_array(comp_range, interval, reference):
332
332
  is_reference = np.abs(comp_arr - reference) < COMPOSITION_TOLERANCE
333
333
  elif len(comp_range) == 1:
334
334
  comp_arr = [comp_range[0]]
335
- is_reference = [True]
335
+ # Check if this single composition equals the reference
336
+ is_reference = [np.abs(comp_range[0] - reference) < COMPOSITION_TOLERANCE]
336
337
  else:
337
338
  raise ValueError("Composition range should be scalar or list of two values!")
338
339
 
calphy/routines.py CHANGED
@@ -94,6 +94,9 @@ class MeltingTemp:
94
94
  calc["mode"] = "ts"
95
95
  calc["temperature"] = [int(self.tmin), int(self.tmax)]
96
96
  calc["reference_phase"] = 'solid'
97
+ # Preserve n_iterations from the original melting_temperature calculation
98
+ if "n_iterations" in data["calculations"][int(self.calc.kernel)]:
99
+ calc["n_iterations"] = data["calculations"][int(self.calc.kernel)]["n_iterations"]
97
100
  calculations["calculations"].append(calc)
98
101
 
99
102
  with open(self.calc.inputfile, 'r') as fin:
@@ -103,6 +106,9 @@ class MeltingTemp:
103
106
  calc["mode"] = "ts"
104
107
  calc["temperature"] = [int(self.tmin), int(self.tmax)]
105
108
  calc["reference_phase"] = 'liquid'
109
+ # Preserve n_iterations from the original melting_temperature calculation
110
+ if "n_iterations" in data["calculations"][int(self.calc.kernel)]:
111
+ calc["n_iterations"] = data["calculations"][int(self.calc.kernel)]["n_iterations"]
106
112
  calculations["calculations"].append(calc)
107
113
 
108
114
  outfile = f'{self.calc.create_identifier()}.{self.attempts}.yaml'