Skip to main content

Seitz

Crystallographic symmetry: determination, classification, and construction, in C++23.

C++23 CMake Compilers License Validated against spglib

Seitz computes the symmetry group of a periodic structure and everything that follows from it: the standardized cell, the Wyckoff decomposition of the atomic orbits, the magnetic (Shubnikov) group of a spin arrangement, the irreducible wedge of a reciprocal-space mesh, the maximal-subgroup graph of the 230 space groups, random structures carrying a prescribed group, and the symmetry-distinct cluster orbits of a substitutional alloy.

Everything ships behind one header:

#include <seitz/seitz.hpp>

Contents

Objects lattices, cells, Seitz operators, operation sets
Determination structure $\to$ space group
Standardization and reduction Niggli, Delaunay, idealization
Groups and Wyckoff positions the catalogs as objects
The subgroup graph maximal $t$- and $k$-subgroups
Magnetic symmetry Shubnikov groups, BNS/UNI
Reciprocal space irreducible meshes, Brillouin zone
Structure generation random crystals on a group
Alloy configurations cluster orbits, CVM
Error model and invariants Result<T>, total functions
Building · Python · License and attribution

Objects

A lattice is a rank-3 $\mathbb{Z}$-module $\Lambda = A\mathbb{Z}^3$, stored as the matrix $A \in GL(3,\mathbb{R})$ whose columns are the basis vectors $\mathbf{a}, \mathbf{b}, \mathbf{c}$. All geometry follows from the Gram (metric) tensor $G = A^{\mathsf{T}}A$, and the cell volume is $|\det A|$.

Lattice const lat{basis};                      // columns are a, b, c
lat.metric();                                  // G = AᵀA
lat.volume();                                  // |det A|
lat.to_cartesian(x);  lat.to_fractional(r);    // A x  and  A⁻¹ r
lat.transformed(P);                            // A P — change of basis
Lattice::from_basis(A);                        // nullopt if |det A| < kZeroPrec

A cell is $(A, X, \tau, \pi)$: a lattice, fractional coordinates $X \in [0,1)^{N \times 3}$, integer types $\tau$, and a per-axis periodicity $\pi \in {\text{periodic}, \text{aperiodic}}^3$. The periodicity is what selects the symmetry path, so one type carries all four dimensionalities — a 3D crystal, a 2D layer (one aperiodic axis, layer groups), a 1D rod (two), and a 0D cluster (three) — and there is no separate entry point per case.

Cell const c{lat, positions, types};                       // all_periodic() by default
Cell const slab = c.with_periodicity(aperiodic_along(2));  // layer-group path
c.transformed(P);  c.supercell({2, 2, 1});  c.translated(s);

A symmetry operation is a Seitz operator ${R \mid t}$ acting on fractional coordinates,

$${R \mid t},x = Rx + t, \qquad R \in GL(3,\mathbb{Z}),\ t \in \mathbb{R}^3/\mathbb{Z}^3 ,$$

with composition ${R_1|t_1}{R_2|t_2} = {R_1R_2 \mid R_1t_2 + t_1}$ and inverse ${R^{-1} \mid -R^{-1}t}$, defined exactly when $R$ is unimodular. Under a change of basis $P$ an operator conjugates as ${R|t} \mapsto {PRP^{-1} \mid Pt}$.

SymmetryOperation const op{.rotation = R, .translation = t};
op.apply(x);                       // Rx + t
auto const g = a * b;              // (a * b).apply(x) == a.apply(b.apply(x))
op.inverse();                      // optional — nullopt if R is not unimodular
conjugated_by(op, P, P_inv);       // P R P⁻¹, P t
same_operation(a, b, symprec);     // equality modulo Z³, within tolerance

An OperationSet<Op> is an immutable, range-modelling set of these — the group itself. It is the object the determination hands back, and it can be interrogated on its own:

