Exemplo n.º 1
0
def test_geometric_retries(failure_engine, input_data):

    failure_engine.iter_modes = ["random_error", "pass", "random_error", "random_error", "pass"]  # Iter 1  # Iter 2
    failure_engine.iter_modes.extend(["pass"] * 20)

    input_data["initial_molecule"] = {
        "symbols": ["He", "He"],
        "geometry": [0, 0, 0, 0, 0, failure_engine.start_distance],
    }
    input_data["input_specification"]["model"] = {"method": "something"}
    input_data["keywords"]["program"] = failure_engine.name

    input_data = OptimizationInput(**input_data)

    ret = qcng.compute_procedure(input_data, "geometric", local_options={"ncores": 13}, raise_error=True)
    assert ret.success is True
    assert ret.trajectory[0].provenance.retries == 1
    assert ret.trajectory[0].provenance.ncores == 13
    assert ret.trajectory[1].provenance.retries == 2
    assert ret.trajectory[1].provenance.ncores == 13
    assert "retries" not in ret.trajectory[2].provenance.dict()

    # Ensure we still fail
    failure_engine.iter_modes = ["random_error", "pass", "random_error", "random_error", "pass"]  # Iter 1  # Iter 2
    ret = qcng.compute_procedure(input_data, "geometric", local_options={"ncores": 13, "retries": 1})
    assert ret.success is False
    assert ret.input_data["trajectory"][0]["provenance"]["retries"] == 1
    assert len(ret.input_data["trajectory"]) == 2
Exemplo n.º 2
0
def test_nwchem_restart(tmpdir):
    # Make the input file
    input_data = {
        "input_specification": {
            "model": {
                "method": "HF",
                "basis": "sto-3g"
            },
            "keywords": {
                "driver__maxiter": 2,
                "set__driver:linopt": 0
            },
            "extras": {
                "allow_restarts": True
            },
        },
        "initial_molecule": qcng.get_molecule("hydrogen"),
    }
    input_data = OptimizationInput(**input_data)

    # Run an initial step, which should not converge
    local_opts = {"scratch_messy": True, "scratch_directory": str(tmpdir)}
    ret = qcng.compute_procedure(input_data,
                                 "nwchemdriver",
                                 local_options=local_opts,
                                 raise_error=False)
    assert not ret.success

    # Run it again, which should converge
    new_ret = qcng.compute_procedure(input_data,
                                     "nwchemdriver",
                                     local_options=local_opts,
                                     raise_error=True)
    assert new_ret.success
Exemplo n.º 3
0
def test_geometric_stdout():
    inp = copy.deepcopy(_base_json)

    inp["initial_molecule"] = dc.get_molecule("water")
    inp["input_specification"]["model"] = {"method": "UFF", "basis": ""}
    inp["keywords"]["program"] = "rdkit"

    ret = dc.compute_procedure(inp, "geometric")
    assert ret["success"] is True
    assert "Converged!" in ret["stdout"]
    assert ret["stderr"] == "No stderr recieved."

    with pytest.raises(ValueError):
        ret = dc.compute_procedure(inp, "rdkit", raise_error=True)
Exemplo n.º 4
0
def test_geometric_rdkit_error():
    inp = copy.deepcopy(_base_json)

    inp["initial_molecule"] = dc.get_molecule("water")
    del inp["initial_molecule"]["connectivity"]
    inp["input_specification"]["model"] = {"method": "UFF", "basis": ""}
    inp["keywords"]["program"] = "rdkit"

    ret = dc.compute_procedure(inp, "geometric")
    assert ret["success"] is False
    assert isinstance(ret["error_message"], str)

    with pytest.raises(ValueError):
        ret = dc.compute_procedure(inp, "rdkit", raise_error=True)
Exemplo n.º 5
0
def test_geometric_psi4(input_data):

    input_data["initial_molecule"] = qcng.get_molecule("hydrogen")
    input_data["input_specification"]["model"] = {
        "method": "HF",
        "basis": "sto-3g"
    }
    input_data["input_specification"]["keywords"] = {
        "scf_properties": ["wiberg_lowdin_indices"]
    }
    input_data["keywords"]["program"] = "psi4"

    input_data = OptimizationInput(**input_data)

    ret = qcng.compute_procedure(input_data, "geometric", raise_error=True)
    assert 10 > len(ret.trajectory) > 1

    assert pytest.approx(ret.final_molecule.measure([0, 1]),
                         1.0e-4) == 1.3459150737
    assert ret.provenance.creator.lower() == "geometric"
    assert ret.trajectory[0].provenance.creator.lower() == "psi4"

    # Check keywords passing
    for single in ret.trajectory:
        assert "scf_properties" in single.keywords
        assert "WIBERG_LOWDIN_INDICES" in single.extras["qcvars"]
Exemplo n.º 6
0
    def _execute_qcengine(self,
                          input_data,
                          procedure="geometric",
                          local_options=None,
                          scf_maxiter=None,
                          geometric_maxiter=None,
                          geometric_coordsys=None,
                          geometric_qccnv=None):
        import qcengine

        # inject reset=True fix, set coordsys to 'dlc'
        input_data['keywords']['reset'] = True
        input_data['keywords']['coordsys'] = 'dlc'

        # inject exposed convergence parameters, if specified
        if scf_maxiter is not None:
            input_data['input_specification']['keywords'][
                'maxiter'] = scf_maxiter
        if geometric_maxiter is not None:
            input_data['keywords']['maxiter'] = geometric_maxiter
        if geometric_coordsys is not None:
            input_data['keywords']['coordsys'] = geometric_coordsys
        if geometric_qccnv is not None:
            input_data['keywords']['qccnv'] = geometric_qccnv

        return qcengine.compute_procedure(input_data,
                                          procedure=procedure,
                                          local_options=local_options)
Exemplo n.º 7
0
def test_geometric_generic(input_data, program, model, bench):

    input_data["initial_molecule"] = qcng.get_molecule("water")
    input_data["input_specification"]["model"] = model
    input_data["keywords"]["program"] = program
    input_data["input_specification"]["extras"] = {
        "_secret_tags": {
            "mysecret_tag": "data1"
        }
    }

    ret = qcng.compute_procedure(input_data, "geometric", raise_error=True)
    assert ret.success is True
    assert "Converged!" in ret.stdout

    r01, r02, r12, a102 = ret.final_molecule.measure([[0, 1], [0, 2], [1, 2],
                                                      [1, 0, 2]])

    assert pytest.approx(r01, 1.0e-4) == bench[0]
    assert pytest.approx(r02, 1.0e-4) == bench[0]
    assert pytest.approx(r12, 1.0e-4) == bench[1]
    assert pytest.approx(a102, 1.0e-4) == bench[2]

    assert "_secret_tags" in ret.trajectory[0].extras
    assert "data1" == ret.trajectory[0].extras["_secret_tags"]["mysecret_tag"]
Exemplo n.º 8
0
def relax_structure(smiles: str,
                    qc_config: QCInputSpecification,
                    compute_config: Optional[Union[TaskConfig, Dict]] = None,
                    compute_connectivity: bool = False,
                    code: str = _code) -> Tuple[str, float]:
    """Compute the atomization energy of a molecule given the SMILES string

    Args:
        smiles (str): SMILES of a molecule
        qc_config (dict): Quantum Chemistry configuration used for evaluating the energy
        compute_config (TaskConfig): Configuration for the quantum chemistry code
        compute_connectivity (bool): Whether we must compute connectivity before calling code
        code (str): Which QC code to use for the evaluation
    Returns:
        (str): Structure of the molecule
        (float): Electronic energy of this molecule
    """
    # Generate 3D coordinates by minimizing MMFF forcefield
    xyz = generate_atomic_coordinates(smiles)
    mol = Molecule.from_data(xyz, dtype='xyz')

    # Generate connectivity, if needed
    if compute_connectivity:
        conn = guess_connectivity(mol.symbols, mol.geometry, default_connectivity=1.0)
        mol = Molecule.from_data({**mol.dict(), 'connectivity': conn})

    # Run the relaxation
    opt_input = OptimizationInput(input_specification=qc_config,
                                  initial_molecule=mol,
                                  keywords={'program': code, 'convergence_set': 'GAU_VERYTIGHT'})
    res: OptimizationResult = \
        compute_procedure(opt_input, 'geometric', local_options=compute_config, raise_error=True)
    return res.final_molecule.to_string('xyz'), res.energies[-1]
