Skip to content

Commit d8b31d6

Browse files
committed
add further fixes for phase dia mode
1 parent 0c4c2b0 commit d8b31d6

File tree

2 files changed

+390
-44
lines changed

2 files changed

+390
-44
lines changed

calphy/phase_diagram.py

Lines changed: 139 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,97 @@ def read_structure_composition(lattice_file, element_list):
298298
return input_chemical_composition
299299

300300

301+
# Constants for phase diagram preparation
302+
COMPOSITION_TOLERANCE = 1E-5
303+
304+
305+
def _create_composition_array(comp_range, interval, reference):
306+
"""
307+
Create composition array from range specification.
308+
309+
Parameters
310+
----------
311+
comp_range : list or scalar
312+
Composition range [min, max] or single value
313+
interval : float
314+
Composition interval
315+
reference : float
316+
Reference composition value
317+
318+
Returns
319+
-------
320+
tuple
321+
(comp_arr, is_reference) - composition array and boolean array marking reference compositions
322+
"""
323+
# Convert to list if scalar
324+
if not isinstance(comp_range, list):
325+
comp_range = [comp_range]
326+
327+
if len(comp_range) == 2:
328+
comp_arr = np.arange(comp_range[0], comp_range[-1], interval)
329+
last_val = comp_range[-1]
330+
if last_val not in comp_arr:
331+
comp_arr = np.append(comp_arr, last_val)
332+
is_reference = np.abs(comp_arr - reference) < COMPOSITION_TOLERANCE
333+
elif len(comp_range) == 1:
334+
comp_arr = [comp_range[0]]
335+
is_reference = [True]
336+
else:
337+
raise ValueError("Composition range should be scalar or list of two values!")
338+
339+
return comp_arr, is_reference
340+
341+
342+
def _create_temperature_array(temp_range, interval):
343+
"""
344+
Create temperature array from range specification.
345+
346+
Parameters
347+
----------
348+
temp_range : list or scalar
349+
Temperature range [min, max] or single value
350+
interval : float
351+
Temperature interval
352+
353+
Returns
354+
-------
355+
ndarray
356+
Temperature array
357+
"""
358+
# Convert to list if scalar
359+
if not isinstance(temp_range, list):
360+
temp_range = [temp_range]
361+
362+
if len(temp_range) == 2:
363+
ntemps = int((temp_range[-1] - temp_range[0]) / interval) + 1
364+
temp_arr = np.linspace(temp_range[0], temp_range[-1], ntemps, endpoint=True)
365+
elif len(temp_range) == 1:
366+
temp_arr = [temp_range[0]]
367+
else:
368+
raise ValueError("Temperature range should be scalar or list of two values!")
369+
370+
return temp_arr
371+
372+
373+
def _add_temperature_calculations(calc_dict, temp_arr, all_calculations):
374+
"""
375+
Helper to add calculations for each temperature point.
376+
377+
Parameters
378+
----------
379+
calc_dict : dict
380+
Base calculation dictionary
381+
temp_arr : array
382+
Array of temperatures
383+
all_calculations : list
384+
List to append calculations to
385+
"""
386+
for temp in temp_arr:
387+
calc_for_temp = copy.deepcopy(calc_dict)
388+
calc_for_temp['temperature'] = int(temp)
389+
all_calculations.append(calc_for_temp)
390+
391+
301392
def fix_data_file(datafile, nelements):
302393
"""
303394
Change the atom types keyword in the structure file
@@ -353,6 +444,31 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
353444
calculation_base_name = inputyamlfile
354445

355446
for phase in data['phases']:
447+
# Validate binary system assumption
448+
n_elements = len(phase['element'])
449+
if n_elements != 2:
450+
raise ValueError(
451+
f"Phase diagram preparation currently supports only binary systems. "
452+
f"Found {n_elements} elements: {phase['element']}"
453+
)
454+
455+
# Validate element ordering consistency with pair_coeff
456+
# This ensures element[0] -> type 1, element[1] -> type 2
457+
if 'pair_coeff' in phase:
458+
from calphy.input import _extract_elements_from_pair_coeff
459+
# pair_coeff can be a list or a string - handle both
460+
pair_coeff = phase['pair_coeff']
461+
if isinstance(pair_coeff, list):
462+
pair_coeff = pair_coeff[0] if pair_coeff else None
463+
pair_coeff_elements = _extract_elements_from_pair_coeff(pair_coeff)
464+
if pair_coeff_elements != phase['element']:
465+
raise ValueError(
466+
f"Element ordering mismatch for phase '{phase.get('phase_name', 'unnamed')}'!\n"
467+
f"Elements in 'element' field: {phase['element']}\n"
468+
f"Elements from pair_coeff: {pair_coeff_elements}\n"
469+
f"These must match exactly in order (element[0] -> LAMMPS type 1, element[1] -> type 2)."
470+
)
471+
356472
phase_reference_state = phase['reference_phase']
357473
phase_name = phase['phase_name']
358474

@@ -369,34 +485,18 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
369485
other_element_list.remove(reference_element)
370486
other_element = other_element_list[0]
371487

372-
#convert to list if scalar
373-
if not isinstance(comps['range'], list):
374-
comps["range"] = [comps["range"]]
375-
if len(comps["range"]) == 2:
376-
comp_arr = np.arange(comps['range'][0], comps['range'][-1], comps['interval'])
377-
last_val = comps['range'][-1]
378-
if last_val not in comp_arr:
379-
comp_arr = np.append(comp_arr, last_val)
380-
ncomps = len(comp_arr)
381-
is_reference = np.abs(comp_arr-comps['reference']) < 1E-5
382-
elif len(comps["range"]) == 1:
383-
ncomps = 1
384-
comp_arr = [comps["range"][0]]
385-
is_reference = [True]
386-
else:
387-
raise ValueError("Composition range should be scalar of list of two values!")
488+
# Create composition array using helper function
489+
comp_arr, is_reference = _create_composition_array(
490+
comps['range'],
491+
comps['interval'],
492+
comps['reference']
493+
)
494+
ncomps = len(comp_arr)
388495

496+
# Create temperature array using helper function
389497
temps = phase["temperature"]
390-
if not isinstance(temps['range'], list):
391-
temps["range"] = [temps["range"]]
392-
if len(temps["range"]) == 2:
393-
ntemps = int((temps['range'][-1]-temps['range'][0])/temps['interval'])+1
394-
temp_arr = np.linspace(temps['range'][0], temps['range'][-1], ntemps, endpoint=True)
395-
elif len(temps["range"]) == 1:
396-
ntemps = 1
397-
temp_arr = [temps["range"][0]]
398-
else:
399-
raise ValueError("Temperature range should be scalar of list of two values!")
498+
temp_arr = _create_temperature_array(temps['range'], temps['interval'])
499+
ntemps = len(temp_arr)
400500

401501
all_calculations = []
402502

@@ -416,17 +516,14 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
416516
outfile = fix_data_file(calc['lattice'], len(calc['element']))
417517

418518
#add ref phase, needed
419-
calc['reference_phase'] = str(phase_reference_state)
519+
calc['reference_phase'] = phase_reference_state
420520
calc['reference_composition'] = comps['reference']
421-
calc['mode'] = str('fe')
521+
calc['mode'] = 'fe'
422522
calc['folder_prefix'] = f'{phase_name}-{comp:.2f}'
423-
calc['lattice'] = str(outfile)
523+
calc['lattice'] = outfile
424524

425-
#now we need to run this for different temp
426-
for temp in temp_arr:
427-
calc_for_temp = copy.deepcopy(calc)
428-
calc_for_temp['temperature'] = int(temp)
429-
all_calculations.append(calc_for_temp)
525+
# Add calculations for each temperature
526+
_add_temperature_calculations(calc, temp_arr, all_calculations)
430527
else:
431528
#off stoichiometric
432529
#copy the dict
@@ -468,7 +565,7 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
468565

469566
#just submit comp scales
470567
#add ref phase, needed
471-
calc['mode'] = str('composition_scaling')
568+
calc['mode'] = 'composition_scaling'
472569
calc['folder_prefix'] = folder_prefix
473570
calc['composition_scaling'] = {}
474571
calc['composition_scaling']['output_chemical_composition'] = output_chemical_composition
@@ -494,22 +591,20 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
494591
_ = calc.pop(key, None)
495592

496593
#add ref phase, needed
497-
calc['mode'] = str('fe')
594+
calc['mode'] = 'fe'
498595
calc['folder_prefix'] = folder_prefix
499-
calc['lattice'] = str(outfile)
596+
calc['lattice'] = outfile
500597

501-
#now we need to run this for different temp
502-
for temp in temp_arr:
503-
calc_for_temp = copy.deepcopy(calc)
504-
calc_for_temp['temperature'] = int(temp)
505-
all_calculations.append(calc_for_temp)
598+
# Add calculations for each temperature
599+
_add_temperature_calculations(calc, temp_arr, all_calculations)
506600

507601
#finish and write up the file
508602
output_data = {"calculations": all_calculations}
603+
base_name = os.path.basename(calculation_base_name)
509604
for rep in ['.yml', '.yaml']:
510-
calculation_base_name = calculation_base_name.replace(rep, '')
605+
base_name = base_name.replace(rep, '')
511606

512-
outfile_phase = phase_name + '_' + calculation_base_name + ".yaml"
607+
outfile_phase = phase_name + '_' + base_name + ".yaml"
513608
with open(outfile_phase, 'w') as fout:
514609
yaml.safe_dump(output_data, fout)
515610
print(f'Total {len(all_calculations)} calculations found for phase {phase_name}, written to {outfile_phase}')

0 commit comments

Comments
 (0)