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
1"""This module contains functions to read from QBox output files"""
3from ase import Atom, Atoms
4from ase.calculators.singlepoint import SinglePointCalculator
5from ase.utils import reader
7import re
8import xml.etree.ElementTree as ET
11# Compile regexs for fixing XML
12re_find_bad_xml = re.compile(r'<(/?)([A-z]+) expectation ([a-z]+)')
15@reader
16def read_qbox(f, index=-1):
17 """Read data from QBox output file
19 Inputs:
20 f - str or fileobj, path to file or file object to read from
21 index - int or slice, which frames to return
23 Returns:
24 list of Atoms or atoms, requested frame(s)
25 """
27 # Check whether this is a QB@all output
28 version = None
29 for line in f:
30 if '<release>' in line:
31 version = ET.fromstring(line)
32 break
33 if version is None:
34 raise Exception('Parse Error: Version not found')
35 is_qball = 'qb@LL' in version.text or 'qball' in version.text
37 # Load in atomic species
38 species = dict()
39 if is_qball:
40 # Read all of the lines between release and the first call to `run`
41 species_data = []
42 for line in f:
43 if '<run' in line:
44 break
45 species_data.append(line)
46 species_data = '\n'.join(species_data)
48 # Read out the species information with regular expressions
49 symbols = re.findall('symbol_ = ([A-Z][a-z]?)', species_data)
50 masses = re.findall('mass_ = ([0-9.]+)', species_data)
51 names = re.findall('name_ = ([a-z]+)', species_data)
52 numbers = re.findall('atomic_number_ = ([0-9]+)', species_data)
54 # Compile them into a dictionary
55 for name, symbol, mass, number in zip(names, symbols, masses, numbers):
56 spec_data = dict(
57 symbol=symbol,
58 mass=float(mass),
59 number=float(number)
60 )
61 species[name] = spec_data
62 else:
63 # Find all species
64 species_blocks = _find_blocks(f, 'species', '<cmd>run')
66 for spec in species_blocks:
67 name = spec.get('name')
68 spec_data = dict(
69 symbol=spec.find('symbol').text,
70 mass=float(spec.find('mass').text),
71 number=int(spec.find('atomic_number').text))
72 species[name] = spec_data
74 # Find all of the frames
75 frames = _find_blocks(f, 'iteration', None)
77 # If index is an int, return one frame
78 if isinstance(index, int):
79 return _parse_frame(frames[index], species)
80 else:
81 return [_parse_frame(frame, species) for frame in frames[index]]
84def _find_blocks(fp, tag, stopwords='[qbox]'):
85 """Find and parse a certain block of the file.
87 Reads a file sequentially and stops when it either encounters the
88 end of the file, or until the it encounters a line that contains a
89 user-defined string *after it has already found at least one
90 desired block*. Use the stopwords ``[qbox]`` to read until the next
91 command is issued.
93 Groups the text between the first line that contains <tag> and the
94 next line that contains </tag>, inclusively. The function then
95 parses the XML and returns the Element object.
97 Inputs:
98 fp - file-like object, file to be read from
99 tag - str, tag to search for (e.g., 'iteration').
100 `None` if you want to read until the end of the file
101 stopwords - str, halt parsing if a line containing this string
102 is encountered
104 Returns:
105 list of xml.ElementTree, parsed XML blocks found by this class
106 """
108 start_tag = '<%s' % tag
109 end_tag = '</%s>' % tag
111 blocks = [] # Stores all blocks
112 cur_block = [] # Block being filled
113 in_block = False # Whether we are currently parsing
114 for line in fp:
116 # Check if the block has started
117 if start_tag in line:
118 if in_block:
119 raise Exception('Parsing failed: Encountered nested block')
120 else:
121 in_block = True
123 # Add data to block
124 if in_block:
125 cur_block.append(line)
127 # Check for stopping conditions
128 if stopwords is not None:
129 if stopwords in line and len(blocks) > 0:
130 break
132 if end_tag in line:
133 if in_block:
134 blocks.append(cur_block)
135 cur_block = []
136 in_block = False
137 else:
138 raise Exception('Parsing failed: End tag found before start '
139 'tag')
141 # Join strings in a block into a single string
142 blocks = [''.join(b) for b in blocks]
144 # Ensure XML compatibility. There are two specific tags in QBall that are
145 # not valid XML, so we need to run a
146 blocks = [re_find_bad_xml.sub(r'<\1\2_expectation_\3', b) for b in blocks]
148 # Parse the blocks
149 return [ET.fromstring(b) for b in blocks]
152def _parse_frame(tree, species):
153 """Parse a certain frame from QBOX output
155 Inputs:
156 tree - ElementTree, <iteration> block from output file
157 species - dict, data about species. Key is name of atom type,
158 value is data about that type
159 Return:
160 Atoms object describing this iteration"""
162 # Load in data about the system
163 energy = float(tree.find("etotal").text)
165 # Load in data about the cell
166 unitcell = tree.find('atomset').find('unit_cell')
167 cell = []
168 for d in ['a', 'b', 'c']:
169 cell.append([float(x) for x in unitcell.get(d).split()])
171 stress_tree = tree.find('stress_tensor')
172 if stress_tree is None:
173 stresses = None
174 else:
175 stresses = [float(stress_tree.find('sigma_%s' % x).text)
176 for x in ['xx', 'yy', 'zz', 'yz', 'xz', 'xy']]
178 # Create the Atoms object
179 atoms = Atoms(pbc=True, cell=cell)
181 # Load in the atom information
182 forces = []
183 for atom in tree.find('atomset').findall('atom'):
184 # Load data about the atom type
185 spec = atom.get('species')
186 symbol = species[spec]['symbol']
187 mass = species[spec]['mass']
189 # Get data about position / velocity / force
190 pos = [float(x) for x in atom.find('position').text.split()]
191 force = [float(x) for x in atom.find('force').text.split()]
192 momentum = [float(x) * mass
193 for x in atom.find('velocity').text.split()]
195 # Create the objects
196 atom = Atom(symbol=symbol, mass=mass, position=pos, momentum=momentum)
197 atoms += atom
198 forces.append(force)
200 # Create the calculator object that holds energy/forces
201 calc = SinglePointCalculator(atoms,
202 energy=energy, forces=forces, stress=stresses)
203 atoms.calc = calc
205 return atoms