ops.rotations();                   // the rotation parts, as integer matrices
ops.pure_translations();           // the centring vectors of the coset {I | t}
ops.conjugated_by(P, P_inv);       // the group in another basis
BOOST_LEAF_AUTO(m, ops.spacegroup<LatticeSetting::conventional>(lattice, tol));
// m.hall, m.bravais_lattice, m.origin_shift, m.type()
BOOST_LEAF_AUTO(p, ops.point_group());  // p.type (one of the 32), p.transformation
BOOST_LEAF_AUTO(q, mag_ops.spacegroup(lattice, tol)); // q.uni, q.type, q.hall, q.setting

The same three questions, asked of a bare set of operations with no atomic positions at all: which space group, which point group, which magnetic space group. MagneticOperations is the same class template over MagneticSymmetryOperation, so it answers the ones that still make sense.


Determination

Given a cell and a tolerance $\varepsilon$, determination recovers the space group $\mathcal{G}$ of the structure. The pipeline is:

  1. Translation subgroup. Find the candidate pure translations $t$ with $\tau(x_i + t) = \tau(x_i)$ for all $i$ up to $\varepsilon$; they generate $\Lambda_{\text{prim}} \supseteq \Lambda$, and the primitive cell is its reduced basis.
  2. Lattice point group. Delaunay-reduce the primitive metric $G$ and collect the integer matrices $R$ with $R^{\mathsf{T}} G R = G$ — the holohedry, a finite group of order $\le 48$.
  3. Space-group operations. For each such $R$, solve for a translation part $t$ that maps the decorated structure onto itself; the pairs that survive are the operations of the input cell.
  4. Matching. Identify the resulting group against the Hall-symbol database by conjugating it into each candidate setting, which yields the Hall number, the Bravais lattice, and the origin shift.
  5. Refinement. Transport the atoms into the standardized setting, assign each to a Wyckoff position by its stabilizer, and record the orbits.

SymmetryAnalyzer is the interface. It owns the cell and the tolerances, memoizes each stage, and is immutable after construction — so determination runs once and every subsequent query is served from the memo. A const analyzer is safe to share across threads from the moment it exists.

auto const sa = analysis::SymmetryAnalyzer::from_cell(cell, Tolerance{},
                                                     /*setting=*/std::nullopt);

leaf::try_handle_all(
    [&]() -> Result<void> {
      BOOST_LEAF_AUTO(hall, sa.hall());          // runs the pipeline
      BOOST_LEAF_AUTO(ops,  sa.operations());    // reuses it
      BOOST_LEAF_AUTO(prim, sa.primitive_cell());
      auto const &type = data::spacegroup_type(hall);
      std::println("{} ({}), |G| = {}, Z' cell of {} atoms",
                   type.international_short, type.number, ops.size(), prim.size());
      return {};
    },
    [](e_spacegroup_search_failed) { std::println("no group at this tolerance"); });
Query Yields
sa.hall() HallNumber — the key everything else is indexed on
sa.spacegroup_type() number, international/Schoenflies symbols, Hall symbol, point group
sa.operations() ${R\mid t}$ of the input cell
sa.cell_operations() operations of the input cell as given, without standardization
sa.lattice_symmetry() the lattice point group ${R : R^{\mathsf{T}}GR = G}$
sa.sites() one Site per atom: Wyckoff letter, site-symmetry symbol, orbit representative
sa.primitive_cell() the primitive cell found in step 1
sa.standardized_cell() conventional, idealized
sa.standardized_cell<CellSetting::primitive, Idealize::no>() the other three settings, resolved at compile time
sa.determine() the whole Dataset at once

The Dataset also carries the Setting that relates the input to the standard description — the change of basis $P$, the origin shift $s$, and the rigid rotation onto the idealized lattice — so any tabulated quantity can be pulled back onto the user's own coordinates.

Because the analyzer's queries return references into its memo, it must outlive them: the rvalue overloads are deleted, which makes from_cell(cell).hall() a compile error rather than a dangling read.


Standardization and reduction

