xtalmet package

Package containing a variety of crystal distance functions and evaluation metrics.

class xtalmet.Crystal(lattice: ArrayLike | Lattice, species: Sequence[str | Element | Species | DummySpecies | dict | Composition], coords: Sequence[ArrayLike], charge: float | None = None, validate_proximity: bool = False, to_unit_cell: bool = False, coords_are_cartesian: bool = False, site_properties: dict | None = None, labels: Sequence[str | None] | None = None, properties: dict | None = None)View on GitHub

Bases: Structure

Container for a single crystal structure.

Initialize a Crystal object.

The parameters are the same as those used in the __init__() method of the pymatgen.core.Structure class.

Parameters:
  • lattice (Lattice/3x3 array) – The lattice, either as a pymatgen.core.Lattice or simply as any 2D array. Each row should correspond to a lattice vector. e.g. [[10,0,0], [20,10,0], [0,0,30]] specifies a lattice with lattice vectors [10,0,0], [20,10,0] and [0,0,30].

  • species ([Species]) –

    Sequence of species on each site. Can take in flexible input, including:

    1. A sequence of element / species specified either as string

      symbols, e.g. [“Li”, “Fe2+”, “P”, …] or atomic numbers, e.g. (3, 56, …) or actual Element or Species objects.

    2. List of dict of elements/species and occupancies, e.g.

      [{“Fe” : 0.5, “Mn”:0.5}, …]. This allows the setup of disordered structures.

  • coords (Nx3 array) – list of fractional/Cartesian coordinates of each species.

  • charge (int) – overall charge of the structure. Defaults to behavior in SiteCollection where total charge is the sum of the oxidation states.

  • validate_proximity (bool) – Whether to check if there are sites that are less than 0.01 Ang apart. Defaults to False.

  • to_unit_cell (bool) – Whether to map all sites into the unit cell, i.e. fractional coords between 0 and 1. Defaults to False.

  • coords_are_cartesian (bool) – Set to True if you are providing coordinates in Cartesian coordinates. Defaults to False.

  • site_properties (dict) – Properties associated with the sites as a dict of sequences, e.g. {“magmom”:[5, 5, 5, 5]}. The sequences have to be the same length as the atomic species and fractional_coords. Defaults to None for no properties.

  • labels (list[str]) – Labels associated with the sites as a list of strings, e.g. [‘Li1’, ‘Li2’]. Must have the same length as the species and fractional coords. Defaults to None for no labels.

  • properties (dict) – Properties associated with the whole structure. Will be serialized when writing the structure to JSON or YAML but is lost when converting to other formats.

Note

The descriptions for args are copied from pymatgen.core.Structure class.

classmethod from_Structure(structure: Structure) → CrystalView on GitHub

Create a Crystal object from a pymatgen Structure object.

Parameters:

structure (Structure) – A pymatgen Structure object.

Returns:

A Crystal object created from the Structure.

Return type:

Crystal

get_embedding(distance: str, **kwargs) → tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]]View on GitHub

Get the embedding of the crystal based on the specified distance metric.

Parameters:
  • distance (str) – The distance metric to use.

  • **kwargs – Additional arguments for embedding methods if needed.

Returns:

The embedding corresponding to the specified distance metric.

Return type:

TYPE_EMB_ALL

Raises:

ValueError – If an unsupported distance metric is provided.

Note

For “smat” distance, the embedding is the Crystal object itself.

get_composition_pymatgen() → CompositionView on GitHub

Get the pymatgen composition of the crystal.

Called when screening using SMACT or E_hull.

Returns:

Pymatgen Composition object.

Return type:

Composition

get_ase_atoms() → AtomsView on GitHub

Get the ASE Atoms object of the crystal.

Called when screening using E_hull. Not influenced by oxidation states.

Returns:

ASE Atoms object.

Return type:

Atoms

xtalmet.distance(distance: str, xtal_1: Structure | Crystal | tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]], xtal_2: Structure | Crystal | tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]], normalize: bool = True, verbose: Literal[False] = False, **kwargs) → floatView on GitHub
xtalmet.distance(distance: str, xtal_1: Structure | Crystal | tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]], xtal_2: Structure | Crystal | tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]], normalize: bool = True, verbose: Literal[True] = False, **kwargs) → tuple[float, tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]], tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]]]

