Skip to main content

xtalforge_core/
lib.rs

1//! Core crystal-structure types for xtalforge.
2//!
3//! This crate holds the element data, lattices, sites, structures and the
4//! P1, supercell and slab operations developed in Phase 1. The foundation is
5//! kept small so every domain can share a single error type per crate.
6
7pub mod bonding;
8pub mod cell;
9pub mod compare;
10pub mod data;
11pub mod deprecation;
12pub mod describe;
13pub mod edits;
14pub mod elements;
15pub mod eos;
16pub mod groups;
17pub mod hull;
18pub mod lattice;
19pub mod limits;
20pub mod local;
21pub mod measure;
22pub mod neighbors;
23pub mod p1;
24pub mod parallel;
25pub mod param_presets;
26pub mod params;
27pub mod polyhedron;
28pub mod rings;
29pub mod scalars;
30pub mod selection;
31pub mod site;
32pub mod slab;
33mod spatial;
34pub mod structure;
35pub mod supercell;
36pub mod surface;
37pub mod symop;
38pub mod transforms;
39
40pub use bonding::{Bond, BondGraph, BondRules, ExplicitBond, TopologyBond, perceive_bonds};
41pub use cell::cell_volume;
42pub use compare::{Alignment, interpolate, kabsch_alignment, periodic_rmsd};
43pub use deprecation::warn_deprecated;
44pub use describe::describe_atom;
45pub use edits::{
46    RattleOptions, RattleReport, SiteSort, SpeciesReplacement, SupercellChoice, add_sites, deform,
47    move_site, optimal_supercell, rattle, remove_sites, replace_site, replace_species,
48    scale_to_density, scale_to_volume, sort_sites, strain, tensor_to_voigt, voigt_to_tensor,
49    wrap_sites,
50};
51pub use elements::{Element, Isotope, element_info};
52pub use eos::{EosFit, EosKind, EosPoint, eos_energy, fit_eos};
53pub use groups::{GroupKind, GroupMatch, find_groups, group_counts};
54pub use hull::{ConvexHull, PhaseEntry, PhasePoint, convex_hull};
55pub use lattice::{Lattice, lattice_info, lattice_minimum_image};
56pub use limits::{Limits, Profile, Verdict, limits_bounds, limits_check};
57pub use local::{
58    AngleDistribution, CoordinationNeighbor, CoordinationShell, CoordinationStrategy,
59    RadialDistribution, RdfOptions, ShapeMeasure, StructureFingerprint, VoronoiCell,
60    VoronoiNeighbor, VoronoiReport, bond_angle_distribution, coordination, fingerprint,
61    radial_distribution, shape_measures, voronoi,
62};
63pub use measure::{angle, centroid, distance, interplanar_angle, torsion};
64pub use neighbors::{Neighbor, neighbors, neighbors_of};
65pub use p1::{P1Atom, P1Cell};
66pub use parallel::{ParallelOptions, set_global_threads};
67pub use param_presets::{PRESET_NAMES, preset, presets};
68pub use params::{ParamSet, ParamSpec, ParamType};
69pub use polyhedron::{FULL_SPHERE, HalfSpace, Polyhedron};
70pub use rings::{Ring, RingStep, is_aromatic, ring_counts, ring_membership, ring_paths, rings};
71pub use scalars::{
72    ScalarFn, ScalarInfo, ScalarRegistry, atom_charges, atom_masses, bond_lengths,
73    coordination_numbers,
74};
75pub use selection::{AtomId, Selection, TrackingTable};
76pub use site::Site;
77pub use slab::{Slab, make_slab, plane_angle, surface_basis};
78pub use structure::{
79    SCHEMA_VERSION, Structure, StructureBuilder, StructureDocument, StructureSummary,
80    StructureWarning, structure_info,
81};
82pub use surface::{
83    AdsorptionKind, AdsorptionSite, SurfaceEnergy, WulffFacet, WulffShape, adsorption_sites,
84    place_molecule, wulff,
85};
86pub use symop::SymOp;
87pub use transforms::{Ellipsoid, Plane, displacement_ellipsoid};
88
89/// Version of the workspace, from the package metadata.
90///
91/// The value is the same on every surface: it comes from the workspace
92/// `Cargo.toml` and is mirrored by the Python and WASM packages at release.
93#[must_use]
94pub fn version() -> &'static str {
95    env!("CARGO_PKG_VERSION")
96}
97
98/// Errors returned by core operations.
99#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
100pub enum Error {
101    /// An argument does not satisfy the documented preconditions.
102    #[error("invalid input: {0}")]
103    InvalidInput(String),
104    /// A compressed data bundle could not be decompressed.
105    #[error("bundle {name}: compressed data is invalid: {reason}")]
106    DataCompression {
107        /// Name of the bundle as declared in its manifest.
108        name: String,
109        /// Message from the decompressor.
110        reason: String,
111    },
112    /// A data bundle failed its SHA-256 checksum.
113    #[error("bundle {name}: SHA-256 checksum mismatch")]
114    DataChecksum {
115        /// Name of the bundle as declared in its manifest.
116        name: String,
117    },
118    /// A data bundle declared a digest that is not 64 hexadecimal characters.
119    #[error("bundle {name}: invalid SHA-256 digest")]
120    DataDigest {
121        /// Name of the bundle as declared in its manifest.
122        name: String,
123    },
124    /// JSON decoding of a data bundle failed.
125    #[error("bundle {name}: JSON decoding failed: {reason}")]
126    DataJson {
127        /// Name of the bundle as declared in its manifest.
128        name: String,
129        /// Message from the JSON decoder.
130        reason: String,
131    },
132    /// A size limit would be exceeded; the verdict explains what would fit.
133    #[error("{0}")]
134    LimitExceeded(crate::limits::Verdict),
135    /// A symmetry search or space-group lookup failed.
136    #[error("symmetry: {0}")]
137    Symmetry(String),
138}
139
140impl Error {
141    /// Stable `snake_case` code for this error, suitable for bindings and JSON.
142    ///
143    /// The string is part of the public API (see `AGENTS.md`, section 5.2).
144    #[must_use]
145    pub fn code(&self) -> &'static str {
146        match self {
147            Self::InvalidInput(_) => "invalid_input",
148            Self::DataCompression { .. } => "data_compression",
149            Self::DataChecksum { .. } => "data_checksum",
150            Self::DataDigest { .. } => "data_digest",
151            Self::DataJson { .. } => "data_json",
152            Self::LimitExceeded(_) => "limit_exceeded",
153            Self::Symmetry(_) => "symmetry",
154        }
155    }
156
157    /// Human-readable message for this error.
158    #[must_use]
159    pub fn message(&self) -> String {
160        self.to_string()
161    }
162
163    /// Stable machine-readable category for the JSON error object.
164    ///
165    /// Categories are `input`, `unsupported`, `numerical`, `limit`, `engine`
166    /// and `internal`.
167    #[must_use]
168    pub fn category(&self) -> &'static str {
169        match self {
170            Self::InvalidInput(_) => "input",
171            Self::DataCompression { .. }
172            | Self::DataChecksum { .. }
173            | Self::DataDigest { .. }
174            | Self::DataJson { .. } => "internal",
175            Self::LimitExceeded(_) => "limit",
176            Self::Symmetry(_) => "numerical",
177        }
178    }
179}