Convert other formats into CON

Note

Executable Org Babel source (Python only) is Executable Chemfiles notebook. CI runs scripts/run-chemfiles-notebook.sh (tangle + drift check + python3 docs/notebooks/chemfiles_ingress.py). Rust and other language snippets on this page are prose only — they are not Babel-run in CI.

Do not hand-edit the tangled .py; update the notebook Org source and re-tangle with READCON_TANGLE_UPDATE=1.

Diátaxis companions: How-to — Chemfiles conversion and selection (tasks), Explanation — Chemfiles ingress and CON topology (why), Reference — Chemfiles conversion and selection (API tables).

        flowchart LR
  subgraph Foreign["Foreign trajectories"]
    XYZ[XYZ]
    PDB[PDB]
    GRO[GRO]
    OTH[…]
  end
  CF[chemfiles reader]
  RC[readcon ConFrame]
  CON[".con / .convel"]
  SEL[selection grammar]
  XYZ --> CF
  PDB --> CF
  GRO --> CF
  OTH --> CF
  CF -->|read_chemfiles*| RC
  RC --> CON
  RC --> SEL
  SEL -->|atom_data indices| RC
    

This tutorial is learning-oriented: one successful path from a non-CON file into CON. Prefer pip install readcon-chemfiles (or Rust --features chemfiles). For native CON files without conversion, start with Tutorial — your first CON checkpoint instead.

What you will build

  1. Install a chemfiles-linked build.

  2. Drive conversion from another format (we use XYZ; the same APIs accept PDB, GRO, LAMMPS dump, and other chemfiles formats).

  3. Inspect geometry and optional bonds on the resulting CON frame.

  4. Run a selection (name O, angles: all) in CON atom_data order.

  5. Write a .con (and optionally multi-frame) file for eOn / amsel / CON consumers.

You do not need a pre-existing .con file. The point is ingress from the wider ecosystem into CON.

Foreign trajectory → chemfiles → ConFrame (optional bonds) → .con.

After ingress, geometries are first-class for chemparseplot / rgpycrumbs /

Choose one install path

Pick one environment. Do not install both readcon and readcon-chemfiles in the same venv (both provide import readcon).

Path B — Rust with chemfiles

cargo build --features chemfiles
cargo test --features chemfiles --lib chemfiles

Path C — Editable Python from this repo

maturin develop --features python,chemfiles
python -c "import readcon; assert readcon.has_chemfiles_support()"

A small XYZ to convert

Create water.xyz:

3
water demo for readcon-core chemfiles tutorial
O  0.000  0.000  0.000
H  0.957  0.000  0.000
H -0.240  0.927  0.000

Any chemfiles-readable file works the same way (structure.pdb, conf.gro, …).

Convert XYZ → CON (Rust)

use readcon_core::chemfiles_import::{
    chemfiles_enabled, con_frame_from_trajectory_path, con_frames_from_trajectory_path,
};
use readcon_core::writer::ConFrameWriter;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    assert!(chemfiles_enabled(), "rebuild with --features chemfiles");

    let frame = con_frame_from_trajectory_path("water.xyz")?;
    println!(
        "atoms={} has_bonds={} bonds={}",
        frame.atom_data.len(),
        frame.has_bonds(),
        frame.bonds().len()
    );

    let mut w = ConFrameWriter::from_path("water_from_xyz.con")?;
    w.write_frame(&frame)?;

    let all = con_frames_from_trajectory_path("water.xyz")?;
    println!("trajectory frames: {}", all.len());
    Ok(())
}

Build with --features chemfiles. Line 2 of the CON file is JSON (con_spec_version 2). Topology, if present in the source format, becomes metadata["bonds"] (indices in atom_data order; see :doc:`chemfiles-explain`).

Convert XYZ → CON (Python)

With readcon-chemfiles, Python mirrors the Rust ingress APIs:

import readcon

assert readcon.has_chemfiles_support(), "pip install readcon-chemfiles"

frame = readcon.read_chemfiles_first("water.xyz")
# or: frames = readcon.read_chemfiles("water.xyz")

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} ({a.x:.3f},{a.y:.3f},{a.z:.3f})")

frame.write_con("water_from_xyz.con")

data = open("water.xyz", encoding="utf-8").read()
mem_frames = readcon.read_chemfiles_memory(data, "XYZ")
assert len(mem_frames) == 1

print("oxygens", frame.select_atoms("name O"))

Plain XYZ usually has no bonds. Geometry still converts; use PDB (or the next section) for angles: / bonds:.

Drive topology

Prefer a bonded source format

let frame = con_frame_from_trajectory_path("ligand.pdb")?;
// has_bonds() often true → angles: / is_bonded work after projection

Or attach bonds after ingress (indices = atom_data order)

use readcon_core::chemfiles_import::con_frame_from_trajectory_path;
use readcon_core::chemfiles_selection::evaluate_selection_on_con_frame;
use readcon_core::types::{Bond, ConFrameBuilder};
use readcon_core::writer::ConFrameWriter;

let imported = con_frame_from_trajectory_path("water.xyz")?;
let mut b = ConFrameBuilder::new(imported.header.boxl, imported.header.angles);
for a in &imported.atom_data {
    b.add_atom(
        a.symbol.as_ref(),
        a.x, a.y, a.z,
        [a.fixed_x, a.fixed_y, a.fixed_z],
        a.atom_id,
        a.mass,
    );
}
b.set_bonds(&[Bond::new(0, 1), Bond::new(0, 2)]);
let frame = b.build();

let angles = evaluate_selection_on_con_frame("angles: all", &frame)?;
assert_eq!(angles.context_size, 3);

let mut w = ConFrameWriter::from_path("water_with_bonds.con")?;
w.write_frame(&frame)?;

Python selection on bonded CON

import readcon

frame = readcon.read_first_frame("water_with_bonds.con")
assert frame.has_bonds
print(frame.select_atoms("type H"))
print(frame.select("bonds: all")["matches"])
print(frame.select("angles: all")["matches"])
frame.write_con("water_selected_roundtrip.con")

Multi-format conversion habit

Treat chemfiles as the format router; CON as the on-disk checkpoint store.

use readcon_core::chemfiles_import::con_frames_from_trajectory_path;
use readcon_core::writer::ConFrameWriter;

for path in ["conf.gro", "system.pdb", "dump.lammpstrj", "traj.xyz"] {
    if !std::path::Path::new(path).exists() {
        continue;
    }
    let frames = con_frames_from_trajectory_path(path)?;
    let out = format!("{path}.converted.con");
    let mut w = ConFrameWriter::from_path(&out)?;
    for f in &frames {
        w.write_frame(f)?;
    }
}

In-memory:

use readcon_core::chemfiles_import::con_frames_from_memory;
let xyz = std::fs::read_to_string("water.xyz")?;
let frames = con_frames_from_memory(&xyz, "XYZ")?;

Checkpoint

You succeeded if:

  • has_chemfiles_support() / chemfiles_enabled() is true for your build.

  • You produced a .con from a non-CON file.

  • You know XYZ may lack bonds; PDB or set_bonds enables topology selectors.

  • You ran at least one selection and saw CON atom_data indices.

Next: :doc:`chemfiles-howto`, :doc:`chemfiles-explain`, :doc:`chemfiles-reference`.