Two classical reductions of a lattice basis, both returning a new Lattice:

  • Niggli (Křivý–Gruber): the unique reduced basis of a lattice, obtained by normalizing the six metric parameters $(a^2, b^2, c^2, bc, ac, ab)$ under the reduction conditions.
  • Delaunay: reduction of the extended basis ${\mathbf{a}, \mathbf{b}, \mathbf{c}, -(\mathbf{a}+\mathbf{b}+\mathbf{c})}$ until all pairwise scalar products are non-positive. This is the reduction the holohedry search runs on, since it exposes the lattice's full point symmetry.
BOOST_LEAF_AUTO(n, lat.niggli(eps));
BOOST_LEAF_AUTO(d, lat.delaunay(symprec));
BOOST_LEAF_AUTO(p, lat.delaunay_in_plane(unique_axis, symprec));  // layer case
lat.rigid_rotation_to(ideal);   // R with ideal = R · lat

Idealization is the second half: a standardized cell may either keep the input's own (possibly strained) metric or be replaced by the exact metric its Bravais class demands. The two axes — CellSetting $\times$ Idealize — are template parameters of standardized_cell, so the four combinations are distinct instantiations rather than runtime flags.


Groups and Wyckoff positions

The catalogs are objects, not parallel arrays. SpaceGroup, PointGroup and RodGroup share a face — number, symbol, order, operations, Wyckoff positions — without a runtime hierarchy.

BOOST_LEAF_AUTO(g, group::SpaceGroup::from_number(GroupFamily::space, 225));
group::SpaceGroup const &h = group::SpaceGroup::of(hall);   // by Hall setting
g->order();  g->symbol();  g->centering();  g->operations();

A Wyckoff position is an orbit type: the set of points whose stabilizer (site-symmetry group) is conjugate to a given subgroup $S \le \mathcal{G}$. Its locus is an affine subspace $x(\lambda) = x_0 + \sum_i \lambda_i b_i$ of dimension $d$ (the degrees of freedom), and the orbit–stabilizer theorem fixes the multiplicity:

$$m \cdot |S| = |\mathcal{G}| .$$

for (group::Wyckoff const &w : g->wyckoffs()) {
  w.multiplicity();  w.letter();  w.site_symmetry();  w.degrees_of_freedom();
  w.operations();                 // the stabilizer S; |S| · m = |G|
  w.sample(params);               // x₀ + Σ λᵢ bᵢ
  w.canonical(xyz);               // idempotent projection onto the locus
  w.orbit(xyz, periodicity);      // the full orbit, one row per image
}
BOOST_LEAF_AUTO(wa, g->wyckoff('a'));

canonical is the projector onto the locus, so an approximate coordinate still generates the exact orbit — the numerically robust way to seat an atom on a special position.


The subgroup graph

Maximal subgroups of the 230 space-group types form a directed graph. An edge $\mathcal{G} \to \mathcal{H}$ records that $\mathcal{H}$ is a maximal subgroup of $\mathcal{G}$, of one of two kinds:

  • translationengleiche ($t$): same translation lattice, smaller point group, $[\mathcal{G}:\mathcal{H}] = |P_{\mathcal{G}}| / |P_{\mathcal{H}}|$;
  • klassengleiche ($k$): same point group, sublattice of index $[\Lambda_{\mathcal{G}} : \Lambda_{\mathcal{H}}]$.

Each edge carries the transformation taking the supergroup's conventional description to the subgroup's — a basis matrix $P$ and an origin shift $s$:

$$(\mathbf{a}_H\ \mathbf{b}_H\ \mathbf{c}_H) = (\mathbf{a}_G\ \mathbf{b}_G\ \mathbf{c}_G),P, \qquad x_G = P x_H + s ,$$

with $s$ defined modulo $P\mathbb{Z}^3$, so an operation of $\mathcal{G}$ can be pushed into $\mathcal{H}$'s frame as ${P^{-1}RP \mid P^{-1}(Rs + t - s)}$ — defined exactly when $P^{-1}RP$ is integral, i.e. when $R$ preserves $\Lambda_H$.

