espei.parameter_selection package#

Submodules#

espei.parameter_selection.fitting_descriptions module#

class espei.parameter_selection.fitting_descriptions.ModelFittingDescription(fitting_steps: [<class 'espei.parameter_selection.fitting_steps.FittingStep'>], model: ~typing.Type[~pycalphad.model.Model] | None = <class 'pycalphad.model.Model'>)#

Bases: object

fitting_steps#
Type:

[FittingStep]

model#
Type:

Type[Model]

espei.parameter_selection.fitting_steps module#

class espei.parameter_selection.fitting_steps.AbstractLinearPropertyStep#

Bases: FittingStep

This class is a base class for generic linear properties.

“Generic” meaning, essentially, that property_name == parameter_name == data_type and PyCalphad models set this property something like `python self.<parameter_name> = self.redlich_kister_sum(phase, param_search, <param_name>_query) ` Note that redlich_kister_sum specifically is an implementation detail and isn’t relevant for this class in particular. Any mixing model could work, although ESPEI currently only generates features by build_redlich_kister_candidate_models.

Fitting steps that want to use this base class should need to subclass this class, override the parameter_name and data_type_read (typically these match), and optionally override the features if a desired.

For models that are ‘nearly linear’, and the transform_data method can be overriden to try to linearize the data with respect to the model parameters.

Most parameters are fit per mole of formula units, but there are some exceptions (e.g. VA parameters). The normalize_parameter_per_mole_formula attribute handles normalization.

features: [<class 'symengine.lib.symengine_wrapper.Expr'>] = [1, T, T**2, T**3, T**(-1)]#
classmethod get_response_vector(fixed_model: ~pycalphad.model.Model, fixed_portions: [<class 'symengine.lib.symengine_wrapper.Basic'>], data: [typing.Dict[str, typing.Any]], sample_condition_dicts: [typing.Dict[str, typing.Any]]) Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]#

Get the response vector, b, for the a linear models in Ax=b with features, A, and coefficients, x, in units of [qty]/mole-formula.

Parameters:
  • fixed_model (Model) – Model with all lower order (in composition) terms already fit. Pure element reference state (GHSER functions) should be set to zero.

  • fixed_portions ([symengine.Basic]) – SymEngine expressions for model parameters and interaction productions for higher order (in T) terms for this property, e.g. [0, 3.0*YS*v.T]. In [qty]/mole-formula.

  • data ([Dict[str, Any]]) – ESPEI single phase datasets for this property.

Returns:

np.ndarray[ – Ravelled data quantities in [qty]/mole-formula

Return type:

]

Notes

pycalphad Model parameters (and therefore fixed_portions) are stored as per mole-formula quantites, but the calculated properties and our data are all in [qty]/mole-atoms. We multiply by mole-atoms/mole-formula to convert the units to [qty]/mole-formula.

normalize_parameter_per_mole_formula: bool = True#
classmethod shift_reference_state(desired_data: [Dict[str, Any]], fixed_model: Model) Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]#
supported_reference_states: [<class 'str'>] = ['', '_MIX']#
static transform_data(d: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], model: Model | None = None) Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]#

Helper function to linearize data in terms of the model parameters.

If data is already linear w.r.t. the parameters, the default implementation of identity (return d can be preserved).

class espei.parameter_selection.fitting_steps.FittingStep#

Bases: object

data_types_read: str#
features: [<class 'symengine.lib.symengine_wrapper.Expr'>]#
classmethod get_feature_sets() [[<class 'symengine.lib.symengine_wrapper.Expr'>]]#
classmethod get_response_vector(fixed_model: ~pycalphad.model.Model, fixed_portions: [<class 'symengine.lib.symengine_wrapper.Basic'>], data: [typing.Dict[str, typing.Any]], sample_condition_dicts: [typing.Dict[str, typing.Any]]) Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]#

Get the response vector, b, for the a linear models in Ax=b with features, A, and coefficients, x, in units of [qty]/mole-formula.

Parameters:
  • fixed_model (Model) – Model with all lower order (in composition) terms already fit. Pure element reference state (GHSER functions) should be set to zero.

  • fixed_portions ([symengine.Basic]) – SymEngine expressions for model parameters and interaction productions for higher order (in T) terms for this property, e.g. [0, 3.0*YS*v.T]. In [qty]/mole-formula.

  • data ([Dict[str, Any]]) – ESPEI single phase datasets for this property.

Returns:

np.ndarray[ – Ravelled data quantities in [qty]/mole-formula

Return type:

]

Notes

