Skip to content

Commit e8777bd

Browse files
committed
Holding Point Push for sync
1 parent c2e27eb commit e8777bd

File tree

5 files changed

+1379
-1131
lines changed

5 files changed

+1379
-1131
lines changed

SStoTi64.ipynb

+1,152-1,130
Large diffs are not rendered by default.

ammap/callableBuilders/EqScheil.py

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""This module is a script that reads the AMMap configuration YAML file and if it detects ``type: LC density`` under
2+
the ``constraints`` key, it will proceed further. It will read the ``name`` key, append it with truncated (6
3+
characters) hash of the YAML file, append it with list of elements forming the elemental spcae, and use it as the
4+
name and if the directory with the name does not exist under the ``ammap/callables`` directory, it will create one
5+
and also create a ``__init__.py`` file to make it a package. It will then read the ``ammap/templates/LCdensity.py``,
6+
prepend it with constants based on the YAML file, and write it to the output directory.
7+
"""
8+
from ruamel.yaml import YAML
9+
import os
10+
import hashlib
11+
from pathlib import Path
12+
import sys
13+
14+
# Read the AMMap configuration YAML file in a safe way
15+
yaml = YAML(typ='safe')
16+
with open(sys.argv[1], 'r') as f:
17+
data = yaml.load(f)
18+
19+
def constructCallable(name: str, hash: str, data: dict):
20+
"""Constructs the callable based on the name, hash, and data.
21+
22+
Args:
23+
name: The name of the callable extracted from the YAML file.
24+
hash: The truncated hash of the YAML file. Uses the default hashlib hashing algorithm (SHA256).
25+
data: The data from the YAML file needed to set up the callable, including the elemental space components
26+
and constraint min / max values to be enforced when evaluating the feasibility of a point.
27+
"""
28+
# Extract the elements from the data and verify compliance
29+
elements = data['elements']
30+
assert isinstance(elements, list), 'Elements key must be a list'
31+
assert all(isinstance(element, str) for element in elements), 'Elements key must be a list of strings'
32+
assert len(elements) > 0, 'Elements key must have at least one element'
33+
34+
# Construct the output directory name
35+
output = f"{name}_{hash}_{''.join(elements)}"
36+
print(f"Constructing the callable: {output}")
37+
38+
# Check if the directory exists, if not create it
39+
if not os.path.exists(f'ammap/callables/{output}'):
40+
os.makedirs(f'ammap/callables/{output}')
41+
Path(f'ammap/callables/{output}/__init__.py').touch()
42+
43+
# Read the template file
44+
with open('ammap/templates/EqScheil.py', 'r') as f:
45+
template = f.read()
46+
47+
# Prepend the template with the constants based on the YAML file
48+
constantsPayload = "# Constants based on the YAML file\n"
49+
if 'elements' not in data:
50+
raise ValueError('No elements key found in the YAML file')
51+
else:
52+
constantsPayload += f"ELEMENTS = {elements}\n"
53+
54+
headerPayload = f'"""Linear Combination (atomic-fraction based) of elemental densities in {"-".join(elements)} system with constraints: '
55+
56+
if 'min' in data:
57+
constantsPayload += f"MIN = {data['min']}\n"
58+
headerPayload += f"{data['min']}g/cm^3 < d"
59+
if 'max' in data:
60+
constantsPayload += f"MAX = {data['max']}\n"
61+
if 'min' in data:
62+
headerPayload += ' and '
63+
headerPayload += f"d < {data['max']}g/cm^3"
64+
65+
headerPayload += '"""\n'
66+
67+
payload = headerPayload + constantsPayload + '\n' + template
68+
69+
# Create the output file and write the payload
70+
with open(f'ammap/callables/{output}/LCdensity.py', 'w') as f:
71+
f.write(payload)
72+
73+
# Check if the constraints key is present in the YAML file. Exit happily if not found.
74+
if 'constraints' not in data:
75+
print('No constraints key found in the YAML file')
76+
sys.exit(0)
77+
78+
# Check if the LC density constraint is present in the YAML file. Exit happily if not found but raise an error if more than one is found.
79+
constraintMatch = [constraint['type'].lower().replace(' ', '') == 'lcdensity' for constraint in data['constraints']]
80+
if not any(constraintMatch):
81+
print('No LC density constraint found in the YAML file')
82+
sys.exit(0)
83+
elif sum(constraintMatch) > 1:
84+
raise ValueError('More than one LC density constraint found in the YAML file')
85+
else:
86+
pass
87+
88+
# Check if the `elementalSpaces` list is present, is a list, and has at least one element. Exit with an error if not.
89+
if 'elementalSpaces' not in data:
90+
raise ValueError('No elementalSpaces key found in the YAML file')
91+
elif not isinstance(data['elementalSpaces'], list):
92+
raise ValueError('elementalSpaces key must be a list')
93+
elif len(data['elementalSpaces']) == 0:
94+
raise ValueError('elementalSpaces key must have at least one element')
95+
96+
# For each item under the `elementalSpaces` key, extract key information, elements, and construct the callable.
97+
constraintIndex = constraintMatch.index(True)
98+
constraint = data['constraints'][constraintIndex]
99+
name = data['name']
100+
hash = hashlib.sha256(str(data).encode()).hexdigest()[:6]
101+
102+
for es in data['elementalSpaces']:
103+
data = {'elements': es['elements']}
104+
if 'min' in constraint:
105+
data.update({'min': constraint['min']})
106+
if 'max' in constraint:
107+
data.update({'max': constraint['max']})
108+
constructCallable(name, hash, data)
109+
110+
111+