using group::SubgroupGraph;
for (int id : SubgroupGraph::maximal_subgroups(225, SubgroupKind::translationengleiche)) {
  group::SubgroupEdge const e = SubgroupGraph::edge(id);
  e.super; e.sub; e.index; e.kind; e.hall; e.basis; e.origin;
  e.in_subgroup_frame(op);            // optional<SymmetryOperation>
}
SubgroupGraph::minimal_supergroups(number);      // the reverse relation
SubgroupGraph::is_subgroup(sub, super);          // reflexive reachability
SubgroupGraph::path(super, sub);                 // shortest chain of edges

The relation table is consteval, and so is its transitive closure: reachability is a bit test in a compile-time Warshall closure, not a search. There is one storage — the graph is a Boost.Graph model (bidirectional_graph, vertex_list_graph, edge_list_graph) over that same table, so breadth_first_search, filtered_graph, reverse_graph and the BGL visitors apply to it unchanged, with no second representation to keep in sync.


Magnetic symmetry

A magnetic (Shubnikov) group extends the Seitz operator with the time-reversal generator $\theta$: elements are ${R \mid t}$ and ${R \mid t}\theta$, the latter reversing axial site tensors. Classification follows the Bärnighausen/BNS construction types:

Type Structure
I $\mathcal{M} = \mathcal{G}$, no anti-operation
II $\mathcal{M} = \mathcal{G} + \mathcal{G}\theta$ (grey)
III $\mathcal{M} = \mathcal{H} + (\mathcal{G} \setminus \mathcal{H})\theta$, $\mathcal{H}$ a $t$-subgroup of index 2
IV as III with $\mathcal{H}$ a $k$-subgroup: $\theta$ paired with an anti-translation
auto const ma = analysis::MagneticSymmetryAnalyzer::from_cell(mcell, MagneticTolerance{});
BOOST_LEAF_AUTO(uni,  ma.uni());               // UNI number (1651 types)
BOOST_LEAF_AUTO(ops,  ma.operations());        // MagneticOperations
BOOST_LEAF_AUTO(std,  ma.standardized_cell()); // tensors rotated into the standard basis
ma.spacegroup_type();                          // BNS/OG symbols, type I-IV

Collinear and non-collinear moments are both handled; the moment tolerance is separate from the positional one, so spin and geometry are compared on their own scales.


Reciprocal space

A sampling mesh is a quotient of the reciprocal lattice. Points are addressed on the doubled grid so that shifted (Monkhorst–Pack) meshes stay integral:

$$q = \frac{2a + \sigma}{2d}, \qquad a \in \mathbb{Z}^3,\ \sigma \in {0,1}^3,\ d \in \mathbb{Z}_{>0}^3 .$$

Mesh is pure grid arithmetic — the bijection between addresses and linear indices, constexpr throughout, with its round-trip identities asserted at compile time.

The reduction acts with the reciprocal point group: real-space rotations transposed, together with the inversion partner when time reversal is imposed. Each grid point is mapped to the smallest index in its orbit, so mapping()[i] == i characterizes the irreducible representatives.

auto const mesh = *kpoint::Mesh::of({8, 8, 8});     // nullopt on a non-positive mesh
BOOST_LEAF_AUTO(rm, sa.reciprocal_mesh(mesh, TimeReversal::on));
rm.num_irreducible();                 // |IBZ|
rm.mapping();                         // grid point → representative
rm.images_of(address);                // the orbit of one address
auto const bz = rm.brillouin_zone(reciprocal_lattice);

BrillouinZone relocates every point to the reciprocal-lattice image nearest the origin, duplicating boundary points that are equidistant from several images within tolerance — the convention that makes zone-boundary weights come out right.


Structure generation

The inverse problem: given a group $\mathcal{G}$ and a composition ${(Z_k, n_k)}$, produce a structure whose symmetry is $\mathcal{G}$. An assignment is a multiset of Wyckoff positions per species satisfying

$$\sum_{i \in \text{assignment}(k)} m_i = n_k ,$$

with positions of zero degrees of freedom usable at most once (two atoms cannot share a fixed point). The generator enumerates valid assignments — pruned by a reachability table over the remaining counts — then, for each, samples the free parameters $\lambda$ and a random lattice of the correct Bravais class until the minimum-distance criterion is met.

