Skip to content

Commit c5c5292

Browse files
lilyminiumpre-commit-ci[bot]mattwthompson
authored
Fix relative tolerance (#604)
* add nobatch * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add fix * add helper test functions * add test data * Revert "add fix" This reverts commit 3d9f043. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Revert "Revert "add fix"" This reverts commit 79a8bea. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Matthew W. Thompson <mattwthompson@protonmail.com>
1 parent 6367937 commit c5c5292

File tree

87 files changed

+98986
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+98986
-1
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import pytest
2+
from openff.units import unit
3+
4+
from openff.evaluator.datasets import (
5+
MeasurementSource,
6+
PhysicalPropertyDataSet,
7+
PropertyPhase,
8+
)
9+
from openff.evaluator.properties import Density, EnthalpyOfMixing
10+
from openff.evaluator.substances import Substance
11+
from openff.evaluator.thermodynamics import ThermodynamicState
12+
13+
14+
@pytest.fixture
15+
def dummy_enthalpy_of_mixing():
16+
thermodynamic_state = ThermodynamicState(
17+
temperature=298.15 * unit.kelvin,
18+
pressure=101.325 * unit.kilopascal,
19+
)
20+
21+
return EnthalpyOfMixing(
22+
thermodynamic_state=thermodynamic_state,
23+
phase=PropertyPhase.Liquid,
24+
value=1.0 * EnthalpyOfMixing.default_unit(),
25+
uncertainty=1.0 * EnthalpyOfMixing.default_unit(),
26+
source=MeasurementSource(doi=" "),
27+
substance=Substance.from_components("CCCO", "O"),
28+
)
29+
30+
31+
@pytest.fixture
32+
def dummy_density():
33+
thermodynamic_state = ThermodynamicState(
34+
temperature=298.15 * unit.kelvin,
35+
pressure=101.325 * unit.kilopascal,
36+
)
37+
38+
return Density(
39+
thermodynamic_state=thermodynamic_state,
40+
phase=PropertyPhase.Liquid,
41+
value=1.0 * Density.default_unit(),
42+
uncertainty=1.0 * Density.default_unit(),
43+
source=MeasurementSource(doi=" "),
44+
substance=Substance.from_components("CCCO"),
45+
)
46+
47+
48+
@pytest.fixture
49+
def dummy_dataset(dummy_density, dummy_enthalpy_of_mixing):
50+
dataset = PhysicalPropertyDataSet()
51+
dataset.add_properties(dummy_density, dummy_enthalpy_of_mixing)
52+
53+
for i, prop in enumerate(dataset.properties):
54+
prop.id = f"{i}"
55+
return dataset
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import os
2+
import pathlib
3+
4+
import pytest
5+
6+
from openff.evaluator._tests.utils import (
7+
_copy_property_working_data,
8+
_write_force_field,
9+
)
10+
from openff.evaluator.backends.dask import DaskLocalCluster
11+
from openff.evaluator.client import BatchMode, EvaluatorClient, RequestOptions
12+
from openff.evaluator.forcefield import SmirnoffForceFieldSource
13+
from openff.evaluator.layers.simulation import SimulationLayer
14+
from openff.evaluator.properties import Density, EnthalpyOfMixing
15+
from openff.evaluator.server.server import EvaluatorServer
16+
from openff.evaluator.storage.localfile import LocalFileStorage
17+
from openff.evaluator.workflow import Workflow
18+
19+
20+
def _get_simulation_request_options(
21+
n_molecules: int = 256,
22+
):
23+
dhmix_schema = EnthalpyOfMixing.default_simulation_schema(
24+
n_molecules=n_molecules,
25+
relative_tolerance=0.2,
26+
)
27+
density_schema = Density.default_simulation_schema(
28+
n_molecules=n_molecules,
29+
relative_tolerance=0.2,
30+
)
31+
32+
options = RequestOptions()
33+
options.calculation_layers = ["SimulationLayer"]
34+
35+
options.add_schema(
36+
"SimulationLayer",
37+
"Density",
38+
density_schema,
39+
)
40+
options.add_schema(
41+
"SimulationLayer",
42+
"EnthalpyOfMixing",
43+
dhmix_schema,
44+
)
45+
return options
46+
47+
48+
class TestSimulationLayer:
49+
50+
@pytest.fixture
51+
def dhmix_density_CCCO(self, tmp_path_factory):
52+
path = tmp_path_factory.mktemp("dhmix-density-CCCO")
53+
path.mkdir(exist_ok=True, parents=True)
54+
55+
_copy_property_working_data(
56+
"test/workflows/simulation/dhmix-density-CCCO",
57+
uuid_prefix=["0", "1"],
58+
destination_directory=path,
59+
)
60+
return path
61+
62+
def test_simulation(self, dummy_enthalpy_of_mixing, dhmix_density_CCCO):
63+
"""
64+
Test direct execution of an EnthalpyOfMixing protocol
65+
"""
66+
os.chdir(dhmix_density_CCCO)
67+
68+
_write_force_field()
69+
schema = EnthalpyOfMixing.default_simulation_schema(
70+
n_molecules=256,
71+
relative_tolerance=0.2,
72+
)
73+
74+
metadata = Workflow.generate_default_metadata(
75+
dummy_enthalpy_of_mixing, "force-field.json"
76+
)
77+
metadata.update(
78+
SimulationLayer._get_workflow_metadata(
79+
".",
80+
dummy_enthalpy_of_mixing,
81+
"force-field.json",
82+
[],
83+
LocalFileStorage(),
84+
schema,
85+
)
86+
)
87+
88+
workflow_schema = schema.workflow_schema
89+
workflow_schema.replace_protocol_types(
90+
{"BaseBuildSystem": "BuildSmirnoffSystem"}
91+
)
92+
uuid_prefix = "1"
93+
workflow = Workflow.from_schema(
94+
workflow_schema, metadata=metadata, unique_id=uuid_prefix
95+
)
96+
workflow_graph = workflow.to_graph()
97+
protocol_graph = workflow_graph._protocol_graph
98+
99+
previous_output_paths = []
100+
101+
for name, protocol in workflow_graph.protocols.items():
102+
path = name.replace("|", "_")
103+
if "conditional" not in name:
104+
output = protocol_graph._execute_protocol(
105+
path,
106+
protocol,
107+
True,
108+
*previous_output_paths,
109+
available_resources=None,
110+
safe_exceptions=False,
111+
)
112+
previous_output_paths.append(output)
113+
114+
# delete existing output so we can re-create it
115+
pattern = "*conditional*/*conditional*output.json"
116+
for file in pathlib.Path(".").rglob(pattern):
117+
file.unlink()
118+
119+
for name, protocol in workflow_graph.protocols.items():
120+
path = name.replace("|", "_")
121+
if "conditional" in name:
122+
output = protocol_graph._execute_protocol(
123+
path,
124+
protocol,
125+
True,
126+
*previous_output_paths,
127+
available_resources=None,
128+
safe_exceptions=False,
129+
)
130+
previous_output_paths.append(output)
131+
132+
def test_simulation_with_server(self, dummy_dataset, dhmix_density_CCCO):
133+
"""
134+
Test the full workflow with a server
135+
"""
136+
force_field_path = "openff-2.1.0.offxml"
137+
force_field_source = SmirnoffForceFieldSource.from_path(force_field_path)
138+
139+
os.chdir(dhmix_density_CCCO)
140+
options = _get_simulation_request_options(
141+
n_molecules=256,
142+
)
143+
options.batch_mode = BatchMode.NoBatch
144+
145+
batch_path = pathlib.Path("SimulationLayer") / "batch_0000"
146+
batch_path.mkdir(parents=True, exist_ok=True)
147+
_copy_property_working_data(
148+
"test/workflows/simulation/dhmix-density-CCCO",
149+
uuid_prefix="0",
150+
destination_directory=batch_path,
151+
)
152+
_copy_property_working_data(
153+
"test/workflows/simulation/dhmix-density-CCCO",
154+
uuid_prefix="1",
155+
destination_directory=batch_path,
156+
)
157+
158+
with DaskLocalCluster(number_of_workers=1) as calculation_backend:
159+
server = EvaluatorServer(
160+
calculation_backend=calculation_backend,
161+
working_directory=".",
162+
delete_working_files=False,
163+
)
164+
with server:
165+
client = EvaluatorClient()
166+
request, error = client.request_estimate(
167+
dummy_dataset, force_field_source, options
168+
)
169+
170+
assert error is None
171+
results, exception = request.results(
172+
synchronous=True, polling_interval=30
173+
)
174+
175+
assert exception is None
176+
assert len(results.queued_properties) == 0
177+
assert len(results.estimated_properties) == 2
178+
assert len(results.unsuccessful_properties) == 0
179+
assert len(results.exceptions) == 0

openff/evaluator/_tests/utils.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import os
2+
import pathlib
3+
import shutil
24
import uuid
35
from contextlib import contextmanager
46

7+
from openff.toolkit import ForceField
58
from openff.units import unit
9+
from openff.utilities.utilities import get_data_dir_path
610

711
from openff.evaluator.datasets import (
812
CalculationSource,
@@ -238,3 +242,56 @@ def build_tip3p_smirnoff_force_field():
238242
)
239243

240244
return SmirnoffForceFieldSource.from_object(smirnoff_force_field_with_tip3p)
245+
246+
247+
def _write_force_field(force_field: str = "openff-2.0.0.offxml"):
248+
"""
249+
Write a force field file to disk.
250+
"""
251+
ff = ForceField(force_field)
252+
with open("force-field.json", "w") as file:
253+
file.write(SmirnoffForceFieldSource.from_object(ff).json())
254+
255+
256+
def _copy_property_working_data(
257+
source_directory: str,
258+
uuid_prefix: str | list[str],
259+
destination_directory: str = ".",
260+
include_data_files: bool = False,
261+
):
262+
"""
263+
Copy test data from one directory to another.
264+
This is typically working data in a *Layer directory.
265+
266+
Parameters
267+
----------
268+
source_directory : str
269+
The directory to copy data from.
270+
This should be relative to to the data directory.
271+
For tests, the path will typically start with `test`.
272+
uuid_prefix : str or list[str]
273+
The prefix of the UUID/s to copy.
274+
destination_directory : str
275+
The directory to copy data to.
276+
This should be relative to the current working directory.
277+
Default is the current working directory.
278+
"""
279+
if isinstance(uuid_prefix, str):
280+
uuid_prefix = [uuid_prefix]
281+
282+
# locate our saved test data
283+
data_directory = pathlib.Path(
284+
get_data_dir_path(source_directory, "openff.evaluator")
285+
)
286+
abs_path = data_directory.resolve()
287+
destination_directory = pathlib.Path(destination_directory)
288+
289+
# copy data files over from data_directory
290+
for path in abs_path.iterdir():
291+
if path.name.startswith(tuple(uuid_prefix)) and path.is_dir():
292+
dest_dir = destination_directory / path.name
293+
shutil.copytree(path, dest_dir)
294+
295+
if include_data_files:
296+
for path in abs_path.glob("data*.json"):
297+
shutil.copy(path, destination_directory)

openff/evaluator/data/test/workflows/simulation/dhmix-density-CCCO/0_decorrelate_observables/0|decorrelate_observables_output.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{".output_trajectory_path": "0_decorrelate_trajectory/uncorrelated_trajectory.dcd"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{".result": {"value": {"value": {"value": -4.5574468423925065, "unit": "kilojoule / mole", "@type": "openff.evaluator.unit.Quantity"}, "error": {"value": 0.029844082370627523, "unit": "kilojoule / mole", "@type": "openff.evaluator.unit.Quantity"}, "@type": "openff.evaluator.unit.Measurement"}, "gradients": [], "@type": "openff.evaluator.utils.observables.Observable"}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{".output_number_of_molecules": 256, ".output_substance": {"components": [{"smiles": "CCCO", "role": {"value": "solv", "@type": "openff.evaluator.substances.components.Component.Role"}, "@type": "openff.evaluator.substances.components.Component"}], "amounts": {"CCCO{solv}": [{"value": 1.0, "@type": "openff.evaluator.substances.amounts.MoleFraction"}]}, "@type": "openff.evaluator.substances.substances.Substance"}, ".assigned_residue_names": {"CCCO{solv}": "UFX"}, ".coordinate_file_path": "1_build_coordinates_component_0/output.pdb"}

0 commit comments

Comments
 (0)