Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import numpy as np
2from math import sqrt
3from itertools import islice
5from ase.io.formats import string2index
6from ase.utils import rotate
7from ase.data import covalent_radii, atomic_numbers
8from ase.data.colors import jmol_colors
11class PlottingVariables:
12 # removed writer - self
13 def __init__(self, atoms, rotation='', show_unit_cell=2,
14 radii=None, bbox=None, colors=None, scale=20,
15 maxwidth=500, extra_offset=(0., 0.)):
16 self.numbers = atoms.get_atomic_numbers()
17 self.colors = colors
18 if colors is None:
19 ncolors = len(jmol_colors)
20 self.colors = jmol_colors[self.numbers.clip(max=ncolors - 1)]
22 if radii is None:
23 radii = covalent_radii[self.numbers]
24 elif isinstance(radii, float):
25 radii = covalent_radii[self.numbers] * radii
26 else:
27 radii = np.array(radii)
29 natoms = len(atoms)
31 if isinstance(rotation, str):
32 rotation = rotate(rotation)
34 cell = atoms.get_cell()
35 disp = atoms.get_celldisp().flatten()
37 if show_unit_cell > 0:
38 L, T, D = cell_to_lines(self, cell)
39 cell_vertices = np.empty((2, 2, 2, 3))
40 for c1 in range(2):
41 for c2 in range(2):
42 for c3 in range(2):
43 cell_vertices[c1, c2, c3] = np.dot([c1, c2, c3],
44 cell) + disp
45 cell_vertices.shape = (8, 3)
46 cell_vertices = np.dot(cell_vertices, rotation)
47 else:
48 L = np.empty((0, 3))
49 T = None
50 D = None
51 cell_vertices = None
53 nlines = len(L)
55 positions = np.empty((natoms + nlines, 3))
56 R = atoms.get_positions()
57 positions[:natoms] = R
58 positions[natoms:] = L
60 r2 = radii**2
61 for n in range(nlines):
62 d = D[T[n]]
63 if ((((R - L[n] - d)**2).sum(1) < r2) &
64 (((R - L[n] + d)**2).sum(1) < r2)).any():
65 T[n] = -1
67 positions = np.dot(positions, rotation)
68 R = positions[:natoms]
70 if bbox is None:
71 X1 = (R - radii[:, None]).min(0)
72 X2 = (R + radii[:, None]).max(0)
73 if show_unit_cell == 2:
74 X1 = np.minimum(X1, cell_vertices.min(0))
75 X2 = np.maximum(X2, cell_vertices.max(0))
76 M = (X1 + X2) / 2
77 S = 1.05 * (X2 - X1)
78 w = scale * S[0]
79 if w > maxwidth:
80 w = maxwidth
81 scale = w / S[0]
82 h = scale * S[1]
83 offset = np.array([scale * M[0] - w / 2, scale * M[1] - h / 2, 0])
84 else:
85 w = (bbox[2] - bbox[0]) * scale
86 h = (bbox[3] - bbox[1]) * scale
87 offset = np.array([bbox[0], bbox[1], 0]) * scale
89 offset[0] = offset[0] - extra_offset[0]
90 offset[1] = offset[1] - extra_offset[1]
91 self.w = w + extra_offset[0]
92 self.h = h + extra_offset[1]
94 positions *= scale
95 positions -= offset
97 if nlines > 0:
98 D = np.dot(D, rotation)[:, :2] * scale
100 if cell_vertices is not None:
101 cell_vertices *= scale
102 cell_vertices -= offset
104 cell = np.dot(cell, rotation)
105 cell *= scale
107 self.cell = cell
108 self.positions = positions
109 self.D = D
110 self.T = T
111 self.cell_vertices = cell_vertices
112 self.natoms = natoms
113 self.d = 2 * scale * radii
114 self.constraints = atoms.constraints
116 # extension for partial occupancies
117 self.frac_occ = False
118 self.tags = None
119 self.occs = None
121 try:
122 self.occs = atoms.info['occupancy']
123 self.tags = atoms.get_tags()
124 self.frac_occ = True
125 except KeyError:
126 pass
129def cell_to_lines(writer, cell):
130 # XXX this needs to be updated for cell vectors that are zero.
131 # Cannot read the code though! (What are T and D? nn?)
132 nlines = 0
133 nsegments = []
134 for c in range(3):
135 d = sqrt((cell[c]**2).sum())
136 n = max(2, int(d / 0.3))
137 nsegments.append(n)
138 nlines += 4 * n
140 positions = np.empty((nlines, 3))
141 T = np.empty(nlines, int)
142 D = np.zeros((3, 3))
144 n1 = 0
145 for c in range(3):
146 n = nsegments[c]
147 dd = cell[c] / (4 * n - 2)
148 D[c] = dd
149 P = np.arange(1, 4 * n + 1, 4)[:, None] * dd
150 T[n1:] = c
151 for i, j in [(0, 0), (0, 1), (1, 0), (1, 1)]:
152 n2 = n1 + n
153 positions[n1:n2] = P + i * cell[c - 2] + j * cell[c - 1]
154 n1 = n2
156 return positions, T, D
159def make_patch_list(writer):
160 from matplotlib.path import Path
161 from matplotlib.patches import Circle, PathPatch, Wedge
163 indices = writer.positions[:, 2].argsort()
164 patch_list = []
165 for a in indices:
166 xy = writer.positions[a, :2]
167 if a < writer.natoms:
168 r = writer.d[a] / 2
169 if writer.frac_occ:
170 site_occ = writer.occs[str(writer.tags[a])]
171 # first an empty circle if a site is not fully occupied
172 if (np.sum([v for v in site_occ.values()])) < 1.0:
173 # fill with white
174 fill = '#ffffff'
175 patch = Circle(xy, r, facecolor=fill,
176 edgecolor='black')
177 patch_list.append(patch)
179 start = 0
180 # start with the dominant species
181 for sym, occ in sorted(site_occ.items(),
182 key=lambda x: x[1],
183 reverse=True):
184 if np.round(occ, decimals=4) == 1.0:
185 patch = Circle(xy, r, facecolor=writer.colors[a],
186 edgecolor='black')
187 patch_list.append(patch)
188 else:
189 # jmol colors for the moment
190 extent = 360. * occ
191 patch = Wedge(
192 xy, r, start, start + extent,
193 facecolor=jmol_colors[atomic_numbers[sym]],
194 edgecolor='black')
195 patch_list.append(patch)
196 start += extent
198 else:
199 if ((xy[1] + r > 0) and (xy[1] - r < writer.h) and
200 (xy[0] + r > 0) and (xy[0] - r < writer.w)):
201 patch = Circle(xy, r, facecolor=writer.colors[a],
202 edgecolor='black')
203 patch_list.append(patch)
204 else:
205 a -= writer.natoms
206 c = writer.T[a]
207 if c != -1:
208 hxy = writer.D[c]
209 patch = PathPatch(Path((xy + hxy, xy - hxy)))
210 patch_list.append(patch)
211 return patch_list
214class ImageChunk:
215 """Base Class for a file chunk which contains enough information to
216 reconstruct an atoms object."""
218 def build(self, **kwargs):
219 """Construct the atoms object from the stored information,
220 and return it"""
221 pass
224class ImageIterator:
225 """Iterate over chunks, to return the corresponding Atoms objects.
226 Will only build the atoms objects which corresponds to the requested
227 indices when called.
228 Assumes ``ichunks`` is in iterator, which returns ``ImageChunk``
229 type objects. See extxyz.py:iread_xyz as an example.
230 """
232 def __init__(self, ichunks):
233 self.ichunks = ichunks
235 def __call__(self, fd, index=None, **kwargs):
236 if isinstance(index, str):
237 index = string2index(index)
239 if index is None or index == ':':
240 index = slice(None, None, None)
242 if not isinstance(index, (slice, str)):
243 index = slice(index, (index + 1) or None)
245 for chunk in self._getslice(fd, index):
246 yield chunk.build(**kwargs)
248 def _getslice(self, fd, indices):
249 try:
250 iterator = islice(self.ichunks(fd),
251 indices.start, indices.stop,
252 indices.step)
253 except ValueError:
254 # Negative indices. Go through the whole thing to get the length,
255 # which allows us to evaluate the slice, and then read it again
256 if not hasattr(fd, 'seekable') or not fd.seekable():
257 raise ValueError(('Negative indices only supported for '
258 'seekable streams'))
260 startpos = fd.tell()
261 nchunks = 0
262 for chunk in self.ichunks(fd):
263 nchunks += 1
264 fd.seek(startpos)
265 indices_tuple = indices.indices(nchunks)
266 iterator = islice(self.ichunks(fd), *indices_tuple)
267 return iterator
270def verify_cell_for_export(cell, check_orthorhombric=True):
271 """Function to verify if the cell size is defined and if the cell is
273 Parameters:
275 cell: cell object
276 cell to be checked.
278 check_orthorhombric: bool
279 If True, check if the cell is orthorhombric, raise an ``ValueError`` if
280 the cell is orthorhombric. If False, doesn't check if the cell is
281 orthorhombric.
283 Raise a ``ValueError`` if the cell if not suitable for export to mustem xtl
284 file or prismatic/computem xyz format:
285 - if cell is not orthorhombic (only when check_orthorhombric=True)
286 - if cell size is not defined
287 """
289 if check_orthorhombric and not cell.orthorhombic:
290 raise ValueError('To export to this format, the cell needs to be '
291 'orthorhombic.')
292 if cell.rank < 3:
293 raise ValueError('To export to this format, the cell size needs '
294 'to be set: current cell is {}.'.format(cell))
297def verify_dictionary(atoms, dictionary, dictionary_name):
298 """
299 Verify a dictionary have a key for each symbol present in the atoms object.
301 Parameters:
303 dictionary: dict
304 Dictionary to be checked.
307 dictionary_name: dict
308 Name of the dictionary to be displayed in the error message.
310 cell: cell object
311 cell to be checked.
314 Raise a ``ValueError`` if the key doesn't match the atoms present in the
315 cell.
316 """
317 # Check if we have enough key
318 for key in set(atoms.symbols):
319 if key not in dictionary:
320 raise ValueError('Missing the {} key in the `{}` dictionary.'
321 ''.format(key, dictionary_name))