Exemple #1
0
def test_submission():

    with tempfile.TemporaryDirectory() as directory:

        with temporarily_change_directory(directory):

            with DaskLocalCluster() as calculation_backend:

                # Spin up a server instance.
                server = EvaluatorServer(
                    calculation_backend=calculation_backend,
                    working_directory=directory,
                )

                with server:

                    # Connect a client.
                    client = EvaluatorClient()

                    # Submit an empty data set.
                    force_field_path = "smirnoff99Frosst-1.1.0.offxml"
                    force_field_source = SmirnoffForceFieldSource.from_path(
                        force_field_path)

                    request, error = client.request_estimate(
                        PhysicalPropertyDataSet(), force_field_source)
                    assert error is None
                    assert isinstance(request, Request)

                    result, error = request.results(polling_interval=0.01)
                    assert error is None
                    assert isinstance(result, RequestResult)
Exemple #2
0
    def _run_yank(directory, available_resources, setup_only):
        """Runs YANK within the specified directory which contains a `yank.yaml`
        input file.

        Parameters
        ----------
        directory: str
            The directory within which to run yank.
        available_resources: ComputeResources
            The compute resources available to yank.
        setup_only: bool
            If true, YANK will only create and validate the setup files,
            but not actually run any simulations. This argument is mainly
            only to be used for testing purposes.

        Returns
        -------
        simtk.pint.Quantity
            The free energy returned by yank.
        simtk.pint.Quantity
            The uncertainty in the free energy returned by yank.
        """

        from simtk import unit as simtk_unit
        from yank.analyze import ExperimentAnalyzer
        from yank.experiment import ExperimentBuilder

        with temporarily_change_directory(directory):

            # Set the default properties on the desired platform
            # before calling into yank.
            setup_platform_with_resources(available_resources)

            exp_builder = ExperimentBuilder("yank.yaml")

            if setup_only is True:
                return (
                    0.0 * simtk_unit.kilojoule_per_mole,
                    0.0 * simtk_unit.kilojoule_per_mole,
                )

            exp_builder.run_experiments()

            analyzer = ExperimentAnalyzer("experiments")
            output = analyzer.auto_analyze()

            free_energy = output["free_energy"]["free_energy_diff_unit"]
            free_energy_uncertainty = output["free_energy"][
                "free_energy_diff_error_unit"
            ]

        return free_energy, free_energy_uncertainty
Exemple #3
0
def test_server_spin_up():

    with tempfile.TemporaryDirectory() as directory:

        with temporarily_change_directory(directory):

            with DaskLocalCluster() as calculation_backend:

                server = EvaluatorServer(
                    calculation_backend=calculation_backend,
                    working_directory=directory,
                )

                with server:
                    sleep(0.5)
def test_base_layer():

    properties_to_estimate = [
        create_dummy_property(Density),
        create_dummy_property(Density),
    ]

    dummy_options = RequestOptions()

    batch = server.Batch()
    batch.queued_properties = properties_to_estimate
    batch.options = dummy_options
    batch.force_field_id = ""
    batch.options.calculation_schemas = {
        "Density": {
            "DummyCalculationLayer": CalculationLayerSchema()
        }
    }

    with tempfile.TemporaryDirectory() as temporary_directory:

        with temporarily_change_directory(temporary_directory):

            # Create a simple calculation backend to test with.
            test_backend = DaskLocalCluster()
            test_backend.start()

            # Create a simple storage backend to test with.
            test_storage = LocalFileStorage()

            layer_directory = "dummy_layer"
            makedirs(layer_directory)

            def dummy_callback(returned_request):

                assert len(returned_request.estimated_properties) == 1
                assert len(returned_request.exceptions) == 2

            dummy_layer = DummyCalculationLayer()

            dummy_layer.schedule_calculation(
                test_backend,
                test_storage,
                layer_directory,
                batch,
                dummy_callback,
                True,
            )
