Skill Tracer
Skills › Scientific Agent Skills › pymatgen › io_formats.md

io_formats.md

344 lines · skills/pymatgen/references/io_formats.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# I/O: parsers, writers, VASP, Q-Chem, and trust boundaries
2
3This reference targets `pymatgen-core==2026.9.23`, which now owns core and
4electronic-structure-code I/O under the unchanged `pymatgen.io` namespace.
5

I/O: parsers, writers, VASP, Q-Chem, and trust boundaries

This reference targets pymatgen-core==2026.9.23, which now owns core and electronic-structure-code I/O under the unchanged pymatgen.io namespace.

Raw lines 1 to 5
6## I/O is a semantic conversion
7
8Parsing and writing are not neutral byte operations. Before any conversion,
9record:
11- object kind: periodic `Structure` or non-periodic `Molecule`
12- input and output formats, including format variants
13- lattice/PBC and coordinate mode
14- units
15- species order, labels, occupancies/disorder, and oxidation states
16- charge/spin for molecules
17- site properties such as selective dynamics, velocities, forces, and magmoms
18- parser warnings and any automatic corrections
20Never overwrite the input or an existing output. Write a new artifact,
21round-trip it, and compare the properties the workflow depends on.

I/O is a semantic conversion

Parsing and writing are not neutral byte operations. Before any conversion, record:

  • object kind: periodic Structure or non-periodic Molecule
  • input and output formats, including format variants
  • lattice/PBC and coordinate mode
  • units
  • species order, labels, occupancies/disorder, and oxidation states
  • charge/spin for molecules
  • site properties such as selective dynamics, velocities, forces, and magmoms
  • parser warnings and any automatic corrections

Never overwrite the input or an existing output. Write a new artifact, round-trip it, and compare the properties the workflow depends on.

Raw lines 6 to 22
23## Convenience interface
25```python
26from pymatgen.core import Molecule, Structure
28structure = Structure.from_file("input.cif", primitive=False, sort=False)
29cif_text = structure.to(fmt="cif")
30poscar_text = structure.to(fmt="poscar")
32molecule = Molecule.from_file("molecule.xyz")
33xyz_text = molecule.to(fmt="xyz")
34```
36Use explicit `fmt` when a filename or extension is ambiguous. Never assume
37automatic detection means the detected interpretation was scientifically
38correct.

Convenience interface

from pymatgen.core import Molecule, Structure

structure = Structure.from_file("input.cif", primitive=False, sort=False)
cif_text = structure.to(fmt="cif")
poscar_text = structure.to(fmt="poscar")

molecule = Molecule.from_file("molecule.xyz")
xyz_text = molecule.to(fmt="xyz")

Use explicit fmt when a filename or extension is ambiguous. Never assume automatic detection means the detected interpretation was scientifically correct.

Raw lines 23 to 39
40## CIF
42Use the current parser method and retain all warnings:
44```python
45import warnings
46from pymatgen.io.cif import CifParser
48with warnings.catch_warnings(record=True) as caught:
49 warnings.simplefilter("always")
50 parser = CifParser(
51 "input.cif",
52 occupancy_tolerance=1.0,
53 site_tolerance=1e-4,
54 frac_tolerance=1e-4,
55 check_cif=True,
56 comp_tol=0.01,
57 )
58 structures = parser.parse_structures(
59 primitive=False,
60 symmetrized=False,
61 check_occu=True,
62 on_error="raise",
63 )
65parser_warnings = list(parser.warnings)
66python_warnings = [str(item.message) for item in caught]
67```
69Important behavior:
71- A CIF can contain multiple data blocks/structures. Select an index explicitly.
72- The parser attempts to repair some out-of-spec content and reports changes.
73- Sites close within `site_tolerance` can be merged.
74- Occupancy slightly above 1 can be rescaled when it falls within
75 `occupancy_tolerance`; increasing that tolerance is a scientific decision,
76 not a generic repair.
77- `frac_tolerance` can round coordinates near common fractions.
78- `check_cif` compares parsed structure composition against CIF composition and
79 may warn about omissions such as difficult-to-locate hydrogens.
80- `parse_structures(primitive=False)` is the current explicit behavior. Do not
81 rely on historical defaults.
83Writing:
85```python
86from pymatgen.io.cif import CifWriter
88writer = CifWriter(
89 structure,
90 symprec=None,
91 significant_figures=8,
92 write_site_properties=False,
94cif_text = str(writer)
95```
97Setting `symprec` asks the writer to find symmetry and can refine to a
98conventional representation depending on `refine_struct`; that changes the
99representation. Report `symprec`, `angle_tolerance`, and `refine_struct`.

