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, line-2 units (Å / ps from chemfiles), and optional bonds / residues.

  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 to chemfiles to ConFrame (optional bonds) to .con. chemparseplot and rgpycrumbs then plot that CON file.

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`.