Hide keyboard shortcuts

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# -*- coding: utf-8 -*- 

2# 

3# Copyright (c) 2020, the cclib development team 

4# 

5# This file is part of cclib (http://cclib.github.io) and is distributed under 

6# the terms of the BSD 3-Clause License. 

7 

8"""Parser for Jaguar output files""" 

9 

10 

11import re 

12 

13import numpy 

14 

15from cclib.parser import logfileparser 

16from cclib.parser import utils 

17 

18 

19class Jaguar(logfileparser.Logfile): 

20 """A Jaguar output file""" 

21 

22 def __init__(self, *args, **kwargs): 

23 

24 # Call the __init__ method of the superclass 

25 super(Jaguar, self).__init__(logname="Jaguar", *args, **kwargs) 

26 

27 def __str__(self): 

28 """Return a string representation of the object.""" 

29 return "Jaguar output file %s" % (self.filename) 

30 

31 def __repr__(self): 

32 """Return a representation of the object.""" 

33 return 'Jaguar("%s")' % (self.filename) 

34 

35 def normalisesym(self, label): 

36 """Normalise the symmetries used by Jaguar. 

37 

38 To normalise, three rules need to be applied: 

39 (1) To handle orbitals of E symmetry, retain everything before the / 

40 (2) Replace two p's by " 

41 (2) Replace any remaining single p's by ' 

42 """ 

43 ans = label.split("/")[0].replace("pp", '"').replace("p", "'") 

44 return ans 

45 

46 def before_parsing(self): 

47 

48 # We need to track whether we are inside geometry optimization in order 

49 # to parse SCF targets/values correctly. 

50 self.geoopt = False 

51 

52 def after_parsing(self): 

53 

54 # This is to make sure we always have optdone after geometry optimizations, 

55 # even if it is to be empty for unconverged runs. We have yet to test this 

56 # with a regression for Jaguar, though. 

57 if self.geoopt and not hasattr(self, 'optdone'): 

58 self.optdone = [] 

59 

60 def extract(self, inputfile, line): 

61 """Extract information from the file object inputfile.""" 

62 

63 # Extract the package version number. 

64 if "Jaguar version" in line: 

65 tokens = line.split() 

66 base_version = tokens[3][:-1] 

67 package_version = "{}+{}".format(base_version, tokens[5]) 

68 self.metadata["package_version"] = package_version 

69 self.metadata["legacy_package_version"] = base_version 

70 

71 # Extract the basis set name 

72 if line[2:12] == "basis set:": 

73 self.metadata["basis_set"] = line.split()[2] 

74 

75 # Extract charge and multiplicity 

76 if line[2:22] == "net molecular charge": 

77 self.set_attribute('charge', int(line.split()[-1])) 

78 self.set_attribute('mult', int(next(inputfile).split()[-1])) 

79 

80 # The Gaussian basis set information is printed before the geometry, and we need 

81 # to do some indexing to get this into cclib format, because fn increments 

82 # for each engular momentum, but cclib does not (we have just P instead of 

83 # all three X/Y/Z with the same parameters. On the other hand, fn enumerates 

84 # the atomic orbitals correctly, so use it to build atombasis. 

85 # 

86 # Gaussian basis set information 

87 # 

88 # renorm mfac*renorm 

89 # atom fn prim L z coef coef coef 

90 # -------- ----- ---- --- ------------- ----------- ----------- ----------- 

91 # C1 1 1 S 7.161684E+01 1.5433E-01 2.7078E+00 2.7078E+00 

92 # C1 1 2 S 1.304510E+01 5.3533E-01 2.6189E+00 2.6189E+00 

93 # ... 

94 # C1 3 6 X 2.941249E+00 2.2135E-01 1.2153E+00 1.2153E+00 

95 # 4 Y 1.2153E+00 

96 # 5 Z 1.2153E+00 

97 # C1 2 8 S 2.222899E-01 1.0000E+00 2.3073E-01 2.3073E-01 

98 # C1 3 7 X 6.834831E-01 8.6271E-01 7.6421E-01 7.6421E-01 

99 # ... 

100 # C2 6 1 S 7.161684E+01 1.5433E-01 2.7078E+00 2.7078E+00 

101 # ... 

102 # 

103 if line.strip() == "Gaussian basis set information": 

104 

105 self.skip_lines(inputfile, ['b', 'renorm', 'header', 'd']) 

106 

107 # This is probably the only place we can get this information from Jaguar. 

108 self.gbasis = [] 

109 

110 atombasis = [] 

111 line = next(inputfile) 

112 fn_per_atom = [] 

113 while line.strip(): 

114 

115 if len(line.split()) > 3: 

116 

117 aname = line.split()[0] 

118 fn = int(line.split()[1]) 

119 prim = int(line.split()[2]) 

120 L = line.split()[3] 

121 z = float(line.split()[4]) 

122 coef = float(line.split()[5]) 

123 

124 # The primitive count is reset for each atom, so use that for adding 

125 # new elements to atombasis and gbasis. We could also probably do this 

126 # using the atom name, although that perhaps might not always be unique. 

127 if prim == 1: 

128 atombasis.append([]) 

129 fn_per_atom = [] 

130 self.gbasis.append([]) 

131 

132 # Remember that fn is repeated when functions are contracted. 

133 if not fn-1 in atombasis[-1]: 

134 atombasis[-1].append(fn-1) 

135 

136 # Here we use fn only to know when a new contraction is encountered, 

137 # so we don't need to decrement it, and we don't even use all values. 

138 # What's more, since we only wish to save the parameters for each subshell 

139 # once, we don't even need to consider lines for orbitals other than 

140 # those for X*, making things a bit easier. 

141 if not fn in fn_per_atom: 

142 fn_per_atom.append(fn) 

143 label = {'S': 'S', 'X': 'P', 'XX': 'D', 'XXX': 'F'}[L] 

144 self.gbasis[-1].append((label, [])) 

145 igbasis = fn_per_atom.index(fn) 

146 self.gbasis[-1][igbasis][1].append([z, coef]) 

147 

148 else: 

149 

150 fn = int(line.split()[0]) 

151 L = line.split()[1] 

152 

153 # Some AO indices are only printed in these lines, for L > 0. 

154 if not fn-1 in atombasis[-1]: 

155 atombasis[-1].append(fn-1) 

156 

157 line = next(inputfile) 

158 

159 # The indices for atombasis can also be read later from the molecular orbital output. 

160 self.set_attribute('atombasis', atombasis) 

161 

162 # This length of atombasis should always be the number of atoms. 

163 self.set_attribute('natom', len(self.atombasis)) 

164 

165 # Effective Core Potential 

166 # 

167 # Atom Electrons represented by ECP 

168 # Mo 36 

169 # Maximum angular term 3 

170 # F Potential 1/r^n Exponent Coefficient 

171 # ----- -------- ----------- 

172 # 0 140.4577691 -0.0469492 

173 # 1 89.4739342 -24.9754989 

174 # ... 

175 # S-F Potential 1/r^n Exponent Coefficient 

176 # ----- -------- ----------- 

177 # 0 33.7771969 2.9278406 

178 # 1 10.0120020 34.3483716 

179 # ... 

180 # O 0 

181 # Cl 10 

182 # Maximum angular term 2 

183 # D Potential 1/r^n Exponent Coefficient 

184 # ----- -------- ----------- 

185 # 1 94.8130000 -10.0000000 

186 # ... 

187 if line.strip() == "Effective Core Potential": 

188 

189 self.skip_line(inputfile, 'blank') 

190 line = next(inputfile) 

191 assert line.split()[0] == "Atom" 

192 assert " ".join(line.split()[1:]) == "Electrons represented by ECP" 

193 

194 self.coreelectrons = [] 

195 line = next(inputfile) 

196 while line.strip(): 

197 if len(line.split()) == 2: 

198 self.coreelectrons.append(int(line.split()[1])) 

199 line = next(inputfile) 

200 

201 if line[2:14] == "new geometry" or line[1:21] == "Symmetrized geometry" or line.find("Input geometry") > 0: 

202 # Get the atom coordinates 

203 if not hasattr(self, "atomcoords") or line[1:21] == "Symmetrized geometry": 

204 # Wipe the "Input geometry" if "Symmetrized geometry" present 

205 self.atomcoords = [] 

206 p = re.compile(r"(\D+)\d+") # One/more letters followed by a number 

207 atomcoords = [] 

208 atomnos = [] 

209 angstrom = next(inputfile) 

210 title = next(inputfile) 

211 line = next(inputfile) 

212 while line.strip(): 

213 temp = line.split() 

214 element = p.findall(temp[0])[0] 

215 atomnos.append(self.table.number[element]) 

216 atomcoords.append(list(map(float, temp[1:]))) 

217 line = next(inputfile) 

218 self.atomcoords.append(atomcoords) 

219 self.atomnos = numpy.array(atomnos, "i") 

220 self.set_attribute('natom', len(atomcoords)) 

221 

222 # Hartree-Fock energy after SCF 

223 if line[1:18] == "SCFE: SCF energy:": 

224 self.metadata["methods"].append("HF") 

225 if not hasattr(self, "scfenergies"): 

226 self.scfenergies = [] 

227 temp = line.strip().split() 

228 scfenergy = float(temp[temp.index("hartrees") - 1]) 

229 scfenergy = utils.convertor(scfenergy, "hartree", "eV") 

230 self.scfenergies.append(scfenergy) 

231 

232 # Energy after LMP2 correction 

233 if line[1:18] == "Total LMP2 Energy": 

234 self.metadata["methods"].append("LMP2") 

235 if not hasattr(self, "mpenergies"): 

236 self.mpenergies = [[]] 

237 lmp2energy = float(line.split()[-1]) 

238 lmp2energy = utils.convertor(lmp2energy, "hartree", "eV") 

239 self.mpenergies[-1].append(lmp2energy) 

240 

241 if line[15:45] == "Geometry optimization complete": 

242 if not hasattr(self, 'optdone'): 

243 self.optdone = [] 

244 self.optdone.append(len(self.geovalues) - 1) 

245 

246 if line.find("number of occupied orbitals") > 0: 

247 # Get number of MOs 

248 occs = int(line.split()[-1]) 

249 line = next(inputfile) 

250 virts = int(line.split()[-1]) 

251 self.nmo = occs + virts 

252 self.homos = numpy.array([occs-1], "i") 

253 

254 self.unrestrictedflag = False 

255 

256 if line[1:28] == "number of occupied orbitals": 

257 self.homos = numpy.array([float(line.strip().split()[-1])-1], "i") 

258 

259 if line[2:27] == "number of basis functions": 

260 nbasis = int(line.strip().split()[-1]) 

261 self.set_attribute('nbasis', nbasis) 

262 

263 if line.find("number of alpha occupied orb") > 0: 

264 # Get number of MOs for an unrestricted calc 

265 

266 aoccs = int(line.split()[-1]) 

267 line = next(inputfile) 

268 avirts = int(line.split()[-1]) 

269 line = next(inputfile) 

270 boccs = int(line.split()[-1]) 

271 line = next(inputfile) 

272 bvirt = int(line.split()[-1]) 

273 

274 self.nmo = aoccs + avirts 

275 self.homos = numpy.array([aoccs-1, boccs-1], "i") 

276 self.unrestrictedflag = True 

277 

278 if line[0:4] == "etot": 

279 # Get SCF convergence information 

280 if not hasattr(self, "scfvalues"): 

281 self.scfvalues = [] 

282 self.scftargets = [[5E-5, 5E-6]] 

283 values = [] 

284 while line[0:4] == "etot": 

285 # Jaguar 4.2 

286 # etot 1 N N 0 N -382.08751886450 2.3E-03 1.4E-01 

287 # etot 2 Y Y 0 N -382.27486023153 1.9E-01 1.4E-03 5.7E-02 

288 # Jaguar 6.5 

289 # etot 1 N N 0 N -382.08751881733 2.3E-03 1.4E-01 

290 # etot 2 Y Y 0 N -382.27486018708 1.9E-01 1.4E-03 5.7E-02 

291 temp = line.split()[7:] 

292 if len(temp) == 3: 

293 denergy = float(temp[0]) 

294 else: 

295 denergy = 0 # Should really be greater than target value 

296 # or should we just ignore the values in this line 

297 ddensity = float(temp[-2]) 

298 maxdiiserr = float(temp[-1]) 

299 if not self.geoopt: 

300 values.append([denergy, ddensity]) 

301 else: 

302 values.append([ddensity]) 

303 try: 

304 line = next(inputfile) 

305 except StopIteration: 

306 self.logger.warning('File terminated before end of last SCF! Last error: {}'.format(maxdiiserr)) 

307 break 

308 self.scfvalues.append(values) 

309 

310 # MO energies and symmetries. 