Compute the distance between two crystals.

Failed embeddings, nonfinite smat geometry and nonfinite backend distances yield NaN. For “elmd+amd”, failure of either component invalidates the whole embedding. Blending coefficients must be finite and non-negative with a finite positive sum.

Parameters:
  • distance (str) – The distance metric to use. Currently supported metrics are listed in SUPPORTED_DISTANCES in constants.py. For more detailed information about each distance metric, please refer to the tutorial notebook.

  • xtal_1 (Structure | Crystal | TYPE_EMB_ALL) – pymatgen Structure or Crystal or an embedding.

  • xtal_2 (Structure | Crystal | TYPE_EMB_ALL) – pymatgen Structure or Crystal or an embedding.

  • normalize (bool) – Whether to normalize the distance d to [0, 1] by using d’ = d / (1 + d). This argument is only considered when d is a continuous distance that is not normalized to [0, 1]. Such distances are listed in CONTINUOUS_UNNORMALIZED_DISTANCES in constants.py. Default is True.

  • verbose (bool) – Whether to return intermediate embeddings. Default is False.

  • **kwargs – Additional keyword arguments for specific distance metrics. It can contain two keys: “args_emb” and “args_dist”. The value of “args_emb” is a dict of arguments for the calculation of embeddings, and the value of “args_dist” is a dict of arguments for the calculation of distance between the embeddings. If embeddings are pre-computed and provided as inputs, the “args_emb” will be ignored.

Returns:

Distance between crystals. If verbose is True, also returns the embeddings and the computing time.

Return type:

float | tuple[np.ndarray, TYPE_EMB_ALL, TYPE_EMB_ALL, dict[str, float]]

Raises:

ValueError – If a distance metric or blending coefficients are invalid.

xtalmet.distance_matrix(distance: str, xtals_1: list[Structure | Crystal | tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]]], xtals_2: list[Structure | Crystal | tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]]] | None = None, normalize: bool = True, multiprocessing: bool = False, n_processes: int | None = None, verbose: bool = False, **kwargs) → ndarray | tuple[ndarray, list[tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]]], dict[str, float]] | tuple[ndarray, list[tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]]], list[tuple[tuple[str, int]] | tuple[int, tuple[str]] | list[float] | ndarray[float32 | float64] | str | tuple[str, ndarray[float32 | float64]]], dict[str, float]]View on GitHub

Compute the distance matrix between two sets of crystals.

If xtals_2 is None, compute the distance matrix within xtals_1.

Parameters:
  • distance (str) – The distance metric to use. Currently supported metrics are listed in SUPPORTED_DISTANCES in constants.py. For more detailed information about each distance metric, please refer to the tutorial notebook.

  • xtals_1 (list[Structure | Crystal | TYPE_EMB_ALL]) – A list of pymatgen Structures or Crystals or embeddings.

  • xtals_2 (list[Structure | Crystal | TYPE_EMB_ALL] | None) – A list of pymatgen Structures or Crystals or embeddings, or None. Default is None.

  • normalize (bool) – Whether to normalize the distances d to [0, 1] by using d’ = d / (1 + d). This argument is only considered when d is a continuous distance that is not normalized to [0, 1]. Such distances are listed in CONTINUOUS_UNNORMALIZED_DISTANCES in constants.py. Default is True.

  • multiprocessing (bool) – Whether to use multiprocessing. Default is False.

  • n_processes (int | None) – Maximum number of processes for multiprocessing. If multiprocessing is False, this argument will be ignored. Default is None.

  • verbose (bool) – Whether to return embeddings and the computing time. Default is False.

  • **kwargs – Additional keyword arguments for specific distance metrics. It can contain two keys: “args_emb” and “args_dist”. The value of “args_emb” is a dict of arguments for the calculation of embeddings, and the value of “args_dist” is a dict of arguments for the calculation of distance matrix using the embeddings. If embeddings are pre-computed and provided as inputs, the “args_emb” will be ignored.

Returns:

Distance matrix, the embeddings of xtals_1 (and xtals_2 if xtals_2 is not None) and the computing time.

Return type:

TYPE_D_MTX_RETURN

Notes

