Skip to content

molpy.io

MolPy IO Module - Unified interface for molecular file I/O.

This module provides a clean, organized interface for reading and writing various molecular file formats. It supports: - Data files (PDB, XYZ, LAMMPS, GROMACS, AMBER, etc.) - Force field files (LAMMPS, XML, AMBER prmtop, GROMACS top) - Trajectory files (LAMMPS dump, XYZ)

Design Principles:

  1. Reader/Writer Pattern: Each format has dedicated Reader and Writer classes
  2. Factory Functions: Convenient read_xxx/write_xxx functions for simple usage
  3. Lazy Imports: Dependencies loaded only when needed
  4. Unified Interface: All readers have read(), all writers have write()

Basic Usage:

# Reading data files
from molpy.io import read_pdb, read_lammps_data

frame = read_pdb("structure.pdb")
frame = read_lammps_data("data.lammps", atom_style="full")

# Writing data files
from molpy.io import write_pdb, write_lammps_data

write_pdb("output.pdb", frame)
write_lammps_data("output.data", frame, atom_style="full")

# Reading force fields
from molpy.io import read_xml_forcefield, read_lammps_forcefield

ff = read_xml_forcefield("oplsaa.xml")
ff = read_lammps_forcefield("forcefield.in")

# Reading trajectories
from molpy.io import read_lammps_trajectory, read_xyz_trajectory

traj = read_lammps_trajectory("dump.lammpstrj")
for frame in traj:
    process(frame)

read_amber

read_amber(prmtop, inpcrd=None, frame=None)

Read AMBER prmtop and optional inpcrd files.

Parameters:

Name Type Description Default
prmtop PathLike

Path to AMBER prmtop file

required
inpcrd PathLike | None

Optional path to AMBER inpcrd file

None
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Tuple of (Frame, ForceField)

Source code in src/molpy/io/readers.py
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
def read_amber_prmtop(
    prmtop: PathLike, inpcrd: PathLike | None = None, frame: Any = None
) -> Any:
    """
    Read AMBER prmtop and optional inpcrd files.

    Args:
        prmtop: Path to AMBER prmtop file
        inpcrd: Optional path to AMBER inpcrd file
        frame: Optional existing Frame to populate

    Returns:
        Tuple of (Frame, ForceField)
    """
    from .forcefield.amber import AmberPrmtopReader

    frame = _ensure_frame(frame)
    prmtop_path = Path(prmtop)
    reader = AmberPrmtopReader(prmtop_path)
    frame, ff = reader.read(frame)

    if inpcrd is not None:
        from .data.amber import AmberInpcrdReader

        inpcrd_reader = AmberInpcrdReader(Path(inpcrd))
        frame = inpcrd_reader.read(frame)

    return frame, ff

read_amber_ac

read_amber_ac(file, frame=None)

Read AC file and return a Frame object.

Parameters:

Name Type Description Default
file PathLike

Path to AC file

required
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Populated Frame object

Source code in src/molpy/io/readers.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def read_amber_ac(file: PathLike, frame: Any = None) -> Any:
    """
    Read AC file and return a Frame object.

    Args:
        file: Path to AC file
        frame: Optional existing Frame to populate

    Returns:
        Populated Frame object
    """
    from .data.ac import AcReader

    frame = _ensure_frame(frame)
    reader = AcReader(Path(file))
    return reader.read(frame)

read_amber_inpcrd

read_amber_inpcrd(inpcrd, frame=None)

Read AMBER inpcrd file and return a Frame object.

Parameters:

Name Type Description Default
inpcrd PathLike

Path to AMBER inpcrd file

required
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Populated Frame object

Source code in src/molpy/io/readers.py
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def read_amber_inpcrd(inpcrd: PathLike, frame: Any = None) -> Any:
    """
    Read AMBER inpcrd file and return a Frame object.

    Args:
        inpcrd: Path to AMBER inpcrd file
        frame: Optional existing Frame to populate

    Returns:
        Populated Frame object
    """
    from .data.amber import AmberInpcrdReader

    frame = _ensure_frame(frame)
    reader = AmberInpcrdReader(Path(inpcrd))
    return reader.read(frame)

read_amber_system

read_amber_system(prmtop, inpcrd=None, system=None)

Read AMBER prmtop and optional inpcrd files (legacy function).

Parameters:

Name Type Description Default
prmtop PathLike

Path to AMBER prmtop file

required
inpcrd PathLike | None

Optional path to AMBER inpcrd file

None
system Any

Optional FrameSystem (unused, kept for compatibility)

None

Returns:

Type Description
Any

Tuple of (Frame, ForceField)

Note

For new code, prefer using read_amber() directly.

Source code in src/molpy/io/__init__.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def read_amber_system(
    prmtop: PathLike,
    inpcrd: PathLike | None = None,
    system: Any = None,
) -> Any:
    """
    Read AMBER prmtop and optional inpcrd files (legacy function).

    Args:
        prmtop: Path to AMBER prmtop file
        inpcrd: Optional path to AMBER inpcrd file
        system: Optional FrameSystem (unused, kept for compatibility)

    Returns:
        Tuple of (Frame, ForceField)

    Note:
        For new code, prefer using read_amber() directly.
    """
    frame, ff = read_amber(prmtop, inpcrd)

    # Original function returned FrameSystem namedtuple
    from collections import namedtuple

    FrameSystem = namedtuple("FrameSystem", ["frame", "forcefield", "box"])
    return FrameSystem(frame=frame, forcefield=ff, box=getattr(frame, "box", None))

read_gro

read_gro(file, frame=None)

Read GROMACS gro file and return a Frame object.

Parameters:

Name Type Description Default
file PathLike

Path to gro file

required
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Populated Frame object

Source code in src/molpy/io/readers.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def read_gro(file: PathLike, frame: Any = None) -> Any:
    """
    Read GROMACS gro file and return a Frame object.

    Args:
        file: Path to gro file
        frame: Optional existing Frame to populate

    Returns:
        Populated Frame object
    """
    from .data.gro import GroReader

    frame = _ensure_frame(frame)
    reader = GroReader(Path(file))
    return reader.read(frame)

read_gromacs_system

read_gromacs_system(gro_file, top_file=None, system=None)

Read GROMACS structure and optional topology files.

Parameters:

Name Type Description Default
gro_file PathLike

Path to GROMACS .gro file

required
top_file PathLike | None

Optional path to GROMACS .top file

None
system Any

Optional FrameSystem (unused, kept for compatibility)

None

Returns:

Type Description
Any

Frame if no topology, or tuple of (Frame, ForceField) if topology provided

Note

For new code, prefer using read_gro() and read_top() separately.

Source code in src/molpy/io/__init__.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def read_gromacs_system(
    gro_file: PathLike,
    top_file: PathLike | None = None,
    system: Any = None,
) -> Any:
    """
    Read GROMACS structure and optional topology files.

    Args:
        gro_file: Path to GROMACS .gro file
        top_file: Optional path to GROMACS .top file
        system: Optional FrameSystem (unused, kept for compatibility)

    Returns:
        Frame if no topology, or tuple of (Frame, ForceField) if topology provided

    Note:
        For new code, prefer using read_gro() and read_top() separately.
    """
    frame = read_gro(gro_file)

    if top_file is not None:
        forcefield = read_top(top_file)
        from collections import namedtuple

        FrameSystem = namedtuple("FrameSystem", ["frame", "forcefield", "box"])
        return FrameSystem(
            frame=frame, forcefield=forcefield, box=getattr(frame, "box", None)
        )

    return frame

read_lammps

read_lammps(data, scripts=None, frame=None, atomstyle='full')

Read LAMMPS data and optional force field files.

Parameters:

Name Type Description Default
data PathLike

Path to LAMMPS data file

required
scripts PathLike | list[PathLike] | None

Optional path(s) to LAMMPS force field scripts

None
frame Any

Optional existing Frame to populate

None
atomstyle str

LAMMPS atom style (default: 'full')

'full'

Returns:

Type Description
Any

Frame object (force field is loaded but returned separately if needed)

Note

For new code, prefer using read_lammps_data() and read_lammps_forcefield() separately.

Source code in src/molpy/io/__init__.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def read_lammps(
    data: PathLike,
    scripts: PathLike | list[PathLike] | None = None,
    frame: Any = None,
    atomstyle: str = "full",
) -> Any:
    """
    Read LAMMPS data and optional force field files.

    Args:
        data: Path to LAMMPS data file
        scripts: Optional path(s) to LAMMPS force field scripts
        frame: Optional existing Frame to populate
        atomstyle: LAMMPS atom style (default: 'full')

    Returns:
        Frame object (force field is loaded but returned separately if needed)

    Note:
        For new code, prefer using read_lammps_data() and read_lammps_forcefield() separately.
    """
    if scripts is not None:
        # Load force field first (though return value not used in original)
        _ = read_lammps_forcefield(scripts)

    return read_lammps_data(data, atomstyle, frame)

read_lammps_data

read_lammps_data(file, atom_style, frame=None)

Read LAMMPS data file and return a Frame object.

Parameters:

Name Type Description Default
file PathLike

Path to LAMMPS data file

required
atom_style str

LAMMPS atom style (e.g., 'full', 'atomic')

required
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Populated Frame object

Source code in src/molpy/io/readers.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def read_lammps_data(file: PathLike, atom_style: str, frame: Any = None) -> Any:
    """
    Read LAMMPS data file and return a Frame object.

    Args:
        file: Path to LAMMPS data file
        atom_style: LAMMPS atom style (e.g., 'full', 'atomic')
        frame: Optional existing Frame to populate

    Returns:
        Populated Frame object
    """
    from .data.lammps import LammpsDataReader

    reader = LammpsDataReader(Path(file), atom_style)
    return reader.read(frame=frame)

read_lammps_forcefield

read_lammps_forcefield(scripts, forcefield=None)

Read LAMMPS force field file and return a ForceField object.

Parameters:

Name Type Description Default
scripts PathLike | list[PathLike]

Path or list of paths to LAMMPS force field scripts

required
forcefield Any

Optional existing ForceField to populate

None

Returns:

Type Description
Any

Populated ForceField object

Source code in src/molpy/io/readers.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
def read_lammps_forcefield(
    scripts: PathLike | list[PathLike], forcefield: Any = None
) -> Any:
    """
    Read LAMMPS force field file and return a ForceField object.

    Args:
        scripts: Path or list of paths to LAMMPS force field scripts
        forcefield: Optional existing ForceField to populate

    Returns:
        Populated ForceField object
    """
    from .forcefield.lammps import LAMMPSForceFieldReader

    reader = LAMMPSForceFieldReader(scripts)
    return reader.read(forcefield=forcefield)

read_lammps_molecule

read_lammps_molecule(file, frame=None)

Read LAMMPS molecule file and return a Frame object.

Parameters:

Name Type Description Default
file PathLike

Path to LAMMPS molecule file

required
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Populated Frame object

Source code in src/molpy/io/readers.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def read_lammps_molecule(file: PathLike, frame: Any = None) -> Any:
    """
    Read LAMMPS molecule file and return a Frame object.

    Args:
        file: Path to LAMMPS molecule file
        frame: Optional existing Frame to populate

    Returns:
        Populated Frame object
    """
    from .data.lammps_molecule import LammpsMoleculeReader

    reader = LammpsMoleculeReader(Path(file))
    return reader.read(frame=frame)

read_lammps_trajectory

read_lammps_trajectory(traj, frame=None)

