def hessian(self): """Extract the Hessian matrix from the Gaussian fchk file.""" # Make the fchk file first with open('formchck.log', 'w+') as formlog: sp.run('formchk lig.chk lig.fchk', shell=True, stdout=formlog, stderr=formlog) with open('lig.fchk', 'r') as fchk: lines = fchk.readlines() hessian_list = [] for count, line in enumerate(lines): if line.startswith('Cartesian Force Constants'): start_pos = count + 1 if line.startswith('Dipole Moment'): end_pos = count if not start_pos and end_pos: raise EOFError( 'Cannot locate Hessian matrix in lig.fchk file.') for line in lines[start_pos:end_pos]: # Extend the list with the converted floats from the file, splitting on spaces and removing '\n' tags. hessian_list.extend([ float(num) * 627.509391 / (0.529**2) for num in line.strip('\n').split() ]) hess_size = 3 * len(self.molecule.molecule['input']) hessian = np.zeros((hess_size, hess_size)) # Rewrite Hessian to full, symmetric 3N * 3N matrix rather than list with just the non-repeated values. m = 0 for i in range(hess_size): for j in range(i + 1): hessian[i, j] = hessian_list[m] hessian[j, i] = hessian_list[m] m += 1 check_symmetry(hessian) return hessian
def call_qcengine(self, engine, driver, input_type): """ Using the created schema, run a particular engine, specifying the driver (job type). e.g. engine: geo, driver: energies. :param engine: The engine to be used psi4 geometric :param driver: The calculation type to be done e.g. energy, gradient, hessian, properties :param input_type: The part of the molecule object that should be used when making the schema :return: The required driver information """ mol = self.generate_qschema(input_type=input_type) # Call psi4 for energy, gradient, hessian or property calculations if engine == 'psi4': psi4_task = qcel.models.ResultInput( molecule=mol, driver=driver, model={ 'method': self.molecule.theory, 'basis': self.molecule.basis }, keywords={'scf_type': 'df'}, ) ret = qcng.compute(psi4_task, 'psi4', local_options={ 'memory': self.molecule.memory, 'ncores': self.molecule.threads }) if driver == 'hessian': hess_size = 3 * len(self.molecule.molecule[input_type]) hessian = np.reshape( ret.return_result, (hess_size, hess_size)) * 627.509391 / (0.529**2) check_symmetry(hessian) return hessian else: return ret.return_result # Call geometric with psi4 to optimise a molecule elif engine == 'geometric': geo_task = { "schema_name": "qcschema_optimization_input", "schema_version": 1, "keywords": { "coordsys": "tric", "maxiter": self.molecule.iterations, "program": "psi4", "convergence_set": self.molecule.convergence, }, "input_specification": { "schema_name": "qcschema_input", "schema_version": 1, "driver": 'gradient', "model": { 'method': self.molecule.theory, 'basis': self.molecule.basis }, "keywords": {}, }, "initial_molecule": mol, } # TODO hide the output stream so it does not spoil the terminal printing # return_dict=True seems to be default False in newer versions. Ergo docs are wrong again. ret = qcng.compute_procedure(geo_task, 'geometric', return_dict=True, local_options={ 'memory': self.molecule.memory, 'ncores': self.molecule.threads }) return ret else: raise KeyError( 'Invalid engine type provided. Please use "geo" or "psi4".')
def hessian(self): """ Parses the Hessian from the output.dat file (from psi4) into a numpy array. Molecule is a numpy array of size N x N. """ hess_size = 3 * len(self.molecule.molecule['input']) # output.dat is the psi4 output file. with open('output.dat', 'r') as file: lines = file.readlines() for count, line in enumerate(lines): if '## Hessian' in line or '## New Matrix (Symmetry' in line: # Set the start of the hessian to the row of the first value. hess_start = count + 5 break else: raise EOFError( 'Cannot locate Hessian matrix in output.dat file.') # Check if the hessian continues over onto more lines (i.e. if hess_size is not divisible by 5) extra = 0 if hess_size % 5 == 0 else 1 # hess_length: # of cols * length of each col # + # of cols - 1 * #blank lines per row of hess_vals # + # blank lines per row of hess_vals if the hess_size continues over onto more lines. hess_length = (hess_size // 5) * hess_size + ( hess_size // 5 - 1) * 3 + extra * (3 + hess_size) hess_end = hess_start + hess_length hess_vals = [] for file_line in lines[hess_start:hess_end]: # Compile lists of the 5 Hessian floats for each row. # Number of floats in last row may be less than 5. # Only the actual floats are added, not the separating numbers. row_vals = [ float(val) for val in file_line.split() if len(val) > 5 ] hess_vals.append(row_vals) # Remove blank list entries hess_vals = [elem for elem in hess_vals if elem] reshaped = [] # Convert from list of (lists, length 5) to 2d array of size hess_size x hess_size for old_row in range(hess_size): new_row = [] for col_block in range(hess_size // 5 + extra): new_row += hess_vals[old_row + col_block * hess_size] reshaped.append(new_row) hess_matrix = np.array(reshaped) # Cache the unit conversion. conversion = 627.509391 / (0.529**2) # Element-wise multiplication hess_matrix *= conversion check_symmetry(hess_matrix) return hess_matrix