pycalphad Model parameters (and therefore fixed_portions) are stored as per mole-formula quantites, but the calculated properties and our data are all in [qty]/mole-atoms. We multiply by mole-atoms/mole-formula to convert the units to [qty]/mole-formula.

parameter_name: str#
supported_reference_states: [<class 'str'>]#
classmethod transform_feature(expr: Expr, model: Model | None = None) Expr#
class espei.parameter_selection.fitting_steps.StepCPM#

Bases: StepHM

data_types_read: str = 'CPM'#
features: [<class 'symengine.lib.symengine_wrapper.Expr'>] = [T*log(T), T**2, T**(-1), T**3]#
classmethod transform_feature(expr: Expr, model: Model | None = None) Expr#
class espei.parameter_selection.fitting_steps.StepHM#

Bases: FittingStep

data_types_read: str = 'HM'#
features: [<class 'symengine.lib.symengine_wrapper.Expr'>] = [1]#
classmethod get_response_vector(fixed_model: ~pycalphad.model.Model, fixed_portions: [<class 'symengine.lib.symengine_wrapper.Basic'>], data: [typing.Dict[str, typing.Any]], sample_condition_dicts: [typing.Dict[str, typing.Any]]) Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]#

Get the response vector, b, for the a linear models in Ax=b with features, A, and coefficients, x, in units of [qty]/mole-formula.

Parameters:
  • fixed_model (Model) – Model with all lower order (in composition) terms already fit. Pure element reference state (GHSER functions) should be set to zero.

  • fixed_portions ([symengine.Basic]) – SymEngine expressions for model parameters and interaction productions for higher order (in T) terms for this property, e.g. [0, 3.0*YS*v.T]. In [qty]/mole-formula.

  • data ([Dict[str, Any]]) – ESPEI single phase datasets for this property.

Returns:

np.ndarray[ – Ravelled data quantities in [qty]/mole-formula

Return type:

]

Notes

pycalphad Model parameters (and therefore fixed_portions) are stored as per mole-formula quantites, but the calculated properties and our data are all in [qty]/mole-atoms. We multiply by mole-atoms/mole-formula to convert the units to [qty]/mole-formula.

parameter_name: str = 'G'#
classmethod shift_reference_state(desired_data: [Dict[str, Any]], fixed_model: Model, mole_atoms_per_mole_formula_unit: Expr) Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]#

Shift _MIX or _FORM data to a common reference state in per mole-atom units.

Parameters:
  • desired_data (List[Dict[str, Any]]) – ESPEI single phase dataset

  • fixed_model (pycalphad.Model) – Model with all lower order (in composition) terms already fit. Pure element reference state (GHSER functions) should be set to zero.

  • mole_atoms_per_mole_formula_unit (float) – Number of moles of atoms in every mole atom unit.

Returns:

Data for this feature in [qty]/mole-formula in a common reference state.

Return type:

np.ndarray

Raises:

ValueError

Notes

pycalphad Model parameters are stored as per mole-formula quantites, but the calculated properties and our data are all in [qty]/mole-atoms. We multiply by mole-atoms/mole-formula to convert the units to [qty]/mole-formula.

supported_reference_states: [<class 'str'>] = ['_MIX', '_FORM']#
classmethod transform_feature(expr: Expr, model: Model | None = None) Expr#
class espei.parameter_selection.fitting_steps.StepLogVA#

Bases: AbstractLinearPropertyStep

data_types_read: str = 'VM'#
features: [<class 'symengine.lib.symengine_wrapper.Expr'>] = [T, T**2, T**3, T**(-1)]#
classmethod get_feature_sets() [[<class 'symengine.lib.symengine_wrapper.Expr'>]]#
normalize_parameter_per_mole_formula: bool = False#
parameter_name: str = 'VA'#
supported_reference_states: [<class 'str'>] = ['', '_MIX']#
static transform_data(d: Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes], model: Model) Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | bool | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]#

Helper function to linearize data in terms of the model parameters.

If data is already linear w.r.t. the parameters, the default implementation of identity (return d can be preserved).

class espei.parameter_selection.fitting_steps.StepSM#

Bases: StepHM

data_types_read: str = 'SM'#
features: [<class 'symengine.lib.symengine_wrapper.Expr'>] = [T]#
classmethod transform_feature(expr: Expr, model: Model | None = None) Expr#
class espei.parameter_selection.fitting_steps.StepV0#

Bases: AbstractLinearPropertyStep

data_types_read: str = 'V0'#
features: [<class 'symengine.lib.symengine_wrapper.Expr'>] = [1]#
parameter_name: str = 'V0'#

espei.parameter_selection.model_building module#

Building candidate models

