Source code for PyGamLab.structures.io.export


from typing import List, Optional
from collections import Counter
import json
from typing import List, Dict, Optional, Tuple
from ..Primatom import GAM_Atom 





[docs] def export(atoms: list[GAM_Atom], fmt: str, file_path: Optional[str] = None, **kwargs) -> None: """ Export a list of GAM_Atom objects to a file in a specified molecular format. This is a universal export function supporting multiple formats, including XYZ, CIF, POSCAR (VASP), PDB, LAMMPS, Gaussian, and the custom .pygam format. Parameters ---------- atoms : list[GAM_Atom] List of atoms to export. fmt : str Target format for export. Supported values include: 'xyz', 'cif', 'poscar', 'pdb', 'lammps', 'gaussian', 'gjf', 'pygamlab', 'pygam', 'gamlab'. file_path : str, optional File path to save the output. If None, a default path will be generated based on the chosen format. **kwargs : dict Additional keyword arguments passed to the corresponding format-specific exporter. Raises ------ ValueError If an unsupported format is specified. """ fmt = fmt.lower() # Generate default filename if not provided if file_path is None: if fmt in ("gjf", "gaussian"): file_path = "structure.gjf" elif fmt == "poscar": file_path = "POSCAR" elif fmt == "lammps": file_path = "data.lammps" elif fmt in ('pygamlab','pygam','gamlab'): file_path = "structure.pygam" else: file_path = f"structure.{fmt}" if fmt == "xyz": to_xyz(atoms, file_path=file_path, **kwargs) elif fmt == "cif": to_cif(atoms, file_path=file_path, **kwargs) elif fmt == "poscar": to_poscar(atoms, file_path=file_path, **kwargs) elif fmt == "pdb": to_pdb(atoms, file_path=file_path, **kwargs) elif fmt == "lammps": to_lammps(atoms, file_path=file_path, **kwargs) elif fmt in ("gjf", "gaussian"): to_gaussian(atoms, file_path=file_path, **kwargs) elif fmt in ('pygamlab','pygam','gamlab'): export_pygam(atoms, file_path=file_path, **kwargs) else: raise ValueError(f"Unsupported format: {fmt}")
def to_pygam(atoms: List[GAM_Atom], system: str = "GAM system", cell: Optional[List[float]] = None, timestep: int = 0, bonds: Optional[List[Tuple[int, int, int, float]]] = None, dynamics: Optional[Dict[str, float]] = None, metadata: Optional[dict] = None) -> str: """ Convert a list of GAM_Atom objects and optional simulation info to a .pygam formatted string. Parameters ---------- atoms : List[GAM_Atom] List of atoms to include in the output. system : str Name or description of the system. cell : list of float, optional Unit cell dimensions [a, b, c] in Angstroms. timestep : int Simulation timestep (default is 0). bonds : list of tuples, optional Bond information as (atom1_id, atom2_id, order, length). dynamics : dict, optional Dynamics-related metadata such as temperature, pressure, etc. metadata : dict, optional Arbitrary additional metadata stored in JSON format. Returns ------- str Formatted string in .pygam file format. """ lines = [ "#========================================================", "# PYGAM STRUCTURE FILE (version 1.0)", "#========================================================", f"SYSTEM {system}", f"NATOMS {len(atoms)}", f"CELL {' '.join(f'{c:.6f}' for c in (cell or [0.0, 0.0, 0.0]))}", "UNITS Angstrom eV fs", f"TIMESTEP {timestep}", "", "#--------------------------------------------------------", "# Atoms section", "# Format: ATOM id element x y z vx vy vz charge state mass", "#--------------------------------------------------------" ] for atom in atoms: vx, vy, vz = atom._velocity.tolist() lines.append( f"ATOM {atom.id} {atom.element} " f"{atom.x:.6f} {atom.y:.6f} {atom.z:.6f} " f"{vx:.6f} {vy:.6f} {vz:.6f} " f"{atom.charge:.3f} {atom.electronic_state.name} {atom.atomic_mass:.3f}" ) if bonds: lines.extend([ "", "#--------------------------------------------------------", "# Bonds section", "# Format: BOND atom1 atom2 order length", "#--------------------------------------------------------" ]) for b1, b2, order, length in bonds: lines.append(f"BOND {b1} {b2} {order} {length:.3f}") if dynamics: lines.extend([ "", "#--------------------------------------------------------", "# Dynamics section", "#--------------------------------------------------------" ]) for key, value in dynamics.items(): lines.append(f"{key.upper()} {value}") if metadata: lines.extend([ "", "#--------------------------------------------------------", "# Metadata block (JSON)", "#--------------------------------------------------------", "METADATA " + json.dumps(metadata, indent=2) ]) lines.append("") lines.append("#========================================================") lines.append("# END OF STEP") lines.append("#========================================================") return "\n".join(lines) def export_pygam(atoms: List[GAM_Atom], file_path: str = "structure.pygam", system: str = "GAM system", cell: Optional[List[float]] = None, timestep: int = 0, bonds: Optional[List[Tuple[int, int, int, float]]] = None, dynamics: Optional[Dict[str, float]] = None, metadata: Optional[dict] = None): """ Export a list of GAM_Atom objects to a .pygam file on disk. Parameters ---------- atoms : List[GAM_Atom] List of atoms to export. file_path : str Destination file path for the .pygam file. system : str Name or description of the system. cell : list of float, optional Unit cell dimensions [a, b, c] in Angstroms. timestep : int Simulation timestep (default is 0). bonds : list of tuples, optional Bond information as (atom1_id, atom2_id, order, length). dynamics : dict, optional Dynamics-related metadata such as temperature, pressure, etc. metadata : dict, optional Arbitrary additional metadata stored in JSON format. Notes ----- This function writes the .pygam file directly to disk. """ pygam_str = to_pygam(atoms, system, cell, timestep, bonds, dynamics, metadata) with open(file_path, "w") as f: f.write(pygam_str) print(f".pygam file successfully exported: {file_path}") # ---------- XYZ ---------- def to_xyz(atoms: List[GAM_Atom], file_path: str = "structure.xyz", comment: str = "Generated by GAM_Atoms") -> None: """ Export a list of GAM_Atom objects to the XYZ file format. Parameters ---------- atoms : List[GAM_Atom] List of atoms to export. file_path : str Destination XYZ file path. comment : str Optional comment line added after the atom count. """ lines = [str(len(atoms))] lines.append(comment) for atom in atoms: lines.append(f"{atom.element} {atom.x:.6f} {atom.y:.6f} {atom.z:.6f}") with open(file_path, "w") as f: f.write("\n".join(lines)) print(f"XYZ file successfully exported: {file_path}") # ---------- CIF (very minimal) ---------- def to_cif(atoms: List[GAM_Atom], file_path: str = "structure.cif", cell: Optional[List[List[float]]] = None, name: str = "GAM_Structure") -> None: """ Export a list of GAM_Atom objects to a CIF (Crystallographic Information File). Parameters ---------- atoms : List[GAM_Atom] List of atoms to export. file_path : str Destination CIF file path. cell : list of lists, optional 3x3 unit cell matrix. Required for proper fractional coordinates. name : str CIF data block name. Notes ----- Coordinates are converted to fractional units if a cell is provided. """ lines = [f"data_{name}"] if cell: a, b, c = cell lines += [ f"_cell_length_a {a[0]:.6f}", f"_cell_length_b {b[1]:.6f}", f"_cell_length_c {c[2]:.6f}", "_cell_angle_alpha 90.000", "_cell_angle_beta 90.000", "_cell_angle_gamma 90.000", ] lines += [ "loop_", "_atom_site_label", "_atom_site_fract_x", "_atom_site_fract_y", "_atom_site_fract_z" ] for atom in atoms: # assuming Cartesian coords and unit cell provided if cell: x = atom.x / cell[0][0] y = atom.y / cell[1][1] z = atom.z / cell[2][2] else: x, y, z = atom.x, atom.y, atom.z lines.append(f"{atom.element} {x:.6f} {y:.6f} {z:.6f}") with open(file_path, "w") as f: f.write("\n".join(lines)) print(f"CIF file successfully exported: {file_path}") # ---------- POSCAR (VASP) ---------- def to_poscar(atoms: List[GAM_Atom], file_path: str = "POSCAR", cell: Optional[List[List[float]]] = None, comment: str = "Generated by GAM_Atoms") -> None: """ Export a list of GAM_Atom objects to VASP POSCAR format. Parameters ---------- atoms : List[GAM_Atom] List of atoms to export. file_path : str Destination POSCAR file path. cell : list of lists 3x3 lattice vectors (required). comment : str Optional comment line in the POSCAR file. Raises ------ ValueError If `cell` is not provided. """ if cell is None: raise ValueError("POSCAR export requires a cell (3x3 matrix).") # count elements counts = Counter(atom.element for atom in atoms) elements = list(counts.keys()) numbers = list(counts.values()) lines = [comment, "1.0"] # lattice for vec in cell: lines.append(f"{vec[0]:.6f} {vec[1]:.6f} {vec[2]:.6f}") # element line lines.append(" ".join(elements)) # counts line lines.append(" ".join(str(n) for n in numbers)) lines.append("Cartesian") for atom in atoms: lines.append(f"{atom.x:.6f} {atom.y:.6f} {atom.z:.6f}") with open(file_path, "w") as f: f.write("\n".join(lines)) print(f"POSCAR file successfully exported: {file_path}") # -------------------------------- def to_pdb(atoms: list[GAM_Atom], file_path: str = "structure.pdb", title: str="Generated by GAM_Atoms") -> None: """ Export a list of GAM_Atom objects to the PDB file format. Parameters ---------- atoms : list[GAM_Atom] List of atoms to export. file_path : str Destination PDB file path. title : str TITLE line in the PDB file. """ lines = [f"TITLE {title}"] for i, atom in enumerate(atoms, start=1): lines.append( f"ATOM {i:5d} {atom.element:>2} MOL 1 " f"{atom.x:8.3f}{atom.y:8.3f}{atom.z:8.3f} 1.00 0.00" ) lines.append("END") with open(file_path, "w") as f: f.write("\n".join(lines)) print(f"PDB file successfully exported: {file_path}") def to_lammps(atoms: list[GAM_Atom], file_path: str = "data.lammps", cell: list[list[float]] = None) -> None: """ Export a list of GAM_Atom objects to LAMMPS data format. Parameters ---------- atoms : list[GAM_Atom] List of atoms to export. file_path : str Destination LAMMPS data file path. cell : list of lists, optional Simulation box dimensions (currently not used in example). """ counts = len(atoms) lines = [f"{counts} atoms", "", "Atoms"] for i, atom in enumerate(atoms, start=1): # example: atom-ID molecule-ID atom-type q x y z lines.append(f"{i} 1 {i} {atom.charge:.3f} {atom.x:.6f} {atom.y:.6f} {atom.z:.6f}") with open(file_path, "w") as f: f.write("\n".join(lines)) print(f"LAMMPS data file successfully exported: {file_path}") def to_gaussian(atoms: list[GAM_Atom], file_path: str = "structure.gjf", charge: int=0, multiplicity: int=1, route: str="#P HF/6-31G(d) OPT") -> None: """ Export a list of GAM_Atom objects to Gaussian input format (.gjf). Parameters ---------- atoms : list[GAM_Atom] List of atoms to export. file_path : str Destination Gaussian input file path. charge : int Total charge of the system (default: 0). multiplicity : int Spin multiplicity of the system (default: 1). route : str Gaussian route section specifying method and keywords. """ lines = [route, "", "Generated by GAM_Atoms", ""] lines.append(f"{charge} {multiplicity}") for atom in atoms: lines.append(f"{atom.element} {atom.x:.6f} {atom.y:.6f} {atom.z:.6f}") lines.append("") with open(file_path, "w") as f: f.write("\n".join(lines)) print(f"Gaussian input file successfully exported: {file_path}")