Exemple #5
0
    def _apply(
        cls,
        data_frame: pandas.DataFrame,
        schema: ImportThermoMLDataSchema,
        n_processes,
    ) -> pandas.DataFrame:

        if schema.cache_file_name is not None and os.path.isfile(
            schema.cache_file_name
        ):

            cached_data = pandas.read_csv(schema.cache_file_name)
            return cached_data

        with temporarily_change_directory():

            logger.debug("Downloading archive data")

            cls._download_data(schema)

            # Get the names of the extracted files
            file_names = glob.glob("*.xml")

            logger.debug("Processing archives")

            with Pool(processes=n_processes) as pool:
                data_frames = [*pool.imap(cls._process_archive, file_names)]

            pool.join()

        logger.debug("Joining archives")

        thermoml_data_frame = pandas.concat(data_frames, ignore_index=True, sort=False)

        for header in thermoml_data_frame:

            if header.find(" Uncertainty ") >= 0 and not schema.retain_uncertainties:
                thermoml_data_frame = thermoml_data_frame.drop(header, axis=1)

        data_frame = pandas.concat(
            [data_frame, thermoml_data_frame], ignore_index=True, sort=False
        )

        if schema.cache_file_name is not None:
            data_frame.to_csv(schema.cache_file_name, index=False)

        return data_frame
Exemple #6
0
def test_launch_batch():

    # Set up a dummy data set
    data_set = PhysicalPropertyDataSet()
    data_set.add_properties(create_dummy_property(Density),
                            create_dummy_property(Density))

    batch = Batch()
    batch.force_field_id = ""
    batch.options = RequestOptions()
    batch.options.calculation_layers = ["QuickCalculationLayer"]
    batch.options.calculation_schemas = {
        "Density": {
            "QuickCalculationLayer": CalculationLayerSchema()
        }
    }
    batch.parameter_gradient_keys = []
    batch.queued_properties = [*data_set]
    batch.validate()

    with tempfile.TemporaryDirectory() as directory:

        with temporarily_change_directory(directory):

            with DaskLocalCluster() as calculation_backend:

                server = EvaluatorServer(
                    calculation_backend=calculation_backend,
                    working_directory=directory,
                )

                server._queued_batches[batch.id] = batch
                server._launch_batch(batch)

                while len(batch.queued_properties) > 0:
                    sleep(0.01)

                assert len(batch.estimated_properties) == 1
                assert len(batch.unsuccessful_properties) == 1
def test_ligand_receptor_yank_protocol():

    full_substance = Substance()

    full_substance.add_component(
        Component(smiles="c1ccccc1", role=Component.Role.Receptor),
        ExactAmount(1),
    )
    full_substance.add_component(
        Component(smiles="C", role=Component.Role.Ligand),
        ExactAmount(1),
    )
    full_substance.add_component(
        Component(smiles="O", role=Component.Role.Solvent),
        MoleFraction(1.0),
    )

    solute_substance = Substance()
    solute_substance.add_component(
        Component(smiles="C", role=Component.Role.Ligand),
        ExactAmount(1),
    )
    solute_substance.add_component(
        Component(smiles="O", role=Component.Role.Solvent),
        MoleFraction(1.0),
    )

    thermodynamic_state = ThermodynamicState(temperature=298.15 * unit.kelvin,
                                             pressure=1.0 * unit.atmosphere)

    with tempfile.TemporaryDirectory() as directory:

        with temporarily_change_directory(directory):

            force_field_path = "ff.json"

            with open(force_field_path, "w") as file:
                file.write(build_tip3p_smirnoff_force_field().json())

            complex_coordinate_path, complex_system = _setup_dummy_system(
                "full", full_substance, 3, force_field_path)

            ligand_coordinate_path, ligand_system = _setup_dummy_system(
                "ligand", solute_substance, 2, force_field_path)

            run_yank = LigandReceptorYankProtocol("yank")
            run_yank.substance = full_substance
            run_yank.thermodynamic_state = thermodynamic_state
            run_yank.number_of_iterations = 1
            run_yank.steps_per_iteration = 1
            run_yank.checkpoint_interval = 1
            run_yank.verbose = True
            run_yank.setup_only = True

            run_yank.ligand_residue_name = "TMP"
            run_yank.receptor_residue_name = "TMP"
            run_yank.solvated_ligand_coordinates = ligand_coordinate_path
            run_yank.solvated_ligand_system = ligand_system
            run_yank.solvated_complex_coordinates = complex_coordinate_path
            run_yank.solvated_complex_system = complex_system

            run_yank.force_field_path = force_field_path
            run_yank.execute("", ComputeResources())
