1#!/usr/bin/env python 2 3from __future__ import division 4 5import itertools 6import time, sys 7from collections import OrderedDict, defaultdict 8from copy import deepcopy 9 10import networkx as nx 11import numpy as np 12from numpy.linalg import multi_dot 13 14from geometric.molecule import Elements, Radii 15from geometric.nifty import click, commadash, ang2bohr, bohr2ang, logger, pvec1d, pmat2d 16from geometric.rotate import get_expmap, get_expmap_der, is_linear 17 18 19## Some vector calculus functions 20def unit_vector(a): 21 """ 22 Vector function: Given a vector a, return the unit vector 23 """ 24 return a / np.linalg.norm(a) 25 26def d_unit_vector(a, ndim=3): 27 term1 = np.eye(ndim)/np.linalg.norm(a) 28 term2 = np.outer(a, a)/(np.linalg.norm(a)**3) 29 answer = term1-term2 30 return answer 31 32def d_cross(a, b): 33 """ 34 Given two vectors a and b, return the gradient of the cross product axb w/r.t. a. 35 (Note that the answer is independent of a.) 36 Derivative is on the first axis. 37 """ 38 d_cross = np.zeros((3, 3), dtype=float) 39 for i in range(3): 40 ei = np.zeros(3, dtype=float) 41 ei[i] = 1.0 42 d_cross[i] = np.cross(ei, b) 43 return d_cross 44 45def d_cross_ab(a, b, da, db): 46 """ 47 Given two vectors a, b and their derivatives w/r.t. a parameter, return the derivative 48 of the cross product 49 """ 50 answer = np.zeros((da.shape[0], 3), dtype=float) 51 for i in range(da.shape[0]): 52 answer[i] = np.cross(a, db[i]) + np.cross(da[i], b) 53 return answer 54 55def ncross(a, b): 56 """ 57 Scalar function: Given vectors a and b, return the norm of the cross product 58 """ 59 cross = np.cross(a, b) 60 return np.linalg.norm(cross) 61 62def d_ncross(a, b): 63 """ 64 Return the gradient of the norm of the cross product w/r.t. a 65 """ 66 ncross = np.linalg.norm(np.cross(a, b)) 67 term1 = a * np.dot(b, b) 68 term2 = -b * np.dot(a, b) 69 answer = (term1+term2)/ncross 70 return answer 71 72def nudot(a, b): 73 r""" 74 Given two vectors a and b, return the dot product (\hat{a}).b. 75 """ 76 ev = a / np.linalg.norm(a) 77 return np.dot(ev, b) 78 79def d_nudot(a, b): 80 r""" 81 Given two vectors a and b, return the gradient of 82 the norm of the dot product (\hat{a}).b w/r.t. a. 83 """ 84 return np.dot(d_unit_vector(a), b) 85 86def ucross(a, b): 87 r""" 88 Given two vectors a and b, return the cross product (\hat{a})xb. 89 """ 90 ev = a / np.linalg.norm(a) 91 return np.cross(ev, b) 92 93def d_ucross(a, b): 94 r""" 95 Given two vectors a and b, return the gradient of 96 the cross product (\hat{a})xb w/r.t. a. 97 """ 98 ev = a / np.linalg.norm(a) 99 return np.dot(d_unit_vector(a), d_cross(ev, b)) 100 101def nucross(a, b): 102 r""" 103 Given two vectors a and b, return the norm of the cross product (\hat{a})xb. 104 """ 105 ev = a / np.linalg.norm(a) 106 return np.linalg.norm(np.cross(ev, b)) 107 108def d_nucross(a, b): 109 r""" 110 Given two vectors a and b, return the gradient of 111 the norm of the cross product (\hat{a})xb w/r.t. a. 112 """ 113 ev = a / np.linalg.norm(a) 114 return np.dot(d_unit_vector(a), d_ncross(ev, b)) 115## End vector calculus functions 116 117class CartesianX(object): 118 def __init__(self, a, w=1.0): 119 self.a = a 120 self.w = w 121 self.isAngular = False 122 self.isPeriodic = False 123 124 def __repr__(self): 125 #return "Cartesian-X %i : Weight %.3f" % (self.a+1, self.w) 126 return "Cartesian-X %i" % (self.a+1) 127 128 def __eq__(self, other): 129 if type(self) is not type(other): return False 130 eq = self.a == other.a 131 if eq and self.w != other.w: 132 logger.warning("Warning: CartesianX same atoms, different weights (%.4f %.4f)\n" % (self.w, other.w)) 133 return eq 134 135 def __ne__(self, other): 136 return not self.__eq__(other) 137 138 def value(self, xyz): 139 xyz = xyz.reshape(-1,3) 140 a = self.a 141 return xyz[a][0]*self.w 142 143 def derivative(self, xyz): 144 xyz = xyz.reshape(-1,3) 145 derivatives = np.zeros_like(xyz) 146 derivatives[self.a][0] = self.w 147 return derivatives 148 149 def second_derivative(self, xyz): 150 xyz = xyz.reshape(-1,3) 151 deriv2 = np.zeros((xyz.shape[0], xyz.shape[1], xyz.shape[0], xyz.shape[1])) 152 return deriv2 153 154class CartesianY(object): 155 def __init__(self, a, w=1.0): 156 self.a = a 157 self.w = w 158 self.isAngular = False 159 self.isPeriodic = False 160 161 def __repr__(self): 162 # return "Cartesian-Y %i : Weight %.3f" % (self.a+1, self.w) 163 return "Cartesian-Y %i" % (self.a+1) 164 165 def __eq__(self, other): 166 if type(self) is not type(other): return False 167 eq = self.a == other.a 168 if eq and self.w != other.w: 169 logger.warning("Warning: CartesianY same atoms, different weights (%.4f %.4f)\n" % (self.w, other.w)) 170 return eq 171 172 def __ne__(self, other): 173 return not self.__eq__(other) 174 175 def value(self, xyz): 176 xyz = xyz.reshape(-1,3) 177 a = self.a 178 return xyz[a][1]*self.w 179 180 def derivative(self, xyz): 181 xyz = xyz.reshape(-1,3) 182 derivatives = np.zeros_like(xyz) 183 derivatives[self.a][1] = self.w 184 return derivatives 185 186 def second_derivative(self, xyz): 187 xyz = xyz.reshape(-1,3) 188 deriv2 = np.zeros((xyz.shape[0], xyz.shape[1], xyz.shape[0], xyz.shape[1])) 189 return deriv2 190 191class CartesianZ(object): 192 def __init__(self, a, w=1.0): 193 self.a = a 194 self.w = w 195 self.isAngular = False 196 self.isPeriodic = False 197 198 def __repr__(self): 199 # return "Cartesian-Z %i : Weight %.3f" % (self.a+1, self.w) 200 return "Cartesian-Z %i" % (self.a+1) 201 202 def __eq__(self, other): 203 if type(self) is not type(other): return False 204 eq = self.a == other.a 205 if eq and self.w != other.w: 206 logger.warning("Warning: CartesianZ same atoms, different weights (%.4f %.4f)\n" % (self.w, other.w)) 207 return eq 208 209 def __ne__(self, other): 210 return not self.__eq__(other) 211 212 def value(self, xyz): 213 xyz = xyz.reshape(-1,3) 214 a = self.a 215 return xyz[a][2]*self.w 216 217 def derivative(self, xyz): 218 xyz = xyz.reshape(-1,3) 219 derivatives = np.zeros_like(xyz) 220 derivatives[self.a][2] = self.w 221 return derivatives 222 223 def second_derivative(self, xyz): 224 xyz = xyz.reshape(-1,3) 225 deriv2 = np.zeros((xyz.shape[0], xyz.shape[1], xyz.shape[0], xyz.shape[1])) 226 return deriv2 227 228class TranslationX(object): 229 def __init__(self, a, w): 230 self.a = a 231 self.w = w 232 assert len(a) == len(w) 233 self.isAngular = False 234 self.isPeriodic = False 235 236 def __repr__(self): 237 # return "Translation-X %s : Weights %s" % (' '.join([str(i+1) for i in self.a]), ' '.join(['%.2e' % i for i in self.w])) 238 return "Translation-X %s" % (commadash(self.a)) 239 240 def __eq__(self, other): 241 if type(self) is not type(other): return False 242 eq = set(self.a) == set(other.a) 243 if eq and np.sum((self.w-other.w)**2) > 1e-6: 244 logger.warning("Warning: TranslationX same atoms, different weights\n") 245 eq = False 246 return eq 247 248 def __ne__(self, other): 249 return not self.__eq__(other) 250 251 def value(self, xyz): 252 xyz = xyz.reshape(-1,3) 253 a = np.array(self.a) 254 return np.sum(xyz[a,0]*self.w) 255 256 def derivative(self, xyz): 257 xyz = xyz.reshape(-1,3) 258 derivatives = np.zeros_like(xyz) 259 for i, a in enumerate(self.a): 260 derivatives[a][0] = self.w[i] 261 return derivatives 262 263 def second_derivative(self, xyz): 264 xyz = xyz.reshape(-1,3) 265 deriv2 = np.zeros((xyz.shape[0], xyz.shape[1], xyz.shape[0], xyz.shape[1])) 266 return deriv2 267 268class TranslationY(object): 269 def __init__(self, a, w): 270 self.a = a 271 self.w = w 272 assert len(a) == len(w) 273 self.isAngular = False 274 self.isPeriodic = False 275 276 def __repr__(self): 277 # return "Translation-Y %s : Weights %s" % (' '.join([str(i+1) for i in self.a]), ' '.join(['%.2e' % i for i in self.w])) 278 return "Translation-Y %s" % (commadash(self.a)) 279 280 def __eq__(self, other): 281 if type(self) is not type(other): return False 282 eq = set(self.a) == set(other.a) 283 if eq and np.sum((self.w-other.w)**2) > 1e-6: 284 logger.warning("Warning: TranslationY same atoms, different weights\n") 285 eq = False 286 return eq 287 288 def __ne__(self, other): 289 return not self.__eq__(other) 290 291 def value(self, xyz): 292 xyz = xyz.reshape(-1,3) 293 a = np.array(self.a) 294 return np.sum(xyz[a,1]*self.w) 295 296 def derivative(self, xyz): 297 xyz = xyz.reshape(-1,3) 298 derivatives = np.zeros_like(xyz) 299 for i, a in enumerate(self.a): 300 derivatives[a][1] = self.w[i] 301 return derivatives 302 303 def second_derivative(self, xyz): 304 xyz = xyz.reshape(-1,3) 305 deriv2 = np.zeros((xyz.shape[0], xyz.shape[1], xyz.shape[0], xyz.shape[1])) 306 return deriv2 307 308class TranslationZ(object): 309 def __init__(self, a, w): 310 self.a = a 311 self.w = w 312 assert len(a) == len(w) 313 self.isAngular = False 314 self.isPeriodic = False 315 316 def __repr__(self): 317 # return "Translation-Z %s : Weights %s" % (' '.join([str(i+1) for i in self.a]), ' '.join(['%.2e' % i for i in self.w])) 318 return "Translation-Z %s" % (commadash(self.a)) 319 320 def __eq__(self, other): 321 if type(self) is not type(other): return False 322 eq = set(self.a) == set(other.a) 323 if eq and np.sum((self.w-other.w)**2) > 1e-6: 324 logger.warning("Warning: TranslationZ same atoms, different weights\n") 325 eq = False 326 return eq 327 328 def __ne__(self, other): 329 return not self.__eq__(other) 330 331 def value(self, xyz): 332 xyz = xyz.reshape(-1,3) 333 a = np.array(self.a) 334 return np.sum(xyz[a,2]*self.w) 335 336 def derivative(self, xyz): 337 xyz = xyz.reshape(-1,3) 338 derivatives = np.zeros_like(xyz) 339 for i, a in enumerate(self.a): 340 derivatives[a][2] = self.w[i] 341 return derivatives 342 343 def second_derivative(self, xyz): 344 xyz = xyz.reshape(-1,3) 345 deriv2 = np.zeros((xyz.shape[0], xyz.shape[1], xyz.shape[0], xyz.shape[1])) 346 return deriv2 347 348class Rotator(object): 349 350 def __init__(self, a, x0): 351 self.a = list(tuple(sorted(a))) 352 x0 = x0.reshape(-1, 3) 353 self.x0 = x0.copy() 354 self.stored_valxyz = np.zeros_like(x0) 355 self.stored_value = None 356 self.stored_derxyz = np.zeros_like(x0) 357 self.stored_deriv = None 358 self.stored_deriv2xyz = np.zeros_like(x0) 359 self.stored_deriv2 = None 360 self.stored_norm = 0.0 361 # Extra variables to account for the case of linear molecules 362 # The reference axis used for computing dummy atom position 363 self.e0 = None 364 # Dot-squared measures alignment of molecule long axis with reference axis. 365 # If molecule becomes parallel with reference axis, coordinates must be reset. 366 self.stored_dot2 = 0.0 367 # Flag that records linearity of molecule 368 self.linear = False 369 370 def reset(self, x0): 371 x0 = x0.reshape(-1, 3) 372 self.x0 = x0.copy() 373 self.stored_valxyz = np.zeros_like(x0) 374 self.stored_value = None 375 self.stored_derxyz = np.zeros_like(x0) 376 self.stored_deriv = None 377 self.stored_deriv2xyz = np.zeros_like(x0) 378 self.stored_deriv2 = None 379 self.stored_norm = 0.0 380 self.e0 = None 381 self.stored_dot2 = 0.0 382 self.linear = False 383 384 def __eq__(self, other): 385 if type(self) is not type(other): return False 386 eq = set(self.a) == set(other.a) 387 if eq and np.sum((self.x0-other.x0)**2) > 1e-6: 388 logger.warning("Warning: Rotator same atoms, different reference positions\n") 389 return eq 390 391 def __repr__(self): 392 return "Rotator %s" % commadash(self.a) 393 394 def __ne__(self, other): 395 return not self.__eq__(other) 396 397 def calc_e0(self): 398 """ 399 Compute the reference axis for adding dummy atoms. 400 Only used in the case of linear molecules. 401 402 We first find the Cartesian axis that is "most perpendicular" to the molecular axis. 403 Next we take the cross product with the molecular axis to create a perpendicular vector. 404 Finally, this perpendicular vector is normalized to make a unit vector. 405 """ 406 ysel = self.x0[self.a, :] 407 vy = ysel[-1]-ysel[0] 408 ev = vy / np.linalg.norm(vy) 409 # Cartesian axes. 410 ex = np.array([1.0,0.0,0.0]) 411 ey = np.array([0.0,1.0,0.0]) 412 ez = np.array([0.0,0.0,1.0]) 413 self.e0 = np.cross(vy, [ex, ey, ez][np.argmin([np.dot(i, ev)**2 for i in [ex, ey, ez]])]) 414 self.e0 /= np.linalg.norm(self.e0) 415 416 def value(self, xyz): 417 xyz = xyz.reshape(-1, 3) 418 if np.max(np.abs(xyz-self.stored_valxyz)) < 1e-12: 419 return self.stored_value 420 else: 421 xsel = xyz[self.a, :] 422 ysel = self.x0[self.a, :] 423 xmean = np.mean(xsel,axis=0) 424 ymean = np.mean(ysel,axis=0) 425 if not self.linear and is_linear(xsel, ysel): 426 # print "Setting linear flag for", self 427 self.linear = True 428 if self.linear: 429 # Handle linear molecules. 430 vx = xsel[-1]-xsel[0] 431 vy = ysel[-1]-ysel[0] 432 # Calculate reference axis (if needed) 433 if self.e0 is None: self.calc_e0() 434 #log.debug(vx) 435 ev = vx / np.linalg.norm(vx) 436 # Measure alignment of molecular axis with reference axis 437 self.stored_dot2 = np.dot(ev, self.e0)**2 438 # Dummy atom is located one Bohr from the molecular center, direction 439 # given by cross-product of the molecular axis with the reference axis 440 xdum = np.cross(vx, self.e0) 441 ydum = np.cross(vy, self.e0) 442 exdum = xdum / np.linalg.norm(xdum) 443 eydum = ydum / np.linalg.norm(ydum) 444 xsel = np.vstack((xsel, exdum+xmean)) 445 ysel = np.vstack((ysel, eydum+ymean)) 446 answer = get_expmap(xsel, ysel) 447 self.stored_norm = np.linalg.norm(answer) 448 self.stored_valxyz = xyz.copy() 449 self.stored_value = answer.copy() 450 return answer 451 452 def derivative(self, xyz): 453 xyz = xyz.reshape(-1, 3) 454 if np.max(np.abs(xyz-self.stored_derxyz)) < 1e-12: 455 return self.stored_deriv 456 else: 457 xsel = xyz[self.a, :] 458 ysel = self.x0[self.a, :] 459 xmean = np.mean(xsel,axis=0) 460 ymean = np.mean(ysel,axis=0) 461 if not self.linear and is_linear(xsel, ysel): 462 # print "Setting linear flag for", self 463 self.linear = True 464 if self.linear: 465 vx = xsel[-1]-xsel[0] 466 vy = ysel[-1]-ysel[0] 467 if self.e0 is None: self.calc_e0() 468 xdum = np.cross(vx, self.e0) 469 ydum = np.cross(vy, self.e0) 470 exdum = xdum / np.linalg.norm(xdum) 471 eydum = ydum / np.linalg.norm(ydum) 472 xsel = np.vstack((xsel, exdum+xmean)) 473 ysel = np.vstack((ysel, eydum+ymean)) 474 deriv_raw = get_expmap_der(xsel, ysel) 475 if self.linear: 476 # Chain rule is applied to get terms from 477 # dummy atom derivatives 478 nxdum = np.linalg.norm(xdum) 479 dxdum = d_cross(vx, self.e0) 480 dnxdum = d_ncross(vx, self.e0) 481 # Derivative of dummy atom position w/r.t. molecular axis vector 482 dexdum = (dxdum*nxdum - np.outer(dnxdum,xdum))/nxdum**2 483 # Here we may compute finite difference derivatives to check 484 # h = 1e-6 485 # fdxdum = np.zeros((3, 3), dtype=float) 486 # for i in range(3): 487 # vx[i] += h 488 # dPlus = np.cross(vx, self.e0) 489 # dPlus /= np.linalg.norm(dPlus) 490 # vx[i] -= 2*h 491 # dMinus = np.cross(vx, self.e0) 492 # dMinus /= np.linalg.norm(dMinus) 493 # vx[i] += h 494 # fdxdum[i] = (dPlus-dMinus)/(2*h) 495 # if np.linalg.norm(dexdum - fdxdum) > 1e-6: 496 # print dexdum - fdxdum 497 # raise Exception() 498 # Apply terms from chain rule 499 deriv_raw[0] -= np.dot(dexdum, deriv_raw[-1]) 500 for i in range(len(self.a)): 501 deriv_raw[i] += np.dot(np.eye(3), deriv_raw[-1])/len(self.a) 502 deriv_raw[-2] += np.dot(dexdum, deriv_raw[-1]) 503 deriv_raw = deriv_raw[:-1] 504 derivatives = np.zeros((xyz.shape[0], 3, 3), dtype=float) 505 for i, a in enumerate(self.a): 506 derivatives[a, :, :] = deriv_raw[i, :, :] 507 self.stored_derxyz = xyz.copy() 508 self.stored_deriv = derivatives.copy() 509 return derivatives 510 511 def second_derivative(self, xyz): 512 xyz = xyz.reshape(-1, 3) 513 if np.max(np.abs(xyz-self.stored_deriv2xyz)) < 1e-12: 514 return self.stored_deriv2 515 else: 516 xsel = xyz[self.a, :] 517 ysel = self.x0[self.a, :] 518 xmean = np.mean(xsel,axis=0) 519 ymean = np.mean(ysel,axis=0) 520 if not self.linear and is_linear(xsel, ysel): 521 # print "Setting linear flag for", self 522 self.linear = True 523 if self.linear: 524 vx = xsel[-1]-xsel[0] 525 vy = ysel[-1]-ysel[0] 526 if self.e0 is None: self.calc_e0() 527 xdum = np.cross(vx, self.e0) 528 ydum = np.cross(vy, self.e0) 529 exdum = xdum / np.linalg.norm(xdum) 530 eydum = ydum / np.linalg.norm(ydum) 531 xsel = np.vstack((xsel, exdum+xmean)) 532 ysel = np.vstack((ysel, eydum+ymean)) 533 deriv_raw, deriv2_raw = get_expmap_der(xsel, ysel, second=True) 534 if self.linear: 535 # Chain rule is applied to get terms from dummy atom derivatives 536 def dexdum_(vx_): 537 xdum_ = np.cross(vx_, self.e0) 538 nxdum_ = np.linalg.norm(xdum_) 539 dxdum_ = d_cross(vx_, self.e0) 540 dnxdum_ = d_ncross(vx_, self.e0) 541 dexdum_ = (dxdum_*nxdum_ - np.outer(dnxdum_,xdum_))/nxdum_**2 542 return dexdum_.copy() 543 # First indices: elements of vx that are being differentiated w/r.t. 544 # Last index: elements of exdum itself 545 dexdum = dexdum_(vx) 546 dexdum2 = np.zeros((3, 3, 3), dtype=float) 547 h = 1.0e-3 548 for i in range(3): 549 vx[i] += h 550 dPlus = dexdum_(vx) 551 vx[i] -= 2*h 552 dMinus = dexdum_(vx) 553 vx[i] += h 554 dexdum2[i] = (dPlus-dMinus)/(2*h) 555 # Build arrays that contain derivative of dummy atom position 556 # w/r.t. real atom positions 557 ddum1 = np.zeros((len(self.a), 3, 3), dtype=float) 558 ddum1[0] = -dexdum 559 ddum1[-1] = dexdum 560 for i in range(len(self.a)): 561 ddum1[i] += np.eye(3)/len(self.a) 562 ddum2 = np.zeros((len(self.a), 3, len(self.a), 3, 3), dtype=float) 563 ddum2[ 0, : , 0, :] = dexdum2 564 ddum2[-1, : , 0, :] = -dexdum2 565 ddum2[ 0, :, -1, :] = -dexdum2 566 ddum2[-1, :, -1, :] = dexdum2 567 # ===== 568 # Do not delete - reference codes using loops for chain rule terms 569 # for j in range(len(self.a)): # Loop over atom 1 570 # for m in range(3): # Loop over xyz of atom 1 571 # for k in range(len(self.a)): # Loop over atom 2 572 # for n in range(3): # Loop over xyz of atom 2 573 # for i in range(3): # Loop over elements of exponential map 574 # for p in range(3): # Loop over xyz of dummy atom 575 # deriv2_raw[j, m, k, n, i] += deriv2_raw[j, m, -1, p, i] * ddum1[k, n, p] 576 # deriv2_raw[j, m, k, n, i] += deriv2_raw[-1, p, k, n, i] * ddum1[j, m, p] 577 # deriv2_raw[j, m, k, n, i] += deriv_raw[-1, p, i] * ddum2[j, m, k, n, p] 578 # for q in range(3): 579 # deriv2_raw[j, m, k, n, i] += deriv2_raw[-1, p, -1, q, i] * ddum1[j, m, p] * ddum1[k, n, q] 580 # ===== 581 deriv2_raw[:-1, :, :-1, :] += np.einsum('jmpi,knp->jmkni', deriv2_raw[:-1, :, -1, :, :], ddum1, optimize=True) 582 deriv2_raw[:-1, :, :-1, :] += np.einsum('pkni,jmp->jmkni', deriv2_raw[-1, :, :-1, :, :], ddum1, optimize=True) 583 deriv2_raw[:-1, :, :-1, :] += np.einsum('pi,jmknp->jmkni', deriv_raw[-1, :, :], ddum2, optimize=True) 584 deriv2_raw[:-1, :, :-1, :] += np.einsum('pqi,jmp,knq->jmkni', deriv2_raw[-1, :, -1, :, :], ddum1, ddum1, optimize=True) 585 deriv2_raw = deriv2_raw[:-1, :, :-1, :, :] 586 second_derivatives = np.zeros((xyz.shape[0], 3, xyz.shape[0], 3, 3), dtype=float) 587 for i, a in enumerate(self.a): 588 for j, b in enumerate(self.a): 589 second_derivatives[a, :, b, :, :] = deriv2_raw[i, :, j, :, :] 590 return second_derivatives 591 592class RotationA(object): 593 def __init__(self, a, x0, Rotators, w=1.0): 594 self.a = tuple(sorted(a)) 595 self.x0 = x0 596 self.w = w 597 if self.a not in Rotators: 598 Rotators[self.a] = Rotator(self.a, x0) 599 self.Rotator = Rotators[self.a] 600 self.isAngular = True 601 self.isPeriodic = False 602 603 def __repr__(self): 604 # return "Rotation-A %s : Weight %.3f" % (' '.join([str(i+1) for i in self.a]), self.w) 605 return "Rotation-A %s" % (commadash(self.a)) 606 607 def __eq__(self, other): 608 if type(self) is not type(other): return False 609 eq = set(self.a) == set(other.a) 610 # if eq and np.sum((self.w-other.w)**2) > 1e-6: 611 # print "Warning: RotationA same atoms, different weights" 612 # if eq and np.sum((self.x0-other.x0)**2) > 1e-6: 613 # print "Warning: RotationA same atoms, different reference positions" 614 return eq 615 616 def __ne__(self, other): 617 return not self.__eq__(other) 618 619 def value(self, xyz): 620 return self.Rotator.value(xyz)[0]*self.w 621 622 def derivative(self, xyz): 623 der_all = self.Rotator.derivative(xyz) 624 derivatives = der_all[:, :, 0]*self.w 625 return derivatives 626 627 def second_derivative(self, xyz): 628 deriv2_all = self.Rotator.second_derivative(xyz) 629 second_derivatives = deriv2_all[:, :, :, :, 0]*self.w 630 return second_derivatives 631 632class RotationB(object): 633 def __init__(self, a, x0, Rotators, w=1.0): 634 self.a = tuple(sorted(a)) 635 self.x0 = x0 636 self.w = w 637 if self.a not in Rotators: 638 Rotators[self.a] = Rotator(self.a, x0) 639 self.Rotator = Rotators[self.a] 640 self.isAngular = True 641 self.isPeriodic = False 642 643 def __repr__(self): 644 # return "Rotation-B %s : Weight %.3f" % (' '.join([str(i+1) for i in self.a]), self.w) 645 return "Rotation-B %s" % (commadash(self.a)) 646 647 def __eq__(self, other): 648 if type(self) is not type(other): return False 649 eq = set(self.a) == set(other.a) 650 # if eq and np.sum((self.w-other.w)**2) > 1e-6: 651 # print "Warning: RotationB same atoms, different weights" 652 # if eq and np.sum((self.x0-other.x0)**2) > 1e-6: 653 # print "Warning: RotationB same atoms, different reference positions" 654 return eq 655 656 def __ne__(self, other): 657 return not self.__eq__(other) 658 659 def value(self, xyz): 660 return self.Rotator.value(xyz)[1]*self.w 661 662 def derivative(self, xyz): 663 der_all = self.Rotator.derivative(xyz) 664 derivatives = der_all[:, :, 1]*self.w 665 return derivatives 666 667 def second_derivative(self, xyz): 668 deriv2_all = self.Rotator.second_derivative(xyz) 669 second_derivatives = deriv2_all[:, :, :, :, 1]*self.w 670 return second_derivatives 671 672class RotationC(object): 673 def __init__(self, a, x0, Rotators, w=1.0): 674 self.a = tuple(sorted(a)) 675 self.x0 = x0 676 self.w = w 677 if self.a not in Rotators: 678 Rotators[self.a] = Rotator(self.a, x0) 679 self.Rotator = Rotators[self.a] 680 self.isAngular = True 681 self.isPeriodic = False 682 683 def __repr__(self): 684 # return "Rotation-C %s : Weight %.3f" % (' '.join([str(i+1) for i in self.a]), self.w) 685 return "Rotation-C %s" % (commadash(self.a)) 686 687 def __eq__(self, other): 688 if type(self) is not type(other): return False 689 eq = set(self.a) == set(other.a) 690 # if eq and np.sum((self.w-other.w)**2) > 1e-6: 691 # print "Warning: RotationC same atoms, different weights" 692 # if eq and np.sum((self.x0-other.x0)**2) > 1e-6: 693 # print "Warning: RotationC same atoms, different reference positions" 694 return eq 695 696 def __ne__(self, other): 697 return not self.__eq__(other) 698 699 def value(self, xyz): 700 return self.Rotator.value(xyz)[2]*self.w 701 702 def derivative(self, xyz): 703 der_all = self.Rotator.derivative(xyz) 704 derivatives = der_all[:, :, 2]*self.w 705 return derivatives 706 707 def second_derivative(self, xyz): 708 deriv2_all = self.Rotator.second_derivative(xyz) 709 second_derivatives = deriv2_all[:, :, :, :, 2]*self.w 710 return second_derivatives 711 712class Distance(object): 713 def __init__(self, a, b): 714 self.a = a 715 self.b = b 716 if a == b: 717 raise RuntimeError('a and b must be different') 718 self.isAngular = False 719 self.isPeriodic = False 720 721 def __repr__(self): 722 return "Distance %i-%i" % (self.a+1, self.b+1) 723 724 def __eq__(self, other): 725 if type(self) is not type(other): return False 726 if self.a == other.a: 727 if self.b == other.b: 728 return True 729 if self.a == other.b: 730 if self.b == other.a: 731 return True 732 return False 733 734 def __ne__(self, other): 735 return not self.__eq__(other) 736 737 def value(self, xyz): 738 xyz = xyz.reshape(-1,3) 739 a = self.a 740 b = self.b 741 return np.sqrt(np.sum((xyz[a]-xyz[b])**2)) 742 743 def derivative(self, xyz): 744 xyz = xyz.reshape(-1,3) 745 derivatives = np.zeros_like(xyz) 746 m = self.a 747 n = self.b 748 u = (xyz[m] - xyz[n]) / np.linalg.norm(xyz[m] - xyz[n]) 749 derivatives[m, :] = u 750 derivatives[n, :] = -u 751 return derivatives 752 753 def second_derivative(self, xyz): 754 xyz = xyz.reshape(-1,3) 755 deriv2 = np.zeros((xyz.shape[0], xyz.shape[1], xyz.shape[0], xyz.shape[1])) 756 m = self.a 757 n = self.b 758 l = np.linalg.norm(xyz[m] - xyz[n]) 759 u = (xyz[m] - xyz[n]) / l 760 mtx = (np.outer(u, u) - np.eye(3))/l 761 deriv2[m, :, m, :] = -mtx 762 deriv2[n, :, n, :] = -mtx 763 deriv2[m, :, n, :] = mtx 764 deriv2[n, :, m, :] = mtx 765 return deriv2 766 767class Angle(object): 768 def __init__(self, a, b, c): 769 self.a = a 770 self.b = b 771 self.c = c 772 self.isAngular = True 773 self.isPeriodic = False 774 if len({a, b, c}) != 3: 775 raise RuntimeError('a, b, and c must be different') 776 777 def __repr__(self): 778 return "Angle %i-%i-%i" % (self.a+1, self.b+1, self.c+1) 779 780 def __eq__(self, other): 781 if type(self) is not type(other): return False 782 if self.b == other.b: 783 if self.a == other.a: 784 if self.c == other.c: 785 return True 786 if self.a == other.c: 787 if self.c == other.a: 788 return True 789 return False 790 791 def __ne__(self, other): 792 return not self.__eq__(other) 793 794 def value(self, xyz): 795 xyz = xyz.reshape(-1,3) 796 a = self.a 797 b = self.b 798 c = self.c 799 # vector from first atom to central atom 800 vector1 = xyz[a] - xyz[b] 801 # vector from last atom to central atom 802 vector2 = xyz[c] - xyz[b] 803 # norm of the two vectors 804 norm1 = np.sqrt(np.sum(vector1**2)) 805 norm2 = np.sqrt(np.sum(vector2**2)) 806 dot = np.dot(vector1, vector2) 807 # Catch the edge case that very rarely this number is -1. 808 if dot / (norm1 * norm2) <= -1.0: 809 if (np.abs(dot / (norm1 * norm2)) + 1.0) < -1e-6: 810 raise RuntimeError('Encountered invalid value in angle') 811 return np.pi 812 if dot / (norm1 * norm2) >= 1.0: 813 if (np.abs(dot / (norm1 * norm2)) - 1.0) > 1e-6: 814 raise RuntimeError('Encountered invalid value in angle') 815 return 0.0 816 return np.arccos(dot / (norm1 * norm2)) 817 818 def normal_vector(self, xyz): 819 xyz = xyz.reshape(-1,3) 820 a = self.a 821 b = self.b 822 c = self.c 823 # vector from first atom to central atom 824 vector1 = xyz[a] - xyz[b] 825 # vector from last atom to central atom 826 vector2 = xyz[c] - xyz[b] 827 # norm of the two vectors 828 norm1 = np.sqrt(np.sum(vector1**2)) 829 norm2 = np.sqrt(np.sum(vector2**2)) 830 crs = np.cross(vector1, vector2) 831 crs /= np.linalg.norm(crs) 832 return crs 833 834 def derivative(self, xyz): 835 xyz = xyz.reshape(-1,3) 836 derivatives = np.zeros_like(xyz) 837 m = self.a 838 o = self.b 839 n = self.c 840 # Unit displacement vectors 841 u_prime = (xyz[m] - xyz[o]) 842 u_norm = np.linalg.norm(u_prime) 843 v_prime = (xyz[n] - xyz[o]) 844 v_norm = np.linalg.norm(v_prime) 845 u = u_prime / u_norm 846 v = v_prime / v_norm 847 VECTOR1 = np.array([1, -1, 1]) / np.sqrt(3) 848 VECTOR2 = np.array([-1, 1, 1]) / np.sqrt(3) 849 if np.linalg.norm(u + v) < 1e-10 or np.linalg.norm(u - v) < 1e-10: 850 # if they're parallel 851 if ((np.linalg.norm(u + VECTOR1) < 1e-10) or 852 (np.linalg.norm(u - VECTOR2) < 1e-10)): 853 # and they're parallel o [1, -1, 1] 854 w_prime = np.cross(u, VECTOR2) 855 else: 856 w_prime = np.cross(u, VECTOR1) 857 else: 858 w_prime = np.cross(u, v) 859 w = w_prime / np.linalg.norm(w_prime) 860 term1 = np.cross(u, w) / u_norm 861 term2 = np.cross(w, v) / v_norm 862 derivatives[m, :] = term1 863 derivatives[n, :] = term2 864 derivatives[o, :] = -(term1 + term2) 865 return derivatives 866 867 def second_derivative(self, xyz): 868 xyz = xyz.reshape(-1,3) 869 deriv2 = np.zeros((xyz.shape[0], xyz.shape[1], xyz.shape[0], xyz.shape[1])) 870 m = self.a 871 o = self.b 872 n = self.c 873 # Unit displacement vectors 874 u_prime = (xyz[m] - xyz[o]) 875 u_norm = np.linalg.norm(u_prime) 876 v_prime = (xyz[n] - xyz[o]) 877 v_norm = np.linalg.norm(v_prime) 878 u = u_prime / u_norm 879 v = v_prime / v_norm 880 # Deriv2 derivatives are set to zero in the case of parallel or antiparallel vectors 881 if np.linalg.norm(u + v) < 1e-10 or np.linalg.norm(u - v) < 1e-10: 882 return deriv2 883 # cosine and sine of the bond angle 884 cq = np.dot(u, v) 885 sq = np.sqrt(1-cq**2) 886 uu = np.outer(u, u) 887 uv = np.outer(u, v) 888 vv = np.outer(v, v) 889 de = np.eye(3) 890 term1 = (uv + uv.T - (3*uu - de)*cq)/(u_norm**2*sq) 891 term2 = (uv + uv.T - (3*vv - de)*cq)/(v_norm**2*sq) 892 term3 = (uu + vv - uv*cq - de)/(u_norm*v_norm*sq) 893 term4 = (uu + vv - uv.T*cq - de)/(u_norm*v_norm*sq) 894 der1 = self.derivative(xyz) 895 def zeta(a_, m_, n_): 896 return (int(a_==m_) - int(a_==n_)) 897 for a in [m, n, o]: 898 for b in [m, n, o]: 899 deriv2[a, :, b, :] = (zeta(a, m, o)*zeta(b, m, o)*term1 900 + zeta(a, n, o)*zeta(b, n, o)*term2 901 + zeta(a, m, o)*zeta(b, n, o)*term3 902 + zeta(a, n, o)*zeta(b, m, o)*term4 903 - (cq/sq) * np.outer(der1[a], der1[b])) 904 return deriv2 905 906class LinearAngle(object): 907 def __init__(self, a, b, c, axis): 908 self.a = a 909 self.b = b 910 self.c = c 911 self.axis = axis 912 self.isAngular = False 913 self.isPeriodic = False 914 if len({a, b, c}) != 3: 915 raise RuntimeError('a, b, and c must be different') 916 self.e0 = None 917 self.stored_dot2 = 0.0 918 919 def reset(self, xyz): 920 xyz = xyz.reshape(-1,3) 921 a = self.a 922 b = self.b 923 c = self.c 924 # Unit vector pointing from a to c. 925 v = xyz[c] - xyz[a] 926 ev = v / np.linalg.norm(v) 927 # Cartesian axes. 928 ex = np.array([1.0,0.0,0.0]) 929 ey = np.array([0.0,1.0,0.0]) 930 ez = np.array([0.0,0.0,1.0]) 931 self.e0 = [ex, ey, ez][np.argmin([np.dot(i, ev)**2 for i in [ex, ey, ez]])] 932 self.stored_dot2 = 0.0 933 934 def __repr__(self): 935 return "LinearAngle%s %i-%i-%i" % (["X","Y"][self.axis], self.a+1, self.b+1, self.c+1) 936 937 def __eq__(self, other): 938 if not hasattr(other, 'axis'): return False 939 if self.axis is not other.axis: return False 940 if type(self) is not type(other): return False 941 if self.b == other.b: 942 if self.a == other.a: 943 if self.c == other.c: 944 return True 945 if self.a == other.c: 946 if self.c == other.a: 947 return True 948 return False 949 950 def __ne__(self, other): 951 return not self.__eq__(other) 952 953 def value(self, xyz): 954 """ 955 This function measures the displacement of the BA and BC unit 956 vectors in the linear angle "ABC". The displacements are measured 957 along two axes that are perpendicular to the AC unit vector. 958 """ 959 xyz = xyz.reshape(-1,3) 960 a = self.a 961 b = self.b 962 c = self.c 963 # Unit vector pointing from a to c. 964 v = xyz[c] - xyz[a] 965 ev = v / np.linalg.norm(v) 966 if self.e0 is None: self.reset(xyz) 967 e0 = self.e0 968 self.stored_dot2 = np.dot(ev, e0)**2 969 # Now make two unit vectors that are perpendicular to this one. 970 c1 = np.cross(ev, e0) 971 e1 = c1 / np.linalg.norm(c1) 972 c2 = np.cross(ev, e1) 973 e2 = c2 / np.linalg.norm(c2) 974 # BA and BC unit vectors in ABC angle 975 vba = xyz[a]-xyz[b] 976 eba = vba / np.linalg.norm(vba) 977 vbc = xyz[c]-xyz[b] 978 ebc = vbc / np.linalg.norm(vbc) 979 if self.axis == 0: 980 answer = np.dot(eba, e1) + np.dot(ebc, e1) 981 else: 982 answer = np.dot(eba, e2) + np.dot(ebc, e2) 983 return answer 984 985 def derivative(self, xyz): 986 xyz = xyz.reshape(-1,3) 987 a = self.a 988 b = self.b 989 c = self.c 990 derivatives = np.zeros_like(xyz) 991 ## Finite difference derivatives 992 ## fderivatives = np.zeros_like(xyz) 993 ## h = 1e-6 994 ## for u in range(xyz.shape[0]): 995 ## for v in range(3): 996 ## xyz[u, v] += h 997 ## vPlus = self.value(xyz) 998 ## xyz[u, v] -= 2*h 999 ## vMinus = self.value(xyz) 1000 ## xyz[u, v] += h 1001 ## fderivatives[u, v] = (vPlus-vMinus)/(2*h) 1002 # Unit vector pointing from a to c. 1003 v = xyz[c] - xyz[a] 1004 ev = v / np.linalg.norm(v) 1005 if self.e0 is None: self.reset(xyz) 1006 e0 = self.e0 1007 c1 = np.cross(ev, e0) 1008 e1 = c1 / np.linalg.norm(c1) 1009 c2 = np.cross(ev, e1) 1010 e2 = c2 / np.linalg.norm(c2) 1011 # BA and BC unit vectors in ABC angle 1012 vba = xyz[a]-xyz[b] 1013 eba = vba / np.linalg.norm(vba) 1014 vbc = xyz[c]-xyz[b] 1015 ebc = vbc / np.linalg.norm(vbc) 1016 # Derivative terms 1017 de0 = np.zeros((3, 3), dtype=float) 1018 dev = d_unit_vector(v) 1019 dc1 = d_cross_ab(ev, e0, dev, de0) 1020 de1 = np.dot(dc1, d_unit_vector(c1)) 1021 dc2 = d_cross_ab(ev, e1, dev, de1) 1022 de2 = np.dot(dc2, d_unit_vector(c2)) 1023 deba = d_unit_vector(vba) 1024 debc = d_unit_vector(vbc) 1025 if self.axis == 0: 1026 derivatives[a, :] = np.dot(deba, e1) + np.dot(-de1, eba) + np.dot(-de1, ebc) 1027 derivatives[b, :] = np.dot(-deba, e1) + np.dot(-debc, e1) 1028 derivatives[c, :] = np.dot(de1, eba) + np.dot(de1, ebc) + np.dot(debc, e1) 1029 else: 1030 derivatives[a, :] = np.dot(deba, e2) + np.dot(-de2, eba) + np.dot(-de2, ebc) 1031 derivatives[b, :] = np.dot(-deba, e2) + np.dot(-debc, e2) 1032 derivatives[c, :] = np.dot(de2, eba) + np.dot(de2, ebc) + np.dot(debc, e2) 1033 ## Finite difference derivatives 1034 ## if np.linalg.norm(derivatives - fderivatives) > 1e-6: 1035 ## print np.linalg.norm(derivatives - fderivatives) 1036 ## raise Exception() 1037 return derivatives 1038 1039 def second_derivative(self, xyz): 1040 xyz = xyz.reshape(-1,3) 1041 a = self.a 1042 b = self.b 1043 c = self.c 1044 deriv2 = np.zeros((xyz.shape[0], 3, xyz.shape[0], 3), dtype=float) 1045 h = 1.0e-3 1046 for i in range(3): 1047 for j in range(3): 1048 ii = [a, b, c][i] 1049 xyz[ii, j] += h 1050 FPlus = self.derivative(xyz) 1051 xyz[ii, j] -= 2*h 1052 FMinus = self.derivative(xyz) 1053 xyz[ii, j] += h 1054 fderiv = (FPlus-FMinus)/(2*h) 1055 deriv2[ii, j, :, :] = fderiv 1056 return deriv2 1057 1058class MultiAngle(object): 1059 def __init__(self, a, b, c): 1060 if type(a) is int: 1061 a = (a,) 1062 if type(c) is int: 1063 c = (c,) 1064 self.a = tuple(a) 1065 self.b = b 1066 self.c = tuple(c) 1067 self.isAngular = True 1068 self.isPeriodic = False 1069 if len({a, b, c}) != 3: 1070 raise RuntimeError('a, b, and c must be different') 1071 1072 def __repr__(self): 1073 stra = ("("+','.join(["%i" % (i+1) for i in self.a])+")") if len(self.a) > 1 else "%i" % (self.a[0]+1) 1074 strc = ("("+','.join(["%i" % (i+1) for i in self.c])+")") if len(self.c) > 1 else "%i" % (self.c[0]+1) 1075 return "%sAngle %s-%i-%s" % ("Multi" if (len(self.a) > 1 or len(self.c) > 1) else "", stra, self.b+1, strc) 1076 1077 def __eq__(self, other): 1078 if type(self) is not type(other): return False 1079 if self.b == other.b: 1080 if set(self.a) == set(other.a): 1081 if set(self.c) == set(other.c): 1082 return True 1083 if set(self.a) == set(other.c): 1084 if set(self.c) == set(other.a): 1085 return True 1086 return False 1087 1088 def __ne__(self, other): 1089 return not self.__eq__(other) 1090 1091 def value(self, xyz): 1092 xyz = xyz.reshape(-1,3) 1093 a = np.array(self.a) 1094 b = self.b 1095 c = np.array(self.c) 1096 xyza = np.mean(xyz[a], axis=0) 1097 xyzc = np.mean(xyz[c], axis=0) 1098 # vector from first atom to central atom 1099 vector1 = xyza - xyz[b] 1100 # vector from last atom to central atom 1101 vector2 = xyzc - xyz[b] 1102 # norm of the two vectors 1103 norm1 = np.sqrt(np.sum(vector1**2)) 1104 norm2 = np.sqrt(np.sum(vector2**2)) 1105 dot = np.dot(vector1, vector2) 1106 # Catch the edge case that very rarely this number is -1. 1107 if dot / (norm1 * norm2) <= -1.0: 1108 if (np.abs(dot / (norm1 * norm2)) + 1.0) < -1e-6: 1109 raise RuntimeError('Encountered invalid value in angle') 1110 return np.pi 1111 return np.arccos(dot / (norm1 * norm2)) 1112 1113 def normal_vector(self, xyz): 1114 xyz = xyz.reshape(-1,3) 1115 a = np.array(self.a) 1116 b = self.b 1117 c = np.array(self.c) 1118 xyza = np.mean(xyz[a], axis=0) 1119 xyzc = np.mean(xyz[c], axis=0) 1120 # vector from first atom to central atom 1121 vector1 = xyza - xyz[b] 1122 # vector from last atom to central atom 1123 vector2 = xyzc - xyz[b] 1124 # norm of the two vectors 1125 norm1 = np.sqrt(np.sum(vector1**2)) 1126 norm2 = np.sqrt(np.sum(vector2**2)) 1127 crs = np.cross(vector1, vector2) 1128 crs /= np.linalg.norm(crs) 1129 return crs 1130 1131 def derivative(self, xyz): 1132 xyz = xyz.reshape(-1,3) 1133 derivatives = np.zeros_like(xyz) 1134 m = np.array(self.a) 1135 o = self.b 1136 n = np.array(self.c) 1137 xyzm = np.mean(xyz[m], axis=0) 1138 xyzn = np.mean(xyz[n], axis=0) 1139 # Unit displacement vectors 1140 u_prime = (xyzm - xyz[o]) 1141 u_norm = np.linalg.norm(u_prime) 1142 v_prime = (xyzn - xyz[o]) 1143 v_norm = np.linalg.norm(v_prime) 1144 u = u_prime / u_norm 1145 v = v_prime / v_norm 1146 VECTOR1 = np.array([1, -1, 1]) / np.sqrt(3) 1147 VECTOR2 = np.array([-1, 1, 1]) / np.sqrt(3) 1148 if np.linalg.norm(u + v) < 1e-10 or np.linalg.norm(u - v) < 1e-10: 1149 # if they're parallel 1150 if ((np.linalg.norm(u + VECTOR1) < 1e-10) or 1151 (np.linalg.norm(u - VECTOR2) < 1e-10)): 1152 # and they're parallel o [1, -1, 1] 1153 w_prime = np.cross(u, VECTOR2) 1154 else: 1155 w_prime = np.cross(u, VECTOR1) 1156 else: 1157 w_prime = np.cross(u, v) 1158 w = w_prime / np.linalg.norm(w_prime) 1159 term1 = np.cross(u, w) / u_norm 1160 term2 = np.cross(w, v) / v_norm 1161 for i in m: 1162 derivatives[i, :] = term1/len(m) 1163 for i in n: 1164 derivatives[i, :] = term2/len(n) 1165 derivatives[o, :] = -(term1 + term2) 1166 return derivatives 1167 1168 def second_derivative(self, xyz): 1169 raise NotImplementedError("Second derivatives have not been implemented for IC type %s" % self.__name__) 1170 1171class Dihedral(object): 1172 def __init__(self, a, b, c, d): 1173 self.a = a 1174 self.b = b 1175 self.c = c 1176 self.d = d 1177 self.isAngular = True 1178 self.isPeriodic = True 1179 if len({a, b, c, d}) != 4: 1180 raise RuntimeError('a, b, c and d must be different') 1181 1182 def __repr__(self): 1183 return "Dihedral %i-%i-%i-%i" % (self.a+1, self.b+1, self.c+1, self.d+1) 1184 1185 def __eq__(self, other): 1186 if type(self) is not type(other): return False 1187 if self.a == other.a: 1188 if self.b == other.b: 1189 if self.c == other.c: 1190 if self.d == other.d: 1191 return True 1192 if self.a == other.d: 1193 if self.b == other.c: 1194 if self.c == other.b: 1195 if self.d == other.a: 1196 return True 1197 return False 1198 1199 def __ne__(self, other): 1200 return not self.__eq__(other) 1201 1202 def value(self, xyz): 1203 xyz = xyz.reshape(-1,3) 1204 a = self.a 1205 b = self.b 1206 c = self.c 1207 d = self.d 1208 vec1 = xyz[b] - xyz[a] 1209 vec2 = xyz[c] - xyz[b] 1210 vec3 = xyz[d] - xyz[c] 1211 cross1 = np.cross(vec2, vec3) 1212 cross2 = np.cross(vec1, vec2) 1213 arg1 = np.sum(np.multiply(vec1, cross1)) * \ 1214 np.sqrt(np.sum(vec2**2)) 1215 arg2 = np.sum(np.multiply(cross1, cross2)) 1216 answer = np.arctan2(arg1, arg2) 1217 return answer 1218 1219 def derivative(self, xyz): 1220 xyz = xyz.reshape(-1,3) 1221 derivatives = np.zeros_like(xyz) 1222 m = self.a 1223 o = self.b 1224 p = self.c 1225 n = self.d 1226 u_prime = (xyz[m] - xyz[o]) 1227 w_prime = (xyz[p] - xyz[o]) 1228 v_prime = (xyz[n] - xyz[p]) 1229 u_norm = np.linalg.norm(u_prime) 1230 w_norm = np.linalg.norm(w_prime) 1231 v_norm = np.linalg.norm(v_prime) 1232 u = u_prime / u_norm 1233 w = w_prime / w_norm 1234 v = v_prime / v_norm 1235 if (1 - np.dot(u, w)**2) < 1e-6: 1236 term1 = np.cross(u, w) * 0 1237 term3 = np.cross(u, w) * 0 1238 else: 1239 term1 = np.cross(u, w) / (u_norm * (1 - np.dot(u, w)**2)) 1240 term3 = np.cross(u, w) * np.dot(u, w) / (w_norm * (1 - np.dot(u, w)**2)) 1241 if (1 - np.dot(v, w)**2) < 1e-6: 1242 term2 = np.cross(v, w) * 0 1243 term4 = np.cross(v, w) * 0 1244 else: 1245 term2 = np.cross(v, w) / (v_norm * (1 - np.dot(v, w)**2)) 1246 term4 = np.cross(v, w) * np.dot(v, w) / (w_norm * (1 - np.dot(v, w)**2)) 1247 # term1 = np.cross(u, w) / (u_norm * (1 - np.dot(u, w)**2)) 1248 # term2 = np.cross(v, w) / (v_norm * (1 - np.dot(v, w)**2)) 1249 # term3 = np.cross(u, w) * np.dot(u, w) / (w_norm * (1 - np.dot(u, w)**2)) 1250 # term4 = np.cross(v, w) * np.dot(v, w) / (w_norm * (1 - np.dot(v, w)**2)) 1251 derivatives[m, :] = term1 1252 derivatives[n, :] = -term2 1253 derivatives[o, :] = -term1 + term3 - term4 1254 derivatives[p, :] = term2 - term3 + term4 1255 return derivatives 1256 1257 def second_derivative(self, xyz): 1258 xyz = xyz.reshape(-1,3) 1259 deriv2 = np.zeros((xyz.shape[0], xyz.shape[1], xyz.shape[0], xyz.shape[1])) 1260 m = self.a 1261 o = self.b 1262 p = self.c 1263 n = self.d 1264 u_prime = (xyz[m] - xyz[o]) 1265 w_prime = (xyz[p] - xyz[o]) 1266 v_prime = (xyz[n] - xyz[p]) 1267 lu = np.linalg.norm(u_prime) 1268 lw = np.linalg.norm(w_prime) 1269 lv = np.linalg.norm(v_prime) 1270 u = u_prime / lu 1271 w = w_prime / lw 1272 v = v_prime / lv 1273 cu = np.dot(u, w) 1274 su = (1 - np.dot(u, w)**2)**0.5 1275 su4 = su**4 1276 cv = np.dot(v, w) 1277 sv = (1 - np.dot(v, w)**2)**0.5 1278 sv4 = sv**4 1279 if su < 1e-6 or sv < 1e-6 : return deriv2 1280 1281 uxw = np.cross(u, w) 1282 vxw = np.cross(v, w) 1283 1284 term1 = np.outer(uxw, w*cu - u)/(lu**2*su4) 1285 term2 = np.outer(vxw, -w*cv + v)/(lv**2*sv4) 1286 term3 = np.outer(uxw, w - 2*u*cu + w*cu**2)/(2*lu*lw*su4) 1287 term4 = np.outer(vxw, w - 2*v*cv + w*cv**2)/(2*lv*lw*sv4) 1288 term5 = np.outer(uxw, u + u*cu**2 - 3*w*cu + w*cu**3)/(2*lw**2*su4) 1289 term6 = np.outer(vxw,-v - v*cv**2 + 3*w*cv - w*cv**3)/(2*lw**2*sv4) 1290 term1 += term1.T 1291 term2 += term2.T 1292 term3 += term3.T 1293 term4 += term4.T 1294 term5 += term5.T 1295 term6 += term6.T 1296 def mk_amat(vec): 1297 amat = np.zeros((3,3)) 1298 for i in range(3): 1299 for j in range(3): 1300 if i == j: continue 1301 k = 3 - i - j 1302 amat[i, j] = vec[k] * (j-i) * ((-0.5)**np.abs(j-i)) 1303 return amat 1304 term7 = mk_amat((-w*cu + u)/(lu*lw*su**2)) 1305 term8 = mk_amat(( w*cv - v)/(lv*lw*sv**2)) 1306 def zeta(a_, m_, n_): 1307 return (int(a_==m_) - int(a_==n_)) 1308 # deriv2_terms = [np.zeros_like(deriv2) for i in range(9)] 1309 # Accumulate the second derivative 1310 for a in [m, n, o, p]: 1311 for b in [m, n, o, p]: 1312 deriv2[a, :, b, :] = (zeta(a, m, o)*zeta(b, m, o)*term1 + 1313 zeta(a, n, p)*zeta(b, n, p)*term2 + 1314 (zeta(a, m, o)*zeta(b, o, p) + zeta(a, p, o)*zeta(b, o, m))*term3 + 1315 (zeta(a, n, p)*zeta(b, p, o) + zeta(a, p, o)*zeta(b, n, p))*term4 + 1316 zeta(a, o, p)*zeta(b, p, o)*term5 + 1317 zeta(a, p, o)*zeta(b, o, p)*term6) 1318 if a != b: 1319 deriv2[a, :, b, :] += ((zeta(a, m, o)*zeta(b, p, o) + zeta(a, p, o)*zeta(b, o, m))*term7 + 1320 (zeta(a, n, o)*zeta(b, p, o) + zeta(a, p, o)*zeta(b, o, n))*term8) 1321 return deriv2 1322 1323 # Accumulate a dictionary of contributions to the second derivatives by term (for debugging) 1324 # deriv2_terms[7][a, :, b, :] = (zeta(a, m, o)*zeta(b, p, o) + zeta(a, p, o)*zeta(b, o, m))*term7 1325 # deriv2_terms[8][a, :, b, :] = (zeta(a, n, o)*zeta(b, p, o) + zeta(a, p, o)*zeta(b, o, n))*term8 1326 # deriv2_terms[1][a, :, b, :] = zeta(a, m, o)*zeta(b, m, o)*term1 1327 # deriv2_terms[2][a, :, b, :] = zeta(a, n, p)*zeta(b, n, p)*term2 1328 # deriv2_terms[3][a, :, b, :] = (zeta(a, m, o)*zeta(b, o, p) + zeta(a, p, o)*zeta(b, o, m))*term3 1329 # deriv2_terms[4][a, :, b, :] = (zeta(a, n, p)*zeta(b, p, o) + zeta(a, p, o)*zeta(b, n, p))*term4 1330 # deriv2_terms[5][a, :, b, :] = zeta(a, o, p)*zeta(b, p, o)*term5 1331 # deriv2_terms[6][a, :, b, :] = zeta(a, p, o)*zeta(b, o, p)*term6 1332 # deriv2_terms[0] = deriv2.copy() 1333 # 1334 #======= 1335 # Term-by-term checking of the second derivative. 1336 # Produces output such as: 1337 # 1x1x a: 0.0000 n: 0.0000 e: 0.0000 Terms: NNNNNNNN 0.0000 0.0000 -0.0000 -0.0000 -0.0000 -0.0000 0.0000 0.0000 1338 # 1x1y a: 0.3337 n: 0.3337 e: 0.0000 Terms: YNNNNNNN 0.3337 0.0000 -0.0000 -0.0000 -0.0000 -0.0000 0.0000 0.0000 1339 # 1x1z a: 0.0590 n: 0.0590 e: -0.0000 Terms: YNNNNNNN 0.0590 0.0000 -0.0000 0.0000 -0.0000 0.0000 0.0000 0.0000 1340 # 1341 # def printTerm(strin, num): 1342 # i = int(strin[0])-1 1343 # j = 'xyz'.index(strin[1]) 1344 # k = int(strin[2])-1 1345 # l = 'xyz'.index(strin[3]) 1346 # ana = deriv2_terms[0][i,j,k,l] 1347 # err = ana-num 1348 # correct = np.abs(num-ana) < 1e-5 1349 # color = '\x1b[92m' if correct else '\x1b[91m' 1350 # print('%i%s%i%s a: % .4f n: % .4f e: % .4f Terms: ' % (i+1, 'xyz'[j], k+1, 'xyz'[l], ana, num, err) + 1351 # ''.join(["Y" if np.abs(deriv2_terms[m][i,j,k,l]) > 1e-5 else "N" for m in range(1, 9)]) + ' ' + 1352 # ' '.join(["%s% .4f\x1b[0m" % (color if np.abs(deriv2_terms[m][i,j,k,l]) > 1e-5 else '', 1353 # deriv2_terms[m][i,j,k,l]) for m in range(1, 9)])) 1354 # print("LP checking single term:") 1355 # printTerm('1x1x', 5.55112e-09) 1356 # printTerm('1x1y', 3.33702e-01) 1357 # printTerm('1x1z', 5.90389e-02) 1358 1359class MultiDihedral(object): 1360 def __init__(self, a, b, c, d): 1361 if type(a) is int: 1362 a = (a, ) 1363 if type(d) is int: 1364 d = (d, ) 1365 self.a = tuple(a) 1366 self.b = b 1367 self.c = c 1368 self.d = tuple(d) 1369 self.isAngular = True 1370 self.isPeriodic = True 1371 if len({a, b, c, d}) != 4: 1372 raise RuntimeError('a, b, c and d must be different') 1373 1374 def __repr__(self): 1375 stra = ("("+','.join(["%i" % (i+1) for i in self.a])+")") if len(self.a) > 1 else "%i" % (self.a[0]+1) 1376 strd = ("("+','.join(["%i" % (i+1) for i in self.d])+")") if len(self.d) > 1 else "%i" % (self.d[0]+1) 1377 return "%sDihedral %s-%i-%i-%s" % ("Multi" if (len(self.a) > 1 or len(self.d) > 1) else "", stra, self.b+1, self.c+1, strd) 1378 1379 def __eq__(self, other): 1380 if type(self) is not type(other): return False 1381 if set(self.a) == set(other.a): 1382 if self.b == other.b: 1383 if self.c == other.c: 1384 if set(self.d) == set(other.d): 1385 return True 1386 if set(self.a) == set(other.d): 1387 if self.b == other.c: 1388 if self.c == other.b: 1389 if set(self.d) == set(other.a): 1390 return True 1391 return False 1392 1393 def __ne__(self, other): 1394 return not self.__eq__(other) 1395 1396 def value(self, xyz): 1397 xyz = xyz.reshape(-1,3) 1398 a = np.array(self.a) 1399 b = self.b 1400 c = self.c 1401 d = np.array(self.d) 1402 xyza = np.mean(xyz[a], axis=0) 1403 xyzd = np.mean(xyz[d], axis=0) 1404 1405 vec1 = xyz[b] - xyza 1406 vec2 = xyz[c] - xyz[b] 1407 vec3 = xyzd - xyz[c] 1408 cross1 = np.cross(vec2, vec3) 1409 cross2 = np.cross(vec1, vec2) 1410 arg1 = np.sum(np.multiply(vec1, cross1)) * \ 1411 np.sqrt(np.sum(vec2**2)) 1412 arg2 = np.sum(np.multiply(cross1, cross2)) 1413 answer = np.arctan2(arg1, arg2) 1414 return answer 1415 1416 def derivative(self, xyz): 1417 xyz = xyz.reshape(-1,3) 1418 derivatives = np.zeros_like(xyz) 1419 m = np.array(self.a) 1420 o = self.b 1421 p = self.c 1422 n = np.array(self.d) 1423 xyzm = np.mean(xyz[m], axis=0) 1424 xyzn = np.mean(xyz[n], axis=0) 1425 1426 u_prime = (xyzm - xyz[o]) 1427 w_prime = (xyz[p] - xyz[o]) 1428 v_prime = (xyzn - xyz[p]) 1429 u_norm = np.linalg.norm(u_prime) 1430 w_norm = np.linalg.norm(w_prime) 1431 v_norm = np.linalg.norm(v_prime) 1432 u = u_prime / u_norm 1433 w = w_prime / w_norm 1434 v = v_prime / v_norm 1435 if (1 - np.dot(u, w)**2) < 1e-6: 1436 term1 = np.cross(u, w) * 0 1437 term3 = np.cross(u, w) * 0 1438 else: 1439 term1 = np.cross(u, w) / (u_norm * (1 - np.dot(u, w)**2)) 1440 term3 = np.cross(u, w) * np.dot(u, w) / (w_norm * (1 - np.dot(u, w)**2)) 1441 if (1 - np.dot(v, w)**2) < 1e-6: 1442 term2 = np.cross(v, w) * 0 1443 term4 = np.cross(v, w) * 0 1444 else: 1445 term2 = np.cross(v, w) / (v_norm * (1 - np.dot(v, w)**2)) 1446 term4 = np.cross(v, w) * np.dot(v, w) / (w_norm * (1 - np.dot(v, w)**2)) 1447 # term1 = np.cross(u, w) / (u_norm * (1 - np.dot(u, w)**2)) 1448 # term2 = np.cross(v, w) / (v_norm * (1 - np.dot(v, w)**2)) 1449 # term3 = np.cross(u, w) * np.dot(u, w) / (w_norm * (1 - np.dot(u, w)**2)) 1450 # term4 = np.cross(v, w) * np.dot(v, w) / (w_norm * (1 - np.dot(v, w)**2)) 1451 for i in self.a: 1452 derivatives[i, :] = term1/len(self.a) 1453 for i in self.d: 1454 derivatives[i, :] = -term2/len(self.d) 1455 derivatives[o, :] = -term1 + term3 - term4 1456 derivatives[p, :] = term2 - term3 + term4 1457 return derivatives 1458 1459 def second_derivative(self, xyz): 1460 raise NotImplementedError("Second derivatives have not been implemented for IC type %s" % self.__name__) 1461 1462class OutOfPlane(object): 1463 def __init__(self, a, b, c, d): 1464 self.a = a 1465 self.b = b 1466 self.c = c 1467 self.d = d 1468 self.isAngular = True 1469 self.isPeriodic = True 1470 if len({a, b, c, d}) != 4: 1471 raise RuntimeError('a, b, c and d must be different') 1472 1473 def __repr__(self): 1474 return "Out-of-Plane %i-%i-%i-%i" % (self.a+1, self.b+1, self.c+1, self.d+1) 1475 1476 def __eq__(self, other): 1477 if type(self) is not type(other): return False 1478 if self.a == other.a: 1479 if {self.b, self.c, self.d} == {other.b, other.c, other.d}: 1480 if [self.b, self.c, self.d] != [other.b, other.c, other.d]: 1481 logger.warning("Warning: OutOfPlane atoms are the same, ordering is different\n") 1482 return True 1483 # if self.b == other.b: 1484 # if self.c == other.c: 1485 # if self.d == other.d: 1486 # return True 1487 # if self.a == other.d: 1488 # if self.b == other.c: 1489 # if self.c == other.b: 1490 # if self.d == other.a: 1491 # return True 1492 return False 1493 1494 def __ne__(self, other): 1495 return not self.__eq__(other) 1496 1497 def value(self, xyz): 1498 xyz = xyz.reshape(-1,3) 1499 a = self.a 1500 b = self.b 1501 c = self.c 1502 d = self.d 1503 vec1 = xyz[b] - xyz[a] 1504 vec2 = xyz[c] - xyz[b] 1505 vec3 = xyz[d] - xyz[c] 1506 cross1 = np.cross(vec2, vec3) 1507 cross2 = np.cross(vec1, vec2) 1508 arg1 = np.sum(np.multiply(vec1, cross1)) * \ 1509 np.sqrt(np.sum(vec2**2)) 1510 arg2 = np.sum(np.multiply(cross1, cross2)) 1511 answer = np.arctan2(arg1, arg2) 1512 return answer 1513 1514 def derivative(self, xyz): 1515 xyz = xyz.reshape(-1,3) 1516 derivatives = np.zeros_like(xyz) 1517 m = self.a 1518 o = self.b 1519 p = self.c 1520 n = self.d 1521 u_prime = (xyz[m] - xyz[o]) 1522 w_prime = (xyz[p] - xyz[o]) 1523 v_prime = (xyz[n] - xyz[p]) 1524 u_norm = np.linalg.norm(u_prime) 1525 w_norm = np.linalg.norm(w_prime) 1526 v_norm = np.linalg.norm(v_prime) 1527 u = u_prime / u_norm 1528 w = w_prime / w_norm 1529 v = v_prime / v_norm 1530 if (1 - np.dot(u, w)**2) < 1e-6: 1531 term1 = np.cross(u, w) * 0 1532 term3 = np.cross(u, w) * 0 1533 else: 1534 term1 = np.cross(u, w) / (u_norm * (1 - np.dot(u, w)**2)) 1535 term3 = np.cross(u, w) * np.dot(u, w) / (w_norm * (1 - np.dot(u, w)**2)) 1536 if (1 - np.dot(v, w)**2) < 1e-6: 1537 term2 = np.cross(v, w) * 0 1538 term4 = np.cross(v, w) * 0 1539 else: 1540 term2 = np.cross(v, w) / (v_norm * (1 - np.dot(v, w)**2)) 1541 term4 = np.cross(v, w) * np.dot(v, w) / (w_norm * (1 - np.dot(v, w)**2)) 1542 # term1 = np.cross(u, w) / (u_norm * (1 - np.dot(u, w)**2)) 1543 # term2 = np.cross(v, w) / (v_norm * (1 - np.dot(v, w)**2)) 1544 # term3 = np.cross(u, w) * np.dot(u, w) / (w_norm * (1 - np.dot(u, w)**2)) 1545 # term4 = np.cross(v, w) * np.dot(v, w) / (w_norm * (1 - np.dot(v, w)**2)) 1546 derivatives[m, :] = term1 1547 derivatives[n, :] = -term2 1548 derivatives[o, :] = -term1 + term3 - term4 1549 derivatives[p, :] = term2 - term3 + term4 1550 return derivatives 1551 1552 def second_derivative(self, xyz): 1553 xyz = xyz.reshape(-1,3) 1554 a = self.a 1555 b = self.b 1556 c = self.c 1557 d = self.d 1558 deriv2 = np.zeros((xyz.shape[0], 3, xyz.shape[0], 3), dtype=float) 1559 h = 1.0e-3 1560 for i in range(4): 1561 for j in range(3): 1562 ii = [a, b, c, d][i] 1563 xyz[ii, j] += h 1564 FPlus = self.derivative(xyz) 1565 xyz[ii, j] -= 2*h 1566 FMinus = self.derivative(xyz) 1567 xyz[ii, j] += h 1568 fderiv = (FPlus-FMinus)/(2*h) 1569 deriv2[ii, j, :, :] = fderiv 1570 return deriv2 1571 1572CacheWarning = False 1573 1574class InternalCoordinates(object): 1575 def __init__(self): 1576 self.stored_wilsonB = OrderedDict() 1577 1578 def addConstraint(self, cPrim, cVal): 1579 raise NotImplementedError("Constraints not supported with Cartesian coordinates") 1580 1581 def haveConstraints(self): 1582 raise NotImplementedError("Constraints not supported with Cartesian coordinates") 1583 1584 def augmentGH(self, xyz, G, H): 1585 raise NotImplementedError("Constraints not supported with Cartesian coordinates") 1586 1587 def calcGradProj(self, xyz, gradx): 1588 raise NotImplementedError("Constraints not supported with Cartesian coordinates") 1589 1590 def clearCache(self): 1591 self.stored_wilsonB = OrderedDict() 1592 1593 def wilsonB(self, xyz): 1594 """ 1595 Given Cartesian coordinates xyz, return the Wilson B-matrix 1596 given by dq_i/dx_j where x is flattened (i.e. x1, y1, z1, x2, y2, z2) 1597 """ 1598 global CacheWarning 1599 t0 = time.time() 1600 xhash = hash(xyz.tostring()) 1601 ht = time.time() - t0 1602 if xhash in self.stored_wilsonB: 1603 ans = self.stored_wilsonB[xhash] 1604 return ans 1605 WilsonB = [] 1606 Der = self.derivatives(xyz) 1607 for i in range(Der.shape[0]): 1608 WilsonB.append(Der[i].flatten()) 1609 self.stored_wilsonB[xhash] = np.array(WilsonB) 1610 if len(self.stored_wilsonB) > 1000 and not CacheWarning: 1611 logger.warning("\x1b[91mWarning: more than 1000 B-matrices stored, memory leaks likely\x1b[0m\n") 1612 CacheWarning = True 1613 ans = np.array(WilsonB) 1614 return ans 1615 1616 def GMatrix(self, xyz): 1617 """ 1618 Given Cartesian coordinates xyz, return the G-matrix 1619 given by G = BuBt where u is an arbitrary matrix (default to identity) 1620 """ 1621 Bmat = self.wilsonB(xyz) 1622 BuBt = np.dot(Bmat,Bmat.T) 1623 return BuBt 1624 1625 def GInverse_SVD(self, xyz): 1626 xyz = xyz.reshape(-1,3) 1627 # Perform singular value decomposition 1628 click() 1629 loops = 0 1630 while True: 1631 try: 1632 G = self.GMatrix(xyz) 1633 time_G = click() 1634 U, S, VT = np.linalg.svd(G) 1635 time_svd = click() 1636 except np.linalg.LinAlgError: 1637 logger.warning("\x1b[1;91m SVD fails, perturbing coordinates and trying again\x1b[0m\n") 1638 xyz = xyz + 1e-2*np.random.random(xyz.shape) 1639 loops += 1 1640 if loops == 10: 1641 raise RuntimeError('SVD failed too many times') 1642 continue 1643 break 1644 # print "Build G: %.3f SVD: %.3f" % (time_G, time_svd), 1645 V = VT.T 1646 UT = U.T 1647 Sinv = np.zeros_like(S) 1648 LargeVals = 0 1649 for ival, value in enumerate(S): 1650 # print "%.5e % .5e" % (ival,value) 1651 if np.abs(value) > 1e-6: 1652 LargeVals += 1 1653 Sinv[ival] = 1/value 1654 # print "%i atoms; %i/%i singular values are > 1e-6" % (xyz.shape[0], LargeVals, len(S)) 1655 Sinv = np.diag(Sinv) 1656 Inv = multi_dot([V, Sinv, UT]) 1657 return Inv 1658 1659 def GInverse_EIG(self, xyz): 1660 xyz = xyz.reshape(-1,3) 1661 click() 1662 G = self.GMatrix(xyz) 1663 time_G = click() 1664 Gi = np.linalg.inv(G) 1665 time_inv = click() 1666 # print "G-time: %.3f Inv-time: %.3f" % (time_G, time_inv) 1667 return Gi 1668 1669 def checkFiniteDifferenceGrad(self, xyz): 1670 xyz = xyz.reshape(-1,3) 1671 Analytical = self.derivatives(xyz) 1672 FiniteDifference = np.zeros_like(Analytical) 1673 h = 1e-5 1674 for i in range(xyz.shape[0]): 1675 for j in range(3): 1676 x1 = xyz.copy() 1677 x2 = xyz.copy() 1678 x1[i,j] += h 1679 x2[i,j] -= h 1680 PMDiff = self.calcDiff(x1,x2) 1681 FiniteDifference[:,i,j] = PMDiff/(2*h) 1682 logger.info("-=# Now checking first derivatives of internal coordinates w/r.t. Cartesians #=-\n") 1683 for i in range(Analytical.shape[0]): 1684 title = "%20s : %20s" % ("IC %i/%i" % (i+1, Analytical.shape[0]), self.Internals[i]) 1685 lines = [title] 1686 maxerr = 0.0 1687 for j in range(Analytical.shape[1]): 1688 lines.append("Atom %i" % (j+1)) 1689 for k in range(Analytical.shape[2]): 1690 error = Analytical[i,j,k] - FiniteDifference[i,j,k] 1691 if np.abs(error) > 1e-5: 1692 color = "\x1b[91m" 1693 else: 1694 color = "\x1b[92m" 1695 lines.append("%s % .5e % .5e %s% .5e\x1b[0m" % ("xyz"[k], Analytical[i,j,k], FiniteDifference[i,j,k], color, Analytical[i,j,k] - FiniteDifference[i,j,k])) 1696 if maxerr < np.abs(error): 1697 maxerr = np.abs(error) 1698 if maxerr > 1e-5: 1699 logger.info('\n'.join(lines)+'\n') 1700 logger.info("%s : Max Error = %.5e\n" % (title, maxerr)) 1701 logger.info("Finite-difference Finished\n") 1702 return FiniteDifference 1703 1704 def checkFiniteDifferenceHess(self, xyz): 1705 xyz = xyz.reshape(-1,3) 1706 Analytical = self.second_derivatives(xyz) 1707 FiniteDifference = np.zeros_like(Analytical) 1708 h = 1e-4 1709 verbose = False 1710 logger.info("-=# Now checking second derivatives of internal coordinates w/r.t. Cartesians #=-\n") 1711 for j in range(xyz.shape[0]): 1712 for m in range(3): 1713 for k in range(xyz.shape[0]): 1714 for n in range(3): 1715 x1 = xyz.copy() 1716 x2 = xyz.copy() 1717 x3 = xyz.copy() 1718 x4 = xyz.copy() 1719 x1[j, m] += h 1720 x1[k, n] += h # (+, +) 1721 x2[j, m] += h 1722 x2[k, n] -= h # (+, -) 1723 x3[j, m] -= h 1724 x3[k, n] += h # (-, +) 1725 x4[j, m] -= h 1726 x4[k, n] -= h # (-, -) 1727 PMDiff1 = self.calcDiff(x1, x2) 1728 PMDiff2 = self.calcDiff(x4, x3) 1729 FiniteDifference[:, j, m, k, n] += (PMDiff1+PMDiff2)/(4*h**2) 1730 # print('\r%i %i' % (j, k), end='') 1731 # print() 1732 for i in range(Analytical.shape[0]): 1733 title = "%20s : %20s" % ("IC %i/%i" % (i+1, Analytical.shape[0]), self.Internals[i]) 1734 lines = [title] 1735 if verbose: logger.info(title+'\n') 1736 maxerr = 0.0 1737 numerr = 0 1738 for j in range(Analytical.shape[1]): 1739 for m in range(Analytical.shape[2]): 1740 for k in range(Analytical.shape[3]): 1741 for n in range(Analytical.shape[4]): 1742 ana = Analytical[i,j,m,k,n] 1743 fin = FiniteDifference[i,j,m,k,n] 1744 error = ana - fin 1745 message = "Atom %i %s %i %s a: % 12.5e n: % 12.5e e: % 12.5e %s" % (j+1, 'xyz'[m], k+1, 'xyz'[n], ana, fin, 1746 error, 'X' if np.abs(error)>1e-5 else '') 1747 if np.abs(error)>1e-5: 1748 numerr += 1 1749 if (ana != 0.0 or fin != 0.0) and verbose: 1750 logger.info(message+'\n') 1751 lines.append(message) 1752 if maxerr < np.abs(error): 1753 maxerr = np.abs(error) 1754 if maxerr > 1e-5 and not verbose: 1755 logger.info('\n'.join(lines)+'\n') 1756 logger.info("%s : Max Error = % 12.5e (%i above threshold)\n" % (title, maxerr, numerr)) 1757 logger.info("Finite-difference Finished\n") 1758 return FiniteDifference 1759 1760 def calcGrad(self, xyz, gradx): 1761 q0 = self.calculate(xyz) 1762 Ginv = self.GInverse(xyz) 1763 Bmat = self.wilsonB(xyz) 1764 # Internal coordinate gradient 1765 # Gq = np.matrix(Ginv)*np.matrix(Bmat)*np.matrix(gradx).T 1766 Gq = multi_dot([Ginv, Bmat, gradx.T]) 1767 return Gq.flatten() 1768 1769 def calcHess(self, xyz, gradx, hessx): 1770 """ 1771 Compute the internal coordinate Hessian. 1772 Expects Cartesian coordinates to be provided in a.u. 1773 """ 1774 xyz = xyz.flatten() 1775 q0 = self.calculate(xyz) 1776 Ginv = self.GInverse(xyz) 1777 Bmat = self.wilsonB(xyz) 1778 Gq = self.calcGrad(xyz, gradx) 1779 deriv2 = self.second_derivatives(xyz) 1780 Bmatp = deriv2.reshape(deriv2.shape[0], xyz.shape[0], xyz.shape[0]) 1781 Hx_BptGq = hessx - np.einsum('pmn,p->mn',Bmatp,Gq) 1782 Hq = np.einsum('ps,sm,mn,nr,rq', Ginv, Bmat, Hx_BptGq, Bmat.T, Ginv, optimize=True) 1783 return Hq 1784 1785 def readCache(self, xyz, dQ): 1786 if not hasattr(self, 'stored_xyz'): 1787 return None 1788 xyz = xyz.flatten() 1789 dQ = dQ.flatten() 1790 if np.linalg.norm(self.stored_xyz - xyz) < 1e-10: 1791 if np.linalg.norm(self.stored_dQ - dQ) < 1e-10: 1792 return self.stored_newxyz 1793 return None 1794 1795 def writeCache(self, xyz, dQ, newxyz): 1796 xyz = xyz.flatten() 1797 dQ = dQ.flatten() 1798 newxyz = newxyz.flatten() 1799 self.stored_xyz = xyz.copy() 1800 self.stored_dQ = dQ.copy() 1801 self.stored_newxyz = newxyz.copy() 1802 1803 def newCartesian(self, xyz, dQ, verbose=True): 1804 cached = self.readCache(xyz, dQ) 1805 if cached is not None: 1806 # print "Returning cached result" 1807 return cached 1808 xyz1 = xyz.copy() 1809 dQ1 = dQ.copy() 1810 # Iterate until convergence: 1811 microiter = 0 1812 ndqs = [] 1813 rmsds = [] 1814 self.bork = False 1815 # Damping factor 1816 damp = 1.0 1817 # Function to exit from loop 1818 def finish(microiter, rmsdt, ndqt, xyzsave, xyz_iter1): 1819 if ndqt > 1e-1: 1820 if verbose: logger.info("Failed to obtain coordinates after %i microiterations (rmsd = %.3e |dQ| = %.3e)\n" % (microiter, rmsdt, ndqt)) 1821 self.bork = True 1822 self.writeCache(xyz, dQ, xyz_iter1) 1823 return xyz_iter1.flatten() 1824 elif ndqt > 1e-3: 1825 if verbose: logger.info("Approximate coordinates obtained after %i microiterations (rmsd = %.3e |dQ| = %.3e)\n" % (microiter, rmsdt, ndqt)) 1826 else: 1827 if verbose: logger.info("Cartesian coordinates obtained after %i microiterations (rmsd = %.3e |dQ| = %.3e)\n" % (microiter, rmsdt, ndqt)) 1828 self.writeCache(xyz, dQ, xyzsave) 1829 return xyzsave.flatten() 1830 fail_counter = 0 1831 while True: 1832 microiter += 1 1833 Bmat = self.wilsonB(xyz1) 1834 Ginv = self.GInverse(xyz1) 1835 # Get new Cartesian coordinates 1836 dxyz = damp*multi_dot([Bmat.T,Ginv,dQ1.T]) 1837 xyz2 = xyz1 + np.array(dxyz).flatten() 1838 if microiter == 1: 1839 xyzsave = xyz2.copy() 1840 xyz_iter1 = xyz2.copy() 1841 # Calculate the actual change in internal coordinates 1842 dQ_actual = self.calcDiff(xyz2, xyz1) 1843 rmsd = np.sqrt(np.mean((np.array(xyz2-xyz1).flatten())**2)) 1844 ndq = np.linalg.norm(dQ1-dQ_actual) 1845 if len(ndqs) > 0: 1846 if ndq > ndqt: 1847 if verbose: logger.info("Iter: %i Err-dQ (Best) = %.5e (%.5e) RMSD: %.5e Damp: %.5e (Bad)\n" % (microiter, ndq, ndqt, rmsd, damp)) 1848 damp /= 2 1849 fail_counter += 1 1850 # xyz2 = xyz1.copy() 1851 else: 1852 if verbose: logger.info("Iter: %i Err-dQ (Best) = %.5e (%.5e) RMSD: %.5e Damp: %.5e (Good)\n" % (microiter, ndq, ndqt, rmsd, damp)) 1853 fail_counter = 0 1854 damp = min(damp*1.2, 1.0) 1855 rmsdt = rmsd 1856 ndqt = ndq 1857 xyzsave = xyz2.copy() 1858 else: 1859 if verbose: logger.info("Iter: %i Err-dQ = %.5e RMSD: %.5e Damp: %.5e\n" % (microiter, ndq, rmsd, damp)) 1860 rmsdt = rmsd 1861 ndqt = ndq 1862 ndqs.append(ndq) 1863 rmsds.append(rmsd) 1864 # Check convergence / fail criteria 1865 if rmsd < 1e-6 or ndq < 1e-6: 1866 return finish(microiter, rmsdt, ndqt, xyzsave, xyz_iter1) 1867 if fail_counter >= 5: 1868 return finish(microiter, rmsdt, ndqt, xyzsave, xyz_iter1) 1869 if microiter == 50: 1870 return finish(microiter, rmsdt, ndqt, xyzsave, xyz_iter1) 1871 # Figure out the further change needed 1872 dQ1 = dQ1 - dQ_actual 1873 xyz1 = xyz2.copy() 1874 1875class PrimitiveInternalCoordinates(InternalCoordinates): 1876 def __init__(self, molecule, connect=False, addcart=False, constraints=None, cvals=None, **kwargs): 1877 super(PrimitiveInternalCoordinates, self).__init__() 1878 self.connect = connect 1879 self.addcart = addcart 1880 self.Internals = [] 1881 self.cPrims = [] 1882 self.cVals = [] 1883 self.Rotators = OrderedDict() 1884 self.elem = molecule.elem 1885 for i in range(len(molecule)): 1886 self.makePrimitives(molecule[i], connect, addcart) 1887 # Assume we're using the first image for constraints 1888 self.makeConstraints(molecule[0], constraints, cvals) 1889 # Reorder primitives for checking with cc's code in TC. 1890 # Note that reorderPrimitives() _must_ be updated with each new InternalCoordinate class written. 1891 self.reorderPrimitives() 1892 1893 def makePrimitives(self, molecule, connect, addcart): 1894 molecule.build_topology() 1895 if 'resid' in molecule.Data.keys(): 1896 frags = [] 1897 current_resid = -1 1898 for i in range(molecule.na): 1899 if molecule.resid[i] != current_resid: 1900 frags.append([i]) 1901 current_resid = molecule.resid[i] 1902 else: 1903 frags[-1].append(i) 1904 else: 1905 frags = [m.nodes() for m in molecule.molecules] 1906 # coordinates in Angstrom 1907 coords = molecule.xyzs[0].flatten() 1908 # Make a distance matrix mapping atom pairs to interatomic distances 1909 AtomIterator, dxij = molecule.distance_matrix(pbc=False) 1910 D = {} 1911 for i, j in zip(AtomIterator, dxij[0]): 1912 assert i[0] < i[1] 1913 D[tuple(i)] = j 1914 dgraph = nx.Graph() 1915 for i in range(molecule.na): 1916 dgraph.add_node(i) 1917 for k, v in D.items(): 1918 dgraph.add_edge(k[0], k[1], weight=v) 1919 mst = sorted(list(nx.minimum_spanning_edges(dgraph, data=False))) 1920 # Build a list of noncovalent distances 1921 noncov = [] 1922 # Connect all non-bonded fragments together 1923 for edge in mst: 1924 if edge not in list(molecule.topology.edges()): 1925 # print "Adding %s from minimum spanning tree" % str(edge) 1926 if connect: 1927 molecule.topology.add_edge(edge[0], edge[1]) 1928 noncov.append(edge) 1929 if not connect: 1930 if addcart: 1931 for i in range(molecule.na): 1932 self.add(CartesianX(i, w=1.0)) 1933 self.add(CartesianY(i, w=1.0)) 1934 self.add(CartesianZ(i, w=1.0)) 1935 else: 1936 for i in frags: 1937 if len(i) >= 2: 1938 self.add(TranslationX(i, w=np.ones(len(i))/len(i))) 1939 self.add(TranslationY(i, w=np.ones(len(i))/len(i))) 1940 self.add(TranslationZ(i, w=np.ones(len(i))/len(i))) 1941 # Reference coordinates are given in Bohr. 1942 sel = coords.reshape(-1,3)[i,:] * ang2bohr 1943 sel -= np.mean(sel, axis=0) 1944 rg = np.sqrt(np.mean(np.sum(sel**2, axis=1))) 1945 self.add(RotationA(i, coords * ang2bohr, self.Rotators, w=rg)) 1946 self.add(RotationB(i, coords * ang2bohr, self.Rotators, w=rg)) 1947 self.add(RotationC(i, coords * ang2bohr, self.Rotators, w=rg)) 1948 else: 1949 for j in i: 1950 self.add(CartesianX(j, w=1.0)) 1951 self.add(CartesianY(j, w=1.0)) 1952 self.add(CartesianZ(j, w=1.0)) 1953 add_tr = False 1954 if add_tr: 1955 i = range(molecule.na) 1956 self.add(TranslationX(i, w=np.ones(len(i))/len(i))) 1957 self.add(TranslationY(i, w=np.ones(len(i))/len(i))) 1958 self.add(TranslationZ(i, w=np.ones(len(i))/len(i))) 1959 # Reference coordinates are given in Bohr. 1960 sel = coords.reshape(-1,3)[i,:] * ang2bohr 1961 sel -= np.mean(sel, axis=0) 1962 rg = np.sqrt(np.mean(np.sum(sel**2, axis=1))) 1963 self.add(RotationA(i, coords * ang2bohr, self.Rotators, w=rg)) 1964 self.add(RotationB(i, coords * ang2bohr, self.Rotators, w=rg)) 1965 self.add(RotationC(i, coords * ang2bohr, self.Rotators, w=rg)) 1966 1967 # # Build a list of noncovalent distances 1968 # noncov = [] 1969 # # Connect all non-bonded fragments together 1970 # while True: 1971 # # List of disconnected fragments 1972 # subg = list(nx.connected_component_subgraphs(molecule.topology)) 1973 # # Break out of loop if all fragments are connected 1974 # if len(subg) == 1: break 1975 # # Find the smallest interatomic distance between any pair of fragments 1976 # minD = 1e10 1977 # for i in range(len(subg)): 1978 # for j in range(i): 1979 # for a in subg[i].nodes(): 1980 # for b in subg[j].nodes(): 1981 # if D[(min(a,b), max(a,b))] < minD: 1982 # minD = D[(min(a,b), max(a,b))] 1983 # # Next, create one connection between pairs of fragments that have a 1984 # # close-contact distance of at most 1.2 times the minimum found above 1985 # for i in range(len(subg)): 1986 # for j in range(i): 1987 # tminD = 1e10 1988 # conn = False 1989 # conn_a = None 1990 # conn_b = None 1991 # for a in subg[i].nodes(): 1992 # for b in subg[j].nodes(): 1993 # if D[(min(a,b), max(a,b))] < tminD: 1994 # tminD = D[(min(a,b), max(a,b))] 1995 # conn_a = min(a,b) 1996 # conn_b = max(a,b) 1997 # if D[(min(a,b), max(a,b))] <= 1.3*minD: 1998 # conn = True 1999 # if conn: 2000 # molecule.topology.add_edge(conn_a, conn_b) 2001 # noncov.append((conn_a, conn_b)) 2002 2003 # Add an internal coordinate for all interatomic distances 2004 for (a, b) in molecule.topology.edges(): 2005 self.add(Distance(a, b)) 2006 2007 # Add an internal coordinate for all angles 2008 # LinThre = 0.99619469809174555 2009 # LinThre = 0.999 2010 # This number works best for the iron complex 2011 LinThre = 0.95 2012 AngDict = defaultdict(list) 2013 for b in molecule.topology.nodes(): 2014 for a in molecule.topology.neighbors(b): 2015 for c in molecule.topology.neighbors(b): 2016 if a < c: 2017 # if (a, c) in molecule.topology.edges() or (c, a) in molecule.topology.edges(): continue 2018 Ang = Angle(a, b, c) 2019 nnc = (min(a, b), max(a, b)) in noncov 2020 nnc += (min(b, c), max(b, c)) in noncov 2021 # if nnc >= 2: continue 2022 # logger.info("LPW: cosine of angle", a, b, c, "is", np.abs(np.cos(Ang.value(coords)))) 2023 if np.abs(np.cos(Ang.value(coords))) < LinThre: 2024 self.add(Angle(a, b, c)) 2025 AngDict[b].append(Ang) 2026 elif connect or not addcart: 2027 # logger.info("Adding linear angle") 2028 # Add linear angle IC's 2029 # LPW 2019-02-16: Linear angle ICs work well for "very" linear angles in molecules (e.g. HCCCN) 2030 # but do not work well for "almost" linear angles in noncovalent systems (e.g. H2O6). 2031 # Bringing back old code to use "translations" for the latter case, but should be investigated 2032 # more deeply in the future. 2033 if nnc == 0: 2034 self.add(LinearAngle(a, b, c, 0)) 2035 self.add(LinearAngle(a, b, c, 1)) 2036 else: 2037 # Unit vector connecting atoms a and c 2038 nac = molecule.xyzs[0][c] - molecule.xyzs[0][a] 2039 nac /= np.linalg.norm(nac) 2040 # Dot products of this vector with the Cartesian axes 2041 dots = [np.abs(np.dot(ei, nac)) for ei in np.eye(3)] 2042 # Functions for adding Cartesian coordinate 2043 # carts = [CartesianX, CartesianY, CartesianZ] 2044 trans = [TranslationX, TranslationY, TranslationZ] 2045 w = np.array([-1.0, 2.0, -1.0]) 2046 # Add two of the most perpendicular Cartesian coordinates 2047 for i in np.argsort(dots)[:2]: 2048 self.add(trans[i]([a, b, c], w=w)) 2049 2050 for b in molecule.topology.nodes(): 2051 for a in molecule.topology.neighbors(b): 2052 for c in molecule.topology.neighbors(b): 2053 for d in molecule.topology.neighbors(b): 2054 if a < c < d: 2055 nnc = (min(a, b), max(a, b)) in noncov 2056 nnc += (min(b, c), max(b, c)) in noncov 2057 nnc += (min(b, d), max(b, d)) in noncov 2058 # if nnc >= 1: continue 2059 for i, j, k in sorted(list(itertools.permutations([a, c, d], 3))): 2060 Ang1 = Angle(b,i,j) 2061 Ang2 = Angle(i,j,k) 2062 if np.abs(np.cos(Ang1.value(coords))) > LinThre: continue 2063 if np.abs(np.cos(Ang2.value(coords))) > LinThre: continue 2064 if np.abs(np.dot(Ang1.normal_vector(coords), Ang2.normal_vector(coords))) > LinThre: 2065 self.delete(Angle(i, b, j)) 2066 self.add(OutOfPlane(b, i, j, k)) 2067 break 2068 2069 # Find groups of atoms that are in straight lines 2070 atom_lines = [list(i) for i in molecule.topology.edges()] 2071 while True: 2072 # For a line of two atoms (one bond): 2073 # AB-AC 2074 # AX-AY 2075 # i.e. AB is the first one, AC is the second one 2076 # AX is the second-to-last one, AY is the last one 2077 # AB-AC-...-AX-AY 2078 # AB-(AC, AX)-AY 2079 atom_lines0 = deepcopy(atom_lines) 2080 for aline in atom_lines: 2081 # Imagine a line of atoms going like ab-ac-ax-ay. 2082 # Our job is to extend the line until there are no more 2083 ab = aline[0] 2084 ay = aline[-1] 2085 for aa in molecule.topology.neighbors(ab): 2086 if aa not in aline: 2087 # If the angle that AA makes with AB and ALL other atoms AC in the line are linear: 2088 # Add AA to the front of the list 2089 if all([np.abs(np.cos(Angle(aa, ab, ac).value(coords))) > LinThre for ac in aline[1:] if ac != ab]): 2090 aline.insert(0, aa) 2091 for az in molecule.topology.neighbors(ay): 2092 if az not in aline: 2093 if all([np.abs(np.cos(Angle(ax, ay, az).value(coords))) > LinThre for ax in aline[:-1] if ax != ay]): 2094 aline.append(az) 2095 if atom_lines == atom_lines0: break 2096 atom_lines_uniq = [] 2097 for i in atom_lines: # 2098 if tuple(i) not in set(atom_lines_uniq): 2099 atom_lines_uniq.append(tuple(i)) 2100 lthree = [l for l in atom_lines_uniq if len(l) > 2] 2101 # TODO: Perhaps should reduce the times this is printed out in reaction paths 2102 # if len(lthree) > 0: 2103 # print "Lines of three or more atoms:", ', '.join(['-'.join(["%i" % (i+1) for i in l]) for l in lthree]) 2104 2105 # Normal dihedral code 2106 for aline in atom_lines_uniq: 2107 # Go over ALL pairs of atoms in a line 2108 for (b, c) in itertools.combinations(aline, 2): 2109 if b > c: (b, c) = (c, b) 2110 # Go over all neighbors of b 2111 for a in molecule.topology.neighbors(b): 2112 # Go over all neighbors of c 2113 for d in molecule.topology.neighbors(c): 2114 # Make sure the end-atoms are not in the line and not the same as each other 2115 if a not in aline and d not in aline and a != d: 2116 nnc = (min(a, b), max(a, b)) in noncov 2117 nnc += (min(b, c), max(b, c)) in noncov 2118 nnc += (min(c, d), max(c, d)) in noncov 2119 # print aline, a, b, c, d 2120 Ang1 = Angle(a,b,c) 2121 Ang2 = Angle(b,c,d) 2122 # Eliminate dihedrals containing angles that are almost linear 2123 # (should be eliminated already) 2124 if np.abs(np.cos(Ang1.value(coords))) > LinThre: continue 2125 if np.abs(np.cos(Ang2.value(coords))) > LinThre: continue 2126 self.add(Dihedral(a, b, c, d)) 2127 2128 ### Following are codes that evaluate angles and dihedrals involving entire lines-of-atoms 2129 ### as single degrees of freedom 2130 ### Unfortunately, they do not seem to improve the performance 2131 # 2132 # def pull_lines(a, front=True, middle=False): 2133 # """ 2134 # Given an atom, pull all lines-of-atoms that it is in, e.g. 2135 # e.g. 2136 # D 2137 # C 2138 # B 2139 # EFGHAIJKL 2140 # returns (B, C, D), (H, G, F, E), (I, J, K, L). 2141 # 2142 # A is the implicit first item in the list. 2143 # Set front to False to make A the implicit last item in the list. 2144 # Set middle to True to return lines where A is in the middle e.g. (H, G, F, E) and (I, J, K, L). 2145 # """ 2146 # answer = [] 2147 # for l in atom_lines_uniq: 2148 # if l[0] == a: 2149 # answer.append(l[:][1:]) 2150 # elif l[-1] == a: 2151 # answer.append(l[::-1][1:]) 2152 # elif middle and a in l: 2153 # answer.append(l[l.index(a):][1:]) 2154 # answer.append(l[:l.index(a)][::-1]) 2155 # if front: return answer 2156 # else: return [l[::-1] for l in answer] 2157 # 2158 # def same_line(al, bl): 2159 # for l in atom_lines_uniq: 2160 # if set(al).issubset(set(l)) and set(bl).issubset(set(l)): 2161 # return True 2162 # return False 2163 # 2164 # ## Multiple angle code; does not improve performance for Fe4N system. 2165 # for b in molecule.topology.nodes(): 2166 # for al in pull_lines(b, front=False, middle=True): 2167 # for cl in pull_lines(b, front=True, middle=True): 2168 # if al[0] == cl[-1]: continue 2169 # if al[-1] == cl[0]: continue 2170 # self.delete(Angle(al[-1], b, cl[0])) 2171 # self.delete(Angle(cl[0], b, al[-1])) 2172 # if len(set(al).intersection(set(cl))) > 0: continue 2173 # if same_line(al, cl): 2174 # continue 2175 # if al[-1] < cl[0]: 2176 # self.add(MultiAngle(al, b, cl)) 2177 # else: 2178 # self.add(MultiAngle(cl[::-1], b, al[::-1])) 2179 # 2180 ## Multiple dihedral code 2181 ## Note: This suffers from a problem where it cannot rebuild the Cartesian coordinates, 2182 ## possibly due to a bug in the MultiDihedral class. 2183 # for aline in atom_lines_uniq: 2184 # for (b, c) in itertools.combinations(aline, 2): 2185 # if b > c: (b, c) = (c, b) 2186 # for al in pull_lines(b, front=False, middle=True): 2187 # if same_line(al, aline): continue 2188 # # print "Same line:", al, aline 2189 # for dl in pull_lines(c, front=True, middle=True): 2190 # if same_line(dl, aline): continue 2191 # # print "Same line:", dl, aline 2192 # # continue 2193 # # if same_line(dl, al): continue 2194 # if al[-1] == dl[0]: continue 2195 # # if len(set(al).intersection(set(dl))) > 0: continue 2196 # # print MultiDihedral(al, b, c, dl) 2197 # self.delete(Dihedral(al[-1], b, c, dl[0])) 2198 # self.add(MultiDihedral(al, b, c, dl)) 2199 2200 def makeConstraints(self, molecule, constraints, cvals): 2201 # Add the list of constraints. 2202 xyz = molecule.xyzs[0].flatten() * ang2bohr 2203 if constraints is not None: 2204 if len(constraints) != len(cvals): 2205 raise RuntimeError("List of constraints should be same length as constraint values") 2206 for cons, cval in zip(constraints, cvals): 2207 self.addConstraint(cons, cval, xyz) 2208 2209 def __repr__(self): 2210 lines = ["Internal coordinate system (atoms numbered from 1):"] 2211 typedict = OrderedDict() 2212 for Internal in self.Internals: 2213 lines.append(Internal.__repr__()) 2214 if str(type(Internal)) not in typedict: 2215 typedict[str(type(Internal))] = 1 2216 else: 2217 typedict[str(type(Internal))] += 1 2218 if len(lines) > 100: 2219 # Print only summary if too many 2220 lines = [] 2221 for k, v in typedict.items(): 2222 lines.append("%s : %i" % (k, v)) 2223 return '\n'.join(lines) 2224 2225 def __eq__(self, other): 2226 answer = True 2227 for i in self.Internals: 2228 if i not in other.Internals: 2229 answer = False 2230 for i in other.Internals: 2231 if i not in self.Internals: 2232 answer = False 2233 return answer 2234 2235 def __ne__(self, other): 2236 return not self.__eq__(other) 2237 2238 def update(self, other): 2239 Changed = False 2240 for i in self.Internals: 2241 if i not in other.Internals: 2242 if hasattr(i, 'inactive'): 2243 i.inactive += 1 2244 else: 2245 i.inactive = 0 2246 if i.inactive == 1: 2247 logger.info("Deleting:" + str(i) + "\n") 2248 self.Internals.remove(i) 2249 Changed = True 2250 else: 2251 i.inactive = 0 2252 for i in other.Internals: 2253 if i not in self.Internals: 2254 logger.info("Adding: " + str(i) + "\n") 2255 self.Internals.append(i) 2256 Changed = True 2257 return Changed 2258 2259 def join(self, other): 2260 Changed = False 2261 for i in other.Internals: 2262 if i not in self.Internals: 2263 logger.info("Adding: " + str(i) + "\n") 2264 self.Internals.append(i) 2265 Changed = True 2266 return Changed 2267 2268 def repr_diff(self, other): 2269 if hasattr(other, 'Prims'): 2270 output = ['Primitive -> Delocalized'] 2271 otherPrims = other.Prims 2272 else: 2273 output = [] 2274 otherPrims = other 2275 alines = ["-- Added: --"] 2276 for i in otherPrims.Internals: 2277 if i not in self.Internals: 2278 alines.append(i.__repr__()) 2279 dlines = ["-- Deleted: --"] 2280 for i in self.Internals: 2281 if i not in otherPrims.Internals: 2282 dlines.append(i.__repr__()) 2283 if len(alines) > 1: 2284 output += alines 2285 if len(dlines) > 1: 2286 output += dlines 2287 return '\n'.join(output) 2288 2289 def resetRotations(self, xyz): 2290 for Internal in self.Internals: 2291 if type(Internal) is LinearAngle: 2292 Internal.reset(xyz) 2293 for rot in self.Rotators.values(): 2294 rot.reset(xyz) 2295 2296 def largeRots(self): 2297 for Internal in self.Internals: 2298 if type(Internal) is LinearAngle: 2299 if Internal.stored_dot2 > 0.75: 2300 # Linear angle is almost parallel to reference axis 2301 return True 2302 if type(Internal) in [RotationA, RotationB, RotationC]: 2303 if Internal in self.cPrims: 2304 continue 2305 if Internal.Rotator.stored_norm > 0.9*np.pi: 2306 # Molecule has rotated by almost pi 2307 return True 2308 if Internal.Rotator.stored_dot2 > 0.9: 2309 # Linear molecule is almost parallel to reference axis 2310 return True 2311 return False 2312 2313 def calculate(self, xyz): 2314 answer = [] 2315 for Internal in self.Internals: 2316 answer.append(Internal.value(xyz)) 2317 return np.array(answer) 2318 2319 def calculateDegrees(self, xyz): 2320 answer = [] 2321 for Internal in self.Internals: 2322 value = Internal.value(xyz) 2323 if Internal.isAngular: 2324 value *= 180/np.pi 2325 answer.append(value) 2326 return np.array(answer) 2327 2328 def getRotatorNorms(self): 2329 rots = [] 2330 for Internal in self.Internals: 2331 if type(Internal) in [RotationA]: 2332 rots.append(Internal.Rotator.stored_norm) 2333 return rots 2334 2335 def getRotatorDots(self): 2336 dots = [] 2337 for Internal in self.Internals: 2338 if type(Internal) in [RotationA]: 2339 dots.append(Internal.Rotator.stored_dot2) 2340 return dots 2341 2342 def printRotations(self, xyz): 2343 rotNorms = self.getRotatorNorms() 2344 if len(rotNorms) > 0: 2345 logger.info("Rotator Norms: " + " ".join(["% .4f" % i for i in rotNorms]) + "\n") 2346 rotDots = self.getRotatorDots() 2347 if len(rotDots) > 0 and np.max(rotDots) > 1e-5: 2348 logger.info("Rotator Dots : " + " ".join(["% .4f" % i for i in rotDots]) + "\n") 2349 linAngs = [ic.value(xyz) for ic in self.Internals if type(ic) is LinearAngle] 2350 if len(linAngs) > 0: 2351 logger.info("Linear Angles: " + " ".join(["% .4f" % i for i in linAngs]) + "\n") 2352 2353 def derivatives(self, xyz): 2354 self.calculate(xyz) 2355 answer = [] 2356 for Internal in self.Internals: 2357 answer.append(Internal.derivative(xyz)) 2358 # This array has dimensions: 2359 # 1) Number of internal coordinates 2360 # 2) Number of atoms 2361 # 3) 3 2362 return np.array(answer) 2363 2364 def second_derivatives(self, xyz): 2365 self.calculate(xyz) 2366 answer = [] 2367 for Internal in self.Internals: 2368 answer.append(Internal.second_derivative(xyz)) 2369 # This array has dimensions: 2370 # 1) Number of internal coordinates 2371 # 2) Number of atoms 2372 # 3) 3 2373 # 4) Number of atoms 2374 # 5) 3 2375 return np.array(answer) 2376 2377 def calcDiff(self, coord1, coord2): 2378 """ Calculate difference in internal coordinates (coord1-coord2), accounting for changes in 2*pi of angles. """ 2379 Q1 = self.calculate(coord1) 2380 Q2 = self.calculate(coord2) 2381 PMDiff = (Q1-Q2) 2382 for k in range(len(PMDiff)): 2383 if self.Internals[k].isPeriodic: 2384 Plus2Pi = PMDiff[k] + 2*np.pi 2385 Minus2Pi = PMDiff[k] - 2*np.pi 2386 if np.abs(PMDiff[k]) > np.abs(Plus2Pi): 2387 PMDiff[k] = Plus2Pi 2388 if np.abs(PMDiff[k]) > np.abs(Minus2Pi): 2389 PMDiff[k] = Minus2Pi 2390 return PMDiff 2391 2392 def GInverse(self, xyz): 2393 return self.GInverse_SVD(xyz) 2394 2395 def add(self, dof): 2396 if dof not in self.Internals: 2397 self.Internals.append(dof) 2398 2399 def delete(self, dof): 2400 for ii in range(len(self.Internals))[::-1]: 2401 if dof == self.Internals[ii]: 2402 del self.Internals[ii] 2403 2404 def addConstraint(self, cPrim, cVal=None, xyz=None): 2405 if cVal is None and xyz is None: 2406 raise RuntimeError('Please provide either cval or xyz') 2407 if cVal is None: 2408 # If coordinates are provided instead of a constraint value, 2409 # then calculate the constraint value from the positions. 2410 # If both are provided, then the coordinates are ignored. 2411 cVal = cPrim.value(xyz) 2412 if cPrim in self.cPrims: 2413 iPrim = self.cPrims.index(cPrim) 2414 if np.abs(cVal - self.cVals[iPrim]) > 1e-6: 2415 logger.info("Updating constraint value to %.4e\n" % cVal) 2416 self.cVals[iPrim] = cVal 2417 else: 2418 if cPrim not in self.Internals: 2419 self.Internals.append(cPrim) 2420 self.cPrims.append(cPrim) 2421 self.cVals.append(cVal) 2422 2423 def reorderPrimitives(self): 2424 # Reorder primitives to be in line with cc's code 2425 newPrims = [] 2426 for cPrim in self.cPrims: 2427 newPrims.append(cPrim) 2428 for typ in [Distance, Angle, LinearAngle, MultiAngle, OutOfPlane, Dihedral, MultiDihedral, CartesianX, CartesianY, CartesianZ, TranslationX, TranslationY, TranslationZ, RotationA, RotationB, RotationC]: 2429 for p in self.Internals: 2430 if type(p) is typ and p not in self.cPrims: 2431 newPrims.append(p) 2432 if len(newPrims) != len(self.Internals): 2433 raise RuntimeError("Not all internal coordinates have been accounted for. You may need to add something to reorderPrimitives()") 2434 self.Internals = newPrims 2435 2436 def getConstraints_from(self, other): 2437 if other.haveConstraints(): 2438 for cPrim, cVal in zip(other.cPrims, other.cVals): 2439 self.addConstraint(cPrim, cVal) 2440 self.reorderPrimitives() 2441 2442 def haveConstraints(self): 2443 return len(self.cPrims) > 0 2444 2445 def getConstraintViolation(self, xyz): 2446 nc = len(self.cPrims) 2447 maxdiff = 0.0 2448 for ic, c in enumerate(self.cPrims): 2449 w = c.w if type(c) in [RotationA, RotationB, RotationC] else 1.0 2450 current = c.value(xyz)/w 2451 reference = self.cVals[ic]/w 2452 diff = (current - reference) 2453 if c.isPeriodic: 2454 if np.abs(diff-2*np.pi) < np.abs(diff): 2455 diff -= 2*np.pi 2456 if np.abs(diff+2*np.pi) < np.abs(diff): 2457 diff += 2*np.pi 2458 if type(c) in [TranslationX, TranslationY, TranslationZ, CartesianX, CartesianY, CartesianZ, Distance]: 2459 factor = bohr2ang 2460 elif c.isAngular: 2461 factor = 180.0/np.pi 2462 if np.abs(diff*factor) > maxdiff: 2463 maxdiff = np.abs(diff*factor) 2464 return maxdiff 2465 2466 def printConstraints(self, xyz, thre=1e-5): 2467 nc = len(self.cPrims) 2468 out_lines = [] 2469 header = "Constraint Current Target Diff." 2470 for ic, c in enumerate(self.cPrims): 2471 w = c.w if type(c) in [RotationA, RotationB, RotationC] else 1.0 2472 current = c.value(xyz)/w 2473 reference = self.cVals[ic]/w 2474 diff = (current - reference) 2475 if c.isPeriodic: 2476 if np.abs(diff-2*np.pi) < np.abs(diff): 2477 diff -= 2*np.pi 2478 if np.abs(diff+2*np.pi) < np.abs(diff): 2479 diff += 2*np.pi 2480 if type(c) in [TranslationX, TranslationY, TranslationZ, CartesianX, CartesianY, CartesianZ, Distance]: 2481 factor = bohr2ang 2482 elif c.isAngular: 2483 factor = 180.0/np.pi 2484 if np.abs(diff*factor) > thre: 2485 out_lines.append("%-30s % 10.5f % 10.5f % 10.5f" % (str(c), current*factor, reference*factor, diff*factor)) 2486 if len(out_lines) > 0: 2487 logger.info(header + "\n") 2488 logger.info('\n'.join(out_lines) + "\n") 2489 # if type(c) in [RotationA, RotationB, RotationC]: 2490 # print c, c.value(xyz) 2491 2492 def getConstraintTargetVals(self): 2493 nc = len(self.cPrims) 2494 cNames = [] 2495 cVals = [] 2496 for ic, c in enumerate(self.cPrims): 2497 w = c.w if type(c) in [RotationA, RotationB, RotationC] else 1.0 2498 reference = self.cVals[ic]/w 2499 if type(c) in [TranslationX, TranslationY, TranslationZ, CartesianX, CartesianY, CartesianZ, Distance]: 2500 factor = bohr2ang 2501 elif c.isAngular: 2502 factor = 180.0/np.pi 2503 cNames.append(str(c)) 2504 cVals.append(reference*factor) 2505 return(cNames, cVals) 2506 2507 def guess_hessian(self, coords): 2508 """ 2509 Build a guess Hessian that roughly follows Schlegel's guidelines. 2510 """ 2511 xyzs = coords.reshape(-1,3)*bohr2ang 2512 Hdiag = [] 2513 def covalent(a, b): 2514 r = np.linalg.norm(xyzs[a]-xyzs[b]) 2515 rcov = Radii[Elements.index(self.elem[a])-1] + Radii[Elements.index(self.elem[b])-1] 2516 return r/rcov < 1.2 2517 2518 for ic in self.Internals: 2519 if type(ic) is Distance: 2520 r = np.linalg.norm(xyzs[ic.a]-xyzs[ic.b]) * ang2bohr 2521 elem1 = min(Elements.index(self.elem[ic.a]), Elements.index(self.elem[ic.b])) 2522 elem2 = max(Elements.index(self.elem[ic.a]), Elements.index(self.elem[ic.b])) 2523 A = 1.734 2524 if elem1 < 3: 2525 if elem2 < 3: 2526 B = -0.244 2527 elif elem2 < 11: 2528 B = 0.352 2529 else: 2530 B = 0.660 2531 elif elem1 < 11: 2532 if elem2 < 11: 2533 B = 1.085 2534 else: 2535 B = 1.522 2536 else: 2537 B = 2.068 2538 if covalent(ic.a, ic.b): 2539 Hdiag.append(A/(r-B)**3) 2540 else: 2541 Hdiag.append(0.1) 2542 elif type(ic) in [Angle, LinearAngle, MultiAngle]: 2543 if type(ic) in [Angle, LinearAngle]: 2544 a = ic.a 2545 c = ic.c 2546 else: 2547 a = ic.a[-1] 2548 c = ic.c[0] 2549 if min(Elements.index(self.elem[a]), 2550 Elements.index(self.elem[ic.b]), 2551 Elements.index(self.elem[c])) < 3: 2552 A = 0.160 2553 else: 2554 A = 0.250 2555 if covalent(a, ic.b) and covalent(ic.b, c): 2556 Hdiag.append(A) 2557 else: 2558 Hdiag.append(0.1) 2559 elif type(ic) in [Dihedral, MultiDihedral]: 2560 r = np.linalg.norm(xyzs[ic.b]-xyzs[ic.c]) 2561 rcov = Radii[Elements.index(self.elem[ic.b])-1] + Radii[Elements.index(self.elem[ic.c])-1] 2562 # Hdiag.append(0.1) 2563 Hdiag.append(0.023) 2564 elif type(ic) is OutOfPlane: 2565 r1 = xyzs[ic.b]-xyzs[ic.a] 2566 r2 = xyzs[ic.c]-xyzs[ic.a] 2567 r3 = xyzs[ic.d]-xyzs[ic.a] 2568 d = 1 - np.abs(np.dot(r1,np.cross(r2,r3))/np.linalg.norm(r1)/np.linalg.norm(r2)/np.linalg.norm(r3)) 2569 # Hdiag.append(0.1) 2570 if covalent(ic.a, ic.b) and covalent(ic.a, ic.c) and covalent(ic.a, ic.d): 2571 Hdiag.append(0.045) 2572 else: 2573 Hdiag.append(0.023) 2574 elif type(ic) in [CartesianX, CartesianY, CartesianZ]: 2575 Hdiag.append(0.05) 2576 elif type(ic) in [TranslationX, TranslationY, TranslationZ]: 2577 Hdiag.append(0.05) 2578 elif type(ic) in [RotationA, RotationB, RotationC]: 2579 Hdiag.append(0.05) 2580 else: 2581 raise RuntimeError('Failed to build guess Hessian matrix. Make sure all IC types are supported') 2582 return np.diag(Hdiag) 2583 2584 2585class DelocalizedInternalCoordinates(InternalCoordinates): 2586 def __init__(self, molecule, imagenr=0, build=False, connect=False, addcart=False, constraints=None, cvals=None, remove_tr=False, cart_only=False, conmethod=0): 2587 super(DelocalizedInternalCoordinates, self).__init__() 2588 # cart_only is just because of how I set up the class structure. 2589 if cart_only: return 2590 # Set the algorithm for constraint satisfaction. 2591 # 0 - Original algorithm implemented in 2016, constraints are satisfied slowly unless "enforce" is enabled 2592 # 1 - Updated algorithm implemented on 2019-03-20, constraints are satisfied instantly, "enforce" is not needed 2593 self.conmethod = conmethod 2594 # HDLC is given by (connect = False, addcart = True) 2595 # Standard DLC is given by (connect = True, addcart = False) 2596 # TRIC is given by (connect = False, addcart = False) 2597 # Build a minimum spanning tree 2598 self.connect = connect 2599 # Add Cartesian coordinates to all. 2600 self.addcart = addcart 2601 # The DLC contains an instance of primitive internal coordinates. 2602 self.Prims = PrimitiveInternalCoordinates(molecule, connect=connect, addcart=addcart, constraints=constraints, cvals=cvals) 2603 self.na = molecule.na 2604 # Whether constraints have been enforced previously 2605 self.enforced = False 2606 self.enforce_fail_printed = False 2607 # Build the DLC's. This takes some time, so we have the option to turn it off. 2608 xyz = molecule.xyzs[imagenr].flatten() * ang2bohr 2609 if build: 2610 self.build_dlc(xyz) 2611 self.remove_tr = remove_tr 2612 if self.remove_tr: 2613 self.remove_TR(xyz) 2614 2615 def clearCache(self): 2616 super(DelocalizedInternalCoordinates, self).clearCache() 2617 self.Prims.clearCache() 2618 2619 def __repr__(self): 2620 return self.Prims.__repr__() 2621 2622 def update(self, other): 2623 return self.Prims.update(other.Prims) 2624 2625 def join(self, other): 2626 return self.Prims.join(other.Prims) 2627 2628 def addConstraint(self, cPrim, cVal, xyz): 2629 self.Prims.addConstraint(cPrim, cVal, xyz) 2630 2631 def getConstraints_from(self, other): 2632 self.Prims.getConstraints_from(other.Prims) 2633 2634 def haveConstraints(self): 2635 return len(self.Prims.cPrims) > 0 2636 2637 def getConstraintViolation(self, xyz): 2638 return self.Prims.getConstraintViolation(xyz) 2639 2640 def printConstraints(self, xyz, thre=1e-5): 2641 self.Prims.printConstraints(xyz, thre=thre) 2642 2643 def getConstraintTargetVals(self): 2644 return self.Prims.getConstraintTargetVals() 2645 2646 def augmentGH(self, xyz, G, H): 2647 """ 2648 Add extra dimensions to the gradient and Hessian corresponding to the constrained degrees of freedom. 2649 The Hessian becomes: H c 2650 cT 0 2651 where the elements of cT are the first derivatives of the constraint function 2652 (typically a single primitive minus a constant) with respect to the DLCs. 2653 2654 Since we picked a DLC to represent the constraint (cProj), we only set one element 2655 in each row of cT to be nonzero. Because cProj = a_i * Prim_i + a_j * Prim_j, we have 2656 d(Prim_c)/d(cProj) = 1.0/a_c where "c" is the index of the primitive being constrained. 2657 2658 The extended elements of the Gradient are equal to the constraint violation. 2659 2660 Parameters 2661 ---------- 2662 xyz : np.ndarray 2663 Flat array containing Cartesian coordinates in atomic units 2664 G : np.ndarray 2665 Flat array containing internal coordinate gradient 2666 H : np.ndarray 2667 Square array containing internal coordinate Hessian 2668 2669 Returns 2670 ------- 2671 GC : np.ndarray 2672 Flat array containing gradient extended by constraint violations 2673 HC : np.ndarray 2674 Square matrix extended by partial derivatives d(Prim)/d(cProj) 2675 """ 2676 # Number of internals (elements of G) 2677 ni = len(G) 2678 # Number of constraints 2679 nc = len(self.Prims.cPrims) 2680 # Total dimension 2681 nt = ni+nc 2682 # Lower block of the augmented Hessian 2683 cT = np.zeros((nc, ni), dtype=float) 2684 c0 = np.zeros(nc, dtype=float) 2685 for ic, c in enumerate(self.Prims.cPrims): 2686 # Look up the index of the primitive that is being constrained 2687 iPrim = self.Prims.Internals.index(c) 2688 # The DLC corresponding to the constrained primitive (a.k.a. cProj) is self.Vecs[self.cDLC[ic]]. 2689 # For a differential change in the DLC, the primitive that we are constraining changes by: 2690 cT[ic, self.cDLC[ic]] = 1.0/self.Vecs[iPrim, self.cDLC[ic]] 2691 # Calculate the further change needed in this constrained variable 2692 c0[ic] = self.Prims.cVals[ic] - c.value(xyz) 2693 if c.isPeriodic: 2694 Plus2Pi = c0[ic] + 2*np.pi 2695 Minus2Pi = c0[ic] - 2*np.pi 2696 if np.abs(c0[ic]) > np.abs(Plus2Pi): 2697 c0[ic] = Plus2Pi 2698 if np.abs(c0[ic]) > np.abs(Minus2Pi): 2699 c0[ic] = Minus2Pi 2700 # The new constraint algorithm satisfies constraints too quickly and could cause 2701 # the energy to blow up. Thus, constraint steps are restricted to 0.1 au/radian 2702 if self.conmethod == 1: 2703 if c0[ic] < -0.1: 2704 c0[ic] = -0.1 2705 if c0[ic] > 0.1: 2706 c0[ic] = 0.1 2707 # Construct augmented Hessian 2708 HC = np.zeros((nt, nt), dtype=float) 2709 HC[0:ni, 0:ni] = H[:,:] 2710 HC[ni:nt, 0:ni] = cT[:,:] 2711 HC[0:ni, ni:nt] = cT.T[:,:] 2712 # Construct augmented gradient 2713 GC = np.zeros(nt, dtype=float) 2714 GC[0:ni] = G[:] 2715 GC[ni:nt] = -c0[:] 2716 return GC, HC 2717 2718 def applyConstraints(self, xyz): 2719 """ 2720 Pass in Cartesian coordinates and return new coordinates that satisfy the constraints exactly. 2721 """ 2722 xyz1 = xyz.copy() 2723 niter = 0 2724 xyzs = [] 2725 ndqs = [] 2726 while True: 2727 dQ = np.zeros(len(self.Internals), dtype=float) 2728 for ic, c in enumerate(self.Prims.cPrims): 2729 # Look up the index of the primitive that is being constrained 2730 iPrim = self.Prims.Internals.index(c) 2731 # Look up the index of the DLC that corresponds to the constraint 2732 iDLC = self.cDLC[ic] 2733 # Calculate the further change needed in this constrained variable 2734 dQ[iDLC] = (self.Prims.cVals[ic] - c.value(xyz1)) 2735 if c.isPeriodic: 2736 Plus2Pi = dQ[iDLC] + 2*np.pi 2737 Minus2Pi = dQ[iDLC] - 2*np.pi 2738 if np.abs(dQ[iDLC]) > np.abs(Plus2Pi): 2739 dQ[iDLC] = Plus2Pi 2740 if np.abs(dQ[iDLC]) > np.abs(Minus2Pi): 2741 dQ[iDLC] = Minus2Pi 2742 dQ[iDLC] /= self.Vecs[iPrim, iDLC] 2743 xyzs.append(xyz1.copy()) 2744 ndqs.append(np.linalg.norm(dQ)) 2745 # print("applyConstraints calling newCartesian (%i), |dQ| = %.3e" % (niter, np.linalg.norm(dQ))) 2746 xyz2 = self.newCartesian(xyz1, dQ, verbose=False) 2747 if np.linalg.norm(dQ) < 1e-6: 2748 return xyz2 2749 if niter > 1 and np.linalg.norm(dQ) > np.linalg.norm(dQ0): 2750 xyz1 = xyzs[np.argmin(ndqs)] 2751 if not self.enforce_fail_printed: 2752 logger.warning("Warning: Failed to enforce exact constraint satisfaction. Please remove possible redundant constraints. See below:\n") 2753 self.printConstraints(xyz1, thre=0.0) 2754 self.enforce_fail_printed = True 2755 return xyz1 2756 xyz1 = xyz2.copy() 2757 niter += 1 2758 dQ0 = dQ.copy() 2759 2760 def newCartesian_withConstraint(self, xyz, dQ, thre=0.1, verbose=False): 2761 xyz2 = self.newCartesian(xyz, dQ, verbose) 2762 constraintSmall = len(self.Prims.cPrims) > 0 2763 for ic, c in enumerate(self.Prims.cPrims): 2764 w = c.w if type(c) in [RotationA, RotationB, RotationC] else 1.0 2765 current = c.value(xyz)/w 2766 reference = self.Prims.cVals[ic]/w 2767 diff = (current - reference) 2768 if np.abs(diff-2*np.pi) < np.abs(diff): 2769 diff -= 2*np.pi 2770 if np.abs(diff+2*np.pi) < np.abs(diff): 2771 diff += 2*np.pi 2772 if np.abs(diff) > thre: 2773 constraintSmall = False 2774 if constraintSmall: 2775 xyz2 = self.applyConstraints(xyz2) 2776 if not self.enforced: 2777 logger.info("<<< Enforcing constraint satisfaction >>>\n") 2778 self.enforced = True 2779 else: 2780 self.enforced = False 2781 return xyz2 2782 2783 def calcGradProj(self, xyz, gradx): 2784 """ 2785 Project out the components of the internal coordinate gradient along the 2786 constrained degrees of freedom. This is used to calculate the convergence 2787 criteria for constrained optimizations. 2788 2789 Parameters 2790 ---------- 2791 xyz : np.ndarray 2792 Flat array containing Cartesian coordinates in atomic units 2793 gradx : np.ndarray 2794 Flat array containing gradient in Cartesian coordinates 2795 2796 Returns 2797 ------- 2798 np.ndarray 2799 Flat array containing gradient in Cartesian coordinates with forces 2800 along constrained directions projected out 2801 """ 2802 if len(self.Prims.cPrims) == 0: 2803 return gradx 2804 q0 = self.calculate(xyz) 2805 Ginv = self.GInverse(xyz) 2806 Bmat = self.wilsonB(xyz) 2807 # Internal coordinate gradient 2808 # Gq = np.matrix(Ginv)*np.matrix(Bmat)*np.matrix(gradx).T 2809 Gq = multi_dot([Ginv, Bmat, gradx.T]) 2810 Gqc = np.array(Gq).flatten() 2811 # Remove the directions that are along the DLCs that we are constraining 2812 for i in self.cDLC: 2813 Gqc[i] = 0.0 2814 # Gxc = np.array(np.matrix(Bmat.T)*np.matrix(Gqc).T).flatten() 2815 Gxc = multi_dot([Bmat.T, Gqc.T]).flatten() 2816 return Gxc 2817 2818 def build_dlc_0(self, xyz): 2819 """ 2820 Build the delocalized internal coordinates (DLCs) which are linear 2821 combinations of the primitive internal coordinates. Each DLC is stored 2822 as a column in self.Vecs. 2823 2824 In short, each DLC is an eigenvector of the G-matrix, and the number of 2825 nonzero eigenvalues of G should be equal to 3*N. 2826 2827 After creating the DLCs, we construct special ones corresponding to primitive 2828 coordinates that are constrained (cProj). These are placed in the front (i.e. left) 2829 of the list of DLCs, and then we perform a Gram-Schmidt orthogonalization. 2830 2831 This function is called at the end of __init__ after the coordinate system is already 2832 specified (including which primitives are constraints). 2833 2834 Parameters 2835 ---------- 2836 xyz : np.ndarray 2837 Flat array containing Cartesian coordinates in atomic units 2838 """ 2839 # Perform singular value decomposition 2840 click() 2841 G = self.Prims.GMatrix(xyz) 2842 # Manipulate G-Matrix to increase weight of constrained coordinates 2843 if self.haveConstraints(): 2844 for ic, c in enumerate(self.Prims.cPrims): 2845 iPrim = self.Prims.Internals.index(c) 2846 G[:, iPrim] *= 1.0 2847 G[iPrim, :] *= 1.0 2848 ncon = len(self.Prims.cPrims) 2849 # Water Dimer: 100.0, no check -> -151.1892668451 2850 time_G = click() 2851 L, Q = np.linalg.eigh(G) 2852 time_eig = click() 2853 # print "Build G: %.3f Eig: %.3f" % (time_G, time_eig) 2854 LargeVals = 0 2855 LargeIdx = [] 2856 for ival, value in enumerate(L): 2857 # print ival, value 2858 if np.abs(value) > 1e-6: 2859 LargeVals += 1 2860 LargeIdx.append(ival) 2861 Expect = 3*self.na 2862 # print "%i atoms (expect %i coordinates); %i/%i singular values are > 1e-6" % (self.na, Expect, LargeVals, len(L)) 2863 # if LargeVals <= Expect: 2864 self.Vecs = Q[:, LargeIdx] 2865 2866 # Vecs has number of rows equal to the number of primitives, and 2867 # number of columns equal to the number of delocalized internal coordinates. 2868 if self.haveConstraints(): 2869 click() 2870 # print "Projecting out constraints...", 2871 V = [] 2872 for ic, c in enumerate(self.Prims.cPrims): 2873 # Look up the index of the primitive that is being constrained 2874 iPrim = self.Prims.Internals.index(c) 2875 # Pick a row out of the eigenvector space. This is a linear combination of the DLCs. 2876 cVec = self.Vecs[iPrim, :] 2877 cVec = np.array(cVec) 2878 cVec /= np.linalg.norm(cVec) 2879 # This is a "new DLC" that corresponds to the primitive that we are constraining 2880 cProj = np.dot(self.Vecs,cVec.T) 2881 cProj /= np.linalg.norm(cProj) 2882 V.append(np.array(cProj).flatten()) 2883 # print c, cProj[iPrim] 2884 # V contains the constraint vectors on the left, and the original DLCs on the right 2885 V = np.hstack((np.array(V).T, np.array(self.Vecs))) 2886 # Apply Gram-Schmidt to V, and produce U. 2887 # The Gram-Schmidt process should produce a number of orthogonal DLCs equal to the original number 2888 thre = 1e-6 2889 while True: 2890 U = [] 2891 for iv in range(V.shape[1]): 2892 v = V[:, iv].flatten() 2893 U.append(v.copy()) 2894 for ui in U[:-1]: 2895 U[-1] -= ui * np.dot(ui, v) 2896 if np.linalg.norm(U[-1]) < thre: 2897 U = U[:-1] 2898 continue 2899 U[-1] /= np.linalg.norm(U[-1]) 2900 if len(U) > self.Vecs.shape[1]: 2901 thre *= 10 2902 elif len(U) == self.Vecs.shape[1]: 2903 break 2904 elif len(U) < self.Vecs.shape[1]: 2905 raise RuntimeError('Gram-Schmidt orthogonalization has failed (expect %i length %i)' % (self.Vecs.shape[1], len(U))) 2906 # print "Gram-Schmidt completed with thre=%.0e" % thre 2907 self.Vecs = np.array(U).T 2908 # Constrained DLCs are on the left of self.Vecs. 2909 self.cDLC = [i for i in range(len(self.Prims.cPrims))] 2910 # Now self.Internals is no longer a list of InternalCoordinate objects but only a list of strings. 2911 # We do not create objects for individual DLCs but 2912 self.Internals = ["Constraint-DLC" if i < ncon else "DLC" + " %i" % (i+1) for i in range(self.Vecs.shape[1])] 2913 2914 def build_dlc_1(self, xyz): 2915 """ 2916 Build the delocalized internal coordinates (DLCs) which are linear 2917 combinations of the primitive internal coordinates. Each DLC is stored 2918 as a column in self.Vecs. 2919 2920 After some thought, build_dlc_0 did not implement constraint satisfaction 2921 in the most correct way. Constraint satisfaction was rather slow and 2922 the --enforce 0.1 may be passed to improve performance. Rethinking how the 2923 G matrix is constructed provides a more fundamental solution. 2924 2925 In the new approach implemented here, constrained primitive ICs (PICs) are 2926 first set aside from the rest of the PICs. Next, a G-matrix is constructed 2927 from the rest of the PICs and diagonalized to form DLCs, called "residual" DLCs. 2928 The union of the cPICs and rDLCs forms a generalized set of DLCs, but the 2929 cPICs are not orthogonal to each other or to the rDLCs. 2930 2931 A set of orthogonal DLCs is constructed by carrying out Gram-Schmidt 2932 on the generalized set. Orthogonalization is carried out on the cPICs in order. 2933 Next, orthogonalization is carried out on the rDLCs using a greedy algorithm 2934 that carries out projection for each cPIC, then keeps the one with the largest 2935 remaining norm. This way we avoid keeping rDLCs that are almost redundant with 2936 the cPICs. The longest projected rDLC is added to the set and continued until 2937 the expected number is reached. 2938 2939 One special note in orthogonalization is that the "overlap" between internal 2940 coordinates corresponds to the G matrix element. Thus, for DLCs that's a linear 2941 combination of PICs, then the overlap is given by: 2942 2943 v_i * B * B^T * v_j = v_i * G * v_j 2944 2945 Notes on usage: 1) When this is activated, constraints tend to be satisfied very 2946 rapidly even if the current coordinates are very far from the constraint values, 2947 leading to possible blowing up of the energies. In augment_GH, maximum steps in 2948 constrained degrees of freedom are restricted to 0.1 a.u./radian for this reason. 2949 2950 2) Although the performance of this method is generally superior to the old method, 2951 the old method with --enforce 0.1 is more extensively tested and recommended. 2952 Thus, this method isn't enabled by default but provided as an optional feature. 2953 2954 Parameters 2955 ---------- 2956 xyz : np.ndarray 2957 Flat array containing Cartesian coordinates in atomic units 2958 """ 2959 click() 2960 G = self.Prims.GMatrix(xyz) 2961 nprim = len(self.Prims.Internals) 2962 cPrimIdx = [] 2963 if self.haveConstraints(): 2964 for ic, c in enumerate(self.Prims.cPrims): 2965 iPrim = self.Prims.Internals.index(c) 2966 cPrimIdx.append(iPrim) 2967 ncon = len(self.Prims.cPrims) 2968 if cPrimIdx != list(range(ncon)): 2969 raise RuntimeError("The constraint primitives should be at the start of the list") 2970 # Form a sub-G-matrix that doesn't include the constrained primitives and diagonalize it to form DLCs. 2971 Gsub = G[ncon:, ncon:] 2972 time_G = click() 2973 L, Q = np.linalg.eigh(Gsub) 2974 # Sort eigenvalues and eigenvectors in descending order (for cleanliness) 2975 L = L[::-1] 2976 Q = Q[:, ::-1] 2977 time_eig = click() 2978 # print "Build G: %.3f Eig: %.3f" % (time_G, time_eig) 2979 # Figure out which eigenvectors from the G submatrix to include 2980 LargeVals = 0 2981 LargeIdx = [] 2982 GEigThre = 1e-6 2983 for ival, value in enumerate(L): 2984 if np.abs(value) > GEigThre: 2985 LargeVals += 1 2986 LargeIdx.append(ival) 2987 # This is the number of nonredundant DLCs that we expect to have at the end 2988 Expect = np.sum(np.linalg.eigh(G)[0] > 1e-6) 2989 2990 if (ncon + len(LargeIdx)) < Expect: 2991 raise RuntimeError("Expected at least %i delocalized coordinates, but got only %i" % (Expect, ncon + len(LargeIdx))) 2992 # print("%i atoms (expect %i coordinates); %i/%i singular values are > 1e-6" % (self.na, Expect, LargeVals, len(L))) 2993 2994 # Create "generalized" DLCs where the first six columns are the constrained primitive ICs 2995 # and the other columns are the DLCs formed from the rest 2996 self.Vecs = np.zeros((nprim, ncon+LargeVals), dtype=float) 2997 for i in range(ncon): 2998 self.Vecs[i, i] = 1.0 2999 self.Vecs[ncon:, ncon:ncon+LargeVals] = Q[:, LargeIdx] 3000 3001 # Perform Gram-Schmidt orthogonalization 3002 def ov(vi, vj): 3003 return multi_dot([vi, G, vj]) 3004 if self.haveConstraints(): 3005 click() 3006 V = self.Vecs.copy() 3007 nv = V.shape[1] 3008 Vnorms = np.array([np.sqrt(ov(V[:,ic], V[:, ic])) for ic in range(nv)]) 3009 # U holds the Gram-Schmidt orthogonalized DLCs 3010 U = np.zeros((V.shape[0], Expect), dtype=float) 3011 Unorms = np.zeros(Expect, dtype=float) 3012 3013 for ic in range(ncon): 3014 # At the top of the loop, V columns are orthogonal to U columns up to ic. 3015 # Copy V column corresponding to the next constraint to U. 3016 U[:, ic] = V[:, ic].copy() 3017 ui = U[:, ic] 3018 Unorms[ic] = np.sqrt(ov(ui, ui)) 3019 if Unorms[ic]/Vnorms[ic] < 0.1: 3020 logger.warning("Constraint %i is almost redundant; after projection norm is %.3f of original\n" % (ic, Unorms[ic]/Vnorms[ic])) 3021 V0 = V.copy() 3022 # Project out newest U column from all remaining V columns. 3023 for jc in range(ic+1, nv): 3024 vj = V[:, jc] 3025 vj -= ui * ov(ui, vj)/Unorms[ic]**2 3026 3027 for ic in range(ncon, Expect): 3028 # Pick out the V column with the largest norm 3029 norms = np.array([np.sqrt(ov(V[:, jc], V[:, jc])) for jc in range(ncon, nv)]) 3030 imax = ncon+np.argmax(norms) 3031 # Add this column to U 3032 U[:, ic] = V[:, imax].copy() 3033 ui = U[:, ic] 3034 Unorms[ic] = np.sqrt(ov(ui, ui)) 3035 # Project out the newest U column from all V columns 3036 for jc in range(ncon, nv): 3037 V[:, jc] -= ui * ov(ui, V[:, jc])/Unorms[ic]**2 3038 3039 # self.Vecs contains the linear combination coefficients that are our new DLCs 3040 self.Vecs = U.copy() 3041 # Constrained DLCs are on the left of self.Vecs. 3042 self.cDLC = [i for i in range(len(self.Prims.cPrims))] 3043 3044 self.Internals = ["Constraint" if i < ncon else "DLC" + " %i" % (i+1) for i in range(self.Vecs.shape[1])] 3045 # # LPW: Coefficients of DLC's are in each column and DLCs corresponding to constraints should basically be like (0 1 0 0 0 ..) 3046 # pmat2d(self.Vecs, format='f', precision=2) 3047 # B = self.Prims.wilsonB(xyz) 3048 # Bdlc = np.einsum('ji,jk->ik', self.Vecs, B) 3049 # Gdlc = np.dot(Bdlc, Bdlc.T) 3050 # # Expect to see a diagonal matrix here 3051 # print("Gdlc") 3052 # pmat2d(Gdlc, format='e', precision=2) 3053 # # Expect to see "large" eigenvalues here (no less than 0.1 ideally) 3054 # print("L, Q") 3055 # L, Q = np.linalg.eigh(Gdlc) 3056 # print(L) 3057 3058 def build_dlc(self, xyz): 3059 if self.conmethod == 1: 3060 return self.build_dlc_1(xyz) 3061 elif self.conmethod == 0: 3062 return self.build_dlc_0(xyz) 3063 else: 3064 raise RuntimeError("Unsupported value of conmethod %i" % self.conmethod) 3065 3066 def remove_TR(self, xyz): 3067 """ 3068 Project overall translation and rotation out of the DLCs. 3069 This feature is intended to be used when an optimization job appears 3070 to contain slow rotations of the whole system, which sometimes happens. 3071 Uses the same logic as build_dlc_1. 3072 """ 3073 # Create three translation and three rotation primitive ICs for the whole system 3074 na = int(len(xyz)/3) 3075 alla = range(na) 3076 sel = xyz.reshape(-1,3).copy() 3077 TRPrims = [] 3078 TRPrims.append(TranslationX(alla, w=np.ones(na)/na)) 3079 TRPrims.append(TranslationY(alla, w=np.ones(na)/na)) 3080 TRPrims.append(TranslationZ(alla, w=np.ones(na)/na)) 3081 sel -= np.mean(sel, axis=0) 3082 rg = np.sqrt(np.mean(np.sum(sel**2, axis=1))) 3083 TRPrims.append(RotationA(alla, xyz, self.Prims.Rotators, w=rg)) 3084 TRPrims.append(RotationB(alla, xyz, self.Prims.Rotators, w=rg)) 3085 TRPrims.append(RotationC(alla, xyz, self.Prims.Rotators, w=rg)) 3086 # If these primitive ICs are already there, then move them to the front 3087 primorder = [] 3088 addPrims = [] 3089 for prim in TRPrims: 3090 if prim in self.Prims.Internals: 3091 primorder.append(self.Prims.Internals.index(prim)) 3092 else: 3093 addPrims.append(prim) 3094 for iprim, prim in enumerate(self.Prims.Internals): 3095 if prim not in TRPrims: 3096 primorder.append(iprim) 3097 self.Prims.Internals = addPrims + [self.Prims.Internals[p] for p in primorder] 3098 self.Vecs = np.vstack((np.zeros((len(addPrims), self.Vecs.shape[1]), dtype=float), self.Vecs[np.array(primorder), :])) 3099 3100 self.clearCache() 3101 # Build DLCs with six extra in the front corresponding to the overall translations and rotations 3102 subVecs = self.Vecs.copy() 3103 self.Vecs = np.zeros((self.Vecs.shape[0], self.Vecs.shape[1]+6), dtype=float) 3104 self.Vecs[:6, :6] = np.eye(6) 3105 self.Vecs[:, 6:] = subVecs.copy() 3106 # pmat2d(self.Vecs, precision=3, format='f') 3107 # sys.exit() 3108 # This is the number of nonredundant DLCs that we expect to see 3109 G = self.Prims.GMatrix(xyz) 3110 Expect = np.sum(np.linalg.eigh(G)[0] > 1e-6) 3111 3112 # Carry out Gram-Schmidt orthogonalization 3113 # Define a function for computing overlap 3114 def ov(vi, vj): 3115 return multi_dot([vi, G, vj]) 3116 V = self.Vecs.copy() 3117 nv = V.shape[1] 3118 Vnorms = np.array([np.sqrt(ov(V[:, ic], V[:, ic])) for ic in range(nv)]) 3119 # G_tmp = np.array([[ov(V[:, ic], V[:, jc]) for jc in range(nv)] for ic in range(nv)]) 3120 # pmat2d(G_tmp, precision=3, format='f') 3121 # U holds the Gram-Schmidt orthogonalized DLCs 3122 U = np.zeros((V.shape[0], Expect), dtype=float) 3123 Unorms = np.zeros(Expect, dtype=float) 3124 ncon = len(self.Prims.cPrims) 3125 # Keep translations, rotations, and any constraints in sequential order 3126 # and project them out of the remaining DLCs 3127 for ic in range(6+ncon): 3128 U[:, ic] = V[:, ic].copy() 3129 ui = U[:, ic] 3130 Unorms[ic] = np.sqrt(ov(ui, ui)) 3131 if Unorms[ic]/Vnorms[ic] < 0.1: 3132 logger.warning("Constraint %i is almost redundant; after projection norm is %.3f of original\n" % (ic-6, Unorms[ic]/Vnorms[ic])) 3133 V0 = V.copy() 3134 # Project out newest U column from all remaining V columns. 3135 for jc in range(ic+1, nv): 3136 vj = V[:, jc] 3137 vj -= ui * ov(ui, vj)/Unorms[ic]**2 3138 # Now keep the remaining DLC with the largest norm, perform projection, 3139 # then repeat until the expected number is found 3140 shift = 6+ncon 3141 for ic in range(shift, Expect): 3142 # Pick out the V column with the largest norm 3143 norms = np.array([np.sqrt(ov(V[:, jc], V[:, jc])) for jc in range(shift, nv)]) 3144 imax = shift+np.argmax(norms) 3145 # Add this column to U 3146 U[:, ic] = V[:, imax].copy() 3147 ui = U[:, ic] 3148 Unorms[ic] = np.sqrt(ov(ui, ui)) 3149 # Project out the newest U column from all V columns 3150 for jc in range(ncon, nv): 3151 V[:, jc] -= ui * ov(ui, V[:, jc])/Unorms[ic]**2 3152 # self.Vecs contains the linear combination coefficients that are our new DLCs 3153 self.Vecs = U[:, 6:].copy() 3154 self.Internals = ["Constraint" if i < ncon else "DLC" + " %i" % (i+1) for i in range(self.Vecs.shape[1])] 3155 3156 def weight_vectors(self, xyz): 3157 """ 3158 Not used anymore: Multiply each DLC by a constant so that a small displacement along each produces the 3159 same Cartesian displacement. Otherwise, some DLCs "move by a lot" and others only "move by a little". 3160 3161 Parameters 3162 ---------- 3163 xyz : np.ndarray 3164 Flat array containing Cartesian coordinates in atomic units 3165 """ 3166 Bmat = self.wilsonB(xyz) 3167 Ginv = self.GInverse(xyz, None) 3168 eps = 1e-6 3169 dxdq = np.zeros(len(self.Internals)) 3170 for i in range(len(self.Internals)): 3171 dQ = np.zeros(len(self.Internals), dtype=float) 3172 dQ[i] = eps 3173 dxyz = multi_dot([Bmat.T, Ginv , dQ.T]) 3174 rmsd = np.sqrt(np.mean(np.sum(np.array(dxyz).reshape(-1,3)**2, axis=1))) 3175 dxdq[i] = rmsd/eps 3176 dxdq /= np.max(dxdq) 3177 for i in range(len(self.Internals)): 3178 self.Vecs[:, i] *= dxdq[i] 3179 3180 def __eq__(self, other): 3181 return self.Prims == other.Prims 3182 3183 def __ne__(self, other): 3184 return not self.__eq__(other) 3185 3186 def largeRots(self): 3187 """ Determine whether a molecule has rotated by an amount larger than some threshold (hardcoded in Prims.largeRots()). """ 3188 return self.Prims.largeRots() 3189 3190 def calcDiff(self, coord1, coord2): 3191 """ Calculate difference in internal coordinates (coord1-coord2), accounting for changes in 2*pi of angles. """ 3192 PMDiff = self.Prims.calcDiff(coord1, coord2) 3193 Answer = np.dot(PMDiff, self.Vecs) 3194 return np.array(Answer).flatten() 3195 3196 def calculate(self, coords): 3197 """ Calculate the DLCs given the Cartesian coordinates. """ 3198 PrimVals = self.Prims.calculate(coords) 3199 Answer = np.dot(PrimVals, self.Vecs) 3200 # To obtain the primitive coordinates from the delocalized internal coordinates, 3201 # simply multiply self.Vecs*Answer.T where Answer.T is the column vector of delocalized 3202 # internal coordinates. That means the "c's" in Equation 23 of Schlegel's review paper 3203 # are simply the rows of the Vecs matrix. 3204 # print np.dot(np.array(self.Vecs[0,:]).flatten(), np.array(Answer).flatten()) 3205 # print PrimVals[0] 3206 # raw_input() 3207 return np.array(Answer).flatten() 3208 3209 def derivatives(self, coords): 3210 """ Obtain the change of the DLCs with respect to the Cartesian coordinates. """ 3211 PrimDers = self.Prims.derivatives(coords) 3212 # The following code does the same as "tensordot" 3213 # print PrimDers.shape 3214 # print self.Vecs.shape 3215 # Answer = np.zeros((self.Vecs.shape[1], PrimDers.shape[1], PrimDers.shape[2]), dtype=float) 3216 # for i in range(self.Vecs.shape[1]): 3217 # for j in range(self.Vecs.shape[0]): 3218 # Answer[i, :, :] += self.Vecs[j, i] * PrimDers[j, :, :] 3219 # print Answer.shape 3220 Answer1 = np.tensordot(self.Vecs, PrimDers, axes=(0, 0)) 3221 return np.array(Answer1) 3222 3223 def second_derivatives(self, coords): 3224 """ Obtain the second derivatives of the DLCs with respect to the Cartesian coordinates. """ 3225 PrimDers = self.Prims.second_derivatives(coords) 3226 Answer2 = np.tensordot(self.Vecs, PrimDers, axes=(0, 0)) 3227 return np.array(Answer2) 3228 3229 def GInverse(self, xyz): 3230 return self.GInverse_SVD(xyz) 3231 3232 def repr_diff(self, other): 3233 if hasattr(other, 'Prims'): 3234 return self.Prims.repr_diff(other.Prims) 3235 else: 3236 if self.Prims.repr_diff(other) == '': 3237 return 'Delocalized -> Primitive' 3238 else: 3239 return 'Delocalized -> Primitive\n' + self.Prims.repr_diff(other) 3240 3241 def guess_hessian(self, coords): 3242 """ Build the guess Hessian, consisting of a diagonal matrix 3243 in the primitive space and changed to the basis of DLCs. """ 3244 Hprim = self.Prims.guess_hessian(coords) 3245 return multi_dot([self.Vecs.T,Hprim,self.Vecs]) 3246 3247 def resetRotations(self, xyz): 3248 """ Reset the reference geometries for calculating the orientational variables. """ 3249 self.Prims.resetRotations(xyz) 3250 3251class CartesianCoordinates(PrimitiveInternalCoordinates): 3252 """ 3253 Cartesian coordinate system, written as a kind of internal coordinate class. 3254 This one does not support constraints, because that requires adding some 3255 primitive internal coordinates. 3256 """ 3257 def __init__(self, molecule, **kwargs): 3258 super(CartesianCoordinates, self).__init__(molecule) 3259 self.Internals = [] 3260 self.cPrims = [] 3261 self.cVals = [] 3262 self.elem = molecule.elem 3263 for i in range(molecule.na): 3264 self.add(CartesianX(i, w=1.0)) 3265 self.add(CartesianY(i, w=1.0)) 3266 self.add(CartesianZ(i, w=1.0)) 3267 if kwargs.get('remove_tr', False): 3268 raise RuntimeError('Do not use remove_tr with Cartesian coordinates') 3269 if 'constraints' in kwargs and kwargs['constraints'] is not None: 3270 raise RuntimeError('Do not use constraints with Cartesian coordinates') 3271 3272 def guess_hessian(self, xyz): 3273 return 0.5*np.eye(len(xyz.flatten())) 3274