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