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)
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
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, )
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
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())
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
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"))