def test_solvation_yank_protocol(solvent_smiles):

    full_substance = Substance()

    full_substance.add_component(
        Component(smiles="CO", role=Component.Role.Solute),
        ExactAmount(1),
    )
    full_substance.add_component(
        Component(smiles=solvent_smiles, role=Component.Role.Solvent),
        MoleFraction(1.0),
    )

    solvent_substance = Substance()
    solvent_substance.add_component(
        Component(smiles=solvent_smiles, role=Component.Role.Solvent),
        MoleFraction(1.0),
    )

    solute_substance = Substance()
    solute_substance.add_component(
        Component(smiles="CO", role=Component.Role.Solute),
        ExactAmount(1),
    )

    thermodynamic_state = ThermodynamicState(temperature=298.15 * unit.kelvin,
                                             pressure=1.0 * unit.atmosphere)

    with tempfile.TemporaryDirectory() as directory:

        with temporarily_change_directory(directory):

            force_field_path = "ff.json"

            with open(force_field_path, "w") as file:
                file.write(build_tip3p_smirnoff_force_field().json())

            solvated_coordinate_path, solvated_system = _setup_dummy_system(
                "full", full_substance, 2, force_field_path)

            vacuum_coordinate_path, vacuum_system = _setup_dummy_system(
                "vacuum", solute_substance, 1, force_field_path)

            run_yank = SolvationYankProtocol("yank")
            run_yank.solute = solute_substance
            run_yank.solvent_1 = solvent_substance
            run_yank.solvent_2 = Substance()
            run_yank.thermodynamic_state = thermodynamic_state
            run_yank.number_of_iterations = 1
            run_yank.steps_per_iteration = 1
            run_yank.checkpoint_interval = 1
            run_yank.verbose = True
            run_yank.setup_only = True
            run_yank.solution_1_coordinates = solvated_coordinate_path
            run_yank.solution_1_system = solvated_system
            run_yank.solution_2_coordinates = vacuum_coordinate_path
            run_yank.solution_2_system = vacuum_system

            run_yank.electrostatic_lambdas_1 = [1.00]
            run_yank.steric_lambdas_1 = [1.00]
            run_yank.electrostatic_lambdas_2 = [1.00]
            run_yank.steric_lambdas_2 = [1.00]
            run_yank.execute("", ComputeResources())
