Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
formation enthalpy data and the model needs to have the lower order terms
in composition.
One possible issue is that the new GM in the fixed model does not have any
individual contributions, so it cannot be used to test excluded model
contributions. The only excluded model contributions in this data are
idmix, but the property we are testing is HM_FORM, so the feature transform
of the idmix property should be zero.
"""
# Hack the namespace to make the copy-pasted Gibbs energy function work
from sympy import log, Piecewise
T = v.T
data = [{'components': ['AL', 'NI', 'VA'], 'phases': ['BCC_B2'], 'solver': {'mode': 'manual', 'sublattice_occupancies': [[1.0, [0.5, 0.5], 1.0], [1.0, [0.75, 0.25], 1.0]], 'sublattice_site_ratios': [0.5, 0.5, 1.0], 'sublattice_configurations': (('AL', ('NI', 'VA'), 'VA'), ('AL', ('NI', 'VA'), 'VA')), 'comment': 'BCC_B2 sublattice configuration (2SL)'}, 'conditions': {'P': 101325.0, 'T': np.array([300.])}, 'reference_state': 'SGTE91', 'output': 'HM_FORM', 'values': np.array([[[-40316.61077, -56361.58554]]]), 'reference': 'C. Jiang 2009 (constrained SQS)', 'excluded_model_contributions': ['idmix']}, {'components': ['AL', 'NI', 'VA'], 'phases': ['BCC_B2'], 'solver': {'mode': 'manual', 'sublattice_occupancies': [[1.0, [0.5, 0.5], 1.0], [1.0, [0.75, 0.25], 1.0]], 'sublattice_site_ratios': [0.5, 0.5, 1.0], 'sublattice_configurations': (('AL', ('NI', 'VA'), 'VA'), ('AL', ('NI', 'VA'), 'VA')), 'comment': 'BCC_B2 sublattice configuration (2SL)'}, 'conditions': {'P': 101325.0, 'T': np.array([300.])}, 'reference_state': 'SGTE91', 'output': 'HM_FORM', 'values': np.array([[[-41921.43363, -57769.49473]]]), 'reference': 'C. Jiang 2009 (relaxed SQS)', 'excluded_model_contributions': ['idmix']}]
NEW_GM = 8.3145*T*(0.5*Piecewise((v.SiteFraction("BCC_B2", 0, "AL")*log(v.SiteFraction("BCC_B2", 0, "AL")), v.SiteFraction("BCC_B2", 0, "AL") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 0, "NI")*log(v.SiteFraction("BCC_B2", 0, "NI")), v.SiteFraction("BCC_B2", 0, "NI") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 0, "VA")*log(v.SiteFraction("BCC_B2", 0, "VA")), v.SiteFraction("BCC_B2", 0, "VA") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 1, "AL")*log(v.SiteFraction("BCC_B2", 1, "AL")), v.SiteFraction("BCC_B2", 1, "AL") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 1, "NI")*log(v.SiteFraction("BCC_B2", 1, "NI")), v.SiteFraction("BCC_B2", 1, "NI") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + 0.5*Piecewise((v.SiteFraction("BCC_B2", 1, "VA")*log(v.SiteFraction("BCC_B2", 1, "VA")), v.SiteFraction("BCC_B2", 1, "VA") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + Piecewise((v.SiteFraction("BCC_B2", 2, "VA")*log(v.SiteFraction("BCC_B2", 2, "VA")), v.SiteFraction("BCC_B2", 2, "VA") > 1.0e-16), (0, True))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI"))) + (45262.9*v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 0, "NI")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 2, "VA") + 45262.9*v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 1, "NI")*v.SiteFraction("BCC_B2", 2, "VA"))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI")) + (1.0*v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 2, "VA")*Piecewise((10083 - 4.813*T, (T >= 298.15) & (T < 2900.0)), (0, True)) + v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 1, "NI")*v.SiteFraction("BCC_B2", 2, "VA")*(9.52839e-8*T**3 + 0.00123463*T**2 + 0.000871898*T*log(T) + 1.31471*T - 64435.3 + 23095.2/T) + v.SiteFraction("BCC_B2", 0, "AL")*v.SiteFraction("BCC_B2", 1, "VA")*v.SiteFraction("BCC_B2", 2, "VA")*(10.0*T + 16432.5) + v.SiteFraction("BCC_B2", 0, "NI")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 2, "VA")*(9.52839e-8*T**3 + 0.00123463*T**2 + 0.000871898*T*log(T) + 1.31471*T - 64435.3 + 23095.2/T) + 1.0*v.SiteFraction("BCC_B2", 0, "NI")*v.SiteFraction("BCC_B2", 1, "NI")*v.SiteFraction("BCC_B2", 2, "VA")*Piecewise((8715.084 - 3.556*T, (T >= 298.15) & (T < 3000.0)), (0, True)) + 32790.6*v.SiteFraction("BCC_B2", 0, "NI")*v.SiteFraction("BCC_B2", 1, "VA")*v.SiteFraction("BCC_B2", 2, "VA") + v.SiteFraction("BCC_B2", 0, "VA")*v.SiteFraction("BCC_B2", 1, "AL")*v.SiteFraction("BCC_B2", 2, "VA")*(10.0*T + 16432.5) + 32790.6*v.SiteFraction("BCC_B2", 0, "VA")*v.SiteFraction("BCC_B2", 1, "NI")*v.SiteFraction("BCC_B2", 2, "VA"))/(0.5*v.SiteFraction("BCC_B2", 0, "AL") + 0.5*v.SiteFraction("BCC_B2", 0, "NI") + 0.5*v.SiteFraction("BCC_B2", 1, "AL") + 0.5*v.SiteFraction("BCC_B2", 1, "NI"))
dbf = Database("""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
$ Date: 2019-12-08 18:05
$ Components: AL, NI, VA
$ Phases: BCC_B2
$ Generated by brandon (pycalphad 0.8.1.post1)
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
ELEMENT AL FCC_A1 26.982 4577.3 28.322 !
ELEMENT NI FCC_A1 58.69 4787.0 29.796 !
ELEMENT VA VACUUM 0.0 0.0 0.0 !
TYPE_DEFINITION % SEQ * !
DEFINE_SYSTEM_DEFAULT ELEMENT 2 !
DEFAULT_COMMAND DEFINE_SYSTEM_ELEMENT VA !
"""
Returns the ideal mixing energy in symbolic form.
"""
# Normalize site ratios
site_ratio_normalization = self._site_ratio_normalization(phase)
site_ratios = phase.sublattices
site_ratios = [c/site_ratio_normalization for c in site_ratios]
ideal_mixing_term = S.Zero
for subl_index, sublattice in enumerate(phase.constituents):
active_comps = set(sublattice).intersection(self.components)
if len(active_comps) == 1:
continue # no mixing if only one species in sublattice
ratio = site_ratios[subl_index]
for comp in active_comps:
sitefrac = \
v.SiteFraction(phase.name, subl_index, comp)
# We lose some precision here, but this makes the limit behave nicely
# We're okay until fractions of about 1e-16
mixing_term = log(sitefrac**sitefrac)
ideal_mixing_term += (mixing_term*ratio)
ideal_mixing_term *= (v.R * v.T)
return ideal_mixing_term
# Special normalization rules for parameters apply under this model
# Reference: Bo Sundman, "Modification of the two-sublattice model for liquids",
# Calphad, Volume 15, Issue 2, 1991, Pages 109-119, ISSN 0364-5916
if not any([m.species.charge < 0 for m in mixing_term.free_symbols]):
pair_rule = {}
# Cation site fractions must always appear with vacancy site fractions
va_subls = [(v.Species('VA') in phase.constituents[idx]) for idx in range(len(phase.constituents))]
va_subl_idx = (len(phase.constituents) - 1) - va_subls[::-1].index(True)
va_present = any((v.Species('VA') in c) for c in param['constituent_array'])
if va_present and (max(len(c) for c in param['constituent_array']) == 1):
# No need to apply pair rule for VA-containing endmember
pass
elif va_subl_idx > -1:
for sym in mixing_term.free_symbols:
if sym.species.charge > 0:
pair_rule[sym] = sym * v.SiteFraction(sym.phase_name, va_subl_idx, v.Species('VA'))
mixing_term = mixing_term.xreplace(pair_rule)
# This parameter is normalized differently due to the variable charge valence of vacancies
mixing_term *= self.site_ratios[va_subl_idx]
rk_terms.append(mixing_term * param['parameter'])
return Add(*rk_terms)
# Fix variable names
variable_rename_dict = {}
for atom in disordered_model.energy.atoms(v.SiteFraction):
# Replace disordered phase site fractions with mole fractions of
# ordered phase site fractions.
# Special case: Pure vacancy sublattices
all_species_in_sublattice = \
dbe.phases[disordered_phase_name].constituents[
atom.sublattice_index]
if atom.species == 'VA' and len(all_species_in_sublattice) == 1:
# Assume: Pure vacancy sublattices are always last
vacancy_subl_index = \
len(dbe.phases[ordered_phase_name].constituents)-1
variable_rename_dict[atom] = \
v.SiteFraction(
ordered_phase_name, vacancy_subl_index, atom.species)
else:
# All other cases: replace site fraction with mole fraction
variable_rename_dict[atom] = \
self.mole_fraction(
atom.species,
ordered_phase_name,
constituents,
dbe.phases[ordered_phase_name].sublattices
)
# Save all of the ordered energy contributions
# This step is why this routine must be called _last_ in build_phase
ordered_energy = Add(*list(self.models.values()))
self.models.clear()
# Copy the disordered energy contributions into the correct bins
for name, value in disordered_model.models.items():
site_fractions = []
for ds in data:
for _ in ds['conditions']['T']:
sf = build_sitefractions(phase_name, ds['solver']['sublattice_configurations'], ds['solver'].get('sublattice_occupancies', np.ones((len(ds['solver']['sublattice_configurations']), len(ds['solver']['sublattice_configurations'][0])), dtype=np.float)))
site_fractions.append(sf)
site_fractions = list(itertools.chain(*site_fractions))
feat_transform = feature_transforms[desired_property]
data_qtys = np.concatenate(shift_reference_state(data, feat_transform, fixed_model, mole_atoms_per_mole_formula_unit), axis=-1)
# Remove existing partial model contributions from the data, convert to per mole-formula units
data_qtys = data_qtys - feat_transform(fixed_model.ast)*mole_atoms_per_mole_formula_unit
# Subtract out high-order (in T) parameters we've already fit, already in per mole-formula units
data_qtys = data_qtys - feat_transform(sum(fixed_portions))
# if any site fractions show up in our data_qtys that aren't in this datasets site fractions, set them to zero.
for sf, i, (_, (sf_product, inter_product)) in zip(site_fractions, data_qtys, samples):
missing_variables = sympy.S(i).atoms(v.SiteFraction) - set(sf.keys())
sf.update({x: 0. for x in missing_variables})
# The equations we have just have the site fractions as YS
# and interaction products as Z, so take the product of all
# the site fractions that we see in our data qtys
if hasattr(inter_product, '__len__'): # Z is an array of [V_I, V_J, V_K]
sf.update({YS: sf_product, V_I: inter_product[0], V_J: inter_product[1], V_K: inter_product[2]})
else: # Z is probably a number
sf.update({YS: sf_product, Z: inter_product})
data_qtys = [sympy.S(i).xreplace(sf).xreplace({v.T: ixx[0]}).evalf()
for i, sf, ixx in zip(data_qtys, site_fractions, samples)]
data_qtys = np.asarray(data_qtys, dtype=np.float)
return data_qtys
def generate_dof(phase, active_comps):
"""
Accept a Phase object and a set() of the active components.
Return a tuple of variable names and the sublattice degrees of freedom.
"""
variables = []
sublattice_dof = []
for idx, sublattice in enumerate(phase.constituents):
dof = 0
for component in sorted(set(sublattice).intersection(active_comps)):
variables.append(v.SiteFraction(phase.name.upper(), idx, component))
dof += 1
sublattice_dof.append(dof)
return variables, sublattice_dof
def moles(self, species):
"Number of moles of species or elements."
species = v.Species(species)
is_pure_element = (len(species.constituents.keys()) == 1 and
list(species.constituents.keys())[0] == species.name)
result = S.Zero
normalization = S.Zero
if is_pure_element:
element = list(species.constituents.keys())[0]
for idx, sublattice in enumerate(self.constituents):
active = set(sublattice).intersection(self.components)
result += self.site_ratios[idx] * \
sum(int(spec.number_of_atoms > 0) * spec.constituents.get(element, 0) * v.SiteFraction(self.phase_name, idx, spec)
for spec in active)
normalization += self.site_ratios[idx] * \
sum(spec.number_of_atoms * v.SiteFraction(self.phase_name, idx, spec)
for spec in active)
else:
for idx, sublattice in enumerate(self.constituents):
active = set(sublattice).intersection({species})
if len(active) == 0:
continue
result += self.site_ratios[idx] * sum(v.SiteFraction(self.phase_name, idx, spec) for spec in active)
normalization += self.site_ratios[idx] * \
sum(int(spec.number_of_atoms > 0) * v.SiteFraction(self.phase_name, idx, spec)
for spec in active)
return result / normalization
for subl_index, comps in enumerate(param['constituent_array']):
comp_symbols = None
# convert strings to symbols
if comps[0] == '*':
# Handle wildcards in constituent array
comp_symbols = \
[
v.SiteFraction(phase.name, subl_index, comp)
for comp in set(phase.constituents[subl_index])\
.intersection(self.components)
]
mixing_term *= Add(*comp_symbols)
else:
comp_symbols = \
[
v.SiteFraction(phase.name, subl_index, comp)
for comp in comps
]
mixing_term *= Mul(*comp_symbols)
# is this a higher-order interaction parameter?
if len(comps) == 2 and param['parameter_order'] > 0:
# interacting sublattice, add the interaction polynomial
mixing_term *= Pow(comp_symbols[0] - \
comp_symbols[1], param['parameter_order'])
if len(comps) == 3:
# 'parameter_order' is an index to a variable when
# we are in the ternary interaction parameter case
# NOTE: The commercial software packages seem to have
# a "feature" where, if only the zeroth
# parameter_order term of a ternary parameter is specified,
# the other two terms are automatically generated in order
def get_internal_constraints(self):
constraints = []
for idx, sublattice in enumerate(self.constituents):
constraints.append(sum(v.SiteFraction(self.phase_name, idx, spec) for spec in sublattice) - 1)
return constraints