Example #1
0
def sample_record() -> MoleculeData:
    md = MoleculeData.from_identifier('O')
    result = OptimizationResult.parse_file(
        _my_path.joinpath('records/xtb-neutral.json'))
    md.add_geometry(result, "xtb")
    md.subsets.append('pytest')
    return md
Example #2
0
    def parse_output(self, outfiles: Dict[str, str],
                     input_model: OptimizationInput) -> OptimizationResult:

        # Get the stdout from the calculation (required)
        stdout = outfiles.pop("stdout")
        stderr = outfiles.pop("stderr")

        # Parse out the atomic results from the file
        atomic_results = harvest_as_atomic_result(input_model, stdout)

        # Isolate the converged result
        final_step = atomic_results[-1]

        return OptimizationResult(
            initial_molecule=input_model.initial_molecule,
            input_specification=input_model.input_specification,
            final_molecule=final_step.molecule,
            trajectory=atomic_results,
            energies=[
                float(r.extras["qcvars"]["CURRENT ENERGY"])
                for r in atomic_results
            ],
            stdout=stdout,
            stderr=stderr,
            success=True,
            provenance=Provenance(creator="NWChemRelax",
                                  version=self.get_version(),
                                  routine="nwchem_opt"),
        )
Example #3
0
    def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "OptimizationResult":
        try:
            import geometric
        except ModuleNotFoundError:
            raise ModuleNotFoundError("Could not find geomeTRIC in the Python path.")

        input_data = input_model.dict()

        # Temporary patch for geomeTRIC
        input_data["initial_molecule"]["symbols"] = list(input_data["initial_molecule"]["symbols"])

        # Set retries to two if zero while respecting local_config
        local_config = config.dict()
        local_config["retries"] = local_config.get("retries", 2) or 2
        input_data["input_specification"]["extras"]["_qcengine_local_config"] = local_config

        # Run the program
        output_data = geometric.run_json.geometric_run_json(input_data)

        output_data["provenance"] = {
            "creator": "geomeTRIC",
            "routine": "geometric.run_json.geometric_run_json",
            "version": geometric.__version__,
        }

        output_data["schema_name"] = "qcschema_optimization_output"
        output_data["input_specification"]["extras"].pop("_qcengine_local_config", None)
        if output_data["success"]:
            output_data = OptimizationResult(**output_data)

        return output_data
Example #4
0
    def compute(self, input_data: "OptimizationInput", config: "TaskConfig") -> "OptimizationResult":
        try:
            import berny
        except ModuleNotFoundError:
            raise ModuleNotFoundError("Could not find Berny in the Python path.")

        # Get berny version from the installed package, use setuptools'
        # pkg_resources for python < 3.8
        if sys.version_info >= (3, 8):
            from importlib.metadata import distribution
        else:
            from pkg_resources import get_distribution as distribution
        berny_version = distribution("pyberny").version

        # Berny uses the stdlib logging module and by default uses per-module
        # loggers. For QCEngine, we create one logger per BernyProcedure
        # instance, by using the instance's id(), and send all logging messages
        # to a string stream
        log_stream = StringIO()
        log = logging.getLogger(f"{__name__}.{id(self)}")
        log.addHandler(logging.StreamHandler(log_stream))
        log.setLevel("INFO")

        input_data = input_data.dict()
        geom_qcng = input_data["initial_molecule"]
        comput = {**input_data["input_specification"], "molecule": geom_qcng}
        program = input_data["keywords"].pop("program")
        trajectory = []
        output_data = input_data.copy()
        try:
            # Pyberny uses angstroms for the Cartesian geometry, but atomic
            # units for everything else, including the gradients (hartree/bohr).
            geom_berny = berny.Geometry(geom_qcng["symbols"], geom_qcng["geometry"] / berny.angstrom)
            opt = berny.Berny(geom_berny, logger=log, **input_data["keywords"])
            for geom_berny in opt:
                geom_qcng["geometry"] = np.stack(geom_berny.coords * berny.angstrom)
                ret = qcengine.compute(comput, program)
                trajectory.append(ret.dict())
                opt.send((ret.properties.return_energy, ret.return_result))
        except Exception:
            output_data["success"] = False
            output_data["error"] = {"error_type": "unknown", "error_message": f"Berny error:\n{traceback.format_exc()}"}
        else:
            output_data["success"] = True
        output_data.update(
            {
                "schema_name": "qcschema_optimization_output",
                "final_molecule": trajectory[-1]["molecule"],
                "energies": [r["properties"]["return_energy"] for r in trajectory],
                "trajectory": trajectory,
                "provenance": {"creator": "Berny", "routine": "berny.Berny", "version": berny_version},
                "stdout": log_stream.getvalue(),  # collect logged messages
            }
        )
        if output_data["success"]:
            output_data = OptimizationResult(**output_data)
        return output_data
Example #5
0
def _compress_optimizationresult(
    result: OptimizationResult,
    compression: CompressionEnum = CompressionEnum.lzma,
    compression_level: Optional[int] = None,
):
    """
    Compresses outputs inside an OptimizationResult, storing them in extras

    Outputs for the AtomicResults stored in the trajectory will be stored in the extras for that AtomicResult
    """

    # Handle the trajectory
    trajectory = [
        _compress_common(x, compression, compression_level)
        for x in result.trajectory
    ]
    result = result.copy(update={"trajectory": trajectory})

    # Now handle the outputs of the optimization itself
    return _compress_common(result, compression, compression_level)
