Executable Chemfiles notebook¶
Note
This page is the Org-mode Babel source for the chemfiles conversion
path. scripts/run-chemfiles-notebook.sh runs
org-babel-tangle → docs/notebooks/chemfiles_ingress.py, fails on
tangle drift, then python3 that file (not a silent Babel-session
fallback). CI (ci_python.yml chemfiles matrix) runs that script after
maturin develop --features python,chemfiles. Learning narrative
(including Rust prose): Convert other formats into CON — only this notebook’s
Python blocks are CI-executed.
Literate, plain-text notebook (not a committed .ipynb). Requires
readcon-chemfiles or maturin develop --features python,chemfiles.
Parameters¶
from __future__ import annotations
import os
from pathlib import Path
work_dir = Path(os.environ.get("READCON_NB_WORK", "docs/notebooks/out/work"))
work_dir.mkdir(parents=True, exist_ok=True)
xyz_path = work_dir / "water.xyz"
con_path = work_dir / "water_from_xyz.con"
bonded_path = work_dir / "water_with_bonds.con"
require_chemfiles = True
print("work_dir =", work_dir.resolve())
Setup¶
import json
import sys
import readcon
print("readcon", getattr(readcon, "__version__", "?"))
ok = readcon.has_chemfiles_support()
print("has_chemfiles_support =", ok)
if require_chemfiles and not ok:
raise SystemExit(
"chemfiles not linked: pip install readcon-chemfiles "
"or maturin develop --features python,chemfiles"
)
Convert XYZ to CON¶
xyz_path.write_text(
"3\n"
"water demo — org-mode executable tutorial\n"
"O 0.000 0.000 0.000\n"
"H 0.957 0.000 0.000\n"
"H -0.240 0.927 0.000\n",
encoding="utf-8",
)
print("wrote", xyz_path.resolve())
frame = readcon.read_chemfiles_first(str(xyz_path))
print("atoms", len(frame.atoms), "has_bonds", frame.has_bonds)
for i, a in enumerate(frame.atoms):
print(
f" [{i}] {a.symbol} id={a.atom_id} "
f"({a.x:.3f}, {a.y:.3f}, {a.z:.3f})"
)
frame.write_con(str(con_path))
assert con_path.is_file()
print("wrote", con_path.resolve())
mem = readcon.read_chemfiles_memory(xyz_path.read_text(encoding="utf-8"), "XYZ")
assert len(mem) == 1 and len(mem[0].atoms) == 3
all_frames = readcon.read_chemfiles(str(xyz_path))
assert len(all_frames) == 1
print("read_chemfiles frames =", len(all_frames))
Topology and selection¶
bonded = readcon.ConFrame(
cell=list(frame.cell),
angles=list(frame.angles),
atoms=list(frame.atoms),
metadata={"con_spec_version": 2, "bonds": [[0, 1], [0, 2]]},
)
assert bonded.has_bonds
oxy = bonded.select_atoms("name O")
assert oxy == [0], oxy
angles = bonded.select("angles: all")
assert angles["context_size"] == 3
assert len(angles["matches"]) >= 1
print("oxygens", oxy)
print("angles", angles["matches"])
bonded.write_con(str(bonded_path))
print("wrote", bonded_path.resolve())
Multi-format habit¶
candidates = [xyz_path]
converted = []
for path in candidates:
path = Path(path)
if not path.is_file():
continue
frames = readcon.read_chemfiles(str(path))
out = work_dir / f"{path.stem}.converted.con"
readcon.write_con(str(out), frames)
converted.append((str(path), str(out), len(frames)))
print(json.dumps({"converted": converted}, indent=2))
Checkpoint¶
summary = {
"has_chemfiles_support": ok,
"xyz": str(xyz_path),
"con": str(con_path),
"bonded_con": str(bonded_path),
"n_atoms": len(frame.atoms),
"n_angle_matches": len(angles["matches"]),
}
(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 chemfiles ingress finished", file=sys.stderr)
Run from the shell¶
scripts/run-chemfiles-notebook.sh
Emacs: C-c C-c on blocks is fine interactively. CI and
scripts/run-chemfiles-notebook.sh re-tangle, refuse drift, and run
docs/notebooks/chemfiles_ingress.py only — do not hand-edit the tangled .py.