Skip to content

Core API behavior and implementation notes

This page complements the generated API reference with behavior that is easy to miss in signatures. Every public class and method in core.py, util.py, calc.py, and elements.py is included below. The walkthroughs are molecules and geometry, I/O, and ensembles.

Units, indexing, and mutable data

  • Coordinates have no attached unit. The examples and default bond cutoffs assume angstroms; distance and RMSD results retain the input units.
  • Legacy internal-coordinate angles use degrees. Space.diff assumes hartree input and always returns kcal/mol using 627.5095.
  • Use zero-based positions, with Atom.id == position, in legacy connectivity and distance/angle editing code. Dihedral measurements/edits and newer glycan and conformer APIs use atom-list positions independently of Atom.id.
  • State and Data accept arbitrary keyword attributes. They do not validate or infer all required metadata.
  • Atoms, fragment memberships, and state collections are generally shared by reference. Use copy.deepcopy when an independent geometry is required.

Atom and Fragment

Public member Behavior
Atom(coord=None, type=None, valence=None, index=None) Converts coord to a NumPy array; stores type and id=index; initializes mass=None and atoms=None. valence is accepted but unused. Provide a three-component coordinate: the None default is not usable because initialization indexes the resulting scalar array.
Atom.coord, .type, .id, .mass Public mutable attributes. Mass is not looked up automatically.
Atom.x, .y, .z Initial coordinate snapshots, not synchronized properties.
Fragment() Starts with an empty atoms list; additional metadata may be attached by split/glycan routines.
Fragment.xyz New array assembled from member atom coordinates.
Fragment.Mass New array assembled from member masses; may contain None until supplied.

State

Public member Behavior / prerequisites
State(**kwargs) Stores exactly the supplied attributes. A geometry normally needs atoms, NAtoms, and id.
from_file(path, software) Builds through StateBuilder, then sets ID from the containing folder or filename. Gaussian is the documented complete legacy route; use Space.from_xyz for XYZ.
xyz, Mass, mass Coordinate array, mass array, and sum of masses. Arrays are newly assembled. mass requires usable mass values.
element_symbol(A) Returns a symbol for atomic number 1–118, accepting an integer or matching numeric string; unknown keys raise KeyError.
add_atoms(atom), remove_atoms(atom) Append/remove a single Atom object; do not update NAtoms, IDs, bonds, or fragments. Return None.
measure_internal(atom_list) Dispatches to StateEditor.measure. Distances return floats, angles return (degrees, axis), dihedrals return degrees.
set_internal(atom_list, val) Dispatches a mutation to the corresponding editor method for 2/3/4 selections. Unsupported lengths do nothing. Return is None.
connectivity_matrix(distXX=1.65, distXH=1.15) Infers a symmetric distance-threshold matrix and sets graph and Nmols. Requires consistent NAtoms, atom IDs and positions. Returns None; no bond orders/PBC. Hydrogen ambiguity branch is currently broken.
system_split() Reads graph, stores connected components as mols (Fragments sharing atoms). Returns None.
show(width=600, height=600) Displays a Py3Dmol stick view in a compatible notebook environment; requires id and NAtoms, rounds displayed coordinates to three decimals, returns None.

StateEditor

StateEditor(state) retains the supplied state; edits mutate it. Static measurement methods accept all Atom objects, or all Python integer positions with state=...; get_dihedral additionally accepts NumPy integer positions. Avoid collinear triples, coincident atoms, and mixed selections.

Public method Return / mutation
get_distance(atom_list, state=None) Euclidean distance, expected length 2.
get_angle(atom_list, state=None) (degrees, cross_product_axis), expected length 3; axis is unnormalized.
get_dihedral(atom_list, state=None, ax=False) Signed degrees in [-180, 180], using the shared, translation-invariant atan2 convention. Requires exactly four finite three-component coordinates; negative/out-of-range positions, coincident central atoms, and collinear triples raise ValueError. With ax=True, returns (degrees, cross_product_axis); the compatibility axis is unnormalized and can be zero for planar geometry.
measure(atom_list, state=None) Dispatches by selection length 2/3/4; other lengths return None.
adjacent_atoms(conn_mat, at) NumPy array of nonzero neighbor positions in row at.
connect_atoms(state, at1, at2) Sets both matrix entries to one; does not update the graph.
disconnect_atoms(state, at1, at2) Sets both matrix entries to zero; does not update the graph.
determine_carried_atoms(state, atom_list) Temporarily removes a bond and returns the Atom objects in the second atom's connected component, normally restoring the bond. If the matrix stays connected, returns None after printing and currently leaves that bond removed.
set_distance(atom_list, new_distance) Requires two Python integer positions; translates the carried component. New distance uses input units. Returns None.
set_angle(atom_list, new_ang) Requires three Python integer positions; rotates the third atom's component around the middle atom, in degrees. Returns None.
set_dihedral(atom_list, new_dih, incr=False, axis_pos="bond", threshold=0.1) Requires four distinct Python or NumPy integer positions within the state. Rotation requires a finite symmetric conn_mat indexed by atom position and containing the selected bond. With incr=True, adds the requested angle. "bond" rotates the third atom's component about the central bond, preserving connectivity and rejecting cyclic bonds. "term" uses a parallel axis through the last atom and rotates atoms beyond it, leaving the selected quartet unchanged. Targets/increments wrap modulo 360°; finite targets and a finite, nonnegative tolerance are required. Returns None.

