Skip to content

Fix transposed Lattice when writing non-symmetric cells - #49

Merged
jameskermode merged 2 commits into
masterfrom
fix/cell-convention
Oct 2, 2026
Merged

jameskermode merged 2 commits into
masterfrom
fix/cell-convention

Conversation

@jameskermode

Copy link
Copy Markdown
Member

Frame.cell stores the lattice vectors as columns (cell[:, i] is a_i). Both readers already return that, for both Lattice syntaxes:

  • old-style Lattice="a1 a2 a3": the nine numbers list a1, a2, a3 in turn;
  • nested Lattice=[[...], [...], [...]]: the matrix as written, which test_new_non_symm_Lattice pins.

Both writers transposed the cell once more before writing. So a read → write → read round trip transposed every non-symmetric cell. Every existing round-trip test used a diagonal or symmetric cell, where a transpose doesn't show.

Fix (Python only; the C code is unchanged):

  • C writer (_write_frame_cextxyz): pass frame.cell. Its old-style output is column-major.
  • Python writer (_write_frame_pure_python): write Lattice in the old 9-number form (a1 a2 a3, repr precision), as the C writer and ASE do. ASE's own extxyz reader rejects the nested form this writer used to produce.
  • ase-extxyz:
    • _atoms_to_frame stores atoms.cell.T, converting ASE's row vectors to columns.
    • ExtXYZTrajectoryWriter no longer transposes. Until now these two errors cancelled each other, but only for the C trajectory writer.
  • Frame docstring: now states the convention. The old text called ASE's convention "column-vector".

Tests:

  • tests/test_cell_convention.py: a triclinic cell through all three parsers (C with cleri, C first-char dispatch, Python), in both Lattice syntaxes, through both writers, plus the full write/read round-trip matrix.
  • python/ase-extxyz/tests/test_io.py:
    • triclinic round trips across both writers and both readers;
    • the trajectory writer;
    • a check of each written file with ASE's own extxyz reader, an independent parser.
  • Locally:
    • core: 107 passed, 2 skipped;
    • ase-extxyz test_io, test_ase_cases, test_kv_parsing_key and the old-style-array tests: 28 passed. The full kv-parsing suite is slow.

One mismatch for the maintainer to settle: the README spec section says a 3x3 Lattice has "rows are cell vectors". That contradicts test_new_non_symm_Lattice and the readers. This PR follows the readers and the test, and leaves the README unchanged.

Found via ace-jax, whose extxyz round trip transposed triclinic cells (ACEsuit/ace-jax#36).

🤖 Generated with Claude Code

jameskermode and others added 2 commits October 2, 2026 08:44
Frame.cell holds the cell vectors as columns, which is what both readers
return for both Lattice syntaxes (old-style 9 numbers listed a1 a2 a3, and
the nested 3x3 form as the matrix itself; test_new_non_symm_Lattice pins
the latter). Both writers transposed it once more before serialising, so a
read -> write -> read round trip transposed every non-symmetric cell:

- C writer: pass frame.cell (its old-style output is column-major).
- Python writer: write Lattice old-style (a1 a2 a3, full precision), like
  the C writer and ASE; ASE's own reader rejects the nested form.
- ase-extxyz: _atoms_to_frame stores atoms.cell.T (ASE rows -> columns), and
  ExtXYZTrajectoryWriter no longer transposes (the two errors cancelled for
  the C trajectory writer only).

Every existing round-trip test used a diagonal or otherwise symmetric cell.
tests/test_cell_convention.py and the new ase-extxyz tests use a triclinic
cell across both readers (cleri and first-char dispatch C parsers, Python)
and both writers, and check the written file with ASE's independent reader.
Frame's docstring now states the convention.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…irs with)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@jameskermode
jameskermode merged commit ebfe7b3 into master Oct 2, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant