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'''Constant pressure/stress and temperature dynamics. 

2 

3Combined Nose-Hoover and Parrinello-Rahman dynamics, creating an NPT 

4(or N,stress,T) ensemble. 

5 

6The method is the one proposed by Melchionna et al. [1] and later 

7modified by Melchionna [2]. The differential equations are integrated 

8using a centered difference method [3]. 

9 

10 1. S. Melchionna, G. Ciccotti and B. L. Holian, "Hoover NPT dynamics 

11 for systems varying in shape and size", Molecular Physics 78, p. 533 

12 (1993). 

13 

14 2. S. Melchionna, "Constrained systems and statistical distribution", 

15 Physical Review E, 61, p. 6165 (2000). 

16 

17 3. B. L. Holian, A. J. De Groot, W. G. Hoover, and C. G. Hoover, 

18 "Time-reversible equilibrium and nonequilibrium isothermal-isobaric 

19 simulations with centered-difference Stoermer algorithms.", Physical 

20 Review A, 41, p. 4552 (1990). 

21''' 

22 

23import sys 

24import weakref 

25 

26import numpy as np 

27 

28from ase.md.md import MolecularDynamics 

29from ase import units 

30 

31linalg = np.linalg 

32 

33# Delayed imports: If the trajectory object is reading a special ASAP version 

34# of HooverNPT, that class is imported from Asap.Dynamics.NPTDynamics. 

35 

36 

37class NPT(MolecularDynamics): 

38 

39 classname = "NPT" # Used by the trajectory. 

40 _npt_version = 2 # Version number, used for Asap compatibility. 

41 

42 def __init__(self, atoms, 

43 timestep, temperature=None, externalstress=None, 

44 ttime=None, pfactor=None, 

45 *, temperature_K=None, 

46 mask=None, trajectory=None, logfile=None, loginterval=1, 

47 append_trajectory=False): 

48 '''Constant pressure/stress and temperature dynamics. 

49 

50 Combined Nose-Hoover and Parrinello-Rahman dynamics, creating an 

51 NPT (or N,stress,T) ensemble. 

52 

53 The method is the one proposed by Melchionna et al. [1] and later 

54 modified by Melchionna [2]. The differential equations are integrated 

55 using a centered difference method [3]. See also NPTdynamics.tex 

56 

57 The dynamics object is called with the following parameters: 

58 

59 atoms: Atoms object 

60 The list of atoms. 

61 

62 timestep: float 

63 The timestep in units matching eV, Å, u. 

64 

65 temperature: float (deprecated) 

66 The desired temperature in eV. 

67 

68 temperature_K: float 

69 The desired temperature in K. 

70 

71 externalstress: float or nparray 

72 The external stress in eV/A^3. Either a symmetric 

73 3x3 tensor, a 6-vector representing the same, or a 

74 scalar representing the pressure. Note that the 

75 stress is positive in tension whereas the pressure is 

76 positive in compression: giving a scalar p is 

77 equivalent to giving the tensor (-p, -p, -p, 0, 0, 0). 

78 

79 ttime: float 

80 Characteristic timescale of the thermostat, in ASE internal units 

81 Set to None to disable the thermostat. 

82 

83 WARNING: Not specifying ttime sets it to None, disabling the 

84 thermostat.  

85 

86 pfactor: float 

87 A constant in the barostat differential equation. If 

88 a characteristic barostat timescale of ptime is 

89 desired, set pfactor to ptime^2 * B (where ptime is in units matching 

90 eV, Å, u; and B is the Bulk Modulus, given in eV/Å^3). 

91 Set to None to disable the barostat. 

92 Typical metallic bulk moduli are of the order of 

93 100 GPa or 0.6 eV/A^3.  

94 

95 WARNING: Not specifying pfactor sets it to None, disabling the 

96 barostat. 

97 

98 mask: None or 3-tuple or 3x3 nparray (optional) 

99 Optional argument. A tuple of three integers (0 or 1), 

100 indicating if the system can change size along the 

101 three Cartesian axes. Set to (1,1,1) or None to allow 

102 a fully flexible computational box. Set to (1,1,0) 

103 to disallow elongations along the z-axis etc. 

104 mask may also be specified as a symmetric 3x3 array 

105 indicating which strain values may change. 

106 

107 Useful parameter values: 

108 

109 * The same timestep can be used as in Verlet dynamics, i.e. 5 fs is fine 

110 for bulk copper. 

111 

112 * The ttime and pfactor are quite critical[4], too small values may 

113 cause instabilites and/or wrong fluctuations in T / p. Too 

114 large values cause an oscillation which is slow to die. Good 

115 values for the characteristic times seem to be 25 fs for ttime, 

116 and 75 fs for ptime (used to calculate pfactor), at least for 

117 bulk copper with 15000-200000 atoms. But this is not well 

118 tested, it is IMPORTANT to monitor the temperature and 

119 stress/pressure fluctuations. 

120 

121 

122 References: 

123 

124 1) S. Melchionna, G. Ciccotti and B. L. Holian, Molecular 

125 Physics 78, p. 533 (1993). 

126 

127 2) S. Melchionna, Physical 

128 Review E 61, p. 6165 (2000). 

129 

130 3) B. L. Holian, A. J. De Groot, W. G. Hoover, and C. G. Hoover, 

131 Physical Review A 41, p. 4552 (1990). 

132 

133 4) F. D. Di Tolla and M. Ronchetti, Physical 

134 Review E 48, p. 1726 (1993). 

135 

136 ''' 

137 

138 MolecularDynamics.__init__(self, atoms, timestep, trajectory, 

139 logfile, loginterval, 

140 append_trajectory=append_trajectory) 

141 # self.atoms = atoms 

142 # self.timestep = timestep 

143 if externalstress is None and pfactor is not None: 

144 raise TypeError("Missing 'externalstress' argument.") 

145 self.zero_center_of_mass_momentum(verbose=1) 

146 self.temperature = units.kB * self._process_temperature( 

147 temperature, temperature_K, 'eV') 

148 self.set_stress(externalstress) 

149 self.set_mask(mask) 

150 self.eta = np.zeros((3, 3), float) 

151 self.zeta = 0.0 

152 self.zeta_integrated = 0.0 

153 self.initialized = 0 

154 self.ttime = ttime 

155 self.pfactor_given = pfactor 

156 self._calculateconstants() 

157 self.timeelapsed = 0.0 

158 self.frac_traceless = 1 

159 

160 def set_temperature(self, temperature=None, *, temperature_K=None): 

161 """Set the temperature. 

162 

163 Parameters: 

164 

165 temperature: float (deprecated) 

166 The new temperature in eV. Deprecated, use ``temperature_K``. 

167 

168 temperature_K: float (keyword-only argument) 

169 The new temperature, in K. 

170 """ 

171 self.temperature = units.kB * self._process_temperature( 

172 temperature, temperature_K, 'eV') 

173 self._calculateconstants() 

174 

175 def set_stress(self, stress): 

176 """Set the applied stress. 

177 

178 Must be a symmetric 3x3 tensor, a 6-vector representing a symmetric 

179 3x3 tensor, or a number representing the pressure. 

180 

181 Use with care, it is better to set the correct stress when creating 

182 the object. 

183 """ 

184 

185 if np.isscalar(stress): 

186 stress = np.array([-stress, -stress, -stress, 0.0, 0.0, 0.0]) 

187 else: 

188 stress = np.array(stress) 

189 if stress.shape == (3, 3): 

190 if not self._issymmetric(stress): 

191 raise ValueError( 

192 "The external stress must be a symmetric tensor.") 

193 stress = np.array((stress[0, 0], stress[1, 1], 

194 stress[2, 2], stress[1, 2], 

195 stress[0, 2], stress[0, 1])) 

196 elif stress.shape != (6,): 

197 raise ValueError("The external stress has the wrong shape.") 

198 self.externalstress = stress 

199 

200 def set_mask(self, mask): 

201 """Set the mask indicating dynamic elements of the computational box. 

202 

203 If set to None, all elements may change. If set to a 3-vector 

204 of ones and zeros, elements which are zero specify directions 

205 along which the size of the computational box cannot change. 

206 For example, if mask = (1,1,0) the length of the system along 

207 the z-axis cannot change, although xz and yz shear is still 

208 possible. May also be specified as a symmetric 3x3 array indicating 

209 which strain values may change. 

210 

211 Use with care, as you may "freeze in" a fluctuation in the strain rate. 

212 """ 

213 if mask is None: 

214 mask = np.ones((3,)) 

215 if not hasattr(mask, "shape"): 

216 mask = np.array(mask) 

217 if mask.shape != (3,) and mask.shape != (3, 3): 

218 raise RuntimeError('The mask has the wrong shape ' + 

219 '(must be a 3-vector or 3x3 matrix)') 

220 else: 

221 mask = np.not_equal(mask, 0) # Make sure it is 0/1 

222 

223 if mask.shape == (3,): 

224 self.mask = np.outer(mask, mask) 

225 else: 

226 self.mask = mask 

227 

228 def set_fraction_traceless(self, fracTraceless): 

229 """set what fraction of the traceless part of the force 

230 on eta is kept. 

231 

232 By setting this to zero, the volume may change but the shape may not. 

233 """ 

234 self.frac_traceless = fracTraceless 

235 

236 def get_strain_rate(self): 

237 """Get the strain rate as an upper-triangular 3x3 matrix. 

238 

239 This includes the fluctuations in the shape of the computational box. 

240 

241 """ 

242 return np.array(self.eta, copy=1) 

243 

244 def set_strain_rate(self, rate): 

245 """Set the strain rate. Must be an upper triangular 3x3 matrix. 

246 

247 If you set a strain rate along a direction that is "masked out" 

248 (see ``set_mask``), the strain rate along that direction will be 

249 maintained constantly. 

250 """ 

251 if not (rate.shape == (3, 3) and self._isuppertriangular(rate)): 

252 raise ValueError("Strain rate must be an upper triangular matrix.") 

253 self.eta = rate 

254 if self.initialized: 

255 # Recalculate h_past and eta_past so they match the current value. 

256 self._initialize_eta_h() 

257 

258 def get_time(self): 

259 "Get the elapsed time." 

260 return self.timeelapsed 

261 

262 def run(self, steps): 

263 """Perform a number of time steps.""" 

264 if not self.initialized: 

265 self.initialize() 

266 else: 

267 if self.have_the_atoms_been_changed(): 

268 raise NotImplementedError( 

269 "You have modified the atoms since the last timestep.") 

270 

271 for i in range(steps): 

272 self.step() 

273 self.nsteps += 1 

274 self.call_observers() 

275 

276 def have_the_atoms_been_changed(self): 

277 "Checks if the user has modified the positions or momenta of the atoms" 

278 limit = 1e-10 

279 h = self._getbox() 

280 if max(abs((h - self.h).ravel())) > limit: 

281 self._warning("The computational box has been modified.") 

282 return 1 

283 expected_r = np.dot(self.q + 0.5, h) 

284 err = max(abs((expected_r - self.atoms.get_positions()).ravel())) 

285 if err > limit: 

286 self._warning("The atomic positions have been modified: " + 

287 str(err)) 

288 return 1 

289 return 0 

290 

291 def step(self): 

292 """Perform a single time step. 

293 

294 Assumes that the forces and stresses are up to date, and that 

295 the positions and momenta have not been changed since last 

296 timestep. 

297 """ 

298 

299 # Assumes the following variables are OK 

300 # q_past, q, q_future, p, eta, eta_past, zeta, zeta_past, h, h_past 

301 # 

302 # q corresponds to the current positions 

303 # p must be equal to self.atoms.GetCartesianMomenta() 

304 # h must be equal to self.atoms.GetUnitCell() 

305 # 

306 # print "Making a timestep" 

307 dt = self.dt 

308 h_future = self.h_past + 2 * dt * np.dot(self.h, self.eta) 

309 if self.pfactor_given is None: 

310 deltaeta = np.zeros(6, float) 

311 else: 

312 stress = self.stresscalculator() 

313 deltaeta = -2 * dt * (self.pfact * linalg.det(self.h) * 

314 (stress - self.externalstress)) 

315 