The setters only manipulate geometry; there is no relaxation, collision checking, ring-closure constraint, or automatic topology update. set_dihedral uses the central-bond direction for a stable rotation axis, supports exact 0° and ±180° targets, and rejects unsupported axis_pos values. Its threshold only skips changes at or below the angular tolerance; it does not alter the target to avoid planar geometry. The distance/angle setters and direct determine_carried_atoms calls retain the legacy connectivity limitations listed above. Keep a copy and remeasure outputs.

Dihedral compatibility: the sign of get_dihedral and its measure/measure_internal wrappers has been corrected to match the existing glycan torsion convention. The legacy translation-dependent sign error and small offsets at 0°/±180° are also fixed. Saved legacy results should be recomputed from coordinates, and absolute/incremental targets for set_dihedral now follow the corrected sign. Glycan torsions and ring calculations retain their existing convention. See the formula and worked example.

Data

Public member Behavior
Data(**kwargs) Attribute container, typically with a NumPy data array.
repr(data) Returns Data({...}) with stored attributes.
Data.from_file(path, software, **kwargs) Uses DataBuilder; accepts "cp2k", "gromacs", or "csv". Metadata and supported options are forwarded to the builder.

Space

Public member Behavior
Space(states=None, theory=None, input_path=None, output_path=None) Holds a list of states and calculation settings. Stores a provided list by reference; creates a fresh list if omitted.
from_xyz(path, *, glycans=False) Reads all XYZ frames into States or Glycans, assigns frame IDs and paths, rejects empty trajectories.
from_dir(path, software="gaussian") Reads <path>/<immediate_subdirectory>/input.log for each subdirectory. Only Gaussian is implemented. Order follows directory enumeration.
energies, free_energies, enthalpies Lists of E, F, H respectively, skipping states missing that attribute.
diff(states=None, quant="E") Relative-to-minimum kcal/mol values from hartree E/F/H; accepts a list of indices or State objects. Invalid quantity raises ValueError, missing attributes raise AttributeError. Empty selection falls back to all states.
sum(states, quant="E") Sum of original-unit E/F/H across State objects. Any integer states argument means all states. Invalid/missing quantities print an error and return None.
add(state) Appends one State or every State in a list; no copy or uniqueness check.
remove(state) Intended for object/index removal, but currently fails immediately with UnboundLocalError; use list methods on .states.
sort(val="E") In-place attribute sort; prints errors and returns None.
table(sort="E") In-place sort and printed tabulate table; fixed headings require consistent energy metadata. Returns None; requires separately installed tabulate.
rmsd_matrix(**kwargs) Delegates to conformers.rmsd_matrix; default aligned heavy-atom RMSDs.
cluster(threshold=1.0, **kwargs) Delegates to cluster_conformers; returns ClusterResult.
remove_duplicates(threshold=0.1, **kwargs) Complete-linkage representatives as a new Space; original collection retained, selected states shared. The linkage method is fixed internally.
to_file(software="gaussian") Passes stored theory/output path and self to InputBuilder; Gaussian works with required settings. XYZ requires the lower-level iterable path described below. Returns None.

Space is not itself iterable or subscriptable: use space.states. See ensemble walkthroughs and conformer analysis.

StateBuilder

StateBuilder(path, software) stores its inputs. Parser methods mutate the builder; build() constructs a new State from all builder attributes, computes legacy connectivity, and splits components.