Read LAMMPS trajectory file and return a trajectory reader.

Parameters:

Name Type Description Default
traj PathLike

Path to LAMMPS trajectory file

required
frame Any

Optional reference Frame for topology

None

Returns:

Type Description
Any

LammpsTrajectoryReader object

Source code in src/molpy/io/readers.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def read_lammps_trajectory(traj: PathLike, frame: Any = None) -> Any:
    """
    Read LAMMPS trajectory file and return a trajectory reader.

    Args:
        traj: Path to LAMMPS trajectory file
        frame: Optional reference Frame for topology

    Returns:
        LammpsTrajectoryReader object
    """
    from .trajectory.lammps import LammpsTrajectoryReader

    return LammpsTrajectoryReader(Path(traj), frame)

read_mol2

read_mol2(file, frame=None)

Read mol2 file and return a Frame object.

Parameters:

Name Type Description Default
file PathLike

Path to mol2 file

required
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Populated Frame object

Source code in src/molpy/io/readers.py
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def read_mol2(file: PathLike, frame: Any = None) -> Any:
    """
    Read mol2 file and return a Frame object.

    Args:
        file: Path to mol2 file
        frame: Optional existing Frame to populate

    Returns:
        Populated Frame object
    """
    from .data.mol2 import Mol2Reader

    frame = _ensure_frame(frame)
    reader = Mol2Reader(Path(file))
    return reader.read(frame)

read_pdb

read_pdb(file, frame=None)

Read PDB file and return a Frame object.

Parameters:

Name Type Description Default
file PathLike

Path to PDB file

required
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Populated Frame object

Source code in src/molpy/io/readers.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def read_pdb(file: PathLike, frame: Any = None) -> Any:
    """
    Read PDB file and return a Frame object.

    Args:
        file: Path to PDB file
        frame: Optional existing Frame to populate

    Returns:
        Populated Frame object
    """
    from .data.pdb import PDBReader

    frame = _ensure_frame(frame)
    reader = PDBReader(Path(file))
    return reader.read(frame)

read_top

read_top(file, forcefield=None)

Read GROMACS topology file and return a ForceField object.

Parameters:

Name Type Description Default
file PathLike

Path to GROMACS .top file

required
forcefield Any

Optional existing ForceField to populate

None

Returns:

Type Description
Any

Populated ForceField object

Source code in src/molpy/io/readers.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def read_top(file: PathLike, forcefield: Any = None) -> Any:
    """
    Read GROMACS topology file and return a ForceField object.

    Args:
        file: Path to GROMACS .top file
        forcefield: Optional existing ForceField to populate

    Returns:
        Populated ForceField object
    """
    from molpy import ForceField

    from .forcefield.top import GromacsTopReader

    if forcefield is None:
        forcefield = ForceField()

    reader = GromacsTopReader(Path(file))
    return reader.read(forcefield)

read_xml_forcefield

read_xml_forcefield(file)

Read XML force field file and return a ForceField object.

Parameters:

Name Type Description Default
file PathLike

Path to XML force field file

required

Returns:

Type Description
Any

ForceField object

Source code in src/molpy/io/readers.py
213
214
215
216
217
218
219
220
221
222
223
224
225
def read_xml_forcefield(file: PathLike) -> Any:
    """
    Read XML force field file and return a ForceField object.

    Args:
        file: Path to XML force field file

    Returns:
        ForceField object
    """
    from .forcefield.xml import read_xml_forcefield as _read_xml

    return _read_xml(file)

read_xsf

read_xsf(file, frame=None)

Read XSF file and return a Frame object.

Parameters:

Name Type Description Default
file PathLike

Path to XSF file

required
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Populated Frame object

Source code in src/molpy/io/readers.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def read_xsf(file: PathLike, frame: Any = None) -> Any:
    """
    Read XSF file and return a Frame object.

    Args:
        file: Path to XSF file
        frame: Optional existing Frame to populate

    Returns:
        Populated Frame object
    """
    from .data.xsf import XsfReader

    reader = XsfReader(Path(file))
    return reader.read(frame)