316 if self.frac_traceless == 1: 

317 eta_future = self.eta_past + self.mask * \ 

318 self._makeuppertriangular(deltaeta) 

319 else: 

320 trace_part, traceless_part = self._separatetrace( 

321 self._makeuppertriangular(deltaeta)) 

322 eta_future = self.eta_past + trace_part + self.frac_traceless * traceless_part 

323 

324 deltazeta = 2 * dt * self.tfact * (self.atoms.get_kinetic_energy() - 

325 self.desiredEkin) 

326 zeta_future = self.zeta_past + deltazeta 

327 # Advance time 

328 # print "Max change in scaled positions:", max(abs(self.q_future.flat - self.q.flat)) 

329 # print "Max change in basis set", max(abs((h_future - self.h).flat)) 

330 self.timeelapsed += dt 

331 self.h_past = self.h 

332 self.h = h_future 

333 self.inv_h = linalg.inv(self.h) 

334 self.q_past = self.q 

335 self.q = self.q_future 

336 self._setbox_and_positions(self.h, self.q) 

337 self.eta_past = self.eta 

338 self.eta = eta_future 

339 self.zeta_past = self.zeta 

340 self.zeta = zeta_future 

341 self._synchronize() # for parallel simulations. 

342 self.zeta_integrated += dt * self.zeta 

343 force = self.forcecalculator() 

344 self._calculate_q_future(force) 

345 self.atoms.set_momenta(np.dot(self.q_future - self.q_past, self.h / (2 * dt)) * 

346 self._getmasses()) 

347 # self.stresscalculator() 

348 

349 def forcecalculator(self): 

350 return self.atoms.get_forces(md=True) 

351 

352 def stresscalculator(self): 

353 return self.atoms.get_stress(include_ideal_gas=True) 

354 

355 def initialize(self): 

356 """Initialize the dynamics. 

357 

358 The dynamics requires positions etc for the two last times to 

359 do a timestep, so the algorithm is not self-starting. This 

360 method performs a 'backwards' timestep to generate a 

361 configuration before the current. 

362 

363 This is called automatically the first time ``run()`` is called. 

364 """ 

365 # print "Initializing the NPT dynamics." 

366 dt = self.dt 

367 atoms = self.atoms 

368 self.h = self._getbox() 

369 if not self._isuppertriangular(self.h): 

370 print("I am", self) 

371 print("self.h:") 

372 print(self.h) 

373 print("Min:", min((self.h[1, 0], self.h[2, 0], self.h[2, 1]))) 

374 print("Max:", max((self.h[1, 0], self.h[2, 0], self.h[2, 1]))) 

375 raise NotImplementedError( 

376 "Can (so far) only operate on lists of atoms where the computational box is an upper triangular matrix.") 

377 self.inv_h = linalg.inv(self.h) 

378 # The contents of the q arrays should migrate in parallel simulations. 

379 # self._make_special_q_arrays() 

380 self.q = np.dot(self.atoms.get_positions(), self.inv_h) - 0.5 

381 # zeta and eta were set in __init__ 

382 self._initialize_eta_h() 

383 deltazeta = dt * self.tfact * (atoms.get_kinetic_energy() - 

384 self.desiredEkin) 

385 self.zeta_past = self.zeta - deltazeta 

386 self._calculate_q_past_and_future() 

387 self.initialized = 1 

388 

389 def get_gibbs_free_energy(self): 

390 """Return the Gibb's free energy, which is supposed to be conserved. 

391 

392 Requires that the energies of the atoms are up to date. 

393 

394 This is mainly intended as a diagnostic tool. If called before the 

395 first timestep, Initialize will be called. 

396 """ 

397 if not self.initialized: 

398 self.initialize() 

399 n = self._getnatoms() 

400 # tretaTeta = sum(diagonal(matrixmultiply(transpose(self.eta), 

401 # self.eta))) 

402 contractedeta = np.sum((self.eta * self.eta).ravel()) 

403 gibbs = (self.atoms.get_potential_energy() + 

404 self.atoms.get_kinetic_energy() 

405 - np.sum(self.externalstress[0:3]) * linalg.det(self.h) / 3.0) 