CIF

Use the current parser method and retain all warnings:

import warnings
from pymatgen.io.cif import CifParser

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    parser = CifParser(
        "input.cif",
        occupancy_tolerance=1.0,
        site_tolerance=1e-4,
        frac_tolerance=1e-4,
        check_cif=True,
        comp_tol=0.01,
    )
    structures = parser.parse_structures(
        primitive=False,
        symmetrized=False,
        check_occu=True,
        on_error="raise",
    )

parser_warnings = list(parser.warnings)
python_warnings = [str(item.message) for item in caught]

Important behavior:

  • A CIF can contain multiple data blocks/structures. Select an index explicitly.
  • The parser attempts to repair some out-of-spec content and reports changes.
  • Sites close within site_tolerance can be merged.
  • Occupancy slightly above 1 can be rescaled when it falls within occupancy_tolerance; increasing that tolerance is a scientific decision, not a generic repair.
  • frac_tolerance can round coordinates near common fractions.
  • check_cif compares parsed structure composition against CIF composition and may warn about omissions such as difficult-to-locate hydrogens.
  • parse_structures(primitive=False) is the current explicit behavior. Do not rely on historical defaults.

Writing:

from pymatgen.io.cif import CifWriter

writer = CifWriter(
    structure,
    symprec=None,
    significant_figures=8,
    write_site_properties=False,
)
cif_text = str(writer)

Setting symprec asks the writer to find symmetry and can refine to a conventional representation depending on refine_struct; that changes the representation. Report symprec, angle_tolerance, and refine_struct.

Raw lines 40 to 100
101### Untrusted CIFs
103A critical arbitrary-code-execution vulnerability in magnetic CIF
104transformation parsing affected pymatgen through 2024.2.8 and was fixed in
1052024.2.20. Use a current pinned release, but still parse attacker-controlled
106files only in a low-privilege isolated process with byte, CPU, memory, disk,
107site-count, and wall-time limits.

Untrusted CIFs

A critical arbitrary-code-execution vulnerability in magnetic CIF transformation parsing affected pymatgen through 2024.2.8 and was fixed in 2024.2.20. Use a current pinned release, but still parse attacker-controlled files only in a low-privilege isolated process with byte, CPU, memory, disk, site-count, and wall-time limits.

Raw lines 101 to 108
109## POSCAR/CONTCAR
111```python
112from pymatgen.io.vasp import Poscar
114poscar = Poscar.from_file(
115 "POSCAR",
116 check_for_potcar=False,
117 read_velocities=True,
119structure = poscar.structure
121direct_text = poscar.get_str(direct=True, significant_figures=16)
122cartesian_text = poscar.get_str(direct=False, significant_figures=16)
123```
125Record:
127- direct/fractional versus Cartesian coordinates
128- scale factor interpretation and units
129- element-name source, especially VASP 4 files
130- species order
131- selective-dynamics flags
132- velocities, predictor-corrector data, and lattice velocities if present
134POSCAR cannot faithfully represent partial occupancies. Oxidation states and
135arbitrary site properties generally do not round-trip. Never "fix" a POSCAR by
136searching nearby directories for POTCAR files without explicit approval.

POSCAR/CONTCAR

from pymatgen.io.vasp import Poscar

poscar = Poscar.from_file(
    "POSCAR",
    check_for_potcar=False,
    read_velocities=True,
)
structure = poscar.structure

