Beispiel #1
0
    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
Beispiel #2
0
    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".')
Beispiel #3
0
    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