read_xyz

read_xyz(file, frame=None)

Read XYZ file and return a Frame object.

Parameters:

Name Type Description Default
file PathLike

Path to XYZ file

required
frame Any

Optional existing Frame to populate

None

Returns:

Type Description
Any

Populated Frame object

Source code in src/molpy/io/readers.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def read_xyz(file: PathLike, frame: Any = None) -> Any:
    """
    Read XYZ file and return a Frame object.

    Args:
        file: Path to XYZ file
        frame: Optional existing Frame to populate

    Returns:
        Populated Frame object
    """
    from .data.xyz import XYZReader

    frame = _ensure_frame(frame)
    reader = XYZReader(Path(file))
    return reader.read(frame)

read_xyz_trajectory

read_xyz_trajectory(file)

Read XYZ trajectory file and return a trajectory reader.

Parameters:

Name Type Description Default
file PathLike

Path to XYZ trajectory file

required

Returns:

Type Description
Any

XYZTrajectoryReader object

Source code in src/molpy/io/readers.py
301
302
303
304
305
306
307
308
309
310
311
312
313
def read_xyz_trajectory(file: PathLike) -> Any:
    """
    Read XYZ trajectory file and return a trajectory reader.

    Args:
        file: Path to XYZ trajectory file

    Returns:
        XYZTrajectoryReader object
    """
    from .trajectory.xyz import XYZTrajectoryReader

    return XYZTrajectoryReader(Path(file))

write_lammps

write_lammps(workdir, frame, forcefield)

Write a complete LAMMPS system (data + forcefield) to a directory.

Parameters:

Name Type Description Default
workdir PathLike

Output directory path

required
frame Any

Frame object containing structure

required
forcefield Any

ForceField object containing parameters

required
Source code in src/molpy/io/writers.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def write_lammps_system(workdir: PathLike, frame: Any, forcefield: Any) -> None:
    """
    Write a complete LAMMPS system (data + forcefield) to a directory.

    Args:
        workdir: Output directory path
        frame: Frame object containing structure
        forcefield: ForceField object containing parameters
    """
    workdir_path = Path(workdir)
    if not workdir_path.exists():
        workdir_path.mkdir(parents=True, exist_ok=True)

    # Use directory name as file stem
    file_stem = workdir_path / workdir_path.stem
    write_lammps_data(file_stem.with_suffix(".data"), frame)
    write_lammps_forcefield(file_stem.with_suffix(".ff"), forcefield)

write_lammps_data

write_lammps_data(file, frame, atom_style='full')

Write a Frame object to a LAMMPS data file.

Parameters:

Name Type Description Default
file PathLike

Output file path

required
frame Any

Frame object to write

required
atom_style str

LAMMPS atom style (default: 'full')

'full'
Source code in src/molpy/io/writers.py
19
20
21
22
23
24
25
26
27
28
29
30
31
def write_lammps_data(file: PathLike, frame: Any, atom_style: str = "full") -> None:
    """
    Write a Frame object to a LAMMPS data file.

    Args:
        file: Output file path
        frame: Frame object to write
        atom_style: LAMMPS atom style (default: 'full')
    """
    from .data.lammps import LammpsDataWriter

    writer = LammpsDataWriter(Path(file), atom_style=atom_style)
    writer.write(frame)

write_lammps_forcefield

write_lammps_forcefield(file, forcefield, precision=6)

Write a ForceField object to a LAMMPS force field file.

Parameters:

Name Type Description Default
file PathLike

Output file path

required
forcefield Any

ForceField object to write

required
precision int

Number of decimal places for floating point values