Exemple #9
0
def pack_box(
    molecules,
    number_of_copies,
    structure_to_solvate=None,
    center_solute=True,
    tolerance=2.0 * unit.angstrom,
    box_size=None,
    mass_density=None,
    box_aspect_ratio=None,
    verbose=False,
    working_directory=None,
    retain_working_files=False,
):
    """Run packmol to generate a box containing a mixture of molecules.

    Parameters
    ----------
    molecules : list of openff.toolkit.topology.Molecule
        The molecules in the system.
    number_of_copies : list of int
        A list of the number of copies of each molecule type, of length
        equal to the length of `molecules`.
    structure_to_solvate: str, optional
        A file path to the PDB coordinates of the structure to be solvated.
    center_solute: str
        If `True`, the structure to solvate will be centered in the
        simulation box. This option is only applied when `structure_to_solvate`
        is set.
    tolerance : openff.evaluator.unit.Quantity
        The minimum spacing between molecules during packing in units
         compatible with angstroms.
    box_size : openff.evaluator.unit.Quantity, optional
        The size of the box to generate in units compatible with angstroms.
        If `None`, `mass_density` must be provided.
    mass_density : openff.evaluator.unit.Quantity, optional
        Target mass density for final system with units compatible with g / mL.
         If `None`, `box_size` must be provided.
    box_aspect_ratio: list of float, optional
        The aspect ratio of the simulation box, used in conjunction with
        the `mass_density` parameter. If none, an isotropic ratio (i.e.
        [1.0, 1.0, 1.0]) is used.
    verbose : bool
        If True, verbose output is written.
    working_directory: str, optional
        The directory in which to generate the temporary working files. If `None`,
        a temporary one will be created.
    retain_working_files: bool
        If True all of the working files, such as individual molecule coordinate
        files, will be retained.

    Returns
    -------
    mdtraj.Trajectory
        The packed box encoded in an mdtraj trajectory.
    list of str
        The residue names which were assigned to each of the
        molecules in the `molecules` list.

    Raises
    ------
    PackmolRuntimeException
        When packmol fails to execute / converge.
    """

    if mass_density is not None and box_aspect_ratio is None:
        box_aspect_ratio = [1.0, 1.0, 1.0]

    # Make sure packmol can be found.
    packmol_path = _find_packmol()

    if packmol_path is None:
        raise IOError("Packmol not found, cannot run pack_box()")

    # Validate the inputs.
    _validate_inputs(
        molecules,
        number_of_copies,
        structure_to_solvate,
        box_aspect_ratio,
        box_size,
        mass_density,
    )

    # Estimate the box_size from mass density if one is not provided.
    if box_size is None:

        box_size = _approximate_box_size_by_density(molecules,
                                                    number_of_copies,
                                                    mass_density,
                                                    box_aspect_ratio)

    # Set up the directory to create the working files in.
    temporary_directory = False

    if working_directory is None:

        working_directory = tempfile.mkdtemp()
        temporary_directory = True

    if len(working_directory) > 0:
        os.makedirs(working_directory, exist_ok=True)

    # Copy the structure to solvate if one is provided.
    if structure_to_solvate is not None:

        import mdtraj

        trajectory = mdtraj.load_pdb(structure_to_solvate)

        # Fix mdtraj #1611
        for atom in trajectory.topology.atoms:
            atom.serial = None

        structure_to_solvate = "solvate.pdb"
        trajectory.save_pdb(
            os.path.join(working_directory, structure_to_solvate))

    assigned_residue_names = []

    with temporarily_change_directory(working_directory):

        # Create PDB files for all of the molecules.
        pdb_file_names = []
        mdtraj_topologies = []

        for index, molecule in enumerate(molecules):

            mdtraj_trajectory = _create_trajectory(molecule)

            pdb_file_name = f"{index}.pdb"
            pdb_file_names.append(pdb_file_name)

            mdtraj_trajectory.save_pdb(pdb_file_name)
            mdtraj_topologies.append(mdtraj_trajectory.topology)

            residue_name = mdtraj_trajectory.topology.residue(0).name
            assigned_residue_names.append(residue_name)

        # Generate the input file.
        output_file_name = "packmol_output.pdb"

        input_file_path = _build_input_file(
            pdb_file_names,
            number_of_copies,
            structure_to_solvate,
            center_solute,
            box_size,
            tolerance,
            output_file_name,
        )

        with open(input_file_path) as file_handle:

            result = subprocess.check_output(
                packmol_path, stdin=file_handle,
                stderr=subprocess.STDOUT).decode("utf-8")

            if verbose:
                logger.info(result)

            packmol_succeeded = result.find("Success!") > 0

        if not retain_working_files:

            os.unlink(input_file_path)

            for file_path in pdb_file_names:
                os.unlink(file_path)

        if not packmol_succeeded:

            if verbose:
                logger.info("Packmol failed to converge")

            if os.path.isfile(output_file_name):
                os.unlink(output_file_name)

            if temporary_directory and not retain_working_files:
                shutil.rmtree(working_directory)

            raise PackmolRuntimeException(result)

        # Add a 2 angstrom buffer to help alleviate PBC issues.
        box_size = [(x + 2.0 * unit.angstrom).to(unit.nanometer).magnitude
                    for x in box_size]

        # Append missing connect statements to the end of the
        # output file.
        trajectory = _correct_packmol_output(output_file_name,
                                             mdtraj_topologies,
                                             number_of_copies,
                                             structure_to_solvate)
        trajectory.unitcell_lengths = box_size
        trajectory.unitcell_angles = [90.0] * 3

        if not retain_working_files:
            os.unlink(output_file_name)

    if temporary_directory and not retain_working_files:
        shutil.rmtree(working_directory)

    return trajectory, assigned_residue_names
