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