Пример #1
0
    def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicResult":
        """
        Runs OpenMM on given structure, inputs, in vacuum.
        """
        self.found(raise_error=True)

        from simtk import openmm
        from simtk import unit

        import openforcefield.topology as offtop

        # Failure flag
        ret_data = {"success": False}

        # generate basis, not given
        if not input_data.model.basis:
            basis = self._generate_basis(input_data)
            ret_data["basis"] = basis

        # get number of threads to use from `TaskConfig.ncores`; otherwise, try environment variable
        nthreads = config.ncores
        if nthreads is None:
            nthreads = os.environ.get("OPENMM_CPU_THREADS")

        # Set workdir to scratch
        # Location resolution order config.scratch_dir, /tmp
        parent = config.scratch_directory
        with temporary_directory(parent=parent, suffix="_openmm_scratch") as tmpdir:

            # Grab molecule, forcefield
            jmol = input_data.molecule

            # TODO: If urls are supported by
            # `openforcefield.typing.engines.smirnoff.ForceField` already, we
            # can eliminate the `offxml` and `url` distinction
            # URL processing can happen there instead
            if getattr(input_data.model, "offxml", None):
                # we were given a file path or relative path
                offxml = input_data.model.offxml

                # Load an Open Force Field `ForceField`
                off_forcefield = self._get_off_forcefield(offxml, offxml)
            elif getattr(input_data.model, "url", None):
                # we were given a url
                with urllib.request.urlopen(input_data.model.url) as req:
                    xml = req.read()

                # Load an Open Force Field `ForceField`
                off_forcefield = self._get_off_forcefield(xml.decode(), xml)
            else:
                raise InputError("OpenMM requires either `model.offxml` or `model.url` to be set")

            # Process molecule with RDKit
            rdkit_mol = RDKitHarness._process_molecule_rdkit(jmol)

            # Create an Open Force Field `Molecule` from the RDKit Molecule
            off_mol = offtop.Molecule(rdkit_mol)

            # Create OpenMM system in vacuum from forcefield, molecule
            off_top = off_mol.to_topology()
            openmm_system = self._get_openmm_system(off_forcefield, off_top)

            # Need an integrator for simulation even if we don't end up using it really
            integrator = openmm.VerletIntegrator(1.0 * unit.femtoseconds)

            # Set platform to CPU explicitly
            platform = openmm.Platform.getPlatformByName("CPU")

            # Set number of threads to use
            # if `nthreads` is `None`, OpenMM default of all logical cores on
            # processor will be used
            if nthreads:
                properties = {"Threads": str(nthreads)}
            else:
                properties = {}

            # Initialize context
            context = openmm.Context(openmm_system, integrator, platform, properties)

            # Set positions from our Open Force Field `Molecule`
            context.setPositions(off_mol.conformers[0])

            # Compute the energy of the configuration
            state = context.getState(getEnergy=True)

            # Get the potential as a simtk.unit.Quantity, put into units of hartree
            q = state.getPotentialEnergy() / unit.hartree

            ret_data["properties"] = {"return_energy": q.value_in_unit(q.unit)}

            # Execute driver
            if input_data.driver == "energy":
                ret_data["return_result"] = ret_data["properties"]["return_energy"]

            elif input_data.driver == "gradient":
                # Get number of atoms
                n_atoms = len(jmol.symbols)

                # Compute the forces
                state = context.getState(getForces=True)

                # Get the gradient as a simtk.unit.Quantity with shape (n_atoms, 3)
                gradient = state.getForces(asNumpy=True)

                # Convert to hartree/bohr and reformat as 1D array
                q = (gradient / (unit.hartree / unit.bohr)).reshape([n_atoms * 3])
                ret_data["return_result"] = q.value_in_unit(q.unit)
            else:
                raise InputError(
                    f"OpenMM can only compute energy and gradient driver methods. Found {input_data.driver}."
                )

        ret_data["success"] = True

        # Move several pieces up a level
        ret_data["provenance"] = Provenance(creator="openmm", version=openmm.__version__, nthreads=nthreads)

        return AtomicResult(**{**input_data.dict(), **ret_data})