Public method Behavior
element_symbol(A) Same 1–118 atomic-number mapping as State.element_symbol.
gaussian() Parses selected Gaussian route types, geometry, available energy/thermal fields, vibrational data, and lower-triangular NMR couplings. Mutates builder attributes and returns None. Format-sensitive; not every Gaussian output layout is supported.
cp2k_geom() Reads the last NAtoms coordinate lines from an XYZ trajectory. Atom IDs are currently unset because indices are passed positionally as valence. Prefer Space.from_xyz.
orca() Incomplete optimization/energy-only parser. An energy match may return the builder, but it does not supply the geometry required by build; the optimization branch has path and variable errors.
build() Dispatches "cp2k", "gaussian", "orca"; unknown software raises ValueError. Returns a State only if parsing, connectivity, and splitting succeed.

DataBuilder

DataBuilder(path, software, **kwargs) retains every keyword as an attribute. Methods populate .data and metadata on the builder; build() returns a Data with a shallow copy of those attributes.

Public method Behavior
csv(skiprows=0, dtype=float, delimiter=",") Calls np.loadtxt; attributes supplied to the builder override these method defaults/arguments. Returns None.
cp2k_md(colvar=None, timestep=None) For XYZ, computes a legacy coordinate or dispatches structured Cremer–Pople analysis. Explicit non-None method arguments override builder colvar/timestep. For .metadynLog, reads the numeric table unchanged. Sets time_unit="fs". Returns None.
gromacs() Parses a limited XVG numeric table/header or a square uppercase-key XPM matrix. Values are not converted. Returns None.
build() Dispatches "cp2k", "gromacs", "csv"; unknown software raises ValueError. Returns Data.

CP2K coordinate extraction requires colvar and timestep, except .metadynLog table loading. Two/four atom selections produce distance/dihedral data; three-atom tuples currently fail numeric array assembly. XYZ selections follow file order, not arbitrary colvar order. The observable="cremer_pople" route requires timestep, accepts rings, degrees, conn_mat, distXX, distXH, and classify, and uses the validated trajectory implementation. Other CP2K/GROMACS suffixes may yield a container with no data attribute because those parser branches do not reject unknown file formats.

InputBuilder

InputBuilder(path=None, theory=None, software="gaussian", space=None) only stores settings. build(space, theory, software, path, names=None) sets all these again plus names, then invokes a writer. For predictable behavior call build; gaussian() reads self.names, which the constructor does not initialize.

Public method Behavior
element_number(A) Case-sensitive element symbol to integer atomic number, 1–118; unknown symbols raise KeyError.
gaussian() Expects space.states. Creates <path>/<id>/input.com, rounds coordinates to three decimals, mutates the dispersion setting, and overwrites existing files. Non-None names currently raises an error; set state IDs directly.
xyz() Expects an iterable of states, such as space.states. Creates <path>/<id>/geometry.xyz with three-decimal coordinates; no trajectory concatenation.
fhiaims() Legacy incomplete writer with a site-specific basis directory, absolute geometry output path, and Atom/string formatting error. Do not rely on it to produce valid FHI-aims inputs.
build(space, theory, software, path, names=None) Dispatches "gaussian", "xyz", "fhiaims"; unknown values silently do nothing. Returns None.

The output parent directory must already exist. Use unique directory-safe state.id values and review generated input before submitting it to external software. Required Gaussian theory keys are disp, nprocs, mem, method, basis_set, jobtype, charge, and multiplicity; optional keys are other_options and extra. See the complete input generation example.

Element mappings

matmalib.elements contains the standard symbols for elements 1 through 118. ELEMENT_SYMBOLS has string atomic-number keys; ELEMENT_NUMBERS has case-sensitive symbol keys and integer values. These are ordinary dictionaries, not a periodic-table database: masses, isotopes, radii, and valences are not supplied.

from matmalib import State, StateBuilder
from matmalib.calc import InputBuilder
from matmalib.elements import ELEMENT_SYMBOLS, ELEMENT_NUMBERS

print("Elements:", len(ELEMENT_SYMBOLS))
for number in (1, 6, 8, 26, 79, 118):
    symbol = ELEMENT_SYMBOLS[str(number)]
    print(number, symbol, ELEMENT_NUMBERS[symbol])

print(State.element_symbol(26))
print(StateBuilder.element_symbol("79"))
print(InputBuilder.element_number("Og"))

The wrappers convert numeric inputs to strings before lookup, so State.element_symbol(6) and State.element_symbol("6") both return "C". 6.0, zero, missing elements, and wrong symbol case are not normalized and raise KeyError. Try changing: look up another integer from 1 through 118, then compare direct dictionary lookup with the wrappers.