Source code for pycalphad.mapping.strategy.strategy_data
"""
Data classes to hold outputs from map strategies
- ``SinglePhaseData`` - holds x, y coordinates for a given phase
- ``StrategyData`` - holds a list of ``SinglePhaseData`` with some functions to loop
over phases, x and y in each ``SinglePhaseData`` object and to get x and y limits
- ``PhaseRegionData`` - alias of ``StrategyData``. This is done to clarify what
``BinaryStrategy.get_zpf_data``, ``BinaryStrategy.get_invariant_data``
``TernaryStrategy.get_zpf_data``, ``TernaryStrategy.get_invariant_data`` and
``IsoplethStrategy.get_invariant_data`` does compared to ``StepStrategy.get_data``
and ``IsoplethStrategy.get_zpf_data``
"""
import copy
from typing import Union
from dataclasses import dataclass, field
import numpy as np
from pycalphad import variables as v
from pycalphad.mapping.primitives import _get_phase_specific_variable
[docs]
@dataclass
class SinglePhaseData:
phase: str
x: float | list[float]
y: float | list[float]
[docs]
@dataclass
class StrategyData:
data: list[SinglePhaseData]
xlim: list[float] = field(init=False)
ylim: list[float] = field(init=False)
def __post_init__(self):
all_x = np.concatenate([np.atleast_1d(d.x) for d in self.data], axis=0)
all_y = np.concatenate([np.atleast_1d(d.y) for d in self.data], axis=0)
self.xlim = [np.amin(all_x[~np.isnan(all_x)]), np.amax(all_x[~np.isnan(all_x)])]
self.ylim = [np.amin(all_y[~np.isnan(all_y)]), np.amax(all_y[~np.isnan(all_y)])]
def __getitem__(self, key: str) -> SinglePhaseData:
phases = [d.phase for d in self.data]
if key in phases:
return self.data[phases.index(key)]
else:
raise KeyError(f"{key} not in dataset.")
@property
def phases(self):
return [d.phase for d in self.data]
@property
def x(self):
return [d.x for d in self.data]
@property
def y(self):
return [d.y for d in self.data]
PhaseRegionData = StrategyData
# Helper functions to grab data from tieline strategies (BinaryStrategy and TernaryStrategy)
# This is to avoid repeating get_XXX_data functions for the two strategies
# TODO: Once BinaryStrategy and TernaryStrategy are merged into a more general TielineStrategy,
# these two functions can be moved into TieLineStrategy
[docs]
def get_invariant_data_from_tieline_strategy(strategy, x: v.StateVariable, y: v.StateVariable, global_x = False, global_y = False) -> list[PhaseRegionData]:
"""
Create a dictionary of data for node plotting in binary and ternary plots.
Parameters
----------
strategy : BinaryStrategy or TernaryStrategy
The strategy used for generating the data.
x : v.StateVariable
The state variable to be used for the x-axis.
y : v.StateVariable
The state variable to be used for the y-axis.
Returns
-------
list of dict
A list where each dictionary contains the following structure::
{
"phases": list of str,
"x": list of float,
"y": list of float
}
The indices in `x` and `y` match the indices in `phases`.
"""
if hasattr(x, 'phase_name') and x.phase_name is None:
if not global_x:
x = copy.deepcopy(x)
x.phase_name = '*'
if hasattr(y, 'phase_name') and y.phase_name is None:
if not global_y:
y = copy.deepcopy(y)
y.phase_name = '*'
invariant_data = []
for node in strategy.node_queue.nodes:
# Nodes in binary and ternary mappings are always 3 composition sets
if len(node.stable_composition_sets) == 3:
node_phases = node.stable_phases_with_multiplicity
data = []
for p in node_phases:
x_data = node.get_property(_get_phase_specific_variable(p, x))
y_data = node.get_property(_get_phase_specific_variable(p, y))
data.append(SinglePhaseData(p, x_data, y_data))
invariant_data.append(StrategyData(data))
return invariant_data
[docs]
def get_tieline_data_from_tieline_strategy(strategy, x: v.StateVariable, y: v.StateVariable, global_x = False, global_y = False) -> list[PhaseRegionData]:
"""
Create a dictionary of data for plotting ZPF lines.
Parameters
----------
strategy : BinaryStrategy or TernaryStrategy
The strategy used for generating the data.
x : v.StateVariable
The state variable to be used for the x-axis.
y : v.StateVariable
The state variable to be used for the y-axis.
Returns
-------
list of dict
A list where each dictionary has the following structure::
{
"<phase_name>": {
"x": list of float,
"y": list of float
}
}
The lengths of the "x" and "y" lists should be equal for each phase in a ZPFLine.
"""
if hasattr(x, 'phase_name') and x.phase_name is None:
if not global_x:
x = copy.deepcopy(x)
x.phase_name = '*'
if hasattr(y, 'phase_name') and y.phase_name is None:
if not global_y:
y = copy.deepcopy(y)
y.phase_name = '*'
zpf_data = []
for zpf_line in strategy.zpf_lines:
phases = zpf_line.stable_phases_with_multiplicity
data = []
for p in phases:
x_data = zpf_line.get_var_list(_get_phase_specific_variable(p, x))
y_data = zpf_line.get_var_list(_get_phase_specific_variable(p, y))
data.append(SinglePhaseData(p, x_data, y_data))
zpf_data.append(StrategyData(data))
return zpf_data