espei.parameter_selection.model_building.build_candidate_models(feature_sets: List[List[Symbol]], interaction_features: List[Symbol], complex_algorithm_candidate_limit=1000)#

Return a list of broadcasted features

Parameters:
  • feature_sets (List[List[symengine.Symbol]]) – List of (non-composition) feature sets, such as [[TlogT], [TlogT, T**(-1)], [TlogT, T**(-1), T**2]]

  • interaction_features (List[symengine.Symbol]) – List of interaction features that will become a successive_list, such as [YS, YS*Z, YS*Z**2]

  • complex_algorithm_candidate_limit (int) – If the complex algorithm (described in the Notes section) will generate at least this many candidates then switch to the simple algorithm for building candidates. The simple algorithm produces a total of N*M feature sets from a cartesian product.

Return type:

list

Notes

This takes two sets of features, e.g. [TlogT, T-1, T2] and [YS, YS*Z, YS*Z**2] and generates a list of candidate models where combined potential and interaction features are broadcasted successively.

Generates candidate models like: L0: A + BT, L1: A L0: A , L1: A + BT

but not lists that are not successive: L0: A + BT, L1: Nothing, L2: A L0: Nothing, L1: A + BT

There’s still some debate whether it makes sense from an information theory perspective to add a L1 B term without an L0 B term. However this might be more representative of how people usually model thermodynamics.

Does not distribute multiplication/sums or make assumptions about the elements of the feature lists. They can be strings, ints, objects, tuples, etc..

The number of features (related to the complexity) is a geometric series. For \(N\) temperature features and \(M\) interaction features, the total number of feature sets should be \(N(1-N^M)/(1-N)\). If \(N=1\), then there are \(M\) total feature sets.

Using this more complex algorithm generates significantly more candidates for typical usage where M=4 (L0, L1, L2, L3). Typically we are performance limited at the espei.paramselect._build_feature_matrix step where we use symengine to xreplace the symbolic features generated here with concrete values. Significant performance gains in that function should allow us to raise the complex_algorithm_candidate_limit ceiling.

espei.parameter_selection.model_building.build_redlich_kister_candidate_models(configuration: Tuple[str | Tuple[str]], feature_sets: List[List[Symbol]], max_binary_redlich_kister_order: int | None = 3, ternary_symmetric_parameter: bool | None = True, ternary_asymmetric_parameters: bool | None = True)#

Return a list of candidate symbolic models for a string configuration and set of symbolic features. The candidate models are a Cartesian product of the successive non-mixing features and successive Redlich-Kister-Muggianu mixing features.

Here “successive” means that we take the features and (exhaustively) generate models of increasing complexity. For example, if we have non-mixing features [1, T, T**2, T**3], then we generate 4 candidates of increasing complexity: [1], [1, T], [1, T, T**2], and [1, T, T**2, T**3]. For a max Redlich-Kister order of 2, we have [L0, L1, L2] candidates and there will be 3 candidate features sets of increasing complexity for mixing: [L0], [L0, L1], and [L0, L1, L2].

Parameters:
  • configuration (Tuple[Union[Tuple[str], str]]) – Configuration tuple, e.g. ((‘A’, ‘B’, ‘C’), ‘A’)

  • feature_sets (List[List[Symbol]]) – Each entry is a list of non-mixing features, for example: temperature features [symengine.S.One, v.T, v.T**2, v.T**3] or pressure features [symengine.S.One, v.P, v.P**3, v.P**3]. Note that only one set of non-mixing features are currently allowed.

  • max_binary_redlich_kister_order (Optional[int]) – For binary mixing configurations: highest order Redlich-Kister interaction parameter to generate. 0 gives L0, 1 gives L0 and L1, etc.

  • ternary_symmetric_parameter (Optional[bool]) – For ternary mixing configurations: if true (the default), add a symmetric interaction parameter.

  • ternary_asymmetric_parameters (Optional[bool]) – For ternary mixing configurations: if true (the default), add asymmetric interaction parameters.

Returns:

List of candidate models

Return type:

List[List[Symbol]]

Notes

Currently only works for binary and ternary interactions.

Candidate models match the following spec: 1. Candidates with multiple features specified will have 2. orders of parameters (L0, L0 and L1, …) have the same number of temperatures

Note that high orders of parameters with multiple temperatures are not required to contain all the temperatures of the low order parameters. For example, the following parameters can be generated L0: A L1: A + BT

espei.parameter_selection.model_building.make_successive(xs: List[object])#

Return a list of successive combinations

Parameters:

xs (list) – List of elements, e.g. [X, Y, Z]

Returns:

List of combinations where each combination include all the preceding elements

Return type:

list

Examples