6
Source code in src/molpy/io/writers.py
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def write_lammps_forcefield(
    file: PathLike, forcefield: Any, precision: int = 6
) -> None:
    """
    Write a ForceField object to a LAMMPS force field file.

    Args:
        file: Output file path
        forcefield: ForceField object to write
        precision: Number of decimal places for floating point values
    """
    from .forcefield.lammps import LAMMPSForceFieldWriter

    writer = LAMMPSForceFieldWriter(Path(file), precision=precision)
    writer.write(forcefield)

write_lammps_molecule

write_lammps_molecule(file, frame, format_type='native')

Write a Frame object to a LAMMPS molecule file.

Parameters:

Name Type Description Default
file PathLike

Output file path

required
frame Any

Frame object to write

required
format_type str

Format type (default: 'native')

'native'
Source code in src/molpy/io/writers.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def write_lammps_molecule(
    file: PathLike, frame: Any, format_type: str = "native"
) -> None:
    """
    Write a Frame object to a LAMMPS molecule file.

    Args:
        file: Output file path
        frame: Frame object to write
        format_type: Format type (default: 'native')
    """
    from .data.lammps_molecule import LammpsMoleculeWriter

    writer = LammpsMoleculeWriter(Path(file), format_type)
    writer.write(frame)

write_lammps_trajectory

write_lammps_trajectory(file, frames, atom_style='full')

Write frames to a LAMMPS trajectory file.

Parameters:

Name Type Description Default
file PathLike

Output file path

required
frames list

List of Frame objects to write

required
atom_style str

LAMMPS atom style (default: 'full')

'full'
Source code in src/molpy/io/writers.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def write_lammps_trajectory(
    file: PathLike, frames: list, atom_style: str = "full"
) -> None:
    """
    Write frames to a LAMMPS trajectory file.

    Args:
        file: Output file path
        frames: List of Frame objects to write
        atom_style: LAMMPS atom style (default: 'full')
    """
    from .trajectory.lammps import LammpsTrajectoryWriter

    with LammpsTrajectoryWriter(Path(file), atom_style) as writer:
        for i, frame in enumerate(frames):
            timestep = getattr(frame, "step", i)
            writer.write_frame(frame, timestep)

write_pdb

write_pdb(file, frame)

Write a Frame object to a PDB file.

Parameters:

Name Type Description Default
file PathLike

Output file path

required
frame Any

Frame object to write

required
Source code in src/molpy/io/writers.py
34
35
36
37
38
39
40
41
42
43
44
45
def write_pdb(file: PathLike, frame: Any) -> None:
    """
    Write a Frame object to a PDB file.

    Args:
        file: Output file path
        frame: Frame object to write
    """
    from .data.pdb import PDBWriter

    writer = PDBWriter(Path(file))
    writer.write(frame)

write_xsf

write_xsf(file, frame)

Write a Frame object to an XSF file.

Parameters:

Name Type Description Default
file PathLike

Output file path

required
frame Any

Frame object to write

required
Source code in src/molpy/io/writers.py
48
49
50
51
52
53
54
55
56
57
58
59
def write_xsf(file: PathLike, frame: Any) -> None:
    """
    Write a Frame object to an XSF file.

    Args:
        file: Output file path
        frame: Frame object to write
    """
    from .data.xsf import XsfWriter

    writer = XsfWriter(Path(file))
    writer.write(frame)

write_xyz_trajectory

write_xyz_trajectory(file, frames)

Write frames to an XYZ trajectory file.

Parameters:

Name Type Description Default
file PathLike

Output file path

required
frames list

List of Frame objects to write

required
Source code in src/molpy/io/writers.py
125
126
127
128
129
130
131
132
133
134
135
136
137
def write_xyz_trajectory(file: PathLike, frames: list) -> None:
    """
    Write frames to an XYZ trajectory file.

    Args:
        file: Output file path
        frames: List of Frame objects to write
    """
    from .trajectory.xyz import XYZTrajectoryWriter

    with XYZTrajectoryWriter(file) as writer:
        for frame in frames:
            writer.write_frame(frame)