406 if self.ttime is not None: 

407 gibbs += (1.5 * n * self.temperature * 

408 (self.ttime * self.zeta)**2 + 

409 3 * self.temperature * (n - 1) * self.zeta_integrated) 

410 else: 

411 assert self.zeta == 0.0 

412 if self.pfactor_given is not None: 

413 gibbs += 0.5 / self.pfact * contractedeta 

414 else: 

415 assert contractedeta == 0.0 

416 return gibbs 

417 

418 def get_center_of_mass_momentum(self): 

419 "Get the center of mass momentum." 

420 return self.atoms.get_momenta().sum(0) 

421 

422 def zero_center_of_mass_momentum(self, verbose=0): 

423 "Set the center of mass momentum to zero." 

424 cm = self.get_center_of_mass_momentum() 

425 abscm = np.sqrt(np.sum(cm * cm)) 

426 if verbose and abscm > 1e-4: 

427 self._warning( 

428 self.classname + 

429 ": Setting the center-of-mass momentum to zero " 

430 "(was %.6g %.6g %.6g)" % tuple(cm)) 

431 self.atoms.set_momenta(self.atoms.get_momenta() - 

432 cm / self._getnatoms()) 

433 

434 def attach_atoms(self, atoms): 

435 """Assign atoms to a restored dynamics object. 

436 

437 This function must be called to set the atoms immediately after the 

438 dynamics object has been read from a trajectory. 

439 """ 

440 try: 

441 self.atoms 

442 except AttributeError: 

443 pass 

444 else: 

445 raise RuntimeError("Cannot call attach_atoms on a dynamics " 

446 "which already has atoms.") 

447 MolecularDynamics.__init__(self, atoms, self.dt) 

448 limit = 1e-6 

449 h = self._getbox() 

450 if max(abs((h - self.h).ravel())) > limit: 

451 raise RuntimeError( 

452 "The unit cell of the atoms does not match the unit cell stored in the file.") 

453 self.inv_h = linalg.inv(self.h) 

454 self.q = np.dot(self.atoms.get_positions(), self.inv_h) - 0.5 

455 self._calculate_q_past_and_future() 

456 self.initialized = 1 

457 

458 def attach(self, function, interval=1, *args, **kwargs): 

459 """Attach callback function or trajectory. 

460 

461 At every *interval* steps, call *function* with arguments 

462 *args* and keyword arguments *kwargs*. 

463 

464 If *function* is a trajectory object, its write() method is 

465 attached, but if *function* is a BundleTrajectory (or another 

466 trajectory supporting set_extra_data(), said method is first 

467 used to instruct the trajectory to also save internal 

468 data from the NPT dynamics object. 

469 """ 

470 if hasattr(function, "set_extra_data"): 

471 # We are attaching a BundleTrajectory or similar 

472 function.set_extra_data("npt_init", 

473 WeakMethodWrapper(self, "get_init_data"), 

474 once=True) 

475 function.set_extra_data("npt_dynamics", 

476 WeakMethodWrapper(self, "get_data")) 

477 MolecularDynamics.attach(self, function, interval, *args, **kwargs) 

478 

479 def get_init_data(self): 

480 "Return the data needed to initialize a new NPT dynamics." 

481 return {'dt': self.dt, 

482 'temperature': self.temperature, 

483 'desiredEkin': self.desiredEkin, 

484 'externalstress': self.externalstress, 

485 'mask': self.mask, 

486 'ttime': self.ttime, 

487 'tfact': self.tfact, 

488 'pfactor_given': self.pfactor_given, 

489 'pfact': self.pfact, 

490 'frac_traceless': self.frac_traceless} 

491 

492 def get_data(self): 

493 "Return data needed to restore the state." 

494 return {'eta': self.eta, 

495 'eta_past': self.eta_past, 

496 'zeta': self.zeta, 

497 'zeta_past': self.zeta_past, 

498 'zeta_integrated': self.zeta_integrated, 

499 'h': self.h, 

500 'h_past': self.h_past, 

501 'timeelapsed': self.timeelapsed} 

502 

503 @classmethod 

