Coverage for eminus/io/pdb.py: 98.39%
62 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-18 08:43 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-18 08:43 +0000
1# SPDX-FileCopyrightText: 2021 The eminus developers
2# SPDX-License-Identifier: Apache-2.0
3"""PDB file handling."""
5import numpy as np
6from scipy.linalg import norm
8from ..logger import log
9from ..units import bohr2ang
10from ..utils import vector_angle
13def write_pdb(obj, filename, fods=None, elec_symbols=("X", "He"), trajectory=False):
14 """Generate PDB files from atoms objects.
16 See :func:`~eminus.io.pdb.create_pdb_str` for more information about the PDB file format.
18 Args:
19 obj: Atoms or SCF object.
20 filename: PDB output file path/name.
22 Keyword Args:
23 fods: FOD coordinates to write.
24 elec_symbols: Identifier for up and down FODs.
25 trajectory: Allow appending to a file to create trajectories.
26 """
27 atoms = obj._atoms
29 if not filename.endswith(".pdb"):
30 filename += ".pdb"
32 if "He" in atoms.atom and atoms.unrestricted:
33 log.warning(
34 'You need to modify "elec_symbols" to write helium with FODs in the spin-'
35 "polarized case."
36 )
38 atom = atoms.atom
39 pos = atoms.pos
40 if fods is not None:
41 if len(fods[0]) != 0:
42 atom = atom + [elec_symbols[0]] * len(fods[0])
43 pos = np.vstack((pos, fods[0]))
44 if len(fods) > 1 and len(fods[1]) != 0:
45 atom = atom + [elec_symbols[1]] * len(fods[1])
46 pos = np.vstack((pos, fods[1]))
48 # Append to a file when using the trajectory keyword
49 if trajectory:
50 mode = "a"
51 else:
52 mode = "w"
54 with open(filename, mode, encoding="utf-8") as fp:
55 fp.write(create_pdb_str(atom, pos, a=atoms.a))
58def create_pdb_str(atom, pos, a=None):
59 """Convert atom symbols and positions to the PDB format.
61 File format definitions:
62 CRYST1: https://wwpdb.org/documentation/file-format-content/format33/sect8.html#CRYST1
64 ATOM: https://wwpdb.org/documentation/file-format-content/format33/sect9.html#ATOM
66 Args:
67 atom: Atom symbols.
68 pos: Atom positions.
70 Keyword Args:
71 a: Cell size.
73 Returns:
74 PDB file format.
75 """
76 # Convert Bohr to Angstrom
77 pos = bohr2ang(pos)
78 if a is not None:
79 a = bohr2ang(a)
81 # PDB files have specific numbers of characters for every data with changing justification
82 # Write everything explicitly down to not lose track of line lengths
83 pdb = ""
84 # Create data for a cuboidal cell
85 if a is not None:
86 pdb += "CRYST1" # 1-6 "CRYST1"
87 pdb += f"{norm(a[0]):>9,.3f}" # 7-15 a
88 pdb += f"{norm(a[1]):>9,.3f}" # 16-24 b
89 pdb += f"{norm(a[2]):>9,.3f}" # 25-33 c
90 pdb += f"{vector_angle(a[1], a[2]):>7,.2f}" # 34-40 alpha
91 pdb += f"{vector_angle(a[0], a[2]):>7,.2f}" # 41-47 beta
92 pdb += f"{vector_angle(a[0], a[1]):>7,.2f}" # 48-54 gamma
93 pdb += " "
94 pdb += "P 1 " # 56-66 Space group
95 # pdb += " 1" # 67-70 Z value
96 pdb += "\n"
98 # Create molecule data
99 pdb += "MODEL 1"
100 for ia in range(len(atom)):
101 pdb += "\nATOM " # 1-6 "ATOM"
102 pdb += f"{ia + 1:>5}" # 7-11 Atom serial number
103 pdb += " "
104 pdb += f"{atom[ia]:>4}" # 13-16 Atom name
105 pdb += " " # 17 Alternate location indicator
106 pdb += "MOL" # 18-20 Residue name
107 pdb += " "
108 pdb += " " # 22 Chain identifier
109 pdb += " 1" # 23-26 Residue sequence number
110 pdb += " " # 27 Code for insertions of residues
111 pdb += " "
112 pdb += f"{pos[ia, 0]:>8,.3f}" # 31-38 X orthogonal coordinate
113 pdb += f"{pos[ia, 1]:>8,.3f}" # 39-46 Y orthogonal coordinate
114 pdb += f"{pos[ia, 2]:>8,.3f}" # 47-54 Z orthogonal coordinate
115 pdb += f"{1:>6,.2f}" # 55-60 Occupancy
116 pdb += f"{0:>6,.2f}" # 61-66 Temperature factor
117 pdb += " "
118 pdb += f"{atom[ia]:>2}" # 77-78 Element symbol
119 # pdb += " " # 79-80 Charge
120 return f"{pdb}\nENDMDL\n"