Tutorial — your first CON checkpoint¶
Note
Diátaxis tutorial (learning-oriented). Source of truth is this Org file.
CI (.github/workflows/ci_python.yml, lean + chemfiles matrices) runs
scripts/run-tutorial-core.sh: Emacs org-babel-tangle into
docs/notebooks/tutorial_core.py, fails if the committed tangle drifts,
then runs that file with python3 against resources/test fixtures.
Do not hand-edit the tangled .py; re-tangle with
READCON_TANGLE_UPDATE=1 scripts/run-tutorial-core.sh and commit.
Multi-language recipes: How-to — CON I/O by language. Format conversion: Convert other formats into CON. API tables: Language bindings. Format rules: The CON File Format Specification.
In this tutorial we open a real CON trajectory from the repository fixtures,
inspect cell and atom_id data, write a round-trip file, then build a small
checkpoint with total energy.
We use Python and the package readcon only (no chemfiles, no ASE).
Paths are relative to the repository root. Locally you may C-c C-c blocks in
Emacs; **CI does not use session execute as a second path** — it only runs the
tangled .py after a drift check so session vs tangle cannot disagree silently.
What we will do¶
Import
readconand locate fixtures underresources/test/.Iterate every frame of
tiny_multi_cuh2.con.Inspect
atom_idand fixed flags on the first frame.Write a round-trip and confirm the frame count.
Build a two-atom frame with total energy and write it.
Parameters¶
from __future__ import annotations
import os
import sys
from pathlib import Path
# Repo root: CI and local runs cwd to repository root.
REPO = Path(os.environ.get("READCON_TUT_ROOT", Path.cwd())).resolve()
FIXTURES = REPO / "resources" / "test"
MULTI = FIXTURES / "tiny_multi_cuh2.con"
FORCES = FIXTURES / "tiny_cuh2_forces.con"
work_dir = Path(os.environ.get("READCON_TUT_WORK", REPO / "docs" / "notebooks" / "out" / "tutorial_core"))
work_dir.mkdir(parents=True, exist_ok=True)
roundtrip_path = work_dir / "tutorial_roundtrip.con"
built_path = work_dir / "tutorial_built.con"
print("REPO =", REPO)
print("work_dir =", work_dir.resolve())
Setup¶
import readcon
print("readcon", getattr(readcon, "__version__", "?"))
print("CON_SPEC_VERSION", readcon.CON_SPEC_VERSION)
assert MULTI.is_file(), f"missing fixture {MULTI}"
assert FORCES.is_file(), f"missing fixture {FORCES}"
Step 1 — open a multi-frame CON¶
The fixture resources/test/tiny_multi_cuh2.con holds two small Cu/H frames.
n = 0
for frame in readcon.iter_con(str(MULTI)):
n += 1
print(n, list(frame.cell), len(frame))
print("frames:", n)
assert n == 2, n
You should see two frames with four atoms each and cell roughly
[15.3456, 21.702, 100.0].
Step 2 — inspect identity columns¶
frame = readcon.read_first_frame(str(MULTI))
print("spec_version:", frame.spec_version)
# energy is a property (None when JSON omits it), not a method
print("energy:", frame.energy)
for atom in list(frame.atoms)[:2]:
print(
atom.symbol,
round(atom.x, 4),
round(atom.y, 4),
round(atom.z, 4),
"fixed=",
atom.is_fixed,
"atom_id=",
atom.atom_id,
)
assert frame.spec_version >= 2
assert frame.energy is None
assert list(frame.atoms)[0].symbol == "Cu"
assert list(frame.atoms)[0].atom_id == 0
assert list(frame.atoms)[0].is_fixed is True
Look at atom_id: column 5 of the coordinate block, the pre-grouping index
used by NEB and dimer tools. Fixed flags come from column 4.
Step 3 — write a round-trip¶
frames = readcon.read_con(str(MULTI))
readcon.write_con(str(roundtrip_path), frames)
again = list(readcon.iter_con(str(roundtrip_path)))
print("wrote", len(frames), "frames; reread", len(again))
assert len(again) == len(frames) == 2
assert len(again[0]) == 4
Open the written file under docs/notebooks/out/tutorial_core/ if you like:
line 2 is JSON metadata.
Step 4 — build a checkpoint with energy¶
Read a forces-bearing fixture, then build a tiny frame of your own:
ref = readcon.read_first_frame(str(FORCES))
print("has_forces:", ref.has_forces, "energy:", ref.energy)
assert ref.has_forces is True
assert ref.energy == -42.5
atoms = [
readcon.Atom("Cu", 0.0, 0.0, 0.0, atom_id=0, mass=63.546),
readcon.Atom("H", 1.5, 0.0, 0.0, atom_id=1, mass=1.008),
]
frame = readcon.ConFrame(
cell=[10.0, 10.0, 10.0],
angles=[90.0, 90.0, 90.0],
atoms=atoms,
)
frame.set_energy(-1.25)
frame.write_con(str(built_path))
check = readcon.read_first_frame(str(built_path))
print("built atoms:", len(check), "energy:", check.energy)
assert len(check) == 2
assert check.energy == -1.25
Checkpoint¶
import json
summary = {
"multi_frames": 2,
"roundtrip": str(roundtrip_path.resolve()),
"built": str(built_path.resolve()),
"built_energy": check.energy,
"has_forces_fixture": bool(ref.has_forces),
}
(work_dir / "summary.json").write_text(
json.dumps(summary, indent=2) + "\n", encoding="utf-8"
)
print(json.dumps(summary, indent=2))
print("OK — org-mode CON checkpoint tutorial finished", file=sys.stderr)
You now:
install and import
readconiterate and materialize multi-frame CON
read
atom_idand fixed flagswrite round-trips
construct a frame with metadata energy
Run from the shell¶
# after: maturin develop --features python (or pip install readcon)
scripts/run-tutorial-core.sh
Emacs: open this file and C-c C-c each Python block for interactive use.
CI and scripts/run-tutorial-core.sh re-tangle, refuse drift, and run
docs/notebooks/tutorial_core.py only — do not hand-edit the tangled .py.