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-tangledocs/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.