504 def read_from_trajectory(cls, trajectory, frame=-1, atoms=None): 

505 """Read dynamics and atoms from trajectory (Class method). 

506 

507 Simultaneously reads the atoms and the dynamics from a BundleTrajectory, 

508 including the internal data of the NPT dynamics object (automatically 

509 saved when attaching a BundleTrajectory to an NPT object). 

510 

511 Arguments: 

512 

513 trajectory 

514 The filename or an open BundleTrajectory object. 

515 

516 frame (optional) 

517 Which frame to read. Default: the last. 

518 

519 atoms (optional, internal use only) 

520 Pre-read atoms. Do not use. 

521 """ 

522 if isinstance(trajectory, str): 

523 if trajectory.endswith('/'): 

524 trajectory = trajectory[:-1] 

525 if trajectory.endswith('.bundle'): 

526 from ase.io.bundletrajectory import BundleTrajectory 

527 trajectory = BundleTrajectory(trajectory) 

528 else: 

529 raise ValueError( 

530 f"Cannot open '{trajectory}': unsupported file format") 

531 # trajectory is now a BundleTrajectory object (or compatible) 

532 if atoms is None: 

533 atoms = trajectory[frame] 

534 init_data = trajectory.read_extra_data('npt_init', 0) 

535 frame_data = trajectory.read_extra_data('npt_dynamics', frame) 

536 dyn = cls(atoms, timestep=init_data['dt'], 

537 temperature=init_data['temperature'], 

538 externalstress=init_data['externalstress'], 

539 ttime=init_data['ttime'], 

540 pfactor=init_data['pfactor_given'], 

541 mask=init_data['mask']) 

542 dyn.desiredEkin = init_data['desiredEkin'] 

543 dyn.tfact = init_data['tfact'] 

544 dyn.pfact = init_data['pfact'] 

545 dyn.frac_traceless = init_data['frac_traceless'] 

546 for k, v in frame_data.items(): 

547 setattr(dyn, k, v) 

548 return (dyn, atoms) 

549 

550 def _getbox(self): 

551 "Get the computational box." 

552 return self.atoms.get_cell() 

553 

554 def _getmasses(self): 

555 "Get the masses as an Nx1 array." 

556 return np.reshape(self.atoms.get_masses(), (-1, 1)) 

557 

558 def _separatetrace(self, mat): 

559 """return two matrices, one proportional to the identity 

560 the other traceless, which sum to the given matrix 

561 """ 

562 tracePart = ((mat[0][0] + mat[1][1] + mat[2][2]) / 3.) * np.identity(3) 

563 return tracePart, mat - tracePart 

564 

565 # A number of convenient helper methods 

566 def _warning(self, text): 

567 "Emit a warning." 

568 sys.stderr.write("WARNING: " + text + "\n") 

569 sys.stderr.flush() 

570 

571 def _calculate_q_future(self, force): 

572 "Calculate future q. Needed in Timestep and Initialization." 

573 dt = self.dt 

574 id3 = np.identity(3) 

575 alpha = (dt * dt) * np.dot(force / self._getmasses(), 

576 self.inv_h) 

577 beta = dt * np.dot(self.h, np.dot(self.eta + 0.5 * self.zeta * id3, 

578 self.inv_h)) 

579 inv_b = linalg.inv(beta + id3) 

580 self.q_future = np.dot(2 * self.q + np.dot(self.q_past, beta - id3) + alpha, 

581 inv_b) 

582 

583 def _calculate_q_past_and_future(self): 

584 def ekin(p, m=self.atoms.get_masses()): 

585 p2 = np.sum(p * p, -1) 

586 return 0.5 * np.sum(p2 / m) / len(m) 

587 p0 = self.atoms.get_momenta() 

588 m = self._getmasses() 

589 p = np.array(p0, copy=1) 

590 dt = self.dt 

591 for i in range(2): 

592 self.q_past = self.q - dt * np.dot(p / m, self.inv_h) 

593 self._calculate_q_future(self.atoms.get_forces(md=True)) 

594 p = np.dot(self.q_future - self.q_past, self.h / (2 * dt)) * m 

595 e = ekin(p) 

596 if e < 1e-5: 