Failed embeddings, including nonfinite numerical features, produce NaN rows and columns, including the diagonal. For “elmd+amd”, either component’s failure invalidates the whole embedding. Only valid inputs reach the backend; their matrix is restored to the original positions in one NaN-filled matrix. When failures occur, this temporarily holds both matrices in memory. All-valid inputs do not allocate a restoration matrix. Verbose embeddings retain input order and failures. AMD’s “out” buffer retains raw, unnormalized distances. Nonfinite backend distances are returned as NaN, including for normalize=False. When raw AMD output shares an “out” buffer, replacing nonfinite distances requires a copy; the caller-owned buffer keeps its raw backend values.

For “smat”, the dense matrix uses 0 for matches and 1 for mismatches. Structures with nonfinite lattice vectors or coordinates are excluded before native matching and produce NaN rows and columns. Composition-incompatible pairs are skipped using the configured comparator and ignored species; subset matching compares all pairs. Serial and parallel calculations both honor args_dist and reuse a matcher. Parallel tasks contain candidate row indices rather than a materialized list of all structure pairs. Participating structures undergo Niggli and optional primitive reduction once per input position in the parent process, after removing ignored species. Workers reuse the reductions; volume scaling and supercell matching remain pairwise. Unequal reduced site counts rule out matches unless subset or supercell matching is enabled. Pairs whose reduced compositions become incompatible fall back to normal matching of the original structures. Input structures and returned embeddings are not replaced by reductions.

For “magpie”, SciPy computes the dense Euclidean distance matrix without a Python worker pool. Multiprocessing settings still apply to embedding computation when structures are provided, but not to the matrix calculation. Continuous normalization updates the final matrix in place, one row at a time, copying first if it shares AMD’s caller-owned raw “out” buffer. “elmd+amd” reuses its internal ElMD matrix for rowwise blending and leaves the raw AMD component, including a supplied “out” buffer, unchanged. These operations avoid additional full-size arithmetic arrays. The two raw component matrices still coexist during blending. Dense output requires 8 bytes per pair: about 2.24 GiB for a 10,000-by-30,000 float64 matrix, excluding embeddings and other evaluation data.

For “comp” and “wyckoff”, identical tuple embeddings are grouped and their matching positions are filled with zero in a dense matrix of ones. No pairwise Python comparisons or matrix worker pools are used. Multiprocessing settings still apply to embedding computation. Evaluator also retains dense matrices when computing only U/N scores, with or without disk caching.

For “elmd” (also the ElMD component of “elmd+amd”), each distinct formula is parsed once per matrix call using the requested metric. Parsed objects are initialized once per worker, while tasks contain only indices of row sections of at most 1,024 columns. At most twice the worker count of tasks and results are retained per batch; no complete pair or result list is built.

For “pdd”, 128-by-128 blocks are dispatched to the requested joblib backend in batches of at most twice its effective worker count. Workers compute blocks serially without nested pools; diagonal blocks use a small condensed array, and off-diagonal blocks are mirrored for a single input set. Explicit args_dist[“n_jobs”] takes precedence over multiprocessing/n_processes. An explicit backend=None inherits joblib’s active backend. The AMD backend’s existing n_jobs semantics are retained, including negative values selecting parallel cdist but serial pdist. verbose reports completed pairs in the parent process.

Raises:

ValueError – If a distance metric or blending coefficients are invalid, or an input set is empty.

class xtalmet.Evaluator(validity: list[str] | None = None, stability: Literal['continuous', 'binary', None] = None, uniqueness: bool = False, novelty: bool = False, distance: str | None = None, ref_xtals: list[Crystal | Structure] | Literal['mp20'] | str | None = None, agg_func: Literal['prod', 'ave'] = 'prod', weights: dict[str, float] | None = None, multiprocessing: bool = False, n_processes: int | None = None, **kwargs)View on GitHub

Bases: object

Class for evaluating a set of crystals.

The evaluation is based on a chosen combination of validity (V), stability (S), uniqueness (U), and novelty (N).

Initialize the Evaluator.

