Source code for pycalphad.plot.binary.compsets

import numpy as np
import warnings

[docs]class BinaryCompset(): """ Composition set for representations of a phase equilibria in a two component system with a temperature condition. Parameters ---------- phase_name : str Name of phase temperature : float Temperature corresponding the the calculation indep_comp : str Name of the independent component composition : float Mole fraction of the independent component site_fracs : np.ndarray Array of floats corresponding to the site fractions. Notes ----- In the future, this representation could be phased out if `equilibrium` returned composition sets. """ # tolerances for defining equality between composition sets SITE_FRAC_ATOL = 0.001 TEMPERATURE_ATOL = 0.1 def __init__(self, phase_name, temperature, indep_comp, composition, site_fracs): self.phase_name = phase_name self.temperature = temperature self.indep_comp = indep_comp self.composition = composition self.site_fracs = site_fracs def __repr__(self,): return "<BinaryCompset({0}, T={1:0.3f}, X({2})={3:0.3f})>".format(self.phase_name, self.temperature, self.indep_comp, self.composition) def __str__(self,): return self.__repr__() def __eq__(self, other): if hasattr(other, 'phase_name') and hasattr(other, 'site_fracs') and hasattr(other, 'temperature'): same_phase = self.phase_name == other.phase_name site_frac_close = np.all(np.isclose(self.site_fracs, other.site_fracs, atol=self.__class__.SITE_FRAC_ATOL)) temp_close = np.isclose(self.temperature, other.temperature, atol=self.__class__.TEMPERATURE_ATOL) return same_phase and site_frac_close and temp_close else: return False
[docs] @classmethod def from_dataset_vertices(cls, ds, indep_comp, indep_comp_idx, num_vertex): compsets = [] Phase = ds["Phase"].squeeze() T = np.array(ds["T"]).squeeze() X = ds["X"].squeeze() Y = ds["Y"].squeeze() for i in range(num_vertex): phase_name = Phase[i] if phase_name != '': compsets.append(cls(str(phase_name), T, indep_comp, X[i, indep_comp_idx], Y[i, :], )) return compsets
[docs]class CompsetPair(): """ Pair of binary composition sets that make up an equilibrium Parameters ---------- compsets : list of BinaryCompset Attributes ---------- compsets : list of BinaryCompset CompSets sorted by composition a : BinaryCompset Composition set in the pair with the lower composition b : BinaryCompset Composition set in the pair with the higher composition phases : list of str List of phase names, sorted by composition unique_phases : set of str Set of phase names compositions : np.ndarray Array of compositions, sorted temperature : float Temperature of the 2D compsets same_phase : bool Whether the composition sets are for the same phase mean_composition : float Mean composition of the CompSets max_composition : float Max composition of the CompSets min_composition : float Min composition of the CompSets """ def __init__(self, compsets): _composition_key_func = lambda c: c.composition sorted_compsets = sorted(compsets, key=_composition_key_func) phases = [c.phase_name for c in sorted_compsets] compositions = np.array([c.composition for c in sorted_compsets]) a = sorted_compsets[0] b = sorted_compsets[1] self._orig_compsets = compsets # with original sorting self.compsets = sorted_compsets self.phases = phases self.unique_phases = frozenset(phases) self.a = a self.b = b self.same_phase = a.phase_name == b.phase_name self.compositions = compositions self.mean_composition = np.mean(compositions) self.min_composition = a.composition self.max_composition = b.composition if a.temperature == b.temperature: self.temperature = a.temperature else: warnings.warn("Temperatures are different for CompSet objects {}. Assuming that the pair temperature is ".format(sorted_compsets, a.temperature)) self.temperature = a.temperature
[docs] def xdiscrepancy(self, ignore_phase=False): """ Calculate the composition discrepancy (absolute difference) between this composition set and another. Parameters ---------- ignore_phase : bool If False, unlike phases will give infinite discrepancy. If True, we only care about the composition and the real discrepancy will be returned. Returns ------- np.float64 """ if self.same_phase or ignore_phase: return np.abs(self.a.composition - self.b.composition) else: return np.infty
[docs] def ydiscrepancy(self): """ Calculate the discrepancy (absolute differences) between the site fractions of the composition sets as an array of discrepancies. Returns ------- Array of np.float64 Notes ----- The phases must match for this to be meaningful. """ if self.same_phase: return np.abs(self.a.site_fracs - self.b.site_fracs) else: return np.infty
[docs] def ydiscrepancy_max(self): """ Calculate the maximum discrepancy (absolute difference) between the site fractions of the composition sets. Returns ------- np.float64 Notes ----- The phases must match for this to be meaningful. """ return np.max(np.abs(self.ydiscrepancy()))
[docs] def Tdiscrepancy(self, other): """ Calculate the temperature discrepancy (absolute difference) between this pair of composition sets and another. Parameters ---------- other : CompsetPair Returns ------- np.float64 """ return np.abs(self.temperature - other.temperature)
def __repr__(self,): compset_strs = ", ".join(["{0}:<X({1})={2:0.3f}>".format(c.phase_name, c.indep_comp, c.composition) for c in self.compsets]) return "CompSet2D(T={0:0.3f}: ({1})".format(self.temperature, compset_strs) def __str__(self,): return self.__repr__()
[docs] def pairwise_xdiscrepancy(self, other): """ Compute the ordered composition discrepancy between this and another pair of CompSet2D objects. Parameters ---------- other : CompsetPair Returns ------- float """ if self.phases == other.phases: return np.abs(self.compositions - other.compositions) else: return np.full(self.compositions.shape, np.infty)
[docs]def get_compsets(eq_dataset, indep_comp=None, indep_comp_index=None): """ Return a CompSet2D object if a pair of composition sets is found in an equilibrium dataset. Otherwise return None. Parameters ---------- eq_dataset : indep_comp : indep_comp_index : Returns ------- CompsetPair """ if indep_comp is None: indep_comp = [c for c in eq_dataset.coords if 'X_' in c][0][2:] if indep_comp_index is None: indep_comp_index = eq_dataset.component.values.tolist().index(indep_comp) extracted_compsets = BinaryCompset.from_dataset_vertices(eq_dataset, indep_comp, indep_comp_index, 3) if len(extracted_compsets) == 2: return CompsetPair(extracted_compsets) else: return None
[docs]def find_two_phase_region_compsets(hull, temperature, indep_comp, indep_comp_idx, misc_gap_tol=0.1, minimum_composition=None): """ From a 1D convex hull at constant T and P, return the composition sets for a two phase region or that have the smallest index composition coordinate Parameters ---------- hull : EquilibriumResult Equilibrium-like from pycalphad that has a `Phase` Data variable. temperature : float Temperature that the calculation was performed at indep_comp : str Name of the independent component indep_comp_idx : str Index of the independent component in the the sorted pure elements misc_gap_tol : float If any site fractions are different by at least this amount, the composition sets are considered distinct and in a miscibility gap. minimum_composition : float Minimum composition in the convex hull to search for composition sets Returns ------- CompsetPair """ phases = hull.Phase.squeeze() compositions = hull.X.squeeze() site_fracs = hull.Y.squeeze() grid_shape = phases.shape[:-1] num_phases = phases.shape[-1] it = np.nditer(np.empty(grid_shape), flags=['multi_index']) # empty grid for indexing while not it.finished: idx = it.multi_index cs = [] # TODO: assumption of only two phases, seems like the third phase index can have bad points # Three phases is probably an error anyways... if minimum_composition is not None and np.all(compositions[idx][:, indep_comp_idx][:2] < minimum_composition): it.iternext() continue for i in np.arange(num_phases): if str(phases[idx][i]) != '': stable_composition_sets = BinaryCompset(str(phases[idx][i]), temperature, indep_comp, compositions[idx][i, indep_comp_idx], site_fracs[idx][i, :]) cs.append(stable_composition_sets) if len(cs) == 2: compsets = CompsetPair(cs) if len(compsets.unique_phases) == 2: return compsets # found a multiphase region else: # Same phase, either single phase region or a miscibility gap. y_discrep = compsets.ydiscrepancy() if np.any(y_discrep[~np.isnan(y_discrep)] > misc_gap_tol): return compsets # miscibility gap it.iternext() return None