597 # The kinetic energy and momenta are virtually zero 

598 return 

599 p = (p0 - p) + p0 

600 

601 def _initialize_eta_h(self): 

602 self.h_past = self.h - self.dt * np.dot(self.h, self.eta) 

603 if self.pfactor_given is None: 

604 deltaeta = np.zeros(6, float) 

605 else: 

606 deltaeta = (-self.dt * self.pfact * linalg.det(self.h) 

607 * (self.stresscalculator() - self.externalstress)) 

608 if self.frac_traceless == 1: 

609 self.eta_past = self.eta - self.mask * \ 

610 self._makeuppertriangular(deltaeta) 

611 else: 

612 trace_part, traceless_part = self._separatetrace( 

613 self._makeuppertriangular(deltaeta)) 

614 self.eta_past = self.eta - trace_part - self.frac_traceless * traceless_part 

615 

616 def _makeuppertriangular(self, sixvector): 

617 "Make an upper triangular matrix from a 6-vector." 

618 return np.array(((sixvector[0], sixvector[5], sixvector[4]), 

619 (0, sixvector[1], sixvector[3]), 

620 (0, 0, sixvector[2]))) 

621 

622 @staticmethod 

623 def _isuppertriangular(m) -> bool: 

624 "Check that a matrix is on upper triangular form." 

625 return m[1, 0] == m[2, 0] == m[2, 1] == 0.0 

626 

627 def _calculateconstants(self): 

628 "(Re)calculate some constants when pfactor, ttime or temperature have been changed." 

629 n = self._getnatoms() 

630 if self.ttime is None: 

631 self.tfact = 0.0 

632 else: 

633 self.tfact = 2.0 / (3 * n * self.temperature * 

634 self.ttime * self.ttime) 

635 if self.pfactor_given is None: 

636 self.pfact = 0.0 

637 else: 

638 self.pfact = 1.0 / (self.pfactor_given * linalg.det(self._getbox())) 

639 # self.pfact = 1.0/(n * self.temperature * self.ptime * self.ptime) 

640 self.desiredEkin = 1.5 * (n - 1) * self.temperature 

641 

642 def _setbox_and_positions(self, h, q): 

643 """Set the computational box and the positions.""" 

644 self.atoms.set_cell(h) 

645 r = np.dot(q + 0.5, h) 

646 self.atoms.set_positions(r) 

647 

648 # A few helper methods, which have been placed in separate methods 

649 # so they can be replaced in the parallel version. 

650 def _synchronize(self): 

651 """Synchronizes eta, h and zeta on all processors in a parallel simulation. 

652 

653 In a parallel simulation, eta, h and zeta are communicated 

654 from the master to all slaves, to prevent numerical noise from 

655 causing them to diverge. 

656 

657 In a serial simulation, do nothing. 

658 """ 

659 pass # This is a serial simulation object. Do nothing. 

660 

661 def _getnatoms(self): 

662 """Get the number of atoms. 

663 

664 In a parallel simulation, this is the total number of atoms on all 

665 processors. 

666 """ 

667 return len(self.atoms) 

668 

669 def _make_special_q_arrays(self): 

670 """Make the arrays used to store data about the atoms. 

671 

672 In a parallel simulation, these are migrating arrays. In a 

673 serial simulation they are ordinary Numeric arrays. 

674 """ 

675 natoms = len(self.atoms) 

676 self.q = np.zeros((natoms, 3), float) 

677 self.q_past = np.zeros((natoms, 3), float) 

678 self.q_future = np.zeros((natoms, 3), float) 

679 

680 

681class WeakMethodWrapper: 

682 """A weak reference to a method. 

683 

684 Create an object storing a weak reference to an instance and 

685 the name of the method to call. When called, calls the method. 

686 

687 Just storing a weak reference to a bound method would not work, 

688 as the bound method object would go away immediately. 

689 """ 

690 

691 def __init__(self, obj, method): 

692 self.obj = weakref.proxy(obj) 

693 self.method = method 

694 

695 def __call__(self, *args, **kwargs): 

696 m = getattr(self.obj, self.method) 

697 return m(*args, **kwargs)