Exemplo n.º 9
0
def relax_structure(xyz: str,
                    qc_config: QCInputSpecification,
                    charge: int = 0,
                    compute_config: Optional[Union[TaskConfig, Dict]] = None,
                    code: str = _code) -> OptimizationResult:
    """Compute the atomization energy of a molecule given the SMILES string

    Args:
        xyz (str): Structure of a molecule in XYZ format
        qc_config (dict): Quantum Chemistry configuration used for evaluating the energy
        charge (int): Charge of the molecule
        compute_config (TaskConfig): Configuration for the quantum chemistry code, such as parallelization settings
        code (str): Which QC code to use for the evaluation
    Returns:
        (OptimizationResult): Full output from the calculation
    """

    # Parse the molecule
    mol = Molecule.from_data(xyz, dtype='xyz', molecular_charge=charge)

    # Run the relaxation
    if code == "nwchem":
        keywords = {"driver__maxiter": 100, "set__driver:linopt": 0}
        relax_code = "nwchemdriver"
    else:
        keywords = {"program": code}
        relax_code = "geometric"
    opt_input = OptimizationInput(input_specification=qc_config,
                                  initial_molecule=mol,
                                  keywords=keywords)
    return compute_procedure(opt_input,
                             relax_code,
                             local_options=compute_config,
                             raise_error=True)
Exemplo n.º 10
0
    def _spawn_optimization(
            grid_point: str, job: List[float],
            input_model: "TorsionDriveInput", config: "TaskConfig"
    ) -> Union[FailedOperation, OptimizationResult]:
        """Spawns an optimization at a particular grid point and returns the result.

        Parameters
        ----------
        grid_point
            A string of the form 'dihedral_1_angle ... dihedral_n_angle' that encodes
            the current dihedrals angles to optimize at.
        job
            The flattened conformer of the molecule to start the optimization at with
            length=(n_atoms * 3)
        input_model
            The input model containing the relevant settings for how to optimize the
            structure.
        config
            The configuration to launch the task using.

        Returns
        -------
            The result of the optimization if successful, otherwise an error containing
            object.
        """

        from qcengine import compute_procedure

        input_molecule = input_model.initial_molecule[0].copy(deep=True).dict()
        input_molecule["geometry"] = np.array(job).reshape(
            len(input_molecule["symbols"]), 3)
        input_molecule = Molecule.from_data(input_molecule)

        dihedrals = input_model.keywords.dihedrals
        angles = grid_point.split()

        keywords = {
            **input_model.optimization_spec.keywords,
            "constraints": {
                "set": [{
                    "type": "dihedral",
                    "indices": dihedral,
                    "value": int(angle),
                } for dihedral, angle in zip(dihedrals, angles)]
            },
        }

        input_data = OptimizationInput(
            keywords=keywords,
            extras={},
            protocols=input_model.optimization_spec.protocols,
            input_specification=input_model.input_specification,
            initial_molecule=input_molecule,
        )

        return compute_procedure(
            input_data,
            procedure=input_model.optimization_spec.procedure,
            local_options=config.dict())