Parameters:
  • validity (list[str] | None) – Approaches to evaluating validity. The currently supported methods are shown in SUPPORTED_VALIDITY in constants.py. If set to None, validity is not evaluated. Default is None.

  • stability (Literal["continuous", "binary", None]) – Stability evaluation method. “continuous” or “binary” or None. If set to None, stability is not evaluated. Default is None.

  • uniqueness (bool) – Whether to evaluate uniqueness. Default is False.

  • novelty (bool) – Whether to evaluate novelty. Default is False.

  • distance (str | None) – Distance metric used for uniqueness and novelty evaluation. The currently supported distances are listed in SUPPORTED_DISTANCES in constants.py. For more detailed information about each distance metric, please refer to the tutorial notebook. If both uniqueness and novelty are False, this argument is ignored. Default is None.

  • ref_xtals (list[Crystal | Structure] | Literal["mp20"] | str | None) – Reference crystal structures (typically a training set) for novelty evaluation. This can be a list of crystal structures, dataset name, or a path to the file containing the pre-computed embeddings of the reference structures. If a dataset name is given, its training data will be downloaded from a fixed Hugging Face revision. MP20 precomputed embeddings are used only for default embedding settings; other settings emit UserWarning and compute references from MP20 training structures during initialization. File inputs require metadata written by save_reference_embeddings() with matching distance and args_emb. If novelty is False, this argument is ignored. Default is None.

  • agg_func (Literal["prod", "ave"]) – Aggregation function for combining V, S, U, and N. “prod” means multiplication, and “ave” means (weighted) average. Default is “prod”.

  • weights (dict[str, float] | None) – Weights for V, S, U, and N. For “ave”, weights are coefficients (normalized internally; equal weights if None). Enabled components must have a positive total weight; weights for disabled components do not contribute to this total. For “prod”, weights are exponents (not normalized; default 1.0 per active metric if None). All supplied weights must be finite and non-negative. The enabled weight sum for “ave” must also be finite. Default is None.

  • multiprocessing (bool) – Whether to use multiprocessing for computing the embeddings of reference crystals. This argument is only effective when novelty is True and ref_xtals is list[Crystal | Structure] or MP20 with non-default embedding settings. Default is False.

  • n_processes (int | None) – Maximum number of processes to use for multiprocessing. If None, the number of logical CPU cores - 1 will be used. We recommend setting this argument to a smaller number than the number of available CPU cores to avoid out-of-memory. If multiprocessing is False, this argument is ignored. Default is None.

  • **kwargs – Additional keyword arguments. It can contain four keys: “args_validity”, “args_stability”, “args_emb”, and “args_dist”. “args_validity” is for the validity evaluation, while “args_stability” is for the stability evaluation. “args_emb” and “args_dist” are for the distance metric used in uniqueness and novelty evaluation: The former is for the embedding calculation, and the latter is for the distance matrix calculation between embeddings. For more details, please refer to the tutorial notebook.

Raises:

ValueError – If novelty has an empty reference set or no valid reference embeddings, a reference file has missing or incompatible metadata, or average aggregation has zero total enabled weight.

Examples

