Coverage for /builds/ase/ase/ase/io/cfg.py: 78.05%
164 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3import numpy as np
5import ase
6from ase.data import chemical_symbols
7from ase.utils import reader, writer
9cfg_default_fields = np.array(['positions', 'momenta', 'numbers', 'magmoms'])
12@writer
13def write_cfg(fd, atoms):
14 """Write atomic configuration to a CFG-file (native AtomEye format).
15 See: http://mt.seas.upenn.edu/Archive/Graphics/A/
16 """
18 fd.write('Number of particles = %i\n' % len(atoms))
19 fd.write('A = 1.0 Angstrom\n')
20 cell = atoms.get_cell(complete=True)
21 for i in range(3):
22 for j in range(3):
23 fd.write('H0(%1.1i,%1.1i) = %f A\n' % (i + 1, j + 1, cell[i, j]))
25 entry_count = 3
26 for x in atoms.arrays.keys():
27 if x not in cfg_default_fields:
28 if len(atoms.get_array(x).shape) == 1:
29 entry_count += 1
30 else:
31 entry_count += atoms.get_array(x).shape[1]
33 vels = atoms.get_velocities()
34 if isinstance(vels, np.ndarray):
35 entry_count += 3
36 else:
37 fd.write('.NO_VELOCITY.\n')
39 fd.write('entry_count = %i\n' % entry_count)
41 i = 0
42 for name, aux in atoms.arrays.items():
43 if name not in cfg_default_fields:
44 if len(aux.shape) == 1:
45 fd.write('auxiliary[%i] = %s [a.u.]\n' % (i, name))
46 i += 1
47 else:
48 if aux.shape[1] == 3:
49 for j in range(3):
50 fd.write('auxiliary[%i] = %s_%s [a.u.]\n' %
51 (i, name, chr(ord('x') + j)))
52 i += 1
54 else:
55 for j in range(aux.shape[1]):
56 fd.write('auxiliary[%i] = %s_%1.1i [a.u.]\n' %
57 (i, name, j))
58 i += 1
60 # Distinct elements
61 spos = atoms.get_scaled_positions()
62 for i in atoms:
63 el = i.symbol
65 fd.write('%f\n' % ase.data.atomic_masses[chemical_symbols.index(el)])
66 fd.write(f'{el}\n')
68 x, y, z = spos[i.index, :]
69 s = f'{x:e} {y:e} {z:e} '
71 if isinstance(vels, np.ndarray):
72 vx, vy, vz = vels[i.index, :]
73 s = s + f' {vx:e} {vy:e} {vz:e} '
75 for name, aux in atoms.arrays.items():
76 if name not in cfg_default_fields:
77 if len(aux.shape) == 1:
78 s += ' %e' % aux[i.index]
79 else:
80 s += (aux.shape[1] * ' %e') % tuple(aux[i.index].tolist())
82 fd.write(f'{s}\n')
85default_color = {
86 'H': [0.800, 0.800, 0.800],
87 'C': [0.350, 0.350, 0.350],
88 'O': [0.800, 0.200, 0.200]}
90default_radius = {'H': 0.435, 'C': 0.655, 'O': 0.730}
93def write_clr(fd, atoms):
94 """Write extra color and radius code to a CLR-file (for use with AtomEye).
95 Hit F12 in AtomEye to use.
96 See: http://mt.seas.upenn.edu/Archive/Graphics/A/
97 """
98 color = None
99 radius = None
100 if atoms.has('color'):
101 color = atoms.get_array('color')
102 if atoms.has('radius'):
103 radius = atoms.get_array('radius')
105 if color is None:
106 color = np.zeros([len(atoms), 3], dtype=float)
107 for a in atoms:
108 color[a.index, :] = default_color[a.symbol]
110 if radius is None:
111 radius = np.zeros(len(atoms), dtype=float)
112 for a in atoms:
113 radius[a.index] = default_radius[a.symbol]
115 radius.shape = (-1, 1)
117 for c1, c2, c3, r in np.append(color, radius, axis=1):
118 fd.write(f'{c1:f} {c2:f} {c3:f} {r:f}\n')
121@reader
122def read_cfg(fd):
123 """Read atomic configuration from a CFG-file (native AtomEye format).
124 See: http://mt.seas.upenn.edu/Archive/Graphics/A/
125 """
126 nat = None
127 naux = 0
128 aux = None
129 auxstrs = None
131 cell = np.zeros([3, 3])
132 transform = np.eye(3)
133 eta = np.zeros([3, 3])
135 current_atom = 0
136 current_symbol = None
137 current_mass = None
139 L = fd.readline()
140 while L:
141 L = L.strip()
142 if len(L) != 0 and not L.startswith('#'):
143 if L == '.NO_VELOCITY.':
144 vels = None
145 naux += 3
146 else:
147 s = L.split('=')
148 if len(s) == 2:
149 key, value = s
150 key = key.strip()
151 value = [x.strip() for x in value.split()]
152 if key == 'Number of particles':
153 nat = int(value[0])
154 spos = np.zeros([nat, 3])
155 masses = np.zeros(nat)
156 syms = [''] * nat
157 vels = np.zeros([nat, 3])
158 if naux > 0:
159 aux = np.zeros([nat, naux])
160 elif key == 'A':
161 pass # unit = float(value[0])
162 elif key == 'entry_count':
163 naux += int(value[0]) - 6
164 auxstrs = [''] * naux
165 if nat is not None:
166 aux = np.zeros([nat, naux])
167 elif key.startswith('H0('):
168 i, j = (int(x) for x in key[3:-1].split(','))
169 cell[i - 1, j - 1] = float(value[0])
170 elif key.startswith('Transform('):
171 i, j = (int(x) for x in key[10:-1].split(','))
172 transform[i - 1, j - 1] = float(value[0])
173 elif key.startswith('eta('):
174 i, j = (int(x) for x in key[4:-1].split(','))
175 eta[i - 1, j - 1] = float(value[0])
176 elif key.startswith('auxiliary['):
177 i = int(key[10:-1])
178 auxstrs[i] = value[0]
179 else:
180 # Everything else must be particle data.
181 # First check if current line contains an element mass or
182 # name. Then we have an extended XYZ format.
183 s = [x.strip() for x in L.split()]
184 if len(s) == 1:
185 if L in chemical_symbols:
186 current_symbol = L
187 else:
188 current_mass = float(L)
189 elif current_symbol is None and current_mass is None:
190 # Standard CFG format
191 masses[current_atom] = float(s[0])
192 syms[current_atom] = s[1]
193 spos[current_atom, :] = [float(x) for x in s[2:5]]
194 vels[current_atom, :] = [float(x) for x in s[5:8]]
195 current_atom += 1
196 elif (current_symbol is not None and
197 current_mass is not None):
198 # Extended CFG format
199 masses[current_atom] = current_mass
200 syms[current_atom] = current_symbol
201 props = [float(x) for x in s]
202 spos[current_atom, :] = props[0:3]
203 off = 3
204 if vels is not None:
205 off = 6
206 vels[current_atom, :] = props[3:6]
207 aux[current_atom, :] = props[off:]
208 current_atom += 1
209 L = fd.readline()
211 # Sanity check
212 if current_atom != nat:
213 raise RuntimeError('Number of atoms reported for CFG file (={}) and '
214 'number of atoms actually read (={}) differ.'
215 .format(nat, current_atom))
217 if np.any(eta != 0):
218 raise NotImplementedError('eta != 0 not yet implemented for CFG '
219 'reader.')
220 cell = np.dot(cell, transform)
222 if vels is None:
223 a = ase.Atoms(
224 symbols=syms,
225 masses=masses,
226 scaled_positions=spos,
227 cell=cell,
228 pbc=True)
229 else:
230 a = ase.Atoms(
231 symbols=syms,
232 masses=masses,
233 scaled_positions=spos,
234 momenta=masses.reshape(-1, 1) * vels,
235 cell=cell,
236 pbc=True)
238 i = 0
239 while i < naux:
240 auxstr = auxstrs[i]
241 if auxstr[-2:] == '_x':
242 a.set_array(auxstr[:-2], aux[:, i:i + 3])
243 i += 3
244 else:
245 a.set_array(auxstr, aux[:, i])
246 i += 1
248 return a