generate::Composition const comp{{11, 4}, {17, 4}};          // Na₄Cl₄
generate::Generator const gen{*g, {.seed = 42, .attempts_per_combination = 50}};

gen.compatible(comp);                 // is any assignment possible at all?
gen.assignments(comp);                // enumerate them
BOOST_LEAF_AUTO(x, gen(comp));        // x.cell, x.assignment (with generating coordinates)

GenerateOptions fixes the search: seed (fully deterministic), scale on the element-aware size estimate, distance acceptance, placement (general_only forbids special positions, so no accidental symmetry is added), a caller-supplied lattice whose metric must be invariant under $\mathcal{G}$, and sites — atoms pinned to a Wyckoff letter, and optionally to a coordinate on it, before the search begins.

The generator is templated on the group family. SpaceGroup (3D and, when its Hall key names the layer family, 2D), RodGroup (1D) and PointGroup (0D) each supply their periodicity, seed window and random metric through GroupTraits<G>; assignment enumeration, the distance test and the search are written once. The result is always a Cell that describes its own periodicity — a cluster is a cell with no periodic axis.


Alloy configurations

For substitutional disorder on a fixed parent lattice, the observables are symmetry orbits of decorated clusters. A cluster expansion writes a configurational property as

$$E(\sigma) = \sum_\alpha m_\alpha J_\alpha , \xi_\alpha(\sigma) ,$$

with $\xi_\alpha$ the orbit-averaged correlation functions of the point-function basis and $m_\alpha$ the orbit multiplicities. Seitz enumerates the orbits and builds the Cluster Variation Method ingredients for a chosen set of maximal clusters — the configurations $j$ on each subcluster $c$ with multiplicities $m_{jc}$, the matrix $V_c$ mapping correlations to cluster probabilities $\rho_c = V_c\xi$, and the Kikuchi–Barker coefficients $k_c$ of the entropy

$$S = -k_B \sum_c k_c \sum_j m_{jc}, \rho_{jc} \ln \rho_{jc} .$$

BOOST_LEAF_AUTO(pool, alloy::ClustersPool::generate(parent, {.radii = {{2, 6.0}, {3, 4.5}}}));
for (alloy::Orbit const &o : pool) { o.multiplicity(); }

BOOST_LEAF_AUTO(cvm, alloy::Cvm::create(parent, maximal_clusters, basis));
cvm.clusters();    // per subcluster: configurations, V-matrix, Kikuchi–Barker coefficient
cvm.functions();   // the basis functions indexing ξ

This layer generates the ingredients; fitting the $J_\alpha$ and minimizing the free energy are left to the caller.


Error model and invariants

Errors are values. Nothing throws and nothing returns a magic sentinel. A fallible call returns Result<T> (= boost::leaf::result<T>), and failures carry typed context: e_spacegroup_search_failed, e_invalid_lattice{det}, e_atoms_too_close{distance}, e_incompatible_lattice, e_invalid_transformation, e_niggli_failed, and so on. "Absent" is std::optional, never -1.

Invalid states are unrepresentable, so most errors never arise. A HallNumber, a UniNumber and a kpoint::Mesh are each built through an of() that is the only way in and the only place the check lives. A function taking such a key is total: it cannot fail on a bad key, because no bad key can be held.

Value semantics throughout, no manual memory management, no ownership to reason about. Everything is safe to share: a const analyzer is usable from any number of threads the moment it is built, the catalogs are immutable, and no query mutates observable state. seitz::warmup() (about 30 ms) builds every group setting up front to move first-use cost off the query path; it is an optimization, never a precondition.

Every result is cross-checked against reference spglib v2.7.0 by an oracle test suite that builds the reference implementation separately and never links it into the library.


Building

One line, and the presets carry the flags — CI runs exactly these:

cmake --preset release && cmake --build --preset release && ctest --preset release
Preset
debug Debug, unit tests + spglib oracle
release Release, unit tests + spglib oracle
asan Debug, oracle, Address + UndefinedBehavior sanitizers
python Release, the Python extension module

Needs GCC 15+, Clang with libstdc++, or MSVC 19.43+; CMake $\ge$ 3.28; and a Python 3.9+ interpreter, used at configure time to transcribe the symmetry tables. Everything else is fetched rather than found — Eigen 5.0.0, Boost 1.88, Catch2 3 — so nothing needs to be installed first. The presets default to gcc-15/g++-15; pass -DCMAKE_CXX_COMPILER= for anything else. Off the presets — pip install ., an IDE, a bare cmake — set CXX, since a default c++ is GCC 13 on Ubuntu 24.04 and too old for this tree:

CXX=g++-15 pip install .
Option Default Effect
SEITZ_BUILD_TESTS ON unit test suite
SEITZ_BUILD_DEMO ON seitz_demo executable
SEITZ_BUILD_ORACLE_TESTS OFF cross-check every result against reference spglib v2.7.0 (all presets turn this ON)
SEITZ_ENABLE_SANITIZERS OFF Address + UndefinedBehavior sanitizers
SEITZ_BUILD_PYTHON OFF the Python extension module

Consuming it

add_subdirectory(Seitz)                 # or: find_package(Seitz REQUIRED)
target_link_libraries(your_target PRIVATE seitz::seitz)

cmake --install build/release --prefix /your/prefix writes the vendored Eigen and Boost packages into the same prefix, so an installed tree is self-contained and find_package(Seitz REQUIRED) needs only CMAKE_PREFIX_PATH. Prebuilt prefixes for Linux and Windows are attached to each GitHub release.

MSBuild consumers, who have no CMake to carry the flags, get the same library as a NuGet package instead — Seitz on nuget.org, x64, headers plus one static seitz.lib per CRT flavour with the Eigen and Boost headers the public interface reaches bundled in:

nuget install Seitz          # or: Install-Package Seitz

Its .targets sets the include paths, /std:c++latest and the raised constexpr budget the subgroup tables need in the consumer's own translation units, so a referencing .vcxproj needs no settings of its own.

vcpkg consumers get an overlay port, kept in the repository and re-pinned to each tag automatically:

git clone https://github.com/reach2sayan/Seitz
vcpkg install seitz --overlay-ports=Seitz/contrib/vcpkg/ports

It takes Eigen and Boost from vcpkg rather than fetching its own (SEITZ_EXTERNAL_EIGEN_AND_BOOST), and pins spglib's sources at the same v2.7.0 the tables are transcribed from. Two things to know: the port builds a throwaway Python venv for those transcribers, so it needs the network at build time; and the GCC 15 floor applies as it does everywhere else, so a Linux triplet on an older default compiler fails at configure with the diagnostic that says so.

Only include/ ships, so a consumer reaches exactly what the umbrella header reaches. seitz is a shared library, 0.1.0 with SONAME 0.1; while the API is pre-1.0 every minor version may break ABI. On Windows, and inside the Python extension, it is a static archive instead.


Python

The same API, NumPy-first: the C++ library does the work, and the Python layer adds validated inputs, serializable records via pydantic, and an exception hierarchy over Result<T> — with pyseitz.results giving the Result<T> discipline back, unchanged, to callers who want it.

Wheels for CPython 3.11-3.14 (Linux x86-64, Windows x64) are on PyPI, and need no compiler or GCC 15 on the target machine:

pip install pyseitz

The distribution and the import are both pyseitz; PyPI refuses seitz as too close to the unrelated seltz.

import numpy as np
import pyseitz as sz

cell = sz.Cell(
    sz.Lattice(3.0 * np.eye(3)),          # columns are the basis vectors
    [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]],
    [26, 26],
)

analyzer = sz.analyze(cell)                            # nothing computed yet
print(analyzer.spacegroup_type.international_short)    # Im-3m
print(len(analyzer.operations))                        # 96

group = sz.SpaceGroup.of(analyzer.hall)
print(group.wyckoffs[-1].multiplicity)                 # the general position