>>> # Evaluator for the conventional SUN metric against the MP20 dataset
>>> # using the StructureMatcher distance.
>>> evaluator = Evaluator(
...     stability="binary",
...             uniqueness=True,
...             novelty=True,
...             distance="smat",
...             ref_xtals="mp20",
...             agg_func="prod",
... )
>>> # Evaluator for the VSUN metric against a custom reference dataset using
>>> # the ElMD distance, with average aggregation.
>>> evaluator = Evaluator(
...     validity=["smact", "structure"],
...     stability="continuous",
...     uniqueness=True,
...     novelty=True,
...     distance="elmd",
...     ref_xtals=ref_xtals,  # list[Crystal | Structure]
...     agg_func="ave",
...     weights={
...         "validity": 0.25,
...         "stability": 0.25,
...         "uniqueness": 0.25,
...         "novelty": 0.25,
...     },
...     multiprocessing=True,
...     n_processes=10,
... )
>>> # Evaluator for the VSUN metric against the MP20 dataset using the AMD
>>> # distance, with custom kwargs.
>>> evaluator = Evaluator(
...     validity=["smact", "structure"],
...     stability="continuous",
...     uniqueness=True,
...     novelty=True,
...     distance="amd",
...     ref_xtals="mp20",
...     agg_func="prod",
...     {
...         "args_validity": {
...             "structure": {
...                 "threshold_distance": 0.5,
...                 "threshold_volume": 0.1,
...             }
...         },
...         "args_stability": {
...             "diagram": "mp_250618",
...             "mace_model": "medium-mpa-0",
...             "intercept": 0.4289,
...         },
...         "args_emb": {"k": 100},
...         "args_dist": {"metric": "chebyshev", "low_memory": False},
...     }
... )
>>> # Evaluator for the SUN metric against the MP20 dataset using the
>>> # ElMD+AMD distance, with custom kwargs.
>>> evaluator = Evaluator(
...     stability="continuous",
...     uniqueness=True,
...     novelty=True,
...     distance="elmd+amd",
...     ref_xtals="mp20",
...     agg_func="prod",
...     {
...         "args_validity": {
...             "structure": {
...                 "threshold_distance": 0.5,
...                 "threshold_volume": 0.1,
...             }
...         },
...         "args_stability": {
...             "diagram": "mp_250618",
...             "mace_model": "medium-mpa-0",
...             "intercept": 0.4289,
...         },
...         "args_emb": {"amd": {"k": 100}},
...         "args_dist": {
...                             "elmd": {"metric": "mod_petti"},
...                             "amd": {"metric": "chebyshev", "low_memory": False},
...                             "coefs": {
...                                     "elmd": float.fromhex("0x1.8d7d565a99f87p-1"),
...                                     "amd": float.fromhex("0x1.ca0aa695981e5p-3")},
...                             },
...                     }
...     }
... )
save_reference_embeddings(path: str | Path) → NoneView on GitHub

Save prepared references with the metadata required for later reuse.

Parameters:

path (str | Path) – Destination gzip-pickle file. Existing files are replaced.

Raises:

ValueError – If novelty is disabled or feature shapes contradict args_emb.

evaluate(xtals: list[Crystal | Structure], dir_intermediate: str | None = None, multiprocessing: bool = False, n_processes: int | None = None) → tuple[float, ndarray, dict[str, float]]View on GitHub

Evaluate the given crystal structures.

Failed embeddings (including nonfinite numerical features) have U/N scores of zero, without changing V/S. Binary U compares only earlier valid samples; N compares only valid references. Continuous U counts failed pairs as zero while retaining the total-sample denominator n - 1. Continuous N takes the minimum over valid references. Generated embeddings and their masks are prepared once per call and shared by U/N, even without a disk cache. Cached matrices still require embeddings; missing embeddings are recomputed. Nonfinite smat geometry is a failed embedding. Nonfinite continuous pair distances contribute zero to U, retaining n - 1; N uses only finite distances and is zero if no finite comparison remains for a generated sample.

Parameters:
  • xtals (list[Crystal | Structure]) – List of crystal structures to be evaluated.

  • dir_intermediate (str | None) – Directory to search for pre-computed intermediate results, such as validity scores, energy above hulls, embeddings, and distance matrices. Versioned records are stored under cache/format-1/<identity>/ within this directory. Reuse requires matching ordered inputs, relevant settings, references, model/phase data, and software identity. Old unversioned files are ignored and preserved. Invalid records are recomputed. If None, no files are loaded or saved. It is recommended to set this argument when evaluating the same large set of crystal structures multiple times, for example trying different aggregation functions. Different conditions retain separate artifacts. Default is None.

  • multiprocessing (bool) – Whether to use multiprocessing for embedding and distance matrix computation. This argument is only effective when uniqueness or novelty evaluation is enabled. Default is False.

  • n_processes (int | None) – Maximum number of processes to use for multiprocessing. If None, the number of logical CPU cores - 1 will be used. We recommend setting this argument to a smaller number than the number of available CPU cores to avoid out-of-memory. If multiprocessing is False, this argument is ignored. Default is None.

Returns:

A tuple containing the overall score (float), individual scores for each crystal structure, and a dictionary of computation times for each evaluation component.

Return type:

tuple[float, np.ndarray, dict[str, float]]

Raises:

ValueError – If xtals is empty, continuous uniqueness has fewer than two samples, novelty has no valid reference embeddings, or distance/args_emb were changed after preparing references.

Examples

