def load_molecule_g98fchk(fn_freq, fn_ener=None, energy=None): """Load a molecule from Gaussian98 formatted checkpoint files. Arguments: | ``fn_freq`` -- The formatted checkpoint file of the frequency job. Optional arguments: | ``fn_ener`` -- The formatted checkpoint file of a single point computation for the energy. When not given, the energy is taken from the frequency job. | ``energy`` -- Override the energy from the formatted checkpoint file with the given value. """ fchk_freq = FCHKFile(fn_freq, ignore_errors=True, field_labels=[ "Cartesian Force Constants", "Total Energy", "Multiplicity", "Cartesian Gradient" ]) if fn_ener is None: fchk_ener = fchk_freq else: fchk_ener = FCHKFile(fn_ener, ignore_errors=True, field_labels=["Total Energy"]) masses = np.array([g98_masses[n - 1] for n in fchk_freq.molecule.numbers]) if energy is None: energy = fchk_ener.fields["Total Energy"] return Molecule( fchk_freq.molecule.numbers, fchk_freq.molecule.coordinates, masses, energy, np.reshape(np.array(fchk_freq.fields["Cartesian Gradient"]), (len(fchk_freq.molecule.numbers), 3)), fchk_freq.get_hessian(), fchk_freq.fields["Multiplicity"], None, # gaussian is very poor at computing the rotational symmetry number False, )
def load_molecule_pcgamess_punch(fn_freq, energy=None, multiplicity=1, symmetry_number=None): """Load a molecule from a PCGAMESS punch file Arguments: | fn_freq -- punch file of the frequency job """ punch = PunchFile(fn_freq) if energy is None: energy = punch.energy return Molecule( punch.numbers, punch.coordinates, punch.masses, energy, punch.gradient, punch.hessian, multiplicity, symmetry_number, )
def load_molecule_cp2k(fn_sp, fn_freq, multiplicity=1, is_periodic=True): """Load a molecule with the Hessian from a CP2K computation Arguments: | fn_sp -- The filename of the single point .out file containing the energy and the forces. | fn_freq -- The filename of the frequency .out file containing the hessian Optional arguments: | multiplicity -- The spin multiplicity of the electronic system [default=1] | is_periodic -- True when the system is periodic in three dimensions. False when the systen is aperiodic. [default=True] | unit_cell -- The unit cell vectors for periodic structures """ # auxiliary routine to read atoms def atom_helper(f): # skip some lines for i in xrange(3): f.readline() # read the atom lines until an empty line is encountered numbers = [] coordinates = [] masses = [] while True: line = f.readline() if len(line.strip()) == 0: break symbol = line[14:19].strip()[:2] atom = periodic[symbol] if atom is None: symbol = symbol[:1] atom = periodic[symbol] if atom is None: numbers.append(0) else: numbers.append(atom.number) coordinates.append( [float(line[22:33]), float(line[34:45]), float(line[46:57])]) masses.append(float(line[72:])) numbers = np.array(numbers) coordinates = np.array(coordinates) * angstrom masses = np.array(masses) * amu return numbers, coordinates, masses # auxiliary routine to read forces def force_helper(f, skip, offset): # skip some lines for i in xrange(skip): f.readline() # Read the actual forces tmp = [] while True: line = f.readline() if line == "\n": break if line == "": raise IOError("End of file while reading gradient (forces).") words = line.split() try: tmp.append([ float(words[offset]), float(words[offset + 1]), float(words[offset + 2]) ]) except StandardError: break return -np.array(tmp) # force to gradient # go through the single point file: energy and gradient energy = None gradient = None with open(fn_sp) as f: while True: line = f.readline() if line == "": break if line.startswith(" ENERGY|"): energy = float(line[58:]) elif line.startswith(" MODULE") and "ATOMIC COORDINATES" in line: numbers, coordinates, masses = atom_helper(f) elif line.startswith(" FORCES|"): gradient = force_helper(f, 0, 1) break elif line.startswith(' ATOMIC FORCES in [a.u.]'): gradient = force_helper(f, 2, 3) break if energy is None or gradient is None: raise IOError( "Could not read energy and/or gradient (forces) from single point file." ) # go through the freq file: lattic vectors and hessian with open(fn_freq) as f: vectors = np.zeros((3, 3), float) for line in f: if line.startswith(" CELL"): break for axis in range(3): line = f.next() vectors[:, axis] = np.array( [float(line[29:39]), float(line[39:49]), float(line[49:59])]) unit_cell = UnitCell(vectors * angstrom) free_indices = _load_free_low(f) if len(free_indices) > 0: total_size = coordinates.size free_size = len(free_indices) hessian = np.zeros((total_size, total_size), float) i2 = 0 while i2 < free_size: num_cols = min(5, free_size - i2) f.next() # skip two lines f.next() for j in xrange(free_size): line = f.next() words = line.split() for i1 in xrange(num_cols): hessian[free_indices[i2 + i1], free_indices[j]] = \ float(words[i1 + 2]) i2 += num_cols else: raise IOError("Could not read hessian from freq file.") # symmetrize hessian = 0.5 * (hessian + hessian.transpose()) # cp2k prints a transformed hessian, here we convert it back to the normal # hessian in atomic units. conv = 1e-3 * np.array([masses, masses, masses]).transpose().ravel()**0.5 hessian *= conv hessian *= conv.reshape((-1, 1)) return Molecule(numbers, coordinates, masses, energy, gradient, hessian, multiplicity, 0, is_periodic, unit_cell=unit_cell)
def load_molecule_qchem(qchemfile, hessfile=None, multiplicity=1, is_periodic=False): """Load a molecule from a Q-Chem frequency run Arguments: | qchemfile -- Filename of the Q-Chem computation output. Optional arguments: | hessfile -- Filename of a separate Hessian file. | multiplicity -- The spin multiplicity of the electronic system [default=1] | is_periodic -- True when the system is periodic in three dimensions. False when the systen is nonperiodic. [default=False] Whether the Hessian is printed to a separate Hessian file, depends on the used version of Q-Chem. The use of the separate Hessian file is slightly more accurate, because the number of printed digits is higher than in the Q-Chem output file. **Warning** At present, the gradient is set to a Nx3 array of zero values, since the gradient is not printed out in the Q-Chem output file in general. This means that the value of the gradient should be checked before applying methods designed for partially optimized structures (currently PHVA, MBH and PHVA_MBH). """ # TODO fill in keyword for printing hessian f = file(qchemfile) # get coords for line in f: if line.strip().startswith("Standard Nuclear Orientation (Angstroms)"): break f.next() f.next() positions = [] symbols = [] for line in f: if line.strip().startswith("----"): break words = line.split() symbols.append(words[1]) coor = [float(words[2]), float(words[3]), float(words[4])] positions.append(coor) positions = np.array(positions) * angstrom N = len(positions) #nb of atoms numbers = np.zeros(N, int) for i, symbol in enumerate(symbols): numbers[i] = periodic[symbol].number #masses = np.zeros(N,float) #for i, symbol in enumerate(symbols): # masses[i] = periodic[symbol].mass # grep the SCF energy energy = None for line in f: if line.strip().startswith("Cycle Energy DIIS Error"): break for line in f: if line.strip().endswith("met"): energy = float(line.split()[1]) # in hartree break # get Hessian hessian = np.zeros((3 * N, 3 * N), float) if hessfile is None: for line in f: if line.strip().startswith( "Hessian of the SCF Energy") or line.strip().startswith( "Final Hessian"): break nb = int(np.ceil(N * 3 / 6)) for i in range(nb): f.next() row = 0 for line in f: words = line.split() hessian[row, 6 * i:6 * (i + 1)] = np.array( sum([[float(word)] for word in words[1:]], [])) #/ angstrom**2 row += 1 if row >= 3 * N: break # get masses masses = np.zeros(N, float) for line in f: if line.strip().startswith("Zero point vibrational"): break f.next() count = 0 for line in f: masses[count] = float(line.split()[-1]) * amu count += 1 if count >= N: break # get Symm Nb for line in f: if line.strip().startswith("Rotational Symmetry Number is"): break symmetry_number = int(line.split()[-1]) f.close() # or get Hessian from other file if hessfile is not None: f = file(hessfile) row = 0 col = 0 for line in f: hessian[row, col] = float( line.split()[0]) * 1000 * calorie / avogadro / angstrom**2 col += 1 if col >= 3 * N: row += 1 col = row f.close() for i in range(len(hessian)): for j in range(0, i): hessian[i, j] = hessian[j, i] # get gradient TODO gradient = np.zeros((N, 3), float) return Molecule(numbers, positions, masses, energy, gradient, hessian, multiplicity, symmetry_number, is_periodic)
def load_molecule_vasp(contcar, outcar_freq, outcar_energy=None, energy=None, multiplicity=1, is_periodic=True): """Load a molecule from VASP 4.6.X and 5.3.X output files Arguments: | contcar -- A CONTCAR file with the structure used as POSCAR file for the Hessian/frequency calculation in VASP. Do not use the CONTCAR file generated by the frequency calculation. Use the CONTCAR from the preceding geometry optimization instead. | outcar_freq -- The OUTCAR file of the Hessian/frequency calculation. Also the gradient and the energy are read from this file. The energy without entropy (but not the extrapolation to sigma=0) is used. Optional arguments: | outcar_energy -- When given, the (first) energy without entropy is read from this file (not the extrapolation to sigma=0) instead of reading the energy from the freq output | energy -- The potential energy, which overrides the contents of outcar_freq. | multiplicity -- The spin multiplicity of the electronic system [default=1] | is_periodic -- True when the system is periodic in three dimensions. False when the systen is nonperiodic. [default=True]. """ # auxiliary function to read energy: def read_energy_without_entropy(f): # Go to the first energy for line in f: if line.startswith( ' FREE ENERGIE OF THE ION-ELECTRON SYSTEM (eV)'): break # Skip three lines and read energy next(f) next(f) next(f) return float(next(f).split()[3]) * electronvolt # Read atomic symbols, coordinates and cell vectors from CONTCAR symbols = [] coordinates = [] with open(contcar) as f: # Skip title. next(f).strip() # Read scale for rvecs. rvec_scale = float(next(f)) # Read rvecs. VASP uses one row per cell vector. rvecs = np.fromstring(next(f) + next(f) + next(f), sep=' ').reshape(3, 3) rvecs *= rvec_scale * angstrom unit_cell = UnitCell(rvecs) # Read symbols unique_symbols = next(f).split() # Read atom counts per symbol symbol_counts = [int(w) for w in next(f).split()] assert len(symbol_counts) == len(unique_symbols) natom = sum(symbol_counts) # Construct array with atomic numbers. numbers = [] for iunique in range(len(unique_symbols)): number = periodic[unique_symbols[iunique]].number numbers.extend([number] * symbol_counts[iunique]) numbers = np.array(numbers) # Check next line while next(f) != 'Direct\n': continue # Load fractional coordinates fractional = np.zeros((natom, 3), float) for iatom in range(natom): words = next(f).split() fractional[iatom, 0] = float(words[0]) fractional[iatom, 1] = float(words[1]) fractional[iatom, 2] = float(words[2]) coordinates = unit_cell.to_cartesian(fractional) if outcar_energy is not None and energy is None: with open(outcar_energy) as f: energy = read_energy_without_entropy(f) # Read energy, gradient, Hessian and masses from outcar_freq. Note that the first # energy/force calculation is done on the unperturbed input structure. with open(outcar_freq) as f: # Loop over the POTCAR sections in the OUTCAR file number = None masses = np.zeros(natom, float) while True: line = next(f) if line.startswith(' VRHFIN ='): symbol = line[11:line.find(':')].strip() number = periodic[symbol].number elif line.startswith(' POMASS ='): mass = float(line[11:line.find(';')]) * amu masses[numbers == number] = mass elif number is not None and line.startswith( '------------------------------'): assert masses.min() > 0 break # Go to the first gradient for line in f: if line.startswith(' POSITION'): break # Skip one line and read the gradient next(f) gradient = np.zeros((natom, 3), float) gunit = electronvolt / angstrom for iatom in range(natom): words = next(f).split() gradient[iatom, 0] = -float(words[3]) * gunit gradient[iatom, 1] = -float(words[4]) * gunit gradient[iatom, 2] = -float(words[5]) * gunit if energy is None: energy = read_energy_without_entropy(f) # Go to the second derivatives for line in f: if line.startswith(' SECOND DERIVATIVES (NOT SYMMETRIZED)'): break # Skip one line. next(f) # Load free atoms (not fixed in space). keys = next(f).split() nfree_dof = len(keys) indices_free = [ 3 * int(key[:-1]) + { 'X': 0, 'Y': 1, 'Z': 2 }[key[-1]] - 3 for key in keys ] assert nfree_dof % 3 == 0 # Load the actual Hessian hunit = electronvolt / angstrom**2 hessian = np.zeros((3 * natom, 3 * natom), float) for ifree0 in range(nfree_dof): line = next(f) irow = indices_free[ifree0] # skip first col words = line.split()[1:] assert len(words) == nfree_dof for ifree1 in range(nfree_dof): icol = indices_free[ifree1] hessian[irow, icol] = -float(words[ifree1]) * hunit # Symmetrize the Hessian hessian = 0.5 * (hessian + hessian.T) return Molecule(numbers, coordinates, masses, energy, gradient, hessian, multiplicity=multiplicity, periodic=is_periodic, unit_cell=unit_cell)
def load_molecule_g03fchk(fn_freq, fn_ener=None, fn_vdw=None, energy=None, fn_punch=None): """Load a molecule from Gaussian03 formatted checkpoint files. Arguments: | ``fn_freq`` -- the formatted checkpoint file of the frequency job Optional arguments: | ``fn_ener`` -- the formatted checkpoint file of a single point computation for the energy. When not given, the energy is taken from the frequency job. | ``fn_vdw`` -- An orca output file containing a Van der Waals correction for the energy. | ``energy`` -- Override the energy from the formatted checkpoint file with the given value. | ``punch`` -- A Gaussian derivatives punch file. When given, the gradient and the Hessian are read from this file instead. """ fchk_freq = FCHKFile(fn_freq, ignore_errors=True, field_labels=[ "Cartesian Force Constants", "Real atomic weights", "Total Energy", "Multiplicity", "Cartesian Gradient", "MicOpt", ]) if fn_ener is None: fchk_ener = fchk_freq else: fchk_ener = FCHKFile(fn_ener, ignore_errors=True, field_labels=[ "Total Energy" ]) if energy is None: energy = fchk_ener.fields["Total Energy"] vdw = 0 if fn_vdw is not None: from tamkin.io.dispersion import load_dftd_orca vdw = load_dftd_orca(fn_vdw) natom = fchk_freq.molecule.size if fchk_freq.molecule.size == 1 and \ "Cartesian Force Constants" not in fchk_freq.fields: gradient = np.zeros((1,3), float) hessian = np.zeros((3,3), float) elif fn_punch is None: gradient = fchk_freq.fields["Cartesian Gradient"].copy() gradient.shape = (natom, 3) hessian = fchk_freq.get_hessian() else: gradient = np.zeros((natom, 3), float) hessian = np.zeros((3*natom, 3*natom), float) iterator = iter_floats_file(fn_punch) for i in xrange(natom): for j in xrange(3): gradient[i,j] = iterator.next() for i in xrange(3*natom): for j in xrange(i+1): v = iterator.next() hessian[i,j] = v hessian[j,i] = v if "MicOpt" in fchk_freq.fields: fixed = (fchk_freq.fields["MicOpt"] == -2).nonzero()[0] if len(fixed) == 0: fixed = None else: fixed = None return Molecule( fchk_freq.molecule.numbers, fchk_freq.molecule.coordinates, fchk_freq.fields["Real atomic weights"]*amu, energy+vdw, gradient, hessian, fchk_freq.fields["Multiplicity"], None, # gaussian is very poor at computing the rotational symmetry number False, title=fchk_freq.title, fixed=fixed, )
def load_molecule_charmm(charmmfile_cor, charmmfile_hess, is_periodic=False): """Read from Hessian-CHARMM-file format Arguments: | charmmfile_cor -- the filename of the .cor file | charmmfile_hess -- the filename of the Hessian file Optional argument: | is_periodic -- True when the system is periodic in three dimensions. False when the systen is aperiodic. [default=True] """ f = file(charmmfile_hess) # skip lines if they start with a * while True: line = f.readline() if not line.startswith("*"): break N = int(line.split()[-1]) assert N > 0 # nb of atoms should be > 0 energy = float(f.readline().split()[-1]) * 1000 * calorie / avogadro gradient = np.zeros((N, 3), float) for i, line in enumerate(f): words = line.split() gradient[i, :] = [float(word) for word in words] if i == (N - 1): break gradient *= 1000 * calorie / avogadro / angstrom hessian = np.zeros((3 * N, 3 * N), float) row = 0 col = 0 for line in f: element = float(line.split()[-1]) hessian[row, col] = element hessian[col, row] = element col += 1 if col >= 3 * N: #if this new col doesn't exist row += 1 #go to next row col = row #to diagonal element if row >= 3 * N: #if this new row doesn't exist break hessian = hessian * 1000 * calorie / avogadro / angstrom**2 positions = np.zeros((N, 3), float) for i, line in enumerate(f): words = line.split() positions[i, :] = [float(word) * angstrom for word in words] if i == (N - 1): break f.close() # Read from coordinates-CHARMM-file # format: header lines, which start with * # N lines with - mass in last column # - atomic type in 4th column f = file(charmmfile_cor) masses = np.zeros(N, float) symbols = [] for line in f: if not line.startswith("*"): # skip header lines break for i, line in enumerate(f): words = line.split() masses[i] = float(words[-1]) * amu # mass symbols.append(words[3]) # symbol if i == (N - 1): break f.close() # get corresponding atomic numbers mass_table = np.zeros(len(periodic)) for i in xrange(1, len(mass_table)): m1 = periodic[i].mass if m1 is None: m1 = 200000.0 m2 = periodic[i + 1].mass if m2 is None: m2 = 200000.0 mass_table[i] = 0.5 * (m1 + m2) atomicnumbers = np.zeros(N, int) for i, mass in enumerate(masses): atomicnumbers[i] = mass_table.searchsorted(mass) return Molecule( atomicnumbers, positions, masses, energy, gradient, hessian, 1, # multiplicity symmetry_number=1, periodic=is_periodic, symbols=tuple(symbols))
def load_molecule_cpmd(fn_out, fn_geometry, fn_hessian, multiplicity=1, is_periodic=True): """Load a molecule with the Hessian from a CPMD computation Arguments: | fn_out -- The filename of the output containing the total energy. | fn_geometry -- The filename of geometry and the gradient of the (partially) optimized system. (This filename is typically GEOMETRY.xyz.) | fn_hessian -- The filename of the of the file containing the Hessian. (This filename is typically MOLVIB.) Optional arguments: | multiplicity -- The spin multiplicity of the electronic system [default=1] | is_periodic -- True when the system is periodic in three dimensions. False when the system is aperiodic. [default=True] """ # go through the output file: grep the total energy energy = None f = file(fn_out) while True: line = f.readline() if line == "": raise IOError( "Could not find final results in %s. Is the output file truncated?" % fn_out) if line == " * FINAL RESULTS *\n": break while True: line = f.readline() if line == "": raise IOError( "Could not find total energy in %s. Is the output file truncated?" % fn_out) if line.startswith(" (K+E1+L+N+X) TOTAL ENERGY ="): words = (line.strip()).split() energy = float(words[4]) break f.close() # load the optimal geometry f = file(fn_geometry) num_atoms = int(f.readline()) numbers = np.zeros(num_atoms, int) coordinates = np.zeros((num_atoms, 3), float) gradient = np.zeros((num_atoms, 3), float) f.readline() i = 0 while True: line = f.readline() if line == "": break # end of file words = (line.strip()).split() if len(words) == 7: numbers[i] = periodic[words[0]].number coordinates[i][0] = float(words[1]) * angstrom coordinates[i][1] = float(words[2]) * angstrom coordinates[i][2] = float(words[3]) * angstrom gradient[i][1] = float(words[4]) gradient[i][1] = float(words[5]) gradient[i][2] = float(words[6]) i += 1 else: raise IOError("Expecting seven words at each atom line in %s." % fn_geometry) if i != num_atoms: raise IOError("The number of atoms is incorrect in %s." % fn_geometry) f.close() # go trhough the freq file: hessian f = file(fn_hessian) line = f.readline() if not line.startswith(" &CART"): raise IOError("File %s does not start with &CART." % fn_hessian) masses = np.zeros(num_atoms, float) for i in xrange(num_atoms): line = f.readline() words = line.split() masses[i] = float(words[4]) * amu f.readline() # &END line = f.readline() if not line.startswith(" &FCON"): raise IOError("File %s does not contain section &FCON." % fn_hessian) num_cart = num_atoms * 3 hessian = np.zeros((num_cart, num_cart), float) for i in xrange(num_cart): line = f.readline() words = line.split() for j in xrange(num_cart): hessian[i, j] = float(words[j]) f.close() return Molecule(numbers, coordinates, masses, energy, gradient, hessian, multiplicity, 0, is_periodic)
def load_molecule_molpro(filename): """Load a molecule from Molpro 2012 output file. Argument: | ``filename`` -- the molpro 2012 output """ lines = open(filename,"r").read().splitlines() # locate positions beginxyz = None begincoordinate = None beginmass = None beginhessian = None begingradient = None for lineno, line in enumerate(lines): if "Current geometry" in line: beginxyz = lineno + 2 if "FREQUENCIES * CALCULATION OF NORMAL MODES" in line: begincoordinate = lineno + 7 if "Atomic Masses" in line: beginmass = lineno if "Force Constants" in line: beginhessian = lineno # if "GRADIENT FOR" in line: # begingradient = lineno + 4 # xyz read, only meta data and energy atomnumber = int(lines[beginxyz].split()[0]) title = lines[beginxyz+1].split()[0] energy = float(lines[beginxyz+1].split("=")[1]) # print(atomnumber, title, energy) # coordinate read numbers=[] coordinates=[] for line in lines[begincoordinate : begincoordinate+atomnumber]: words = line.split() charge = int(float(words[2])) x = float(words[3]) y = float(words[4]) z = float(words[5]) numbers.append(charge) coordinates.append([x, y, z]) # print(charge, x, y, z) # masses masses = [] for line in lines[beginmass+1:]: if len(line) > 8: words = line[8:].split() for word in words: masses.append(float(word)*amu) else: break # print(masses) # gradient gradient = np.zeros((atomnumber, 3)) # TODO, Gradient is more difficult than I thought... # format of gradient from CCSD(T)-F12 is different from DFT ''' if begingradient != None: for i, line in enumerate(lines[begingradient: begingradient + atomnumber]): words = line.split() gradient[i,0] = float(words[1]) gradient[i,1] = float(words[2]) gradient[i,2] = float(words[3]) ''' # print(gradient) # hessian hessian = np.ndarray((3*atomnumber, 3*atomnumber), dtype= float) headerline = beginhessian+1 rowbegin = 0 while rowbegin < 3*atomnumber: for r, line in zip(\ range(rowbegin, 3*atomnumber),\ lines[headerline+1 : headerline+1+3*atomnumber-rowbegin]\ ): words = line[16:].split() # print(words) for c in range(rowbegin, rowbegin+min(5, 3*atomnumber-rowbegin, len(words))): # print(r,c) hessian[r,c] = float(words[c-rowbegin]) hessian[c,r]=hessian[r,c] headerline += 3*atomnumber-rowbegin+1 rowbegin += 5 # print(hessian) return Molecule( np.array(numbers), np.array(coordinates), np.array(masses), energy, gradient, hessian, title=title, multiplicity=1, )
def create_enm_molecule(molecule, selected=None, numbers=None, masses=None, rcut=8.0 * angstrom, K=1.0, periodic=None): """Create a molecule according to the Elastic Network Model Argument: | molecule -- The molecule to start from. can be two types: (i) a Molecule object or (ii) a numpy array with shape (N,3) with coordinates in atomic units. When a Molecule object is given, atom numbers, masses and periodic are inherited from the molecule, unless they are specified explicitly in the optional arguments. Optional arguments: | selected -- Selection of atoms to include in the ENM model. This can be a list or array of atom indices (length <= N), or an array of booleans (length = N). | numbers -- atom numbers in the ENM model (length = N). default is array of ones or the numbers from the molecule object. | masses -- atomic masses in atomic units in the ENM model (length = N). default is array of hydrogen masses or the masses from the molecule object. | rcut -- cutoff distance between interacting pairs in atomic units | K -- strength of the interaction in atomic units (Hartree/Bohr**2). The interaction strength is the same for all interacting pairs. """ if isinstance(molecule, Molecule): coordinates = molecule.coordinates if numbers is None: numbers = molecule.numbers if masses is None: masses = molecule.masses if periodic is None: periodic = molecule.periodic else: coordinates = np.array(molecule, copy=False) if numbers is None: # pretend being hydrogens numbers = np.ones(len(coordinates)) if masses is None: # pretend being hydrogens masses = np.ones(len(coordinates), float) * amu if periodic is None: periodic = False if selected is not None: coordinates = coordinates[selected] numbers = numbers[selected] masses = masses[selected] rcut2 = rcut**2 N = len(coordinates) hessian = np.zeros((3 * N, 3 * N), float) for i in range(N): for j in range(i + 1, N): x = np.reshape((coordinates[i, :] - coordinates[j, :]), (3, 1)) dist2 = np.sum(x**2) if dist2 < rcut2: corr = K * np.dot(x, x.transpose()) / dist2 hessian[3 * i:3 * (i + 1), 3 * i:3 * (i + 1)] += corr hessian[3 * i:3 * (i + 1), 3 * j:3 * (j + 1)] -= corr hessian[3 * j:3 * (j + 1), 3 * i:3 * (i + 1)] -= corr hessian[3 * j:3 * (j + 1), 3 * j:3 * (j + 1)] += corr return Molecule( numbers, coordinates, masses, 0.0, #energy np.zeros((len(coordinates), 3), float), #gradient hessian, 1, # multiplicity 1, # rotational symmetry number periodic, )