Exemplo n.º 11
0
def test_geometric_torchani():
    inp = copy.deepcopy(_base_json)

    inp["initial_molecule"] = qcng.get_molecule("water")
    inp["input_specification"]["model"] = {"method": "ANI1x", "basis": None}
    inp["keywords"]["program"] = "torchani"

    ret = qcng.compute_procedure(inp, "geometric", raise_error=True)
    assert ret.success is True
    assert "Converged!" in ret.stdout
Exemplo n.º 12
0
def test_geometric_torchani():
    inp = copy.deepcopy(_base_json)

    inp["initial_molecule"] = dc.get_molecule("water")
    inp["input_specification"]["model"] = {"method": "ANI1", "basis": None}
    inp["keywords"]["program"] = "torchani"

    ret = dc.compute_procedure(inp, "geometric")
    assert ret["success"] is True
    assert "Converged!" in ret["stdout"]
    assert ret["stderr"] == "No stderr recieved."
Exemplo n.º 13
0
def test_geometric_rdkit_error(input_data):

    input_data["initial_molecule"] = qcng.get_molecule("water").copy(exclude={"connectivity_"})
    input_data["input_specification"]["model"] = {"method": "UFF", "basis": ""}
    input_data["keywords"]["program"] = "rdkit"

    input_data = OptimizationInput(**input_data)

    ret = qcng.compute_procedure(input_data, "geometric")
    assert ret.success is False
    assert isinstance(ret.error.error_message, str)
Exemplo n.º 14
0
def test_geometric_stdout(input_data):

    input_data["initial_molecule"] = qcng.get_molecule("water")
    input_data["input_specification"]["model"] = {"method": "UFF", "basis": ""}
    input_data["keywords"]["program"] = "rdkit"

    input_data = OptimizationInput(**input_data)

    ret = qcng.compute_procedure(input_data, "geometric", raise_error=True)
    assert ret.success is True
    assert "Converged!" in ret.stdout
Exemplo n.º 15
0
def test_geometric_psi4():
    inp = copy.deepcopy(_base_json)

    inp["initial_molecule"] = dc.get_molecule("hydrogen")
    inp["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"}
    inp["keywords"]["program"] = "psi4"

    ret = dc.compute_procedure(inp, "geometric")
    assert 10 > len(ret["trajectory"]) > 1

    geom = ret["final_molecule"]["geometry"]
    assert pytest.approx(_bond_dist(geom, 0, 1), 1.e-4) == 1.3459150737
Exemplo n.º 16
0
def test_berny_stdout(input_data):

    input_data["initial_molecule"] = qcng.get_molecule("water")
    input_data["input_specification"]["model"] = {
        "method": "HF",
        "basis": "sto-3g"
    }
    input_data["keywords"]["program"] = "psi4"

    input_data = OptimizationInput(**input_data)

    ret = qcng.compute_procedure(input_data, "berny", raise_error=True)
    assert ret.success is True
    assert "All criteria matched" in ret.stdout
Exemplo n.º 17
0
def test_geometric_local_options(input_data):

    input_data["initial_molecule"] = qcng.get_molecule("hydrogen")
    input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"}
    input_data["keywords"]["program"] = "psi4"

    input_data = OptimizationInput(**input_data)

    # Set some extremely large number to test
    ret = qcng.compute_procedure(input_data, "geometric", raise_error=True, local_options={"memory": "5000"})
    assert pytest.approx(ret.trajectory[0].provenance.memory, 1) == 4900

    # Make sure we cleaned up
    assert "_qcengine_local_config" not in ret.input_specification
    assert "_qcengine_local_config" not in ret.trajectory[0].extras
Exemplo n.º 18
0
def test_optimization_protocols(input_data):

    input_data["initial_molecule"] = qcng.get_molecule("water")
    input_data["input_specification"]["model"] = {"method": "UFF"}
    input_data["keywords"]["program"] = "rdkit"
    input_data["protocols"] = {"trajectory": "initial_and_final"}

    input_data = OptimizationInput(**input_data)

    ret = qcng.compute_procedure(input_data, "geometric", raise_error=True)
    assert ret.success, ret.error.error_message

    assert len(ret.trajectory) == 2
    assert ret.initial_molecule.get_hash() == ret.trajectory[0].molecule.get_hash()
    assert ret.final_molecule.get_hash() == ret.trajectory[1].molecule.get_hash()