>>> # Evaluate the conventional SUN metric using the StructureMatcher
>>> # distance ("smat") against the MP20 dataset.
>>> evaluator = Evaluator(
...     stability="binary",
...             uniqueness=True,
...             novelty=True,
...             distance="smat",
...             ref_xtals="mp20",
...             agg_func="prod",
... )
>>> evaluator.evaluate(
...     xtals=xtals,  # list[Crystal | Structure]
...     dir_intermediate="intermediate_results/",
...     multiprocessing=True,
...     n_processes=10,
... )
>>> 0.28, np.array([...]), {"aggregation": ..., ...}
>>> # Evaluate the VSUN metric using the ElMD distance against a custom
>>> # reference dataset, with average aggregation.
>>> evaluator = Evaluator(
...     validity=["smact", "structure"],
...     stability="continuous",
...     uniqueness=True,
...     novelty=True,
...     distance="elmd",
...     ref_xtals=ref_xtals,  # list[Crystal | Structure]
...     agg_func="ave",
...     weights={
...         "validity": 0.25,
...         "stability": 0.25,
...         "uniqueness": 0.25,
...         "novelty": 0.25,
...     },
...     multiprocessing=True,
...     n_processes=10,
... )
>>> evaluator.evaluate(
...     xtals=xtals,  # list[Crystal | Structure]
...     dir_intermediate=None,
...     multiprocessing=False,
...     n_processes=None,
... )
>>> 0.6119424269941065, np.array([...]), {"aggregation": ..., ...}
>>> # Evaluate the VSUN metric using the AMD distance against the MP20
>>> # dataset, with custom kwargs.
>>> evaluator = Evaluator(
...     validity=["smact", "structure"],
...     stability="continuous",
...     uniqueness=True,
...     novelty=True,
...     distance="amd",
...     ref_xtals="mp20",
...     agg_func="prod",
...     {
...         "args_validity": {
...             "structure": {
...                 "threshold_distance": 0.5,
...                 "threshold_volume": 0.1,
...             }
...         },
...         "args_stability": {
...             "diagram": "mp_250618",
...             "mace_model": "medium-mpa-0",
...             "intercept": 0.4289,
...         },
...         "args_emb": {"k": 100},
...         "args_dist": {"metric": "chebyshev", "low_memory": False},
...     }
... )
>>> evaluator.evaluate(
...     xtals=xtals,  # list[Crystal | Structure]
...     dir_intermediate="intermediate_results/",
...     multiprocessing=True,
...     n_processes=10,
... )
>>> 0.019558713928249892, np.array([...]), {"aggregation": ..., ...}
>>> # Evaluator for the SUN metric against the MP20 dataset using the
>>> # ElMD+AMD distance, with custom kwargs.
>>> evaluator = Evaluator(
...     stability="continuous",
...     uniqueness=True,
...     novelty=True,
...     distance="elmd+amd",
...     ref_xtals="mp20",
...     agg_func="prod",
...     {
...         "args_validity": {
...             "structure": {
...                 "threshold_distance": 0.5,
...                 "threshold_volume": 0.1,
...             }
...         },
...         "args_stability": {
...             "diagram": "mp_250618",
...             "mace_model": "medium-mpa-0",
...             "intercept": 0.4289,
...         },
...         "args_emb": {"amd": {"k": 100}},
...         "args_dist": {
...                             "elmd": {"metric": "mod_petti"},
...                             "amd": {"metric": "chebyshev", "low_memory": False},
...                             "coefs": {
...                                     "elmd": float.fromhex("0x1.8d7d565a99f87p-1"),
...                                     "amd": float.fromhex("0x1.ca0aa695981e5p-3")},
...                             },
...                     }
...     }
... )
>>> evaluator.evaluate(
...     xtals=xtals,  # list[Crystal | Structure]
...     dir_intermediate="intermediate_results/",
...     multiprocessing=True,
...     n_processes=10,
... )
>>> 0.16403383975840835, np.array([...]), {"aggregation": ..., ...}

Note

Here, I demonstrate how VSUN (or its subsets) is computed. Validity \(V(x)\) of each crystal \(x\) is 0 (invalid) or 1 (valid) depending on whether it passes the specified validity checks. If you specify multiple validity methods \(V_1(x)\), \(V_2(x)\), …, the overall validity is the product of individual validity scores:

\[V(x) = \prod_i V_i(x)\]

