Getting started

Passing a path creates a SQLite ledger when needed.

import numpy as np
from ase import Atoms

from stoms.atomsledger import (
    AtomsLedger,
    MethodSpec,
    Provenance,
    StructureSourceSpec,
)

ledger = AtomsLedger("study.atomsledger")
atoms = Atoms("H2", positions=[(0, 0, 0), (0, 0, 0.74)])

structure_id = ledger.add_structure(
    atoms,
    StructureSourceSpec(kind="direct", name="manual-build"),
    Provenance(
        created_by="researcher",
        purpose="reference structure",
        tags=("study:h2", "split:train"),
    ),
)

evaluation_id = ledger.add_evaluation(
    structure_id,
    method=MethodSpec(
        method_identifier="vasp-r2scan",
        family="DFT",
        name="VASP-r2SCAN",
        version="6.5.1",
        code="VASP",
    ),
    properties={"energy": -1.0, "forces": np.zeros((2, 3))},
    provenance=Provenance(
        created_by="researcher",
        purpose="reference label",
        tags=("quality:reference",),
    ),
)

add_structure strips an attached ASE calculator. Exact repeats reuse the existing ID. Calculator-free content that exists with different immutable provenance or source specification is reported instead of inserted. Pass allow_equivalent_atoms=True only when a distinct record is intentional.

Query and export

from stoms.atomsledger import DatasetQuery, StructureQuery

selection = ledger.select(
    DatasetQuery(structures=StructureQuery(tags_all=("split:train",))),
    evaluator="VASP-r2SCAN",
)
energy = selection.project_energy()
frames_written = ledger.to_extxyz("h2-training.extxyz", selection=selection)

Strict selection requires exactly one matching evaluation for every structure; otherwise it raises StrictSelectionError. Numerical projections preserve aligned structure and evaluation IDs. extxyz writes REF_energy and REF_forces by default and retains STOMS traceability metadata.

Dynamic query, frozen membership

from stoms.atomsledger import DatasetQuery, EvaluationQuery, StructureQuery

query_id = ledger.create_stored_query(
    label="h2 training candidates",
    query=DatasetQuery(
        structures=StructureQuery(tags_all=("split:train",)),
        evaluations=EvaluationQuery(
            method_identifiers=("vasp-r2scan",),
            properties_all=("energy", "forces"),
        ),
        require_evaluation=True,
    ),
    provenance=Provenance(
        created_by="researcher", purpose="define training candidates"
    ),
)
aggregation = ledger.materialize_stored_query(
    stored_query_id=query_id,
    name="iteration-0-training",
    external_key="workflow:iteration:0:training",
)

Stored queries are immutable definitions whose membership may grow. An aggregation freezes ordered membership. Stored-query labels are nonunique; a string selector is the query_... ID, not a label.