-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add a combine force field CLI input * update docs * fix docs
- Loading branch information
Showing
4 changed files
with
186 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import copy | ||
from typing import List, Optional | ||
|
||
import click | ||
import rich | ||
from openff.toolkit.typing.engines.smirnoff import ForceField, ParameterLookupError | ||
from rich import pretty | ||
from rich.padding import Padding | ||
|
||
from openff.bespokefit.cli.utilities import exit_with_messages, print_header | ||
from openff.bespokefit.executor.utilities import handle_common_errors | ||
|
||
|
||
@click.command("combine") | ||
@click.option( | ||
"--output", | ||
"output_file", | ||
type=click.STRING, | ||
help="The name of the file the combined force field should be wrote to.", | ||
required=True, | ||
) | ||
@click.option( | ||
"--ff", | ||
"force_field_files", | ||
type=click.Path(exists=True, dir_okay=False), | ||
help="The file name of any local force fields to include in the combined force field.", | ||
required=False, | ||
multiple=True, | ||
) | ||
@click.option( | ||
"--id", | ||
"task_ids", | ||
type=click.STRING, | ||
help="The task ids from which the final force field should be added to the combined force field.", | ||
required=False, | ||
multiple=True, | ||
) | ||
def combine_cli( | ||
output_file: str, | ||
force_field_files: Optional[List[str]], | ||
task_ids: Optional[List[str]], | ||
): | ||
""" | ||
Combine force fields from local files and task ids. | ||
""" | ||
pretty.install() | ||
|
||
console = rich.get_console() | ||
print_header(console) | ||
|
||
if not force_field_files and not task_ids: | ||
|
||
exit_with_messages( | ||
"[[red]ERROR[/red]] At least one of the `--ff` or `--id` should be specified", | ||
console=console, | ||
exit_code=2, | ||
) | ||
|
||
all_force_fields = [] | ||
|
||
if force_field_files: | ||
all_force_fields.extend( | ||
[ | ||
ForceField( | ||
force_field, load_plugins=True, allow_cosmetic_attributes=True | ||
) | ||
for force_field in force_field_files | ||
] | ||
) | ||
|
||
if task_ids: | ||
from openff.bespokefit.executor import BespokeExecutor | ||
|
||
with handle_common_errors(console) as error_state: | ||
results = [BespokeExecutor.retrieve(task_id) for task_id in task_ids] | ||
if error_state["has_errored"]: | ||
raise click.exceptions.Exit(code=2) | ||
|
||
all_force_fields.extend([result.bespoke_force_field for result in results]) | ||
|
||
# Now combine all unique torsions | ||
master_ff = copy.deepcopy(all_force_fields[0]) | ||
for ff in all_force_fields[1:]: | ||
for parameter in ff.get_parameter_handler("ProperTorsions").parameters: | ||
try: | ||
_ = master_ff.get_parameter_handler("ProperTorsions")[parameter.smirks] | ||
except ParameterLookupError: | ||
master_ff.get_parameter_handler("ProperTorsions").add_parameter( | ||
parameter=parameter | ||
) | ||
|
||
master_ff.to_file(filename=output_file, discard_cosmetic_attributes=True) | ||
|
||
message = Padding( | ||
f"The combined force field has been saved to [repr.filename]{output_file}[/repr.filename]", | ||
(1, 0, 1, 0), | ||
) | ||
console.print(message) |
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,72 @@ | ||
import os | ||
|
||
import requests_mock | ||
from openff.toolkit.typing.engines.smirnoff import ForceField | ||
|
||
from openff.bespokefit.cli.combine import combine_cli | ||
from openff.bespokefit.executor.services import current_settings | ||
from openff.bespokefit.executor.services.coordinator.models import ( | ||
CoordinatorGETResponse, | ||
CoordinatorGETStageStatus, | ||
) | ||
|
||
|
||
def test_combine_no_args(runner): | ||
""" | ||
Make sure an error is raised when we supply no args | ||
""" | ||
|
||
output = runner.invoke(combine_cli, args=["--output", "my_ff.offxml"]) | ||
|
||
assert output.exit_code == 2 | ||
assert "At least one of the" in output.stdout | ||
|
||
|
||
def test_combine_local_and_tasks(tmpdir, runner, bespoke_optimization_results): | ||
""" | ||
Make sure local force field files can be combined with task force fields | ||
""" | ||
|
||
# make some local files to work with | ||
for ff_name in ["openff-1.0.0.offxml", "openff-2.0.0.offxml"]: | ||
ForceField(ff_name).to_file(ff_name) | ||
|
||
settings = current_settings() | ||
|
||
with requests_mock.Mocker() as m: | ||
mock_href = ( | ||
f"http://127.0.0.1:" | ||
f"{settings.BEFLOW_GATEWAY_PORT}" | ||
f"{settings.BEFLOW_API_V1_STR}/" | ||
f"{settings.BEFLOW_COORDINATOR_PREFIX}/1" | ||
) | ||
mock_response = CoordinatorGETResponse( | ||
id="1", | ||
self="", | ||
smiles="CC", | ||
stages=[ | ||
CoordinatorGETStageStatus( | ||
type="optimization", status="success", error=None, results=None | ||
) | ||
], | ||
results=bespoke_optimization_results, | ||
) | ||
m.get(mock_href, text=mock_response.json(by_alias=True)) | ||
|
||
output = runner.invoke( | ||
combine_cli, | ||
args=[ | ||
"--output", | ||
"my_ff.offxml", | ||
"--ff", | ||
"openff-2.0.0.offxml", | ||
"--ff", | ||
"openff-1.0.0.offxml", | ||
"--id", | ||
"1", | ||
], | ||
) | ||
|
||
assert os.path.isfile("my_ff.offxml") | ||
assert output.exit_code == 0 | ||
assert "The combined force field has been saved to" in output.stdout |