for e in sz.subgroups.maximal_subgroups(225, sz.SubgroupKind.translationengleiche):
    print(e.sub, e.index)

print(sz.DatasetRecord.from_analyzer(analyzer).model_dump_json(indent=2))

Magnetic structures and reciprocal-space sampling are bound the same way. A MagneticCell takes the moments as an array, and its rank is that array's shape rather than a flag:

magnetic = sz.MagneticCell(cell, np.array([1.0, -1.0]), sz.TensorKind.axial)
print(sz.analyze_magnetic(magnetic).spacegroup_type.bns_number)

reciprocal = analyzer.reciprocal_mesh(sz.Mesh([16, 16, 16]))
print(reciprocal.num_irreducible)                      # the irreducible wedge
zone = reciprocal.brillouin_zone(sz.Lattice(np.linalg.inv(cell.lattice.matrix).T))

The analyzer memoizes, so it is the object you keep rather than a call you repeat, and every query on it is thread-safe. "Absent" is None; failures raise a pyseitz.errors.SeitzError subclass carrying its context — InvalidLatticeError.determinant, AtomsTooCloseError.distance. Layer groups are not a separate entry point: a cell built with periodicity=sz.aperiodic_along(2) goes through the same analyzer.

Raising is the default because it is what Python callers expect, but it is not the only door. pyseitz.results mirrors the fallible surface as Ok/Err values, so the error model above survives the crossing rather than being traded away at it:

from result import Ok, Err
from pyseitz import errors, results

match results.read_cif(path):
    case Ok(structures):
        print(len(structures))
    case Err(errors.CifSyntaxError() as error):
        print(error.line, error.column)

results.attempt(lambda: analyzer.hall)      # for properties and methods
results.checked(group.wyckoff)("z")         # a callable, wrapped once

The two surfaces share one hierarchy by identity, not by parallel definition: an Err holds the very exception object that would have been raised, payload attribute and all, and raise res.err_value is the trip back. Only a SeitzError becomes an Err — a TypeError or a pydantic ValidationError still raises, because a bug in the caller is not a crystallographic failure.

From a checkout, the python preset builds the extension and runs the pytest suite against the build tree:

cmake --preset python && cmake --build --preset python && ctest --preset python

License and attribution

Seitz is released under the BSD-3-Clause license; see LICENSE.

It stands on two existing projects and is grateful to both.

  • spglib (Atsushi Togo and contributors), BSD-3-Clause. The symmetry algorithms — space-group determination, lattice reduction, Wyckoff refinement, magnetic space-group machinery, and k-point reduction — and the symmetry databases are derived from spglib's C core. The reference implementation is also used, built separately and never linked into the library, as a validation oracle in the test suite. This library targets spglib v2.7.0.

  • PyXtal (Qiang Zhu, Scott Fredericks, and contributors), MIT. The structure-generation layer and the object-oriented framing — groups as standalone objects owning their Wyckoff positions, named factory functions, and random crystal/cluster generation by seating a composition on Wyckoff sites — follow PyXtal's design.

The alloy layer follows the cluster-expansion and CVM formulations of ATAT (A. van de Walle) and clusterX.

Both upstream licenses (BSD-3-Clause and MIT) are permissive and impose no copyleft, so this combined and derived work is distributed under the BSD-3-Clause license. The original spglib and PyXtal copyright notices are retained for the portions derived from them — the symmetry core and the spacegroup_* / msg_* / sitesym_* / hall_generators* data tables from spglib, and the rod-group tables from PyXtal. The full verbatim notices are in the THIRD-PARTY NOTICES section of the license file. The element covalent-radius data is from Cordero et al. (2008), cited there.

Metadata