Exemplo n.º 19
0
def test_geometric_psi4():
    inp = copy.deepcopy(_base_json)

    inp["initial_molecule"] = qcng.get_molecule("hydrogen")
    inp["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"}
    inp["keywords"]["program"] = "psi4"

    inp = OptimizationInput(**inp)

    ret = qcng.compute_procedure(inp, "geometric", raise_error=True)
    assert 10 > len(ret.trajectory) > 1

    assert pytest.approx(ret.final_molecule.measure([0, 1]), 1.e-4) == 1.3459150737
    assert ret.provenance.creator.lower() == "geometric"
    assert ret.trajectory[0].provenance.creator.lower() == "psi4"
Exemplo n.º 20
0
def test_berny_failed_gradient_computation(input_data):

    input_data["initial_molecule"] = qcng.get_molecule("water")
    input_data["input_specification"]["model"] = {
        "method": "HF",
        "basis": "sto-3g"
    }
    input_data["input_specification"]["keywords"] = {
        "badpsi4key": "badpsi4value"
    }
    input_data["keywords"]["program"] = "psi4"

    input_data = OptimizationInput(**input_data)

    ret = qcng.compute_procedure(input_data, "berny", raise_error=False)
    assert isinstance(ret, FailedOperation)
    assert ret.success is False
    assert ret.error.error_type == qcng.exceptions.InputError.error_type
Exemplo n.º 21
0
def test_torsiondrive_generic():

    input_data = TorsionDriveInput(
        keywords=TDKeywords(dihedrals=[(2, 0, 1, 5)], grid_spacing=[180]),
        input_specification=QCInputSpecification(driver=DriverEnum.gradient,
                                                 model=Model(method="UFF",
                                                             basis=None)),
        initial_molecule=[qcng.get_molecule("ethane")] * 2,
        optimization_spec=OptimizationSpecification(
            procedure="geomeTRIC",
            keywords={
                "coordsys": "dlc",
                "maxiter": 300,
                "program": "rdkit",
            },
        ),
    )

    ret = qcng.compute_procedure(input_data, "torsiondrive", raise_error=True)

    assert ret.error is None
    assert ret.success

    expected_grid_ids = {"180", "0"}

    assert {*ret.optimization_history} == expected_grid_ids

    assert {*ret.final_energies} == expected_grid_ids
    assert {*ret.final_molecules} == expected_grid_ids

    assert (pytest.approx(ret.final_molecules["180"].measure([2, 0, 1, 5]),
                          abs=1.0e-2) == 180.0
            or pytest.approx(ret.final_molecules["180"].measure([2, 0, 1, 5]),
                             abs=1.0e-2) == -180.0)
    assert pytest.approx(ret.final_molecules["0"].measure([2, 0, 1, 5]),
                         abs=1.0e-2) == 0.0

    assert ret.provenance.creator.lower() == "torsiondrive"
    assert ret.optimization_history["180"][0].provenance.creator.lower(
    ) == "geometric"
    assert ret.optimization_history["180"][0].trajectory[
        0].provenance.creator.lower() == "rdkit"

    assert ret.stdout == "All optimizations converged at lowest energy. Job Finished!\n"
Exemplo n.º 22
0
def test_geometric_local_options():
    inp = copy.deepcopy(_base_json)

    inp["initial_molecule"] = dc.get_molecule("hydrogen")
    inp["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"}
    inp["keywords"]["program"] = "psi4"

    inp = OptimizationInput(**inp)

    # Set some extremely large number to test
    ret = dc.compute_procedure(inp,
                               "geometric",
                               raise_error=True,
                               local_options={"memory": "5000"})
    assert pytest.approx(ret["trajectory"][0]["provenance"]["memory"],
                         1) == 4900

    # Make sure we cleaned up
    assert "_qcengine_local_config" not in ret["input_specification"]
    assert "_qcengine_local_config" not in ret["trajectory"][0]