>>> make_successive(['W', 'X', 'Y', 'Z'])
[['W'], ['W', 'X'], ['W', 'X', 'Y'], ['W', 'X', 'Y', 'Z']]

espei.parameter_selection.redlich_kister module#

Tools for construction Redlich-Kister polynomials used in parameter selection.

espei.parameter_selection.redlich_kister.calc_interaction_product(site_fractions)#

Calculate the interaction product for sublattice site fractions

Callers should take care that the site fractions correspond to constituents in sorted order, since there’s an order-dependent subtraction.

Parameters:

site_fractions (List[List[float]]) – List of site fractions for each sublattice. The list should a ragged 2d list of shape (sublattices, site fractions).

Returns:

A scalar for binary interactions and a list of 3 floats for ternary interactions

Return type:

Union[float, List[float]]

Examples

>>> # interaction product for an (A) site_fractions
>>> calc_interaction_product([[1.0]])  
1.0
>>> # interaction product for [(A,B), (A,B)(A)] site fractions that are equal
>>> calc_interaction_product([[0.5, 0.5]])  
0.0
>>> calc_interaction_product([[0.5, 0.5], 1])  
0.0
>>> # interaction product for an [(A,B)] site_fractions
>>> calc_interaction_product([[0.1, 0.9]])  
-0.8
>>> # interaction product for an [(A,B)(A,B)] site_fractions
>>> calc_interaction_product([[0.2, 0.8], [0.4, 0.6]])  
0.12
>>> # ternary case, (A,B,C) interaction
>>> calc_interaction_product([[0.333, 0.333, 0.334]])
[0.333, 0.333, 0.334]
>>> # ternary 2SL case, (A,B,C)(A) interaction
>>> calc_interaction_product([[0.333, 0.333, 0.334], 1.0])
[0.333, 0.333, 0.334]

espei.parameter_selection.selection module#

Fit, score and select models

espei.parameter_selection.selection.fit_model(feature_matrix, data_quantities, ridge_alpha, weights=None)#

Return model coefficients fit by scikit-learn’s LinearRegression

Parameters:
  • feature_matrix (ArrayLike) – (\(M \times N\)) regressor matrix. The transformed model inputs (y_i, T, P, etc.)

  • data_quantities (ArrayLike) – Size (\(M\)) response vector. Target values of the output (e.g. HM_MIX) to reproduce.

  • ridge_alpha (float) – Value of the \(\alpha\) hyperparameter used in ridge regression. Defaults to 1.0e-100, which should be degenerate with ordinary least squares regression. For now, the parameter is applied to all features.

Returns:

List of model coefficients of size (\(N\))

Return type:

list

Notes

Solve \(Ax = b\) where \(x\) are the desired model coefficients, \(A\) is the feature_matrix and \(b\) corrresponds to data_quantities.

espei.parameter_selection.selection.score_model(feature_matrix, data_quantities, model_coefficients, feature_list, weights, aicc_factor=None, rss_numerical_limit=1e-16)#

Use the modified AICc to score a model that has been fit.

The modified AICc is given by

\[\mathrm{mAICc} = n \ln \frac{\mathrm{RSS}}{n} + 2pk + \frac {2p^2k^2 + 2pk} {n - pk - 1}\]
Parameters:
  • feature_matrix (ArrayLike) – (\(M \times N\)) regressor matrix. The transformed model inputs (y_i, T, P, etc.)

  • data_quantities (ArrayLike) – Size (\(M\)) response vector. Target values of the output (e.g. HM_MIX) to reproduce.

  • model_coefficients (list) – Size (\(N\)) list of fitted model coefficients to be scored.

  • feature_list (list) – Polynomial coefficients corresponding to each column of feature_matrix. Has shape (N,). Purely a logging aid.

  • aicc_factor (float) – Multiplication factor for the AICc’s parameter penalty.

  • rss_numerical_limit (float) – Anything with an absolute value smaller than this is set to zero.

Returns:

A model score

Return type:

float

espei.parameter_selection.selection.select_model(candidate_models, ridge_alpha, weights, aicc_factor=None)#

Select a model from a series of candidates by fitting and scoring them

Parameters:
  • candidate_models (list) – List of tuples of (features, feature_matrix, data_quantities)

  • ridge_alpha (float) – Value of the \(\alpha\) hyperparameter used in ridge regression. Defaults to 1.0e-100, which should be degenerate with ordinary least squares regression. For now, the parameter is applied to all features.

  • aicc_factor (float) – Multiplication factor for the AICc’s parameter penalty.

Returns:

Tuple of (feature_list, model_coefficients) for the highest scoring model

Return type:

tuple

Module contents#