Source code for pycalphad.property_framework.tzero

from typing import Dict, List, Optional, Tuple, Union, TYPE_CHECKING
import numpy.typing as npt
from pycalphad.core.composition_set import CompositionSet
if TYPE_CHECKING:
    from pycalphad.core.workspace import Workspace
from pycalphad.core.solver import Solver
from pycalphad.property_framework import as_property, JanssonDerivative, ConditionableComputableProperty, \
    ModelComputedProperty

[docs] def find_first_compset(phase_name: str, wks: "Workspace"): for _, compsets in wks.enumerate_composition_sets(): for compset in compsets: if compset.phase_record.phase_name == phase_name: return compset return None
[docs] class T0(object): "T0: temperature where the energy of two phases are equal, GM(ONE) = GM(TWO)" _phase_one: CompositionSet _phase_two: CompositionSet solver: Solver property_to_optimize: ConditionableComputableProperty minimum_value: float = 298.15 maximum_value: float = 6000 residual_tol: float = 0.01 maximum_iterations: int = 50 implementation_units = property(lambda self: self.property_to_optimize.implementation_units) display_units = property(lambda self: self.property_to_optimize.display_units) def __init__(self, phase_one: Union[CompositionSet, str], phase_two: Union[CompositionSet, str], wks: Optional["Workspace"]): if wks is None: if not isinstance(phase_one, CompositionSet) and not isinstance(phase_two, CompositionSet): raise ValueError('T0 calculation requires a starting point for both phases;' ' either CompositionSet objects should be specified, or pass in a Workspace' ' of a previous calculation including the phases.' ) if not isinstance(phase_one, CompositionSet): phase_one_orig = phase_one phase_one = find_first_compset(phase_one, wks) if phase_one is None: raise ValueError(f'{phase_one_orig} is never stable in the specified Workspace') if not isinstance(phase_two, CompositionSet): phase_two_orig = phase_two phase_two = find_first_compset(phase_two, wks) if phase_two is None: raise ValueError(f'{phase_two_orig} is never stable in the specified Workspace') self._phase_one = phase_one self._phase_two = phase_two self.solver = Solver() # This cannot be a class-level attribute because we cannot assume pycalphad.variables is initialized # if it isn't, we will get back a ModelComputedProperty instead of the TemperatureType we want # We cannot just import pycalphad.variables because of a circular import self.property_to_optimize = as_property('T') def __str__(self): return f'{self.__class__.__name__}({self._phase_one.phase_record.phase_name},{self._phase_two.phase_record.phase_name})' @property def shape(self) -> Tuple[int]: return tuple()
[docs] def compute_property(self, equilibrium_compsets: List[CompositionSet], cur_conds: Dict[str, float], chemical_potentials: npt.ArrayLike) -> float: s = self.solver initial_conditions = cur_conds gm_one = ModelComputedProperty('GM', self._phase_one.phase_record.phase_name) gm_one_grad = JanssonDerivative(gm_one, self.property_to_optimize) gm_two = ModelComputedProperty('GM', self._phase_two.phase_record.phase_name) gm_two_grad = JanssonDerivative(gm_two, self.property_to_optimize) conditions = initial_conditions.copy() for _ in range(self.maximum_iterations): one_result = s.solve([self._phase_one], conditions) two_result = s.solve([self._phase_two], conditions) one_gm = gm_one.compute_property([self._phase_one], conditions, one_result.chemical_potentials) one_grad = gm_one_grad.compute_property([self._phase_one], conditions, one_result.chemical_potentials) two_gm = gm_two.compute_property([self._phase_two], conditions, two_result.chemical_potentials) two_grad = gm_two_grad.compute_property([self._phase_two], conditions, two_result.chemical_potentials) t0_grad = 2*one_gm*one_grad - 2*(one_grad*two_gm + two_grad*one_gm) + 2*two_gm*two_grad residual = (one_gm-two_gm)**2 if abs(t0_grad) < 1e-10: t0_step = 0 else: t0_step = -residual/t0_grad conditions[self.property_to_optimize] = max(min(conditions[self.property_to_optimize] + t0_step, self.maximum_value), self.minimum_value) if residual < self.residual_tol: break if residual > self.residual_tol: return float('nan') return conditions[self.property_to_optimize]