Exemplo n.º 23
0
def test_nwchem_relax(linopt):
    # Make the input file
    input_data = {
        "input_specification": {
            "model": {
                "method": "HF",
                "basis": "sto-3g"
            },
            "keywords": {
                "set__driver:linopt": linopt
            },
        },
        "initial_molecule": qcng.get_molecule("hydrogen"),
    }
    input_data = OptimizationInput(**input_data)

    # Run the relaxation
    ret = qcng.compute_procedure(input_data, "nwchemdriver", raise_error=True)
    assert 10 > len(ret.trajectory) > 1

    assert pytest.approx(ret.final_molecule.measure([0, 1]),
                         1.0e-4) == 1.3459150737
Exemplo n.º 24
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.atoms)
                conversion = constants.HA_TO_KCAL_P_MOL / (
                    constants.BOHR_TO_ANGS**2)
                hessian = np.reshape(ret.return_result,
                                     (hess_size, hess_size)) * conversion
                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,
            }
            return qcng.compute_procedure(geo_task,
                                          'geometric',
                                          return_dict=True,
                                          local_options={
                                              'memory': self.molecule.memory,
                                              'ncores': self.molecule.threads
                                          })

        else:
            raise KeyError(
                'Invalid engine type provided. Please use "geo" or "psi4".')
Exemplo n.º 25
0
def test_procedure_avail_bounce():

    with pytest.raises(qcng.exceptions.InputError) as exc:
        qcng.compute_procedure({}, "bad_program", raise_error=True)

    assert "not registered" in str(exc.value)
Exemplo n.º 26
0
    def optimise(
        self,
        molecule: Ligand,
        allow_fail: bool = False,
        return_result: bool = False,
        extras: Optional[Dict[str, Any]] = None,
    ) -> Tuple[Ligand, Optional[qcel.models.OptimizationResult]]:
        """
        For the given specification in the class run an optimisation on the ligand.

        Run the specified optimisation on the ligand the final coordinates are extracted and stored in the ligand.
        The optimisation schema is dumped to file along with the optimised geometry and the trajectory.

        Args:
            molecule:
                The molecule which should be optimised
            allow_fail:
                If we should not raise an error if the molecule fails to be optimised, this will extract the last geometry
                from the trajectory and return it.
            return_result:
                If the full result json should also be returned useful for extracting the trajectory.
            extras:
                A dictionary of extras that should be used to update the optimiser keywords.

        Returns:
            A new copy of the molecule at the optimised coordinates.
        """
        # first validate the settings
        self._validate_specification()
        # now validate that the programs are installed
        self.check_available(program=self.program, optimiser=self.optimiser)

        # now we need to distribute the job
        model = self.qc_model
        specification = qcel.models.procedures.QCInputSpecification(
            model=model, keywords={"dft_spherical_points": 590, "dft_radial_points": 99}
        )
        initial_mol = molecule.to_qcschema()
        optimiser_keywords = self.build_optimiser_keywords()
        if extras is not None:
            optimiser_keywords.update(extras)
        opt_task = qcel.models.OptimizationInput(
            initial_molecule=initial_mol,
            input_specification=specification,
            keywords=optimiser_keywords,
        )
        opt_result = qcng.compute_procedure(
            input_data=opt_task,
            procedure=self.optimiser,
            raise_error=False,
            local_options=self.local_options,
        )
        # dump info to file
        result_mol = self.handle_output(molecule=molecule, opt_output=opt_result)
        # check if we can/have failed and raise the error
        if not opt_result.success and not allow_fail:
            raise RuntimeError(
                f"{opt_result.error.error_type}: {opt_result.error.error_message}"
            )

        full_result = opt_result if return_result else None
        return result_mol, full_result
