Skip to content

Commit

Permalink
Merge pull request #189 from openforcefield/issue-188
Browse files Browse the repository at this point in the history
Added validation guardrail for AlchemicalNetworks with self-Transformations
  • Loading branch information
dotsdl authored Oct 6, 2023
2 parents b5a4e58 + 0c73cd8 commit 8f2b77e
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 1 deletion.
9 changes: 8 additions & 1 deletion alchemiscale/interface/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,14 @@ def create_network(
validate_scopes(scope, token)

an = AlchemicalNetwork.from_dict(network)
an_sk = n4js.create_network(network=an, scope=scope)

try:
an_sk = n4js.create_network(network=an, scope=scope)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=e.args[0],
)

# create taskhub for this network
n4js.create_taskhub(an_sk)
Expand Down
3 changes: 3 additions & 0 deletions alchemiscale/interface/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from ..storage.models import Task, ProtocolDAGResultRef, TaskStatusEnum
from ..strategies import Strategy
from ..security.models import CredentialedUserIdentity
from ..validators import validate_network_nonself


class AlchemiscaleClientError(AlchemiscaleBaseClientError):
Expand Down Expand Up @@ -116,6 +117,8 @@ def create_network(
f"`scope` '{scope}' contains wildcards ('*'); `scope` must be *specific*"
)

validate_network_nonself(network)

from rich.progress import Progress

sk = self.get_scoped_key(network, scope)
Expand Down
2 changes: 2 additions & 0 deletions alchemiscale/storage/statestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

from ..security.models import CredentialedEntity
from ..settings import Neo4jStoreSettings, get_neo4jstore_settings
from ..validators import validate_network_nonself


@lru_cache()
Expand Down Expand Up @@ -610,6 +611,7 @@ def create_network(self, network: AlchemicalNetwork, scope: Scope):
some of its components already exist in the database.
"""
validate_network_nonself(network)

ndict = network.to_shallow_dict()

Expand Down
64 changes: 64 additions & 0 deletions alchemiscale/tests/unit/test_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pytest

from openfe_benchmarks import tyk2
from gufe import ChemicalSystem, Transformation, AlchemicalNetwork
from gufe.tests.test_protocol import DummyProtocol, BrokenProtocol

from alchemiscale import validators


@pytest.fixture(scope="session")
def network_self_transformation():
tyk2s = tyk2.get_system()
ligand = tyk2s.ligand_components[0]

cs = ChemicalSystem(
components={"ligand": ligand, "solvent": tyk2s.solvent_component},
name=f"{ligand.name}_water",
)

tf = Transformation(
stateA=cs,
stateB=cs,
protocol=DummyProtocol(settings=DummyProtocol.default_settings()),
name=f"{ligand.name}->{ligand.name}_water",
)

return AlchemicalNetwork(edges=[tf], name="self_transformation")


@pytest.fixture(scope="session")
def network_nonself_transformation():
tyk2s = tyk2.get_system()
ligand = tyk2s.ligand_components[0]
ligand2 = tyk2s.ligand_components[1]

cs = ChemicalSystem(
components={"ligand": ligand, "solvent": tyk2s.solvent_component},
name=f"{ligand.name}_water",
)

cs2 = ChemicalSystem(
components={"ligand": ligand2, "solvent": tyk2s.solvent_component},
name=f"{ligand2.name}_water",
)

tf = Transformation(
stateA=cs,
stateB=cs2,
protocol=DummyProtocol(settings=DummyProtocol.default_settings()),
name=f"{ligand.name}->{ligand2.name}_water",
)

return AlchemicalNetwork(edges=[tf], name="nonself_transformation")


def test_validate_network_nonself(
network_self_transformation, network_nonself_transformation
):
with pytest.raises(ValueError, match="uses the same `ChemicalSystem`"):
validators.validate_network_nonself(network_self_transformation)

out = validators.validate_network_nonself(network_nonself_transformation)

assert out is None
22 changes: 22 additions & 0 deletions alchemiscale/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
:mod:`alchemiscale.validators` --- validation guardrails for user input
=======================================================================
"""

from gufe import AlchemicalNetwork, Transformation


def validate_network_nonself(network: AlchemicalNetwork):
"""Check that the given AlchemicalNetwork features no Transformations with
the same ChemicalSystem for its two states.
A ``ValueError`` is raised if a `Transformation` is detected.
"""
for transformation in network.edges:
if transformation.stateA == transformation.stateB:
raise ValueError(
f"`Transformation` '{transformation.key}' uses the same `ChemicalSystem` '{transformation.stateA.key}' for both states; "
"this is currently not supported in `alchemiscale`"
)

0 comments on commit 8f2b77e

Please sign in to comment.