Exemple #10
0
def main():

    setup_timestamp_logging()

    # Retrieve the current version.
    version = evaluator.__version__.replace(".", "-").replace("v", "")

    if "+" in version:
        version = "latest"

    # Create a new directory to run the current versions results in.
    os.makedirs(os.path.join(version, "results"))

    with temporarily_change_directory(version):

        # Load in the force field
        force_field = ForceField(
            "openff-1.2.0.offxml",
            get_data_filename("forcefield/tip3p.offxml"),
        )

        force_field_source = SmirnoffForceFieldSource.from_object(force_field)
        force_field_source.json("force-field.json")

        # Load in the data set, retaining only a specific host / guest pair.
        binding_affinity = TaproomDataSet(
            host_codes=["acd"],
            guest_codes=["bam"],
            default_ionic_strength=150 * unit.millimolar,
        ).properties[0]

        # Set up the calculation
        schema = HostGuestBindingAffinity.default_paprika_schema(
            n_solvent_molecules=2000).workflow_schema
        schema.replace_protocol_types({
            "BaseBuildSystem": ("BuildSmirnoffSystem" if isinstance(
                force_field_source, SmirnoffForceFieldSource) else
                                "BuildTLeapSystem" if isinstance(
                                    force_field_source, TLeapForceFieldSource)
                                else "BaseBuildSystem")
        })

        metadata = Workflow.generate_default_metadata(binding_affinity,
                                                      "force-field.json",
                                                      UNDEFINED)

        workflow = Workflow.from_schema(schema, metadata, "acd_bam")

        # Run the calculation
        with DaskLSFBackend(
                minimum_number_of_workers=1,
                maximum_number_of_workers=50,
                resources_per_worker=QueueWorkerResources(
                    number_of_gpus=1,
                    preferred_gpu_toolkit=QueueWorkerResources.GPUToolkit.CUDA,
                    per_thread_memory_limit=5 * unit.gigabyte,
                    wallclock_time_limit="05:59",
                ),
                setup_script_commands=[
                    "conda activate openff-evaluator-paprika",
                    "module load cuda/10.0",
                ],
                queue_name="gpuqueue",
        ) as calculation_backend:

            results = workflow.execute(
                root_directory="workflow",
                calculation_backend=calculation_backend).result()

        # Save the results
        results.json("results.json", format=True)