Exemplo n.º 27
0
def optimize_qc_engine(
    elements: Iterable[int] | Iterable[str],
    coordinates: ArrayLike2D,
    charge: int | None = None,
    multiplicity: int | None = None,
    connectivity_matrix: ArrayLike2D | None = None,
    program: str = "xtb",
    model: dict[str, Any] | None = None,
    keywords: dict[str, Any] | None = None,
    local_options: dict[str, Any] | None = None,
    procedure: str = "berny",
    return_trajectory: bool = False,
) -> tuple[Array2DFloat | Array3DFloat, Array1DFloat]:
    """Optimize molecule with QCEngine.

    Args:
        elements: Elements as atomic symbols or numbers
        coordinates: Coordinates (Å)
        charge: Molecular charge
        multiplicity: Multiplicity
        connectivity_matrix: Connectivity matrix
        program: QCEngine program
        model: QCEngine model
        keywords: QCEngine keywords
        local_options: QCEngine local options
        procedure: QCEngine procedure
        return_trajectory: Return coordinates for all steps

    Returns:
        opt_coordinates (ndarray): Conformer coordinates (Å)
        energies (ndarray): Energies for all steps (a.u.)

    Raises:
        Exception: When QCEngine calculation fails
    """
    if (program.lower() == "rdkit" and charge is not None
            and connectivity_matrix is not None):
        _check_qcng_rdkit(charge, connectivity_matrix)

    # Set defaults
    if model is None:
        model = {"method": "GFN2-xTB"}
    if keywords is None:
        keywords = {}
    if local_options is None:
        local_options = {}

    # Create molecule object
    molecule = _generate_qcel_molecule(elements, coordinates, charge,
                                       multiplicity, connectivity_matrix)

    # Create optimization input
    opt_input = {
        "keywords": {
            "program": program
        },
        "input_specification": {
            "driver": "gradient",
            "model": model,
            "keywords": keywords,
        },
        "initial_molecule": molecule,
    }

    # Perform optimization
    opt = qcng.compute_procedure(opt_input,
                                 procedure=procedure,
                                 local_options=local_options)
    if not opt.success:
        raise Exception(opt.error.error_message)

    # Take out results
    energies: Array1DFloat = np.array(opt.energies)
    if return_trajectory:
        opt_coordinates: Array2DFloat = np.array(
            [result.molecule.geometry for result in opt.trajectory])
    else:
        opt_coordinates = opt.final_molecule.geometry
    opt_coordinates *= BOHR_TO_ANGSTROM

    return opt_coordinates, energies
Exemplo n.º 28
0
    def call_qcengine(self, engine, driver):
        """
        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
        :return: The required driver information
        """

        mol = self.generate_qschema()
        options = {"memory": self.molecule.memory, "ncores": self.molecule.threads}

        # Call psi4 for energy, gradient, hessian or property calculations
        if engine == "psi4":
            psi4_task = qcel.models.AtomicInput(
                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=options)

            if driver == "hessian":
                hess_size = 3 * len(self.molecule.atoms)
                conversion = constants.HA_TO_KCAL_P_MOL / (constants.BOHR_TO_ANGS ** 2)
                hessian = (
                    np.reshape(ret.return_result, (hess_size, hess_size)) * conversion
                )
                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": "dlc",
                    "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,
            }
            return qcng.compute_procedure(
                geo_task, "geometric", return_dict=True, local_options=options
            )

        elif engine == "torchani":
            ani_task = qcel.models.AtomicInput(
                molecule=mol,
                driver=driver,
                model={"method": "ANI1x", "basis": None},
                keywords={"scf_type": "df"},
            )

            return qcng.compute(
                ani_task, "torchani", local_options=options
            ).return_result

        else:
            raise KeyError(
                'Invalid engine type provided. Please use "geometric", "psi4" or "torchani".'
            )
Exemplo n.º 29
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".')