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 data;
10pub mod describe;
11pub mod elements;
12pub mod groups;
13pub mod lattice;
14pub mod limits;
15pub mod measure;
16pub mod neighbors;
17pub mod p1;
18pub mod param_presets;
19pub mod params;
20pub mod rings;
21pub mod scalars;
22pub mod selection;
23pub mod site;
24pub mod slab;
25mod spatial;
26pub mod structure;
27pub mod supercell;
28pub mod symop;
29pub mod transforms;
30
31pub use bonding::{Bond, BondGraph, BondRules, ExplicitBond, TopologyBond, perceive_bonds};
32pub use cell::cell_volume;
33pub use describe::describe_atom;
34pub use elements::{Element, Isotope, element_info};
35pub use groups::{GroupKind, GroupMatch, find_groups, group_counts};
36pub use lattice::{Lattice, lattice_info, lattice_minimum_image};
37pub use limits::{Limits, Profile, Verdict, limits_bounds, limits_check};
38pub use measure::{angle, centroid, distance, interplanar_angle, torsion};
39pub use neighbors::{Neighbor, neighbors, neighbors_of};
40pub use p1::{P1Atom, P1Cell};
41pub use param_presets::{PRESET_NAMES, preset, presets};
42pub use params::{ParamSet, ParamSpec, ParamType};
43pub use rings::{Ring, is_aromatic, ring_counts, ring_membership, rings};
44pub use scalars::{atom_charges, atom_masses, bond_lengths, coordination_numbers};
45pub use selection::{AtomId, Selection, TrackingTable};
46pub use site::Site;
47pub use slab::{Slab, make_slab, plane_angle, surface_basis};
48pub use structure::{
49    SCHEMA_VERSION, Structure, StructureBuilder, StructureDocument, StructureSummary,
50    structure_info,
51};
52pub use symop::SymOp;
53pub use transforms::{Ellipsoid, Plane, displacement_ellipsoid};
54
55/// Version of the workspace, from the package metadata.
56///
57/// The value is the same on every surface: it comes from the workspace
58/// `Cargo.toml` and is mirrored by the Python and WASM packages at release.
59#[must_use]
60pub fn version() -> &'static str {
61    env!("CARGO_PKG_VERSION")
62}
63
64/// Errors returned by core operations.
65#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
66pub enum Error {
67    /// An argument does not satisfy the documented preconditions.
68    #[error("invalid input: {0}")]
69    InvalidInput(String),
70    /// A compressed data bundle could not be decompressed.
71    #[error("bundle {name}: compressed data is invalid: {reason}")]
72    DataCompression {
73        /// Name of the bundle as declared in its manifest.
74        name: String,
75        /// Message from the decompressor.
76        reason: String,
77    },
78    /// A data bundle failed its SHA-256 checksum.
79    #[error("bundle {name}: SHA-256 checksum mismatch")]
80    DataChecksum {
81        /// Name of the bundle as declared in its manifest.
82        name: String,
83    },
84    /// A data bundle declared a digest that is not 64 hexadecimal characters.
85    #[error("bundle {name}: invalid SHA-256 digest")]
86    DataDigest {
87        /// Name of the bundle as declared in its manifest.
88        name: String,
89    },
90    /// JSON decoding of a data bundle failed.
91    #[error("bundle {name}: JSON decoding failed: {reason}")]
92    DataJson {
93        /// Name of the bundle as declared in its manifest.
94        name: String,
95        /// Message from the JSON decoder.
96        reason: String,
97    },
98    /// A size limit would be exceeded; the verdict explains what would fit.
99    #[error("{0}")]
100    LimitExceeded(crate::limits::Verdict),
101    /// A symmetry search or space-group lookup failed.
102    #[error("symmetry: {0}")]
103    Symmetry(String),
104}
105
106impl Error {
107    /// Stable `snake_case` code for this error, suitable for bindings and JSON.
108    ///
109    /// The string is part of the public API (see `AGENTS.md`, section 5.2).
110    #[must_use]
111    pub fn code(&self) -> &'static str {
112        match self {
113            Self::InvalidInput(_) => "invalid_input",
114            Self::DataCompression { .. } => "data_compression",
115            Self::DataChecksum { .. } => "data_checksum",
116            Self::DataDigest { .. } => "data_digest",
117            Self::DataJson { .. } => "data_json",
118            Self::LimitExceeded(_) => "limit_exceeded",
119            Self::Symmetry(_) => "symmetry",
120        }
121    }
122
123    /// Human-readable message for this error.
124    #[must_use]
125    pub fn message(&self) -> String {
126        self.to_string()
127    }
128}