def test_workflow_layer():
    """Test the `WorkflowLayer` calculation layer. As the `SimulationLayer`
    is the simplest implementation of the abstract layer, we settle for
    testing this."""

    properties_to_estimate = [
        create_dummy_property(Density),
        create_dummy_property(Density),
    ]

    # Create a very simple workflow which just returns some placeholder
    # value.
    estimated_value = Observable(
        (1 * unit.kelvin).plus_minus(0.1 * unit.kelvin))
    protocol_a = DummyProtocol("protocol_a")
    protocol_a.input_value = estimated_value

    schema = WorkflowSchema()
    schema.protocol_schemas = [protocol_a.schema]
    schema.final_value_source = ProtocolPath("output_value", protocol_a.id)

    layer_schema = SimulationSchema()
    layer_schema.workflow_schema = schema

    options = RequestOptions()
    options.add_schema("SimulationLayer", "Density", layer_schema)

    batch = server.Batch()
    batch.queued_properties = properties_to_estimate
    batch.options = options

    with tempfile.TemporaryDirectory() as directory:

        with temporarily_change_directory(directory):

            # Create a directory for the layer.
            layer_directory = "simulation_layer"
            os.makedirs(layer_directory)

            # Set-up a simple storage backend and add a force field to it.
            force_field = SmirnoffForceFieldSource.from_path(
                "smirnoff99Frosst-1.1.0.offxml")

            storage_backend = LocalFileStorage()
            batch.force_field_id = storage_backend.store_force_field(
                force_field)

            # Create a simple calculation backend to test with.
            with DaskLocalCluster() as calculation_backend:

                def dummy_callback(returned_request):

                    assert len(returned_request.estimated_properties) == 2
                    assert len(returned_request.exceptions) == 0

                simulation_layer = SimulationLayer()

                simulation_layer.schedule_calculation(
                    calculation_backend,
                    storage_backend,
                    layer_directory,
                    batch,
                    dummy_callback,
                    True,
                )
    def _run_tleap(molecule, force_field_source, directory):
        """Uses tleap to apply parameters to a particular molecule,
        generating a `.prmtop` and a `.rst7` file with the applied parameters.

        Parameters
        ----------
        molecule: openff.toolkit.topology.Molecule
            The molecule to parameterize.
        force_field_source: TLeapForceFieldSource
            The tleap source which describes which parameters to apply.
        directory: str
            The directory to store and temporary files / the final
            parameters in.

        Returns
        -------
        str
            The file path to the `prmtop` file.
        str
            The file path to the `rst7` file.
        """
        from simtk import unit as simtk_unit

        # Change into the working directory.
        with temporarily_change_directory(directory):

            initial_file_path = "initial.sdf"
            molecule.to_file(initial_file_path, file_format="SDF")

            # Save the molecule charges to a file.
            charges = [
                x.value_in_unit(simtk_unit.elementary_charge)
                for x in molecule.partial_charges
            ]

            with open("charges.txt", "w") as file:
                file.write(textwrap.fill(" ".join(map(str, charges)), width=70))

            if force_field_source.leap_source == "leaprc.gaff2":
                amber_type = "gaff2"
            elif force_field_source.leap_source == "leaprc.gaff":
                amber_type = "gaff"
            else:

                raise ValueError(
                    f"The {force_field_source.leap_source} source is currently "
                    f"unsupported. Only the 'leaprc.gaff2' and 'leaprc.gaff' "
                    f" sources are supported."
                )

            # Run antechamber to find the correct atom types.
            processed_mol2_path = "antechamber.mol2"

            antechamber_process = subprocess.Popen(
                [
                    "antechamber",
                    "-i",
                    initial_file_path,
                    "-fi",
                    "sdf",
                    "-o",
                    processed_mol2_path,
                    "-fo",
                    "mol2",
                    "-at",
                    amber_type,
                    "-rn",
                    "MOL",
                    "-an",
                    "no",
                    "-pf",
                    "yes",
                    "-c",
                    "rc",
                    "-cf",
                    "charges.txt",
                ],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )

            antechamber_output, antechamber_error = antechamber_process.communicate()
            antechamber_exit_code = antechamber_process.returncode

            with open("antechamber_output.log", "w") as file:
                file.write(f"error code: {antechamber_exit_code}\nstdout:\n\n")
                file.write("stdout:\n\n")
                file.write(antechamber_output.decode())
                file.write("\nstderr:\n\n")
                file.write(antechamber_error.decode())

            if not os.path.isfile(processed_mol2_path):

                raise RuntimeError(
                    f"antechamber failed to assign atom types to the input mol2 file "
                    f"({initial_file_path})"
                )

            frcmod_path = None

            if amber_type == "gaff" or amber_type == "gaff2":

                # Optionally run parmchk to find any missing parameters.
                frcmod_path = "parmck2.frcmod"

                prmchk2_process = subprocess.Popen(
                    [
                        "parmchk2",
                        "-i",
                        processed_mol2_path,
                        "-f",
                        "mol2",
                        "-o",
                        frcmod_path,
                        "-s",
                        amber_type,
                    ],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                )

                prmchk2_output, prmchk2_error = prmchk2_process.communicate()
                prmchk2_exit_code = prmchk2_process.returncode

                with open("prmchk2_output.log", "w") as file:
                    file.write(f"error code: {prmchk2_exit_code}\nstdout:\n\n")
                    file.write(prmchk2_output.decode())
                    file.write("\nstderr:\n\n")
                    file.write(prmchk2_error.decode())

                if not os.path.isfile(frcmod_path):

                    raise RuntimeError(
                        f"parmchk2 failed to assign missing {amber_type} parameters "
                        f"to the antechamber created mol2 file ({processed_mol2_path})",
                    )

            # Build the tleap input file.
            template_lines = [f"source {force_field_source.leap_source}"]

            if frcmod_path is not None:
                template_lines.append(
                    f"loadamberparams {frcmod_path}",
                )

            prmtop_file_name = "structure.prmtop"
            rst7_file_name = "structure.rst7"

            template_lines.extend(
                [
                    f"MOL = loadmol2 {processed_mol2_path}",
                    'setBox MOL "centers"',
                    "check MOL",
                    f"saveamberparm MOL {prmtop_file_name} {rst7_file_name}",
                ]
            )

            input_file_path = "tleap.in"

            with open(input_file_path, "w") as file:
                file.write("\n".join(template_lines))

            # Run tleap.
            tleap_process = subprocess.Popen(
                ["tleap", "-s ", "-f ", input_file_path], stdout=subprocess.PIPE
            )

            tleap_output, _ = tleap_process.communicate()
            tleap_exit_code = tleap_process.returncode

            with open("tleap_output.log", "w") as file:
                file.write(f"error code: {tleap_exit_code}\nstdout:\n\n")
                file.write(tleap_output.decode())

            if not os.path.isfile(prmtop_file_name) or not os.path.isfile(
                rst7_file_name
            ):
                raise RuntimeError("tleap failed to execute.")

            with open("leap.log", "r") as file:

                if re.search(
                    "ERROR|WARNING|Warning|duplicate|FATAL|Could|Fatal|Error",
                    file.read(),
                ):

                    raise RuntimeError("tleap failed to execute.")

        return (
            os.path.join(directory, prmtop_file_name),
            os.path.join(directory, rst7_file_name),
        )
    def _execute(self, directory, available_resources):

        from openff.toolkit.topology import Molecule, Topology

        force_field_source = ForceFieldSource.from_json(self.force_field_path)
        cutoff = pint_quantity_to_openmm(force_field_source.cutoff)

        # Load in the systems topology
        openmm_pdb_file = app.PDBFile(self.coordinate_file_path)

        # Create an OFF topology for better insight into the layout of the system
        # topology.
        unique_molecules = {}

        for component in self.substance:
            unique_molecule = Molecule.from_smiles(component.smiles)
            unique_molecules[unique_molecule.to_smiles()] = unique_molecule

        # Parameterize each component in the system.
        system_templates = {}

        for index, (smiles, unique_molecule) in enumerate(unique_molecules.items()):

            if smiles in ["O", "[H]O[H]", "[H][O][H]"]:

                component_system = self._build_tip3p_system(
                    cutoff,
                    openmm_pdb_file.topology.getUnitCellDimensions(),
                )

            else:

                component_directory = os.path.join(directory, str(index))
                os.makedirs(component_directory, exist_ok=True)

                with temporarily_change_directory(component_directory):

                    component_system = self._parameterize_molecule(
                        unique_molecule, force_field_source, cutoff
                    )

            system_templates[smiles] = component_system

        # Apply the parameters to the topology.
        topology = Topology.from_openmm(
            openmm_pdb_file.topology, unique_molecules.values()
        )

        # Create the full system object from the component templates.
        system = self._create_empty_system(cutoff)

        for topology_molecule in topology.topology_molecules:

            smiles = topology_molecule.reference_molecule.to_smiles()
            system_template = system_templates[smiles]

            index_map = {}

            for index, topology_atom in enumerate(topology_molecule.atoms):
                index_map[topology_atom.atom.molecule_particle_index] = index

            # Append the component template to the full system.
            self._append_system(system, system_template, index_map)

        if openmm_pdb_file.topology.getPeriodicBoxVectors() is not None:

            system.setDefaultPeriodicBoxVectors(
                *openmm_pdb_file.topology.getPeriodicBoxVectors()
            )

        # Serialize the system object.
        system_path = os.path.join(directory, "system.xml")

        with open(system_path, "w") as file:
            file.write(openmm.XmlSerializer.serialize(system))

        self.parameterized_system = ParameterizedSystem(
            substance=self.substance,
            force_field=force_field_source,
            topology_path=self.coordinate_file_path,
            system_path=system_path,
        )
def main():

    setup_timestamp_logging()

    # Retrieve the current version.
    version = evaluator.__version__.replace(".", "-").replace("v", "")

    if "+" in version:
        version = "latest"

    # Create a new directory to run the current versions results in.
    os.makedirs(os.path.join(version, "results"))

    with temporarily_change_directory(version):

        with DaskLSFBackend(
                minimum_number_of_workers=1,
                maximum_number_of_workers=12,
                resources_per_worker=QueueWorkerResources(
                    number_of_gpus=1,
                    preferred_gpu_toolkit=QueueWorkerResources.GPUToolkit.CUDA,
                    per_thread_memory_limit=5 * unit.gigabyte,
                    wallclock_time_limit="05:59",
                ),
                setup_script_commands=[
                    f"conda activate openff-evaluator-{version}",
                    "module load cuda/10.0",
                ],
                queue_name="gpuqueue",
        ) as calculation_backend:

            with EvaluatorServer(
                    calculation_backend,
                    working_directory="outputs",
                    storage_backend=LocalFileStorage("cached-data"),
            ):

                client = EvaluatorClient()

                for allowed_layer in ["SimulationLayer", "ReweightingLayer"]:

                    data_set = define_data_set(
                        allowed_layer == "ReweightingLayer")

                    options = RequestOptions()
                    options.calculation_layers = [allowed_layer]
                    options.calculation_schemas = {
                        property_type: {}
                        for property_type in data_set.property_types
                    }

                    if allowed_layer == "SimulationLayer":

                        options.add_schema(
                            "SimulationLayer",
                            "SolvationFreeEnergy",
                            solvation_free_energy_schema(),
                        )

                    request, _ = client.request_estimate(
                        data_set,
                        ForceField("openff-1.2.0.offxml"),
                        options,
                        parameter_gradient_keys=[
                            ParameterGradientKey("vdW", smirks, attribute)
                            for smirks in [
                                "[#1:1]-[#6X4]",
                                "[#1:1]-[#6X4]-[#7,#8,#9,#16,#17,#35]",
                                "[#1:1]-[#8]",
                                "[#6X4:1]",
                                "[#8X2H1+0:1]",
                                "[#1]-[#8X2H2+0:1]-[#1]",
                            ] for attribute in ["epsilon", "rmin_half"]
                        ],
                    )

                    results, _ = request.results(synchronous=True,
                                                 polling_interval=60)
                    results.json(
                        os.path.join("results", f"{allowed_layer}.json"))