6 Convert two 2D Cartesian box vectors into LAMMPS triclinic box parameters.
8 Assumes the user has already rotated the system, if desired.
12 ell1, ell2 : array-like
13 2D Cartesian simulation cell vectors.
15 Lower and upper z bounds.
20 Dictionary with xlo, xhi, ylo, yhi, zlo, zhi, xy, xz, yz.
22 ell1 = np.asarray(ell1, dtype=float).reshape(2)
23 ell2 = np.asarray(ell2, dtype=float).reshape(2)
26 lx = np.linalg.norm(ell1)
28 raise ValueError(
"ell1 has zero length.")
33 ell2_perp = ell2 - xy * ex
34 ly = np.linalg.norm(ell2_perp)
36 raise ValueError(
"ell1 and ell2 are collinear.")
61 comment="LAMMPS data file",
64 Write a LAMMPS data file for atomistic simulations.
66 This function generates a triclinic simulation box from the in-plane
67 lattice vectors (ell1, ell2) and writes atomic data in either
68 'atomic' or 'full' atom_style format.
75 positions : list[(x, y, z)]
76 Cartesian atomic positions.
78 atom_types : list[int]
79 Integer atom types (1-based), consistent with LAMMPS conventions.
82 Atomic charges. Used only when atom_style='full'. Ignored otherwise.
84 ell1, ell2 : array-like
85 2D Cartesian lattice vectors defining the in-plane periodic cell.
86 These are converted internally into a triclinic LAMMPS box.
88 atom_style : str, optional
89 LAMMPS atom style. Supported options:
90 - 'atomic' : writes (id, type, x, y, z)
91 - 'full' : writes (id, molecule-ID, type, charge, x, y, z)
93 molecule_ids : list[int] or None, optional
94 Molecule (or layer) IDs for each atom, required for certain
95 many-body or interlayer potentials (e.g., ilp/tmd).
96 If None and atom_style='full', all atoms are assigned molecule ID = 1.
98 z_padding : float, optional
99 Half-width of the simulation box in the z-direction. The box is
100 constructed as [ -z_padding, z_padding ]. This should be large
101 enough to avoid spurious interactions between periodic images
102 in z when using boundary p p p.
104 comment : str, optional
105 Header comment written at the top of the data file.
107 if len(positions) != len(atom_types):
108 raise ValueError(
"positions and atom_types must have the same length.")
110 if atom_style ==
"full" and len(positions) != len(charges):
111 raise ValueError(
"positions and charges must have the same length for atom_style='full'.")
113 if atom_style ==
"full" and molecule_ids
is not None and len(positions) != len(molecule_ids):
114 raise ValueError(
"positions and molecule_ids must have the same length for atom_style='full'.")
118 n_atoms = len(positions)
119 n_atom_types = max(atom_types)
121 with open(filename,
"w", encoding=
"utf-8")
as f:
122 f.write(f
"{comment}\n\n")
123 f.write(f
"{n_atoms} atoms\n")
124 f.write(f
"{n_atom_types} atom types\n\n")
126 f.write(f
"{box['xlo']:.16f} {box['xhi']:.16f} xlo xhi\n")
127 f.write(f
"{box['ylo']:.16f} {box['yhi']:.16f} ylo yhi\n")
128 f.write(f
"{box['zlo']:.16f} {box['zhi']:.16f} zlo zhi\n")
129 f.write(f
"{box['xy']:.16f} {box['xz']:.16f} {box['yz']:.16f} xy xz yz\n\n")
131 if atom_style ==
"atomic":
132 f.write(
"Atoms # atomic\n\n")
133 for i, ((x, y, z), atom_type)
in enumerate(zip(positions, atom_types), start=1):
134 f.write(f
"{i:d} {atom_type:d} {x:.16f} {y:.16f} {z:.16f}\n")
136 elif atom_style ==
"full":
137 f.write(
"Atoms # full\n\n")
138 if molecule_ids
is None:
139 molecule_ids = [1] * n_atoms
141 for i, ((x, y, z), mol_id, atom_type, charge)
in enumerate(
142 zip(positions, molecule_ids, atom_types, charges), start=1
145 f
"{i:d} {mol_id:d} {atom_type:d} "
146 f
"{charge:.16f} {x:.16f} {y:.16f} {z:.16f}\n"
149 raise ValueError(
"atom_style must be either 'atomic' or 'full'.")