311 # Jaguar 7.0: provides energies and symmetries for both 

312 # restricted and unrestricted calculations, like this: 

313 # Alpha Orbital energies/symmetry label: 

314 # -10.25358 Bu -10.25353 Ag -10.21931 Bu -10.21927 Ag 

315 # -10.21792 Bu -10.21782 Ag -10.21773 Bu -10.21772 Ag 

316 # ... 

317 # Jaguar 6.5: prints both only for restricted calculations, 

318 # so for unrestricted calculations the output it looks like this: 

319 # Alpha Orbital energies: 

320 # -10.25358 -10.25353 -10.21931 -10.21927 -10.21792 -10.21782 

321 # -10.21773 -10.21772 -10.21537 -10.21537 -1.02078 -0.96193 

322 # ... 

323 # Presence of 'Orbital energies' is enough to catch all versions. 

324 if "Orbital energies" in line: 

325 

326 # Parsing results is identical for restricted/unrestricted 

327 # calculations, just assert later that alpha/beta order is OK. 

328 spin = int(line[2:6] == "Beta") 

329 

330 # Check if symmetries are printed also. 

331 issyms = "symmetry label" in line 

332 

333 if not hasattr(self, "moenergies"): 

334 self.moenergies = [] 

335 if issyms and not hasattr(self, "mosyms"): 

336 self.mosyms = [] 

337 

338 # Grow moeneriges/mosyms and make sure they are empty when 

339 # parsed multiple times - currently cclib returns only 

340 # the final output (ex. in a geomtry optimization). 

341 if len(self.moenergies) < spin+1: 

342 self.moenergies.append([]) 

343 self.moenergies[spin] = [] 

344 if issyms: 

345 if len(self.mosyms) < spin+1: 

346 self.mosyms.append([]) 

347 self.mosyms[spin] = [] 

348 

349 line = next(inputfile).split() 

350 while len(line) > 0: 

351 if issyms: 

