-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds YAML input to CLI planning tools (#583)
* [skip ci] draft of yaml format for CI * adds yaml reader for cli options input fixes #580 * change plan_rXfe_network_main functions to expect list of mappers * move settings yaml parsing to be parameter * add YAML_OPTIONS as parameter * add YAML_OPTIONS to plan_rbfe_network currently does nothing * cli: add interpretation of settings yaml * cli: enable settings yaml reading in plan_rbfe_network * cli: some extra documentation on load_yaml * cli: fix tests to account for normalisation * cli: add test for custom rbfe yaml usage * cli: update plan_rhfe to allow yaml options * cli: use v1/2 compatible Pydantic * cli: fixup pydantic v1/2 compat * remove draft yaml idea * cli: centralise parsing of yaml options and defaults * add pyyaml to deps --------- Co-authored-by: Irfan Alibay <[email protected]>
- Loading branch information
1 parent
94929f5
commit c96cb57
Showing
8 changed files
with
355 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
# This code is part of OpenFE and is licensed under the MIT license. | ||
# For details, see https://github.com/OpenFreeEnergy/openfe | ||
"""Pydantic models for the definition of advanced CLI options | ||
""" | ||
import click | ||
from collections import namedtuple | ||
try: | ||
# todo; once we're fully v2, we can use ConfigDict not nested class | ||
from pydantic.v1 import BaseModel # , ConfigDict | ||
except ImportError: | ||
from pydantic import BaseModel | ||
from plugcli.params import Option | ||
from typing import Any, Optional | ||
import yaml | ||
import warnings | ||
|
||
|
||
PlanNetworkOptions = namedtuple('PlanNetworkOptions', | ||
['mapper', 'scorer', | ||
'ligand_network_planner', 'solvent']) | ||
|
||
|
||
class MapperSelection(BaseModel): | ||
# model_config = ConfigDict(extra='allow', str_to_lower=True) | ||
class Config: | ||
extra = 'allow' | ||
anystr_lower = True | ||
|
||
method: Optional[str] = None | ||
settings: dict[str, Any] = {} | ||
|
||
|
||
class NetworkSelection(BaseModel): | ||
# model_config = ConfigDict(extra='allow', str_to_lower=True) | ||
class Config: | ||
extra = 'allow' | ||
anystr_lower = True | ||
|
||
method: Optional[str] = None | ||
settings: dict[str, Any] = {} | ||
|
||
|
||
class CliYaml(BaseModel): | ||
# model_config = ConfigDict(extra='allow') | ||
class Config: | ||
extra = 'allow' | ||
|
||
mapper: Optional[MapperSelection] = None | ||
network: Optional[NetworkSelection] = None | ||
|
||
|
||
def parse_yaml_planner_options(contents: str) -> CliYaml: | ||
"""Parse and minimally validate a user provided yaml | ||
Parameters | ||
---------- | ||
contents : str | ||
raw yaml formatted input to parse | ||
Returns | ||
------- | ||
options : CliOptions | ||
will have keys for mapper and network topology choices | ||
Raises | ||
------ | ||
ValueError | ||
for any malformed inputs | ||
""" | ||
raw = yaml.safe_load(contents) | ||
|
||
if False: | ||
# todo: warnings about extra fields we don't expect? | ||
expected = {'mapper', 'network'} | ||
for field in raw: | ||
if field in expected: | ||
continue | ||
warnings.warn(f"Ignoring unexpected section: '{field}'") | ||
|
||
return CliYaml(**raw) | ||
|
||
|
||
def load_yaml_planner_options(path: Optional[str], context) -> PlanNetworkOptions: | ||
"""Load cli options from yaml file path and resolve these to objects | ||
Parameters | ||
---------- | ||
path : str | ||
path to the yaml file | ||
context | ||
unused | ||
Returns | ||
------- | ||
PlanNetworkOptions : namedtuple | ||
a namedtuple with fields 'mapper', 'scorer', 'network_planning_algorithm', | ||
and 'solvent' fields. | ||
these fields each hold appropriate objects ready for use | ||
""" | ||
from gufe import SolventComponent | ||
from openfe.setup.ligand_network_planning import ( | ||
generate_radial_network, | ||
generate_minimal_spanning_network, | ||
generate_maximal_network, | ||
generate_minimal_redundant_network, | ||
) | ||
from openfe.setup import ( | ||
LomapAtomMapper, | ||
) | ||
from openfe.setup.atom_mapping.lomap_scorers import ( | ||
default_lomap_score, | ||
) | ||
from functools import partial | ||
|
||
if path is not None: | ||
with open(path, 'r') as f: | ||
raw = f.read() | ||
|
||
# convert raw yaml to normalised pydantic model | ||
opt = parse_yaml_planner_options(raw) | ||
else: | ||
opt = None | ||
|
||
# convert normalised inputs to objects | ||
if opt and opt.mapper: | ||
mapper_choices = { | ||
'lomap': LomapAtomMapper, | ||
'lomapatommapper': LomapAtomMapper, | ||
} | ||
|
||
try: | ||
cls = mapper_choices[opt.mapper.method] | ||
except KeyError: | ||
raise KeyError(f"Bad mapper choice: '{opt.mapper.method}'") | ||
mapper_obj = cls(**opt.mapper.settings) | ||
else: | ||
mapper_obj = LomapAtomMapper(time=20, threed=True, element_change=False, | ||
max3d=1) | ||
|
||
# todo: choice of scorer goes here | ||
mapping_scorer = default_lomap_score | ||
|
||
if opt and opt.network: | ||
network_choices = { | ||
'generate_radial_network': generate_radial_network, | ||
'radial': generate_radial_network, | ||
'generate_minimal_spanning_network': generate_minimal_spanning_network, | ||
'mst': generate_minimal_spanning_network, | ||
'generate_minimal_redundant_network': generate_minimal_redundant_network, | ||
'generate_maximal_network': generate_maximal_network, | ||
} | ||
|
||
try: | ||
func = network_choices[opt.network.method] | ||
except KeyError: | ||
raise KeyError(f"Bad network algorithm choice: '{opt.network.method}'") | ||
|
||
ligand_network_planner = partial(func, **opt.network.settings) | ||
else: | ||
ligand_network_planner = generate_minimal_spanning_network | ||
|
||
# todo: choice of solvent goes here | ||
solvent = SolventComponent() | ||
|
||
return PlanNetworkOptions( | ||
mapper_obj, | ||
mapping_scorer, | ||
ligand_network_planner, | ||
solvent, | ||
) | ||
|
||
|
||
YAML_OPTIONS = Option( | ||
'-s', "--settings", "yaml_settings", | ||
type=click.Path(exists=True, dir_okay=False), | ||
help="Path to planning settings yaml file.", | ||
getter=load_yaml_planner_options, | ||
) |
Oops, something went wrong.