Utilities#

Pure computation with no dependencies on the rest of the package.

Seed-id generation: the element set -> deterministic combination ids (3.0).

Pure stdlib – no science imports, no store, no I/O. This is the n-ary knob’s engine: the user configures run.elements plus run.combination_sizes ([1] for single elements, [2] for binary, [2, 3] to add ternary, …; omitted = all sizes 1..N, expanded by the config schema), and every run enumerates the same combinations in the same order, so seed ids are stable across runs and machines (enqueue idempotency then makes re-seeding a no-op).

A seed id is the sorted element symbols joined with - ("Cl-Na", "Cl-Na-O"): filesystem-safe, human-readable, and unambiguous, since element symbols never contain -.

matmelt.core.seeds.element_combinations(elements, sizes)[source]#

Enumerate the seed ids for elements at each combination size.

Parameters:
  • elements (Sequence[str]) – Element symbols from run.elements. Deduplicated and sorted before anything is enumerated, so the result does not depend on config order.

  • sizes (Sequence[int]) – Combination sizes to enumerate, deduplicated and sorted. Size 1 yields the bare element symbols (elemental seeds). A size larger than the element set yields nothing for that size – enumeration stays silent and total, and the preflight is what reports such no-ops.

Returns:

Seed ids, grouped by ascending size and lexicographic within each size.

Return type:

list of str

Raises:

ValueError – If any size is below 1. The config schema enforces the same floor; this guard is for direct callers.

Examples

>>> element_combinations(["Na", "Cl"], [2])
['Cl-Na']
>>> element_combinations(["O", "Na", "Cl"], [1, 3])
['Cl', 'Na', 'O', 'Cl-Na-O']
>>> element_combinations(["Na", "Cl"], [3])   # more elements than we have
[]

DFTB+ .gen geometry -> CIF text: the export writer (M10).

Pure stdlib and pure computation – no science imports, no AiiDA, no I/O, no Rich. The cif command feeds this the geom.out.gen text that DFTB+ relaxed to (fetched from the user’s AiiDA repository, or cached in calc_result.json) and writes the returned document to disk; keeping the conversion here means every cell/coordinate rule is unit-testable in the science-free dev venv, and no CIF is ever produced by a dependency we do not own.

The written document is deliberately plain: space group P 1 (a relaxed DFTB+ cell carries no symmetry claim – the atoms have moved), one _atom_site_* loop in fractional coordinates, and whatever provenance the caller passes as _mcli_* items (material id, WorkChain/CalcJob pk, energy, …). It round-trips through the extract runner’s own CIF reader, and VESTA/ASE read it directly.

Two deliberate non-transformations, so an exported CIF is exactly what the calculation produced:

  • coordinates are not wrapped into [0, 1) – an atom that relaxed to 0.999998 stays there rather than jumping across the cell;

  • the .gen origin row is ignored (as ASE does) – lattice vectors alone define the cell.

extract_runner.py holds the inverse helpers (CIF -> cell vectors); it is a self-contained runner staged to compute backends and may never import this module, so the two directions live apart by design.

matmelt.core.cif.Vector#

A cartesian or fractional triple.

matmelt.core.cif.Lattice#

Three lattice vectors (rows), in angstrom.

class matmelt.core.cif.Geometry(species, fractional, lattice)[source]#

Bases: object

One periodic structure, ready to serialize.

species#

One element symbol per site, already expanded from the .gen species table.

Type:

tuple of str

fractional#

One (x, y, z) fractional triple per site, in the same order as species.

Type:

tuple

lattice#

The three cell vectors (rows), in angstrom.

Type:

tuple

matmelt.core.cif.parse_gen(text)[source]#

Parse DFTB+ .gen text into a Geometry.

Parameters:

text (str) – The file’s contents. Blank lines and # comments are ignored.

Returns:

The structure, always in fractional coordinates.

Return type:

Geometry

Raises:

ValueError – For a cluster geometry (C), which has no cell to write; for an unknown geometry type; and for any malformed header, species row, atom row, or lattice block. Every rejection names the offending row – a geometry that cannot be read must never be guessed at.

Notes

Both periodic forms are supported: F (fractional – what our HSD template and DFTB+’s geom.out.gen produce) and S (cartesian, converted here against the lattice).

matmelt.core.cif.cell_volume(lattice)[source]#

Compute the cell volume.

Parameters:

lattice (tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]]) – The three cell vectors, in angstrom.

Returns:

Volume in cubic angstrom – the absolute determinant, so a left-handed cell reports a positive volume.

Return type:

float

Examples

>>> cell_volume(((4.0, 0.0, 0.0), (0.0, 4.0, 0.0), (0.0, 0.0, 4.0)))
64.0
matmelt.core.cif.cell_parameters(lattice)[source]#

Convert lattice vectors to the six CIF cell parameters.

Parameters:

lattice (tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]]) – The three cell vectors, in angstrom.

Returns:

(a, b, c, alpha, beta, gamma) with lengths in angstrom and angles in degrees. Following the crystallographic convention the CIF tags name, alpha is the angle between b and c, beta between a and c, and gamma between a and b.

Return type:

tuple

Raises:

ValueError – If any vector has zero length – it has no direction, so the honest answer is a refusal rather than a NaN angle.

Examples

>>> cell_parameters(((3.0, 0.0, 0.0), (0.0, 3.0, 0.0), (0.0, 0.0, 5.0)))
(3.0, 3.0, 5.0, 90.0, 90.0, 90.0)
matmelt.core.cif.cart_to_frac(position, lattice)[source]#

Convert cartesian coordinates to fractional ones.

The inverse of extract_runner.frac_to_cart.

Parameters:
Returns:

The fractional triple. Not wrapped into [0, 1) – see the module docstring.

Return type:

tuple

Raises:

ValueError – If the lattice vectors are linearly dependent, so the cell has no volume and cannot be inverted.

matmelt.core.cif.formula_sum(species)[source]#

Build the _chemical_formula_sum text for a set of sites.

Parameters:

species (Sequence[str]) – One element symbol per site; order is irrelevant.

Returns:

Alphabetical symbols with their counts, the count omitted when it is 1.

Return type:

str

Examples

>>> formula_sum(["Na", "Cl", "Na", "Cl", "O"])
'Cl2 Na2 O'
matmelt.core.cif.geometry_to_cif(geometry, *, data_name, extra=None)[source]#

Serialize a Geometry as a P1 CIF document.

Parameters:
  • geometry (Geometry) – The structure to write.

  • data_name (str) – Names the data block – a material id. CIF forbids whitespace here, and an empty name too.

  • extra (Mapping[str, Any] | None, default: None) – Provenance items written as _mcli_<key> tags in the given order. Keys must match [A-Za-z0-9_]+; values may be strings, numbers, bools, or None.

Returns:

The complete CIF document, newline-terminated.

Return type:

str

Raises:

ValueError – For an empty or whitespace-bearing data_name; a geometry whose species and coordinate counts disagree or that holds no sites; a degenerate cell; or a provenance key with illegal characters.

matmelt.core.cif.gen_to_cif(gen_text, *, data_name, extra=None)[source]#

Parse DFTB+ .gen text and serialize it as a P1 CIF document.

The one call the cif command makes per material.

Parameters:
  • gen_text (str) – The geom.out.gen contents DFTB+ relaxed to.

  • data_name (str) – Names the CIF data block – a material id.

  • extra (Mapping[str, Any] | None, default: None) – Provenance items, written as _mcli_<key> tags.

Returns:

The complete CIF document.

Return type:

str

Raises:

ValueError – From parse_gen for unreadable geometry, or from geometry_to_cif for an unwritable one.