Coverage for /builds/ase/ase/ase/io/xtd.py: 67.83%
115 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 xml.etree.ElementTree as ET
4from xml.dom import minidom
6import numpy as np
8from ase import Atoms
9from ase.io.xsd import SetChild, _write_xsd_html
11_image_header = ' ' * 74 + '0.0000\n!DATE Jan 01 00:00:00 2000\n'
12_image_footer = 'end\nend\n'
15def _get_atom_str(an, xyz):
16 s = f'{an:<5}'
17 s += f'{xyz[0]:>15.9f}{xyz[1]:>15.9f}{xyz[2]:>15.9f}'
18 s += ' XXXX 1 xx '
19 s += f'{an:<2}'
20 s += ' 0.000\n'
21 return s
24def write_xtd(filename, images, connectivity=None, moviespeed=10):
25 """Takes Atoms object, and write materials studio file
26 atoms: Atoms object
27 filename: path of the output file
28 moviespeed: speed of animation. between 0 and 10
30 note: material studio file cannot use a partial periodic system. If partial
31 perodic system was inputted, full periodicity was assumed.
32 """
33 if moviespeed < 0 or moviespeed > 10:
34 raise ValueError('moviespeed only between 0 and 10 allowed')
36 if hasattr(images, 'get_positions'):
37 images = [images]
39 XSD, ATR = _write_xsd_html(images, connectivity)
40 ATR.attrib['NumChildren'] = '2'
41 natoms = len(images[0])
43 bonds = []
44 if connectivity is not None:
45 for i in range(connectivity.shape[0]):
46 for j in range(i + 1, connectivity.shape[0]):
47 if connectivity[i, j]:
48 bonds.append([i, j])
50 # non-periodic system
51 s = '!BIOSYM archive 3\n'
52 if not images[0].pbc.all():
53 # Write trajectory
54 SetChild(ATR, 'Trajectory', dict(
55 ID=str(natoms + 3 + len(bonds)),
56 Increment='-1',
57 End=str(len(images)),
58 Type='arc',
59 Speed=str(moviespeed),
60 FrameClassType='Atom',
61 ))
63 # write frame information file
64 s += 'PBC=OFF\n'
65 for image in images:
66 s += _image_header
67 s += '\n'
68 an = image.get_chemical_symbols()
69 xyz = image.get_positions()
70 for i in range(natoms):
71 s += _get_atom_str(an[i], xyz[i, :])
72 s += _image_footer
74 # periodic system
75 else:
76 SetChild(ATR, 'Trajectory', dict(
77 ID=str(natoms + 9 + len(bonds)),
78 Increment='-1',
79 End=str(len(images)),
80 Type='arc',
81 Speed=str(moviespeed),
82 FrameClassType='Atom',
83 ))
85 # write frame information file
86 s += 'PBC=ON\n'
87 for image in images:
88 s += _image_header
89 s += 'PBC'
90 vec = image.cell.lengths()
91 s += f'{vec[0]:>10.4f}{vec[1]:>10.4f}{vec[2]:>10.4f}'
92 angles = image.cell.angles()
93 s += '{:>10.4f}{:>10.4f}{:>10.4f}'.format(*angles)
94 s += '\n'
95 an = image.get_chemical_symbols()
97 angrad = np.deg2rad(angles)
98 cell = np.zeros((3, 3))
99 cell[0, :] = [vec[0], 0, 0]
100 cell[1, :] = (np.array([np.cos(angrad[2]), np.sin(angrad[2]), 0])
101 * vec[1])
102 cell[2, 0] = vec[2] * np.cos(angrad[1])
103 cell[2, 1] = ((vec[1] * vec[2] * np.cos(angrad[0])
104 - cell[1, 0] * cell[2, 0]) / cell[1, 1])
105 cell[2, 2] = np.sqrt(vec[2]**2 - cell[2, 0]**2 - cell[2, 1]**2)
106 xyz = np.dot(image.get_scaled_positions(), cell)
107 for i in range(natoms):
108 s += _get_atom_str(an[i], xyz[i, :])
109 s += _image_footer
111 # print arc file
112 if isinstance(filename, str):
113 farcname = filename[:-3] + 'arc'
114 else:
115 farcname = filename.name[:-3] + 'arc'
117 with open(farcname, 'w') as farc:
118 farc.write(s)
120 # check if file is an object or not.
121 openandclose = False
122 try:
123 if isinstance(filename, str):
124 fd = open(filename, 'w')
125 openandclose = True
126 else: # Assume it's a 'file-like object'
127 fd = filename
129 # Return a pretty-printed XML string for the Element.
130 rough_string = ET.tostring(XSD, 'utf-8')
131 reparsed = minidom.parseString(rough_string)
132 Document = reparsed.toprettyxml(indent='\t')
134 # write
135 fd.write(Document)
136 finally:
137 if openandclose:
138 fd.close()
141def read_xtd(filename, index=-1):
142 """Import xtd file (Materials Studio)
144 Xtd files always come with arc file, and arc file
145 contains all the relevant information to make atoms
146 so only Arc file needs to be read
147 """
148 if isinstance(filename, str):
149 arcfilename = filename[:-3] + 'arc'
150 else:
151 arcfilename = filename.name[:-3] + 'arc'
153 # This trick with opening a totally different file is a gross violation of
154 # common sense.
155 with open(arcfilename) as fd:
156 return read_arcfile(fd, index)
159def read_arcfile(fd, index):
160 images = []
162 # the first line is comment
163 fd.readline()
164 pbc = 'ON' in fd.readline()
165 L = fd.readline()
166 while L != '':
167 if '!' not in L: # flag for the start of an image
168 L = fd.readline()
169 continue
170 if pbc:
171 L = fd.readline()
172 cell = [float(d) for d in L.split()[1:]]
173 else:
174 fd.readline()
175 symbols = []
176 coords = []
177 while True:
178 line = fd.readline()
179 L = line.split()
180 if not line or 'end' in L:
181 break
182 symbols.append(L[0])
183 coords.append([float(x) for x in L[1:4]])
184 if pbc:
185 image = Atoms(symbols, positions=coords, cell=cell, pbc=pbc)
186 else:
187 image = Atoms(symbols, positions=coords, pbc=pbc)
188 images.append(image)
189 L = fd.readline()
191 if not index:
192 return images
193 else:
194 return images[index]