Release files for pyseitz 0.1.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for pyseitz 0.1.5
File
pyseitz-0.1.5-cp314-cp314-win_amd64.whl CPython 3.14 CPython 3.14 Windows x86-64 Details
pyseitz-0.1.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ x86-64, Linux glibc 2.27+ x86-64 Details
pyseitz-0.1.5-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details
pyseitz-0.1.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.28+ x86-64, Linux glibc 2.27+ x86-64 Details
pyseitz-0.1.5-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
pyseitz-0.1.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 Details
pyseitz-0.1.5-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details
pyseitz-0.1.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.28+ x86-64, Linux glibc 2.27+ x86-64 Details

Total release size: 10.1 MB

Release files / pyseitz-0.1.5-cp314-cp314-win_amd64.whl

Download URL pyseitz-0.1.5-cp314-cp314-win_amd64.whl
Size 1.1 MB
Tags CPython 3.14 Windows x86-64
SHA-256 checksum
How to use checksums
f7690c0382fbbcef70f092d964f9e3ae21fc4f3455793b19e0821a5147143403
BLAKE2b-256 checksum
How to use checksums
5fbe7bc5361607e51d3be6c1bc5440b03731c73c2d04227ca1b25d7e6c73497d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / pyseitz-0.1.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL pyseitz-0.1.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 1.4 MB
Tags CPython 3.14 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
3cb354d3221e8c2a5fb6613ee5ef8ad94fe22a05ef5096de48b201d9905709e9
BLAKE2b-256 checksum
How to use checksums
40fa647fa409a76f0e066f2e44dd5d7b22f3f3837dba8af8dfe72f78700f6513
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / pyseitz-0.1.5-cp313-cp313-win_amd64.whl

Download URL pyseitz-0.1.5-cp313-cp313-win_amd64.whl
Size 1.1 MB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
c7524e647ace647c9001a14e196c47b363c19a798e6411db4342c6e436940454
BLAKE2b-256 checksum
How to use checksums
0615a55bbd1d8f25d9118bafc9268bbb76534f8aa4d5c655ce749d93cc7cb0c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / pyseitz-0.1.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL pyseitz-0.1.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 1.4 MB
Tags CPython 3.13 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
2f86b6c1404162a7eae542c9d9fce79d113451582a22a664118d0833eadccc37
BLAKE2b-256 checksum
How to use checksums
d23530209f14e9a8f972d01d61b6b46da5b89c0b6b936b8efd6cf3a18f55b2eb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / pyseitz-0.1.5-cp312-cp312-win_amd64.whl

Download URL pyseitz-0.1.5-cp312-cp312-win_amd64.whl
Size 1.1 MB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
b9ab133f5c602f922c13cb11bdf4e705223f16e2c899e3e09dd9a73cfb5ec3f4
BLAKE2b-256 checksum
How to use checksums
7b4e30a8efbf8c1cddb469df1701b5d11d3e6d3f883cddf4f805268145918b19
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / pyseitz-0.1.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL pyseitz-0.1.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 1.4 MB
Tags CPython 3.12 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
29a585673006c9d7ca4e6c3f09cef8f9bb2174925347893aa0ec66b5f834d6b3
BLAKE2b-256 checksum
How to use checksums
e8a4b99031631a8b4676aa289a5c4f6331420c10cfcd5ec2394697b2e1132383
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / pyseitz-0.1.5-cp311-cp311-win_amd64.whl

Download URL pyseitz-0.1.5-cp311-cp311-win_amd64.whl
Size 1.1 MB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
4c5752192d9550c52fb6899c00e356f758c50e314e11f9620db89f94c37ba0cb
BLAKE2b-256 checksum
How to use checksums
73cf732f6cc648f49c76ba325b7d5ba6bc49cc2673b6a5966cd192463f6010d0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release files / pyseitz-0.1.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL pyseitz-0.1.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 1.4 MB
Tags CPython 3.11 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
b60724262a1a5064b2878facd50d93c080e97af7287ca3728c5f77ab58cff781
BLAKE2b-256 checksum
How to use checksums
ab3582ca3e9a2b6dadcadd05cb004474ca5b45549cafa2d3b59f0d4fd5c397f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.8

8 release files

0.1.7

8 release files

0.1.6

8 release files

This release

0.1.5 This release

8 release files

0.1.4

8 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page