Stability \(S(x)\) is computed from the energy above hull, and has two variants: binary and continuous. The binary stability score \(S_b(x)\) is defined as follows.

\[\begin{split}S_b(x) = \begin{cases} 1 & \text{if } E_\text{hull}(x) \le \text{threshold} \\ 0 & \text{otherwise} \end{cases}\end{split}\]

“threshold” can be specified in kwargs when initializing the Evaluator. The default value is 0.1 [eV/atom]. The continuous stability score \(S_c(x)\) is defined as follows.

\[\begin{split}S_c(x) = \begin{cases} 1 & \text{if } E_\text{hull}(x) \le 0 \\ 1 - \frac{E_\text{hull}(x)}{\text{intercept}} & \text{if } 0 \le E_\text{hull}(x) \le \text{intercept} \\ 0 & \text{otherwise} \end{cases}\end{split}\]

“intercept” can be specified in kwargs when initializing the Evaluator. The default value 0.4289 [eV/atom]. In both cases, a higher score closer to 1 indicates a more stable structure. The definition of uniqueness \(U(x)\) depends on the chosen distance metric. For a binary distance \(d_b\), uniqueness of the i-th crystal \(x_i\) in the set of crystals \(\{x_1, x_2, \ldots, x_n\}\) is defined as follows.

\[U_b(x_i) = I \left(\land_{j=1}^{i-1} d_b(x_i, x_j) \neq 0 \right),\]

where \(I\) is the indicator function. For a continuous distance \(d_c\), uniqueness is defined as follows.

\[U_c(x_i) = \frac{1}{n-1} \sum_{j=1}^{n} d_c(x_i, x_j).\]

In both cases, the score ranges from 0 to 1, since the binary distance takes values of 0 or 1, and the continuous distance is normalized to be between 0 and 1. A higher score indicates a more unique structure. Novelty \(N(x)\) is defined similarly to uniqueness, but against a reference set of crystals. For a binary distance \(d_b\), novelty of the i-th crystal \(x_i\) is defined as follows.

\[N_b(x_i) = I \left(\land_{j=1}^{m} d_b(x_i, y_j) \neq 0 \right),\]

where \(\{y_1, y_2, \ldots, y_m\}\) is the reference set of crystals. For a continuous distance \(d_c\), novelty is defined as follows.

\[N_c(x_i) = \min_{j=1 \ldots m} d_c(x_i, y_j).\]

In both cases, the socre ranges from 0 to 1, and a higher score indicates a more novel structure. Finally, the VSUN score of each crystal \(x\) is computed by aggregating the individual scores using either multiplication or (weighted) average:

\[\begin{split}\text{VSUN}(x) = \begin{cases} V(x) S(x) U(x) N(x) & \text{if agg_func = "prod"} \\ w_V V(x) + w_S S(x) + w_U U(x) + w_N N(x) & \text{if agg_func ="ave"} \end{cases}\end{split}\]

where \(w_V\), \(w_S\), \(w_U\), and \(w_N\) are the normalized weights for validity, stability, uniqueness, and novelty, respectively. If only a subset of VSUN is evaluated, each crystal’s score is computed using only the specified components in the similar manner. The overall single score for the entire set of crystals is then obtained by averaging the individual scores.

\[\text{VSUN} = \frac{1}{n} \sum_{i=1}^{n} \text{VSUN}(x_i)\]
class xtalmet.StabilityCalculator(diagram: Literal['mp_250618', 'mp'] | PatchedPhaseDiagram | str = 'mp_250618', mace_model: str = 'medium-mpa-0', binary=True, threshold: float = 0.1, intercept: float = 0.4289)View on GitHub

Bases: object

Class to calculate stability scores of crystal structures.

Initialize StabilityCalculator.

Parameters:
  • diagram (Literal["mp_250618", "mp"] | PatchedPhaseDiagram | str) – A phased diagram to use. If “mp_250618” is specified, the diagram constructed using this class from the MP entries on June 18, 2025, will be used. If “mp” is specified, the diagram will be constructed on the spot. You can also pass your own diagram or a path to it. Live MP entries are checked with MaterialsProject2020Compatibility; the diagram uses uncorrected energies of the accepted GGA/GGA+U entries.

  • mace_model (str) – The MACE model to use for energy prediction. Default is “medium-mpa-0”.

  • binary (bool) – If True, compute binary stability scores (1 for stable, 0 for unstable). If False, compute continuous stability scores between 0 and 1. Default is True.

  • threshold (float) – Energy above hull threshold for stability in eV/atom. Must be finite when binary is True. Default is 0.1 eV/atom.

  • intercept (float) – Intercept for linear scaling of stability scores in eV/atom. Only used if binary is False. Default is 0.4289 eV/atom, which is the 99.9th percentile of the energy above hull values for the MP20 test data.