352 energies = [float(line[2*i]) for i in range(len(line)//2)] 

353 syms = [line[2*i+1] for i in range(len(line)//2)] 

354 else: 

355 energies = [float(e) for e in line] 

356 energies = [utils.convertor(e, "hartree", "eV") for e in energies] 

357 self.moenergies[spin].extend(energies) 

358 if issyms: 

359 syms = [self.normalisesym(s) for s in syms] 

360 self.mosyms[spin].extend(syms) 

361 line = next(inputfile).split() 

362 

363 line = next(inputfile) 

364 

365 # The second trigger string is in the version 8.3 unit test and the first one was 

366 # encountered in version 6.x and is followed by a bit different format. In particular, 

367 # the line with occupations is missing in each block. Here is a fragment of this block 

368 # from version 8.3: 

369 # 

370 # ***************************************** 

371 # 

372 # occupied + virtual orbitals: final wave function 

373 # 

374 # ***************************************** 

375 # 

376 # 

377 # 1 2 3 4 5 

378 # eigenvalues- -11.04064 -11.04058 -11.03196 -11.03196 -11.02881 

379 # occupations- 2.00000 2.00000 2.00000 2.00000 2.00000 

380 # 1 C1 S 0.70148 0.70154 -0.00958 -0.00991 0.00401 

381 # 2 C1 S 0.02527 0.02518 0.00380 0.00374 0.00371 

382 # ... 

383 # 

384 if line.find("Occupied + virtual Orbitals- final wvfn") > 0 or \ 

385 line.find("occupied + virtual orbitals: final wave function") > 0: 

386 

387 self.skip_lines(inputfile, ['b', 's', 'b', 'b']) 

388 

389 if not hasattr(self, "mocoeffs"): 

390 self.mocoeffs = [] 

391 

392 aonames = [] 

393 lastatom = "X" 

394 

395 readatombasis = False 

396 if not hasattr(self, "atombasis"): 

397 self.atombasis = [] 

398 for i in range(self.natom): 

399 self.atombasis.append([]) 

400 readatombasis = True 

401 

402 offset = 0 

403 

404 spin = 1 + int(self.unrestrictedflag) 

405 for s in range(spin): 

406 mocoeffs = numpy.zeros((len(self.moenergies[s]), self.nbasis), "d") 

407 

408 if s == 1: # beta case 

409 self.skip_lines(inputfile, ['s', 'b', 'title', 'b', 's', 'b', 'b']) 

410 

411 for k in range(0, len(self.moenergies[s]), 5): 

412 self.updateprogress(inputfile, "Coefficients") 

413 

414 # All known version have a line with indices followed by the eigenvalues. 

415 self.skip_lines(inputfile, ['numbers', 'eigens']) 

416 

417 # Newer version also have a line with occupation numbers here. 

418 line = next(inputfile) 

419 if "occupations-" in line: 

420 line = next(inputfile) 

421 

422 for i in range(self.nbasis): 

423 

424 info = line.split() 

425 

426 # Fill atombasis only first time around. 

427 if readatombasis and k == 0: 

428 orbno = int(info[0]) 

429 atom = info[1] 

430 if atom[1].isalpha(): 

431 atomno = int(atom[2:]) 

432 else: 

433 atomno = int(atom[1:]) 

434 self.atombasis[atomno-1].append(orbno-1) 

435 

436 if not hasattr(self, "aonames"): 

437 if lastatom != info[1]: 

438 scount = 1 

439 pcount = 3 

440 dcount = 6 # six d orbitals in Jaguar 

441 

442 if info[2] == 'S': 

443 aonames.append("%s_%i%s" % (info[1], scount, info[2])) 

444 scount += 1 

445 

446 if info[2] == 'X' or info[2] == 'Y' or info[2] == 'Z': 

447 aonames.append("%s_%iP%s" % (info[1], pcount / 3, info[2])) 

448 pcount += 1 

449 

450 if info[2] == 'XX' or info[2] == 'YY' or info[2] == 'ZZ' or \ 

451 info[2] == 'XY' or info[2] == 'XZ' or info[2] == 'YZ': 

452 

453 aonames.append("%s_%iD%s" % (info[1], dcount / 6, info[2])) 

454 dcount += 1 

455 

456 lastatom = info[1] 

457 

458 for j in range(len(info[3:])): 

459 mocoeffs[j+k, i] = float(info[3+j]) 

460 

461 line = next(inputfile) 

462 

463 if not hasattr(self, "aonames"): 

464 self.aonames = aonames 

465 

466 offset += 5 

467 self.mocoeffs.append(mocoeffs) 

468 

469 # Atomic charges from Mulliken population analysis: 

470 # 

471 # Atom C1 C2 C3 C4 C5 

472 # Charge 0.00177 -0.06075 -0.05956 0.00177 -0.06075 

473 # 

474 # Atom H6 H7 H8 C9 C10 

475 # ... 

476 if line.strip() == "Atomic charges from Mulliken population analysis:": 

477 

478 if not hasattr(self, 'atomcharges'): 

479 self.atomcharges = {} 

480 

481 charges = [] 

482 self.skip_line(inputfile, "blank") 

483 line = next(inputfile) 

484 while "sum of atomic charges" not in line: 

485 assert line.split()[0] == "Atom" 

486 line = next(inputfile) 

487 assert line.split()[0] == "Charge" 

488 charges.extend([float(c) for c in line.split()[1:]]) 

489 self.skip_line(inputfile, "blank") 

490 line = next(inputfile) 

491 

492 self.atomcharges['mulliken'] = charges 

493 

494 if (line[2:6] == "olap") or (line.strip() == "overlap matrix:"): 

495 

496 if line[6] == "-": 

497 return 

498 # This was continue (in loop) before parser refactoring. 

499 # continue # avoid "olap-dev" 

500 self.aooverlaps = numpy.zeros((self.nbasis, self.nbasis), "d") 

501 

502 for i in range(0, self.nbasis, 5): 

503 self.updateprogress(inputfile, "Overlap") 

504 

505 self.skip_lines(inputfile, ['b', 'header']) 

506 

507 for j in range(i, self.nbasis): 

508 temp = list(map(float, next(inputfile).split()[1:])) 

509 self.aooverlaps[j, i:(i+len(temp))] = temp 

510 self.aooverlaps[i:(i+len(temp)), j] = temp 

511 

512 if line[2:24] == "start of program geopt": 

513 if not self.geoopt: 

514 # Need to keep only the RMS density change info 

515 # if this is a geooptz 

516 self.scftargets = [[self.scftargets[0][0]]] 

517 if hasattr(self, "scfvalues"): 

518 self.scfvalues[0] = [[x[0]] for x in self.scfvalues[0]] 

519 self.geoopt = True 

520 else: 

521 self.scftargets.append([5E-5]) 

522 

523 # Get Geometry Opt convergence information 

524 # 

525 # geometry optimization step 7 

526 # energy: -382.30219111487 hartrees 

527 # [ turning on trust-radius adjustment ] 

528 # ** restarting optimization from step 6 ** 

529 # 

530 # 

531 # Level shifts adjusted to satisfy step-size constraints 

532 # Step size: 0.0360704 

533 # Cos(theta): 0.8789215 

534 # Final level shift: -8.6176299E-02 

535 # 

536 # energy change: 2.5819E-04 . ( 5.0000E-05 ) 

537 # gradient maximum: 5.0947E-03 . ( 4.5000E-04 ) 

538 # gradient rms: 1.2996E-03 . ( 3.0000E-04 ) 

539 # displacement maximum: 1.3954E-02 . ( 1.8000E-03 ) 

540 # displacement rms: 4.6567E-03 . ( 1.2000E-03 ) 

541 # 

542 if line[2:28] == "geometry optimization step": 

543 

544 if not hasattr(self, "geovalues"): 

545 self.geovalues = [] 

546 self.geotargets = numpy.zeros(5, "d") 

547 

548 gopt_step = int(line.split()[-1]) 

549 

550 energy = next(inputfile) 

551 blank = next(inputfile) 

552 

553 # A quick hack for messages that show up right after the energy 

554 # at this point, which include: 

555 # ** restarting optimization from step 2 ** 

556 # [ turning on trust-radius adjustment ] 

557 # as found in regression file ptnh3_2_H2O_2_2plus.out and other logfiles. 

558 restarting_from_1 = False 

559 while blank.strip(): 

560 if blank.strip() == "** restarting optimization from step 1 **": 

561 restarting_from_1 = True 

562 blank = next(inputfile) 

563 

564 # One or more blank lines, depending on content. 

565 line = next(inputfile) 

566 while not line.strip(): 

567 line = next(inputfile) 

568 

569 # Note that the level shift message is followed by a blank, too. 

570 if "Level shifts adjusted" in line: 

571 while line.strip(): 

572 line = next(inputfile) 

573 line = next(inputfile) 

574 

575 # The first optimization step does not produce an energy change, and 

576 # ther is also no energy change when the optimization is restarted 

577 # from step 1 (since step 1 had no change). 

578 values = [] 

579 target_index = 0 

580 if (gopt_step == 1) or restarting_from_1: 

581 values.append(0.0) 

582 target_index = 1 

583 while line.strip(): 

584 if len(line) > 40 and line[41] == "(": 

585 # A new geo convergence value 

586 values.append(float(line[26:37])) 

587 self.geotargets[target_index] = float(line[43:54]) 

588 target_index += 1 

589 line = next(inputfile) 

590 self.geovalues.append(values) 

591 

592 # IR output looks like this: 

593 # frequencies 72.45 113.25 176.88 183.76 267.60 312.06 

594 # symmetries Au Bg Au Bu Ag Bg 

595 # intensities 0.07 0.00 0.28 0.52 0.00 0.00 

596 # reduc. mass 1.90 0.74 1.06 1.42 1.19 0.85 

597 # force const 0.01 0.01 0.02 0.03 0.05 0.05 

598 # C1 X 0.00000 0.00000 0.00000 -0.05707 -0.06716 0.00000 

599 # C1 Y 0.00000 0.00000 0.00000 0.00909 -0.02529 0.00000 

600 # C1 Z 0.04792 -0.06032 -0.01192 0.00000 0.00000 0.11613 

601 # C2 X 0.00000 0.00000 0.00000 -0.06094 -0.04635 0.00000 

602 # ... etc. ... 

603 # This is a complete ouput, some files will not have intensities, 

604 # and older Jaguar versions sometimes skip the symmetries. 

605 if line[2:23] == "start of program freq": 

606 

607 self.skip_line(inputfile, 'blank') 

608 

609 # Version 8.3 has two blank lines here, earlier versions just one. 

610 line = next(inputfile) 

611 if not line.strip(): 

612 line = next(inputfile) 

613 

614 self.vibfreqs = [] 

615 self.vibdisps = [] 

616 self.vibrmasses = [] 

617 forceconstants = False 

618 intensities = False 

619 while line.strip(): 

620 if "force const" in line: 

621 forceconstants = True 

622 if "intensities" in line: 

623 intensities = True 

624 line = next(inputfile) 

625 

626 # In older version, the last block had an extra blank line after it, 

627 # which could be caught. This is not true in newer version (including 8.3), 

628 # but in general it would be better to bound this loop more strictly. 

629 freqs = next(inputfile) 

630 while freqs.strip() and not "imaginary frequencies" in freqs: 

631 

632 # Number of modes (columns printed in this block). 

633 nmodes = len(freqs.split())-1 

634 

635 # Append the frequencies. 

636 self.vibfreqs.extend(list(map(float, freqs.split()[1:]))) 

637 line = next(inputfile).split() 

638 

639 # May skip symmetries (older Jaguar versions). 

640 if line[0] == "symmetries": 

641 if not hasattr(self, "vibsyms"): 

642 self.vibsyms = [] 

643 self.vibsyms.extend(list(map(self.normalisesym, line[1:]))) 

644 line = next(inputfile).split() 

645 if intensities: 

646 if not hasattr(self, "vibirs"): 

647 self.vibirs = [] 

648 self.vibirs.extend(list(map(float, line[1:]))) 

649 line = next(inputfile).split() 

650 self.vibrmasses.extend(list(map(float, line[2:]))) 

651 if forceconstants: 

652 line = next(inputfile).split() 

653 if not hasattr(self, "vibfconsts"): 

654 self.vibfconsts = [] 

655 self.vibfconsts.extend(list(map(float, line[2:]))) 

656 

657 # Start parsing the displacements. 

658 # Variable 'q' holds up to 7 lists of triplets. 

659 q = [[] for i in range(7)] 

660 for n in range(self.natom): 

661 # Variable 'p' holds up to 7 triplets. 

662 p = [[] for i in range(7)] 

663 for i in range(3): 

664 line = next(inputfile) 

665 disps = [float(disp) for disp in line.split()[2:]] 

666 for j in range(nmodes): 

667 p[j].append(disps[j]) 

668 for i in range(nmodes): 

669 q[i].append(p[i]) 

670 

671 self.vibdisps.extend(q[:nmodes]) 

672 

673 self.skip_line(inputfile, 'blank') 

674 freqs = next(inputfile) 

675 

676 # Convert new data to arrays. 

677 self.vibfreqs = numpy.array(self.vibfreqs, "d") 

678 self.vibdisps = numpy.array(self.vibdisps, "d") 

679 self.vibrmasses = numpy.array(self.vibrmasses, "d") 

680 if hasattr(self, "vibirs"): 

681 self.vibirs = numpy.array(self.vibirs, "d") 

682 if hasattr(self, "vibfconsts"): 

683 self.vibfconsts = numpy.array(self.vibfconsts, "d") 

684 

685 # Parse excited state output (for CIS calculations). 

686 # Jaguar calculates only singlet states. 

687 if line[2:15] == "Excited State": 

688 if not hasattr(self, "etenergies"): 

689 self.etenergies = [] 

690 if not hasattr(self, "etoscs"): 

691 self.etoscs = [] 

692 if not hasattr(self, "etsecs"): 

693 self.etsecs = [] 

694 self.etsyms = [] 

695 etenergy = float(line.split()[3]) 

696 etenergy = utils.convertor(etenergy, "eV", "wavenumber") 

697 self.etenergies.append(etenergy) 

698 

699 self.skip_lines(inputfile, ['line', 'line', 'line', 'line']) 

700 

701 line = next(inputfile) 

702 self.etsecs.append([]) 

703 # Jaguar calculates only singlet states. 

704 self.etsyms.append('Singlet-A') 

705 while line.strip() != "": 

706 fromMO = int(line.split()[0])-1 

707 toMO = int(line.split()[2])-1 

708 coeff = float(line.split()[-1]) 

709 self.etsecs[-1].append([(fromMO, 0), (toMO, 0), coeff]) 

710 line = next(inputfile) 

711 # Skip 3 lines 

712 for i in range(4): 

713 line = next(inputfile) 

714 strength = float(line.split()[-1]) 

715 self.etoscs.append(strength) 

716 

717 if line[:20] == ' Total elapsed time:' \ 

718 or line[:18] == ' Total cpu seconds': 

719 self.metadata['success'] = True