Example #6
0
    def compute(self, input_model: "OptimizationInput",
                config: "TaskConfig") -> "Optimization":
        if self.found(raise_error=True):
            import optking

        input_data = input_model.dict()

        # Set retries to two if zero while respecting local_config
        local_config = config.dict()
        local_config["retries"] = local_config.get("retries", 2) or 2
        input_data["input_specification"]["extras"][
            "_qcengine_local_config"] = local_config

        # Run the program
        output_data = optking.optwrapper.optimize_qcengine(input_data)

        output_data["schema_name"] = "qcschema_optimization_output"
        output_data["input_specification"]["extras"].pop(
            "_qcengine_local_config", None)
        if output_data["success"]:
            output_data = OptimizationResult(**output_data)

        return output_data
def test_add_data():
    md = MoleculeData.from_identifier("O")

    # Load the xtb geometry
    xtb_geom = OptimizationResult.parse_file(
        _my_path.joinpath('records/xtb-neutral.json'))
    md.add_geometry(xtb_geom)
    assert "xtb" in md.data
    assert "neutral" in md.data["xtb"]
    assert isclose(md.data["xtb"][
        OxidationState.NEUTRAL].atomization_energy["xtb-no_zpe"],
                   -0.515,
                   abs_tol=1e-2)
    assert ("xtb", "neutral") == md.match_geometry(xtb_geom.final_molecule)

    # Load in a relaxed oxidized geometry
    xtb_geom_ox = OptimizationResult.parse_file(
        _my_path.joinpath('records/xtb-oxidized.json'))
    md.add_geometry(xtb_geom_ox)
    assert "xtb" in md.data
    assert "oxidized" in md.data["xtb"]
    assert ("xtb",
            "neutral") == md.match_geometry(xtb_geom_ox.initial_molecule)
    assert ("xtb", "oxidized") == md.match_geometry(xtb_geom_ox.final_molecule)
    assert md.data['xtb'][OxidationState.OXIDIZED].total_energy[OxidationState.OXIDIZED]['xtb'] != \
           md.data['xtb'][OxidationState.NEUTRAL].total_energy[OxidationState.OXIDIZED]['xtb']

    # Load in a oxidized energy for the neutral structure
    xtb_energy = AtomicResult.parse_file(
        _my_path.joinpath('records/xtb-neutral_xtb-oxidized-energy.json'))
    md.add_single_point(xtb_energy)

    # Add in solvation energies
    xtb_energy = AtomicResult.parse_file(
        _my_path.joinpath('records/xtb-neutral_acn.json'))
    md.add_single_point(xtb_energy)
    assert "acetonitrile" in md.data['xtb']['neutral'].solvation_energy[
        'neutral']

    xtb_energy = AtomicResult.parse_file(
        _my_path.joinpath('records/xtb-oxidized_acn.json'))
    md.add_single_point(xtb_energy)
    assert "acetonitrile" in md.data['xtb']['oxidized'].solvation_energy[
        'oxidized']

    # Show that we can compute a redox potential
    recipe = RedoxEnergyRecipe(name="xtb-vertical",
                               geometry_level="xtb",
                               energy_level="xtb",
                               adiabatic=False)
    result = recipe.compute_redox_potential(md, OxidationState.OXIDIZED)
    assert md.oxidation_potential['xtb-vertical'] == result

    recipe = RedoxEnergyRecipe(name="xtb",
                               geometry_level="xtb",
                               energy_level="xtb",
                               adiabatic=True)
    result = recipe.compute_redox_potential(md, OxidationState.OXIDIZED)
    assert md.oxidation_potential['xtb'] == result
    assert md.oxidation_potential['xtb'] < md.oxidation_potential[
        'xtb-vertical']

    recipe = RedoxEnergyRecipe(name="xtb-acn",
                               geometry_level="xtb",
                               energy_level="xtb",
                               adiabatic=True,
                               solvent='acetonitrile',
                               solvation_level='xtb')
    result = recipe.compute_redox_potential(md, OxidationState.OXIDIZED)
    assert md.oxidation_potential['xtb-acn'] == result
    assert md.oxidation_potential['xtb-acn'] != md.oxidation_potential['xtb']

    # Add a single point small_basis computation
    smb_hessian = AtomicResult.parse_file(
        _my_path.joinpath('records/xtb-neutral_smb-neutral-hessian.json'))
    md.add_single_point(smb_hessian)
    assert isclose(md.data["xtb"][OxidationState.NEUTRAL].zpe[
        OxidationState.NEUTRAL]['small_basis'],
                   0.02155,
                   abs_tol=1e-3)

    # Add an NWChem with solvent
    smb_solvent = AtomicResult.parse_file(
        _my_path.joinpath('records/xtb-neutral_smb-neutral_water.json'))
    md.add_single_point(smb_solvent)
    assert 'small_basis' in md.data['xtb']['neutral'].total_energy_in_solvent[
        'neutral']['water']