Raises:

ValueError – If the active threshold is nonfinite, or the active intercept is nonfinite or nonpositive. Unused calibration arguments are ignored.

compute_stability_scores(xtals: list[Crystal], e_above_hulls_precomputed: ndarray[float] | None = None) → tuple[ndarray[float], ndarray[float], float]View on GitHub

Compute stability scores for a list of crystals.

NaN and infinite energies receive zero stability, including precomputed energies. Finite negative energies receive stability one. Precomputed arrays are returned unchanged.

Parameters:
  • xtals (list[Crystal]) – List of crystals to compute stability scores for.

  • e_above_hulls_precomputed (np.ndarray[float] | None) – Precomputed energy above hull values. If None, they will be computed internally.

Returns:

A tuple of stability scores, raw energy above hull values, and computation time in seconds.

Return type:

tuple[np.ndarray[float], np.ndarray[float], float]

class xtalmet.SMACTValidatorView on GitHub

Bases: SingleValidator

Class to calculate validity of crystal structures using SMACT.

Initialize SMACTValidator.

validate(xtals: list[Crystal]) → ndarray[float]View on GitHub

Validate a list of crystals using SMACT.

Uses SMACT 4 defaults: ICSD24 oxidation states with consensus 3, commonality “low”, and no mixed valence. Scores can differ from SMACT 3, which used commonality “medium”.

Parameters:

xtals (list[Crystal]) – List of crystals to validate.

Returns:

Array of validity scores for each crystal. A value of 1.0 indicates that the crystal passed the SMACT screening, while 0.0 indicates that it failed.

Return type:

np.ndarray[float]

References

class xtalmet.StructureValidator(threshold_distance: float = 0.5, threshold_volume: float = 0.1)View on GitHub

Bases: SingleValidator

Class to calculate structure-based validity of crystal structures.

Initialize StructureValidator.

Parameters:
  • threshold_distance (float) – Minimum allowed distance in angstroms between atoms, including periodic images of the same atom. Finite nonpositive values disable the distance check.

  • threshold_volume (float) – Minimum allowed unit-cell volume in cubic angstroms.

References

  • Xie et al., (2022). Crystal Diffusion Variational Autoencoder for Periodic Material Generation. In International Conference on Learning Representations.

validate(xtals: list[Crystal]) → ndarray[float]View on GitHub

Validate a list of crystals using structure-based method.

Distances include periodic images; only a site’s pair with itself in the same image is excluded. Distances and volumes equal to their thresholds pass. Nonfinite Cartesian coordinates or distance thresholds yield a score of 0.0.

Parameters:

xtals (list[Crystal]) – List of crystals to validate.

Returns:

Array of validity scores for each crystal. A value of 1.0 indicates that the crystal passed the structure-based screening, while 0.0 indicates that it failed.

Return type:

np.ndarray[float]

class xtalmet.Validator(methods: list[str], **kwargs)View on GitHub

Bases: object

Class to calculate validity of crystal structures.

Initialize Validator.

Parameters:
  • methods (list[str]) – List of validity evaluation methods to use. The currently supported methods are shown in SUPPORTED_VALIDITY in constants.py.

  • **kwargs – Additional keyword arguments for each validity evaluation method.

validate(xtals: list[Crystal], skip: list[str]) → tuple[dict[str, ndarray[float]], dict[str, float]]View on GitHub

Validate a list of crystals using the specified methods.

Parameters:
  • xtals (list[Crystal]) – List of crystals to validate.

  • skip (list[str]) – List of validity methods to skip.

Returns:

A dictionary of individual scores from each validator, and a dictionary of time taken for each validity method.

Return type:

tuple[dict[str, np.ndarray[float]], dict[str, float]]

Submodules