ammap/callables/cracking.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def getCD(temperature, solidFraction, CDPoints = [0.7,0.98], numDataThreshold =
200200
# else:
201201
# CD1.append(None)
202202
# CD2.append(None)
203-
return CD1, CD2
203+
# return CD1, CD2
204204

205205
def getNeighborCSC(temperature, solidFraction, CSCPoints=[0.4,0.9,0.99],numDataThreshold = 10):
206206
"""Calculate the Critical Solidification Criteria.

ammap/templates/EqScheil.py

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from pycalphad import Database, equilibrium, variables as v
2+
from pycalphad.core.utils import instantiate_models, filter_phases, unpack_components
3+
from pycalphad.codegen.callables import build_phase_records
4+
import pandas as pd
5+
import math
6+
7+
# Problem Specific setup for our 9-element space exploration. Make sure that the elements
8+
# are in the same order as the composition vector that will be passed to the equilibrium_callable.
9+
dbf = Database("ammap/databases/TDB") #GLOBAL from yaml
10+
T = 'TEMPERATURE' #GLOBAL from yaml
11+
elementalSpaceComponents = 'ELEMENTS' #GLOBAL from yaml
12+
13+
# Setup the pycalphad models
14+
phases = list(set(dbf.phases.keys()))
15+
comps = [s.upper() for s in elementalSpaceComponents]+['VA']
16+
phases_filtered = filter_phases(dbf, unpack_components(dbf, comps), phases)
17+
models = instantiate_models(dbf, comps, phases_filtered)
18+
phase_records = build_phase_records(dbf, comps, phases_filtered, {v.N, v.P, v.T}, models=models)
19+
expected_conds=[v.T]+[v.X(el) for el in comps[:-2]]
20+
default_conds={v.P: 101325, v.N: 1.0}
21+
22+
# A neat callable for the equilibrium calculation that we will pass to the parallel graph exploration
23+
def equilibrium_callable(elP):
24+
# Round to 6 decimal places, but make sure that 0.0 is not rounded to 0.0. pyCalphad will not like
25+
# if the components are exactly 0 or sum to exactly 1 in the equilibrium calculation for reasons
26+
# that are beyond the scope of this tutorial.
27+
elP_round = [round(v-0.000001, 6) if v>0.000001 else 0.0000001 for v in elP]
28+
conds = {**default_conds, **dict(zip(expected_conds, [T] + elP_round))}
29+
eq_res = equilibrium(
30+
dbf, comps, phases_filtered,
31+
conds, model=models, phase_records=phase_records, calc_opts=dict(pdens=2000))
32+
# Process the result into what we want -> a list of phases present
33+
nPhases = eq_res.Phase.data.shape[-1]
34+
phaseList = list(eq_res.Phase.data.reshape([nPhases]))
35+
phasePresentList = [pn for pn in phaseList if pn!='']
36+
if len(phasePresentList)==0:
37+
# Decide on what to do if the equilibrium calculation failed to converge. By default, we will
38+
# just pass and let the graph exploration scheme decide what to do about it.
39+
# print(f"Point: {elP_round} failed to converge.")
40+
pass
41+
allphase = list(eq_res.Phase.data)
42+
pFrac=eq_res.NP.data.shape[-1]
43+
pFracList=list(eq_res.NP.data.reshape([nPhases]))
44+
pFracPresent= [pn for pn in pFracList if not math.isnan(pn)]
45+
return{
46+
'Phases':phasePresentList,
47+
# 'allPhase': allphase,
48+
'PhaseFraction':pFracPresent
49+
}
50+
51+
# Some extra code for the future tutorial on Scheil solidification :)
52+
from scheil import simulate_scheil_solidification
53+
# Meta Settings
54+
scheil_start_temperature = 'startTemperature' #GLOBAL from yaml
55+
liquid_phase_name = 'LIQUIDPHASE' #GLOBAL from yaml
56+
def scheil_callable(elP):
57+
elP_round = [round(v-0.000001, 6) if v>0.000001 else 0.0000001 for v in elP]
58+
initial_composition = {**dict(zip([v.X(el) for el in comps[:-2]], elP_round))}
59+
60+
sol_res = simulate_scheil_solidification(
61+
dbf, comps, phases_filtered,
62+
initial_composition, scheil_start_temperature, step_temperature=1.0)
63+
64+
phaseFractions = {}
65+
for phase, ammounts in sol_res.cum_phase_amounts.items():
66+
finalAmmount = round(ammounts[-1], 6)
67+
if finalAmmount > 0:
68+
phaseFractions[phase] = finalAmmount
69+
test = sol_res.cum_phase_amounts
70+
Lfrac = sol_res.fraction_liquid
71+
Sfrac = sol_res.fraction_solid
72+
scheilT = sol_res.temperatures
73+
solT = sol_res.temperatures[len(scheilT) - 1]
74+
ddict = sol_res.x_phases
75+
keys_to_remove_from_ddict = []
76+
for ddict_key, dict_value in ddict.items():
77+
keys_to_remove_from_dict = [key for key, value in dict_value.items() if pd.isna(value).all()]
78+
# Remove marked keys from the dictionary
79+
for key in keys_to_remove_from_dict:
80+
del dict_value[key]
81+
# If the dictionary is empty, mark the key in the defaultdict for removal
82+
if not dict_value:
83+
keys_to_remove_from_ddict.append(ddict_key)
84+
# Remove the marked keys from the defaultdict
85+
for key in keys_to_remove_from_ddict:
86+
del ddict[key]
87+
yPhase = sol_res.Y_phases
88+
for i, value in enumerate(Lfrac):
89+
if value < 1:
90+
break
91+
liqT = sol_res.temperatures[i-1]
92+
return {
93+
'scheilT': scheilT,
94+
'finalPhase': phaseFractions,
95+
'Lfrac': Lfrac,
96+
'Sfrac': Sfrac,
97+
'solT': solT,
98+
'liqT': liqT,
99+
'phaseFractions': test,
100+
'xPhase': ddict#,
101+
#'yPhase': yPhase
102+
}
103+
104+
if __name__ == "main":
105+
pass

config_SS-Ti64.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ constraints:
1818
# - SIGMA
1919
- type: LC density
2020
max: 11.5
21+
- type: scheil
22+
startTemperature: 2500
23+
liquidPhase: 'LIQUID'
24+
- type: cracking
25+
criteria:
26+
- FR
27+
- Kou
28+
- CSC
29+
- iCSC
30+
- sRDG
2131

2232
elementalSpaces:
2333
- name: SS_V

0 commit comments

Comments
 (0)