Пример #2
0
    def compute(self, input_data: "AtomicInput",
                config: "TaskConfig") -> "AtomicResult":
        """
        Runs OpenMM on given structure, inputs, in vacuum.
        """
        self.found(raise_error=True)

        from simtk import openmm
        from simtk import unit

        with capture_stdout():
            import openforcefield.topology as offtop

        # Failure flag
        ret_data = {"success": False}

        # generate basis, not given
        if not input_data.model.basis:
            raise InputError("Method must contain a basis set.")

        # Make sure we are using smirnoff or antechamber
        basis = input_data.model.basis.lower()
        if basis in ["smirnoff", "antechamber"]:

            with capture_stdout():
                # try and make the molecule from the cmiles
                cmiles = None
                if input_data.molecule.extras:
                    cmiles = input_data.molecule.extras.get(
                        "canonical_isomeric_explicit_hydrogen_mapped_smiles",
                        None)
                    if cmiles is None:
                        cmiles = input_data.molecule.extras.get(
                            "cmiles", {}
                        ).get(
                            "canonical_isomeric_explicit_hydrogen_mapped_smiles",
                            None)

                if cmiles is not None:
                    off_mol = offtop.Molecule.from_mapped_smiles(
                        mapped_smiles=cmiles)
                    # add the conformer
                    conformer = unit.Quantity(value=np.array(
                        input_data.molecule.geometry),
                                              unit=unit.bohr)
                    off_mol.add_conformer(conformer)
                else:
                    # Process molecule with RDKit
                    rdkit_mol = RDKitHarness._process_molecule_rdkit(
                        input_data.molecule)

                    # Create an Open Force Field `Molecule` from the RDKit Molecule
                    off_mol = offtop.Molecule(rdkit_mol)

            # now we need to create the system
            openmm_system = self._generate_openmm_system(
                molecule=off_mol,
                method=input_data.model.method,
                keywords=input_data.keywords)
        else:
            raise InputError(
                "Accepted bases are: {'smirnoff', 'antechamber', }")

        # Need an integrator for simulation even if we don't end up using it really
        integrator = openmm.VerletIntegrator(1.0 * unit.femtoseconds)

        # Set platform to CPU explicitly
        platform = openmm.Platform.getPlatformByName("CPU")

        # Set number of threads to use
        # if `nthreads` is `None`, OpenMM default of all logical cores on
        # processor will be used
        nthreads = config.ncores
        if nthreads is None:
            nthreads = os.environ.get("OPENMM_CPU_THREADS")

        if nthreads:
            properties = {"Threads": str(nthreads)}
        else:
            properties = {}

        # Initialize context
        context = openmm.Context(openmm_system, integrator, platform,
                                 properties)

        # Set positions from our Open Force Field `Molecule`
        context.setPositions(off_mol.conformers[0])

        # Compute the energy of the configuration
        state = context.getState(getEnergy=True)

        # Get the potential as a simtk.unit.Quantity, put into units of hartree
        q = state.getPotentialEnergy(
        ) / unit.hartree / unit.AVOGADRO_CONSTANT_NA

        ret_data["properties"] = {"return_energy": q}

        # Execute driver
        if input_data.driver == "energy":
            ret_data["return_result"] = ret_data["properties"]["return_energy"]

        elif input_data.driver == "gradient":
            # Compute the forces
            state = context.getState(getForces=True)

            # Get the gradient as a simtk.unit.Quantity with shape (n_atoms, 3)
            gradient = state.getForces(asNumpy=True)

            # Convert to hartree/bohr and reformat as 1D array
            q = (gradient / (unit.hartree / unit.bohr)
                 ).reshape(-1) / unit.AVOGADRO_CONSTANT_NA

            # Force to gradient
            ret_data["return_result"] = -1 * q
        else:
            raise InputError(
                f"OpenMM can only compute energy and gradient driver methods. Found {input_data.driver}."
            )

        ret_data["success"] = True
        ret_data["extras"] = input_data.extras

        # Move several pieces up a level
        ret_data["provenance"] = Provenance(creator="openmm",
                                            version=openmm.__version__,
                                            nthreads=nthreads)

        return AtomicResult(**{**input_data.dict(), **ret_data})