direct_text = poscar.get_str(direct=True, significant_figures=16)
cartesian_text = poscar.get_str(direct=False, significant_figures=16)

Record:

  • direct/fractional versus Cartesian coordinates
  • scale factor interpretation and units
  • element-name source, especially VASP 4 files
  • species order
  • selective-dynamics flags
  • velocities, predictor-corrector data, and lattice velocities if present

POSCAR cannot faithfully represent partial occupancies. Oxidation states and arbitrary site properties generally do not round-trip. Never "fix" a POSCAR by searching nearby directories for POTCAR files without explicit approval.

Raw lines 109 to 137
Involves a person without explicit approval :136
138## XYZ and other low-context formats
140XYZ is a Cartesian, non-periodic coordinate format. Converting a periodic
141structure to XYZ drops lattice and periodicity. Basic XYZ also does not define
142oxidation states, partial occupancies, bonds, charge, spin multiplicity, or
143arbitrary site properties.
145`Structure.to(fmt="xyz")` is unsupported in the tested registry. The bundled
146converter uses `pymatgen.io.xyz.XYZ(structure)` explicitly, after acknowledging
147the loss of lattice/PBC. Read the result as a `Molecule`.
149CSSR and XSF have their own representational limits. Treat support in
150`Structure.to()` as syntactic capability, not proof of losslessness.

XYZ and other low-context formats

XYZ is a Cartesian, non-periodic coordinate format. Converting a periodic structure to XYZ drops lattice and periodicity. Basic XYZ also does not define oxidation states, partial occupancies, bonds, charge, spin multiplicity, or arbitrary site properties.

Structure.to(fmt="xyz") is unsupported in the tested registry. The bundled converter uses pymatgen.io.xyz.XYZ(structure) explicitly, after acknowledging the loss of lattice/PBC. Read the result as a Molecule.

CSSR and XSF have their own representational limits. Treat support in Structure.to() as syntactic capability, not proof of losslessness.

Raw lines 138 to 151
152## JSON and MSON
154Pymatgen core objects implement `as_dict()`/`from_dict()`:
156```python
157import json
158from pymatgen.core import Structure
160text = json.dumps(structure.as_dict(), allow_nan=False, sort_keys=True)
161payload = json.loads(text)
162restored = Structure.from_dict(payload)
163```
165For untrusted input, use a bounded strict JSON parser, reject duplicate keys and
166non-finite values, validate the expected schema, and call a specific
167constructor. Do not use pickle. Do not pass attacker-controlled `@module` or
168`@class` metadata to a general dynamic object decoder.
170`Structure.from_dict()` itself decodes site-property MSON values. The bundled
171loader therefore rejects nested MSON metadata, duplicate keys and non-finite
172numbers before constructing a structure. It accepts plain `.json`/`.mson`
173files only; decompress and inspect compressed JSON separately.
175YAML is not used by the bundled CLIs. If a workflow truly needs YAML, use a
176safe loader plus schema validation; YAML safety does not solve object-schema or
177resource-exhaustion risks.

JSON and MSON

Pymatgen core objects implement as_dict()/from_dict():

import json
from pymatgen.core import Structure

text = json.dumps(structure.as_dict(), allow_nan=False, sort_keys=True)
payload = json.loads(text)
restored = Structure.from_dict(payload)

For untrusted input, use a bounded strict JSON parser, reject duplicate keys and non-finite values, validate the expected schema, and call a specific constructor. Do not use pickle. Do not pass attacker-controlled @module or @class metadata to a general dynamic object decoder.

Structure.from_dict() itself decodes site-property MSON values. The bundled loader therefore rejects nested MSON metadata, duplicate keys and non-finite numbers before constructing a structure. It accepts plain .json/.mson files only; decompress and inspect compressed JSON separately.

YAML is not used by the bundled CLIs. If a workflow truly needs YAML, use a safe loader plus schema validation; YAML safety does not solve object-schema or resource-exhaustion risks.

Raw lines 152 to 178
179## VASP input objects
181```python
182from pymatgen.io.vasp import Incar, Kpoints, Poscar
184incar = Incar({"ENCUT": 520, "ISMEAR": 0, "SIGMA": 0.05})
185kpoints = Kpoints.automatic_density(structure, 1000)
186poscar = Poscar(structure)
187```
189Input sets encode versioned methodological choices:
191```python
192from pymatgen.io.vasp.sets import MPNonSCFSet, MPRelaxSet, MPStaticSet
194relax = MPRelaxSet(structure)
195static = MPStaticSet(structure)
196bands = MPNonSCFSet(structure, mode="line")
197```
199Before writing:
2011. inspect the generated INCAR, KPOINTS, POSCAR, and POTCAR specification
2022. record input-set class, pymatgen/core versions, all user overrides, and the
203 source structure checksum
2043. check magnetic moments, DFT+U, functional, pseudopotential family, ENCUT,
205 k-point density/path, smearing, spin/SOC, symmetry, and convergence criteria
2064. write to a new calculation directory
208POTCAR datasets are VASP-licensed and not distributed by pymatgen. A
209`POTCAR.spec` is not a POTCAR. Do not redistribute pseudopotential contents or
210silently use files from an unrelated installation.

VASP input objects

from pymatgen.io.vasp import Incar, Kpoints, Poscar

incar = Incar({"ENCUT": 520, "ISMEAR": 0, "SIGMA": 0.05})
kpoints = Kpoints.automatic_density(structure, 1000)
poscar = Poscar(structure)

Input sets encode versioned methodological choices:

from pymatgen.io.vasp.sets import MPNonSCFSet, MPRelaxSet, MPStaticSet

relax = MPRelaxSet(structure)
static = MPStaticSet(structure)
bands = MPNonSCFSet(structure, mode="line")

Before writing:

  1. inspect the generated INCAR, KPOINTS, POSCAR, and POTCAR specification
  2. record input-set class, pymatgen/core versions, all user overrides, and the source structure checksum
  3. check magnetic moments, DFT+U, functional, pseudopotential family, ENCUT, k-point density/path, smearing, spin/SOC, symmetry, and convergence criteria
  4. write to a new calculation directory

POTCAR datasets are VASP-licensed and not distributed by pymatgen. A POTCAR.spec is not a POTCAR. Do not redistribute pseudopotential contents or silently use files from an unrelated installation.

Raw lines 179 to 211
212## VASP output parsing
214This file-dependent example is illustrative; signatures were checked against
215the pinned core release, but a licensed calculation was not run.
217```python
218from pymatgen.io.vasp import Vasprun
220run = Vasprun(
221 "vasprun.xml",
222 ionic_step_skip=None,
223 parse_dos=True,
224 parse_eigen=True,
225 parse_projected_eigen=False,
226 parse_potcar_file=False,
227 exception_on_bad_xml=True,
230final_structure = run.final_structure
231final_energy_eV = float(run.final_energy)
232band_structure = run.get_band_structure(line_mode=True)
233complete_dos = run.complete_dos
234```
236Use `BSVasprun` when only eigenvalue/band-structure information is needed.
237Projected eigenvalues can take extreme time and memory; leave
238`parse_projected_eigen=False` unless they are required and resources are
239bounded.
241Parser success does not establish:
243- electronic or ionic convergence
244- a correct k-path or line-mode reconstruction
245- comparable energies
246- valid pseudopotential hashes
247- correct Fermi level, occupations, spin/SOC, or projection interpretation
249Preserve source-file checksums and parsing options. Large XML, HDF5, CHGCAR,
250LOCPOT, WAVECAR, and trajectory files need explicit byte and memory limits.
252For collinear spin-polarized LOCPOT/ELFCAR, the current primary channels are
253`spin_up` and `spin_down`; CHGCAR retains `total` and `diff`. Old LOCPOT/ELFCAR
254aliases warn. Volumetric interpolation now uses periodic `i/n` grid positions;
255`interpolator` is a method, without the old `.grid`/`.values` attributes or
256SciPy `method`/`fill_value` call options.

VASP output parsing

This file-dependent example is illustrative; signatures were checked against the pinned core release, but a licensed calculation was not run.

from pymatgen.io.vasp import Vasprun

run = Vasprun(
    "vasprun.xml",
    ionic_step_skip=None,
    parse_dos=True,
    parse_eigen=True,
    parse_projected_eigen=False,
    parse_potcar_file=False,
    exception_on_bad_xml=True,
)

final_structure = run.final_structure
final_energy_eV = float(run.final_energy)
band_structure = run.get_band_structure(line_mode=True)
complete_dos = run.complete_dos

Use BSVasprun when only eigenvalue/band-structure information is needed. Projected eigenvalues can take extreme time and memory; leave parse_projected_eigen=False unless they are required and resources are bounded.

Parser success does not establish:

  • electronic or ionic convergence
  • a correct k-path or line-mode reconstruction
  • comparable energies
  • valid pseudopotential hashes
  • correct Fermi level, occupations, spin/SOC, or projection interpretation

Preserve source-file checksums and parsing options. Large XML, HDF5, CHGCAR, LOCPOT, WAVECAR, and trajectory files need explicit byte and memory limits.

For collinear spin-polarized LOCPOT/ELFCAR, the current primary channels are spin_up and spin_down; CHGCAR retains total and diff. Old LOCPOT/ELFCAR aliases warn. Volumetric interpolation now uses periodic i/n grid positions; interpolator is a method, without the old .grid/.values attributes or SciPy method/fill_value call options.

Raw lines 212 to 257
258## Band structures and DOS
260`Vasprun.get_band_structure()` returns a `BandStructure` or
261`BandStructureSymmLine` depending on inputs. Relevant methods include
262`is_metal()`, `get_band_gap()`, `get_vbm()`, and `get_cbm()`.
264`run.complete_dos` is a `CompleteDos`; current analyses include total,
265element-, site-, and orbital-projected DOS. Verify energy reference, Fermi
266level, normalization, smearing, spin channels, projection completeness, and
267whether the DOS and band run correspond to the same structure/method.

Band structures and DOS

Vasprun.get_band_structure() returns a BandStructure or BandStructureSymmLine depending on inputs. Relevant methods include is_metal(), get_band_gap(), get_vbm(), and get_cbm().

run.complete_dos is a CompleteDos; current analyses include total, element-, site-, and orbital-projected DOS. Verify energy reference, Fermi level, normalization, smearing, spin channels, projection completeness, and whether the DOS and band run correspond to the same structure/method.

Raw lines 258 to 268
269## Q-Chem
271Current imports:
273```python
274from pymatgen.io.qchem.inputs import QCInput
275from pymatgen.io.qchem.outputs import QCOutput
277job = QCInput(
278 molecule,
279 rem={
280 "job_type": "sp",
281 "method": "wb97x-v",
282 "basis": "def2-svpd",
283 },
285text = str(job)
287parsed = QCOutput("qchem.out")
288data = parsed.data
289```
291The input construction is exercised with a synthetic water molecule. Output
292parsing is illustrative and needs an actual Q-Chem output file.
294`QCInput` accepts explicit sections such as `rem`, `opt`, `pcm`, `solvent`,
295`smx`, `scan`, `plots`, `nbo`, `geom_opt`, and others. Validate each setting
296against the licensed Q-Chem version and manual. Preserve molecule atom order,
297charge, spin multiplicity, method/basis, solvent model, job type, and input
298text.
300`QCOutput` parses a file into structured data; inspect parser errors,
301completion, SCF/geometry convergence, imaginary frequencies, and units before
302using a result.

Q-Chem

Current imports:

from pymatgen.io.qchem.inputs import QCInput
from pymatgen.io.qchem.outputs import QCOutput

job = QCInput(
    molecule,
    rem={
        "job_type": "sp",
        "method": "wb97x-v",
        "basis": "def2-svpd",
    },
)
text = str(job)

parsed = QCOutput("qchem.out")
data = parsed.data

The input construction is exercised with a synthetic water molecule. Output parsing is illustrative and needs an actual Q-Chem output file.

QCInput accepts explicit sections such as rem, opt, pcm, solvent, smx, scan, plots, nbo, geom_opt, and others. Validate each setting against the licensed Q-Chem version and manual. Preserve molecule atom order, charge, spin multiplicity, method/basis, solvent model, job type, and input text.

QCOutput parses a file into structured data; inspect parser errors, completion, SCF/geometry convergence, imaginary frequencies, and units before using a result.

Raw lines 269 to 303
304## External and native programs
306Pymatgen interfaces can call or depend on optional external tools, including:
308- enumlib (`enum.x`, `makestr.x`) for derivative-structure enumeration
309- Bader analysis executable
310- packmol
311- ffmpeg
312- Zeo++/Voro++
313- graph and visualization libraries
315Do not invoke them automatically. Verify official source, version, hash,
316license, native build scripts, executable path, exact argv, working directory,
317input/output paths, and CPU/RAM/disk/time bounds. Never interpolate untrusted
318text into a shell command.

External and native programs

Pymatgen interfaces can call or depend on optional external tools, including:

  • enumlib (enum.x, makestr.x) for derivative-structure enumeration
  • Bader analysis executable
  • packmol
  • ffmpeg
  • Zeo++/Voro++
  • graph and visualization libraries

Do not invoke them automatically. Verify official source, version, hash, license, native build scripts, executable path, exact argv, working directory, input/output paths, and CPU/RAM/disk/time bounds. Never interpolate untrusted text into a shell command.

Raw lines 304 to 319
320## Safe conversion sequence
3221. Inventory source bytes, checksum, format, and parser warnings.
3232. Validate lattice/PBC, coordinates, species order, occupancy/disorder,
324 oxidation states, labels, and site properties.
3253. Generate a dry-run representation-loss plan.
3264. Refuse an incompatible target (for example, disordered structure to POSCAR).
3275. Require explicit acknowledgement for remaining losses.
3286. Render in memory, enforce an output-byte bound, and create a new file
329 exclusively.
3307. Parse the output under the same safety bounds.
3318. Compare formula, site count, lattice, PBC, coordinates, occupancy, labels,
332 and required properties with explicit numerical tolerances.
3339. Record both checksums and every warning in the artifact manifest.

Safe conversion sequence

  1. Inventory source bytes, checksum, format, and parser warnings.
  2. Validate lattice/PBC, coordinates, species order, occupancy/disorder, oxidation states, labels, and site properties.
  3. Generate a dry-run representation-loss plan.
  4. Refuse an incompatible target (for example, disordered structure to POSCAR).
  5. Require explicit acknowledgement for remaining losses.
  6. Render in memory, enforce an output-byte bound, and create a new file exclusively.
  7. Parse the output under the same safety bounds.
  8. Compare formula, site count, lattice, PBC, coordinates, occupancy, labels, and required properties with explicit numerical tolerances.
  9. Record both checksums and every warning in the artifact manifest.
Raw lines 320 to 334
335## Sources (verified 2026-09-30)
337- [pymatgen I/O API](https://pymatgen.org/pymatgen.io.html)
338- [CIF parser and writer API](https://pymatgen.org/pymatgen.io.html)
339- [VASP I/O API](https://pymatgen.org/pymatgen.io.vasp.html)
340- [Q-Chem I/O API](https://pymatgen.org/pymatgen.io.qchem.html)
341- [pymatgen installation and external programs](https://pymatgen.org/installation.html)
342- [pymatgen-core source](https://github.com/materialsproject/pymatgen-core)
343- [CVE-2024-23346 official advisory](https://github.com/materialsproject/pymatgen/security/advisories/GHSA-vgv8-5cpj-qj2f)
344- [pymatgen changelog](https://pymatgen.org/CHANGES.html)