-
Notifications
You must be signed in to change notification settings - Fork 2
Feature openmm torsion restraints #237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0820d49
cb15779
d9ed802
0b5cd79
6d263c4
0c443c1
c8404bb
dbc1589
652e98c
4b59a49
e154cb1
18c7c6d
4d0e869
fad9cf4
77a12c1
ac554fd
a182f87
9881a24
50b9d94
51271c4
be89d20
39f8200
5524d71
c0c07c1
e6c7b4b
6c4af15
844f070
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,77 @@ | ||
| import functools | ||
| import logging | ||
| import re | ||
|
|
||
| import openmm | ||
| from openff.toolkit import Molecule | ||
| from openff.toolkit import ForceField, Molecule | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| def _smirnoff(molecule: Molecule, force_field_path: str) -> openmm.System: | ||
| from openff.toolkit import ForceField | ||
|
|
||
| smirnoff_force_field = ForceField( | ||
| force_field_path, | ||
| load_plugins=True, | ||
| def _shorthand_to_full_force_field_name( | ||
| shorthand: str, | ||
| make_unconstrained: bool = True, | ||
| ) -> str: | ||
| """Make i.e. `openff-2.1.0` into `openff_unconstrained-2.1.0.offxml`.""" | ||
| if make_unconstrained: | ||
| # Split on '-' immediately followed by a number; | ||
| # cannot split on '-' because of i.e. 'de-force-1.0.0' | ||
| prefix, version, _ = re.split(r"-([\d.]+)", shorthand, maxsplit=1) | ||
|
|
||
| return f"{prefix}_unconstrained-{version}.offxml" | ||
| else: | ||
| return shorthand + ".offxml" | ||
|
|
||
|
|
||
| @functools.lru_cache(maxsize=1) | ||
| def _lazy_load_force_field(force_field_name: str) -> ForceField: | ||
| """Attempt to load a force field from a shorthand string or a file path. | ||
|
|
||
| Caching is used to speed up loading; a single force field takes O(100 ms) to | ||
| load, but the cache takes O(10 ns) to access. The cache key is simply the | ||
| argument passed to this function; a hash collision should only occur when | ||
| two identical strings are expected to return different force fields, which | ||
| seems like an assumption that the toolkit has always made anyway. | ||
| """ | ||
| if not force_field_name.endswith(".offxml"): | ||
| force_field_name = _shorthand_to_full_force_field_name( | ||
| force_field_name, | ||
| make_unconstrained=True, | ||
| ) | ||
|
|
||
| return ForceField( | ||
| force_field_name, | ||
| allow_cosmetic_attributes=True, | ||
| load_plugins=True, | ||
| ) | ||
|
|
||
| if "Constraints" in smirnoff_force_field.registered_parameter_handlers: | ||
| smirnoff_force_field.deregister_parameter_handler("Constraints") | ||
|
|
||
| return smirnoff_force_field.create_openmm_system(molecule.to_topology()) | ||
| def _smirnoff(molecule: Molecule, force_field_path: str) -> openmm.System: | ||
| from openff.toolkit import ForceField | ||
|
|
||
| try: | ||
| force_field = _lazy_load_force_field(force_field_path) | ||
| except KeyError: | ||
| # Attempt to load from local path | ||
| try: | ||
| force_field = ForceField( | ||
| force_field_path, | ||
| allow_cosmetic_attributes=True, | ||
| load_plugins=True, | ||
| ) | ||
| except Exception as error: | ||
| # The toolkit does a poor job of distinguishing between a string | ||
| # argument being a file that does not exist and a file that it should | ||
| # try to parse (polymorphic input), so just have to clobber whatever | ||
|
Comment on lines
+63
to
+65
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 For this project, I think making an effort to parse OFFXML as raw strings is a net negative. Hopefully we continue to use only files |
||
| raise NotImplementedError( | ||
| f"Could not find or parse force field {force_field_path}", | ||
| ) from error | ||
|
|
||
| if "Constraints" in force_field.registered_parameter_handlers: | ||
| logger.info("Deregistering Constraints handler from SMIRNOFF force field") | ||
| force_field.deregister_parameter_handler("Constraints") | ||
|
Comment on lines
+70
to
+72
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should document this (I forgot this was the default beavior) |
||
|
|
||
| return force_field.create_openmm_system(molecule.to_topology()) | ||
|
|
||
|
|
||
| def _gaff(molecule: Molecule, force_field_name: str) -> openmm.System: | ||
|
|
@@ -62,3 +119,18 @@ def _espaloma(molecule: Molecule, force_field_name: str) -> openmm.System: | |
| model(mol_graph.heterograph) | ||
|
|
||
| return espaloma.graphs.deploy.openmm_system_from_graph(mol_graph, forcefield=ff[0]) | ||
|
|
||
|
|
||
| NON_SMIRNOFF_SYSTEM_BUILDERS = { | ||
| "gaff": _gaff, | ||
| "espaloma": _espaloma, | ||
| } | ||
|
|
||
|
|
||
| def build_omm_system(force_field: str, molecule: Molecule) -> openmm.System: | ||
| """Get an OpenMM System for a given force field and molecule.""" | ||
| for prefix, builder in NON_SMIRNOFF_SYSTEM_BUILDERS.items(): | ||
| if force_field.startswith(prefix): | ||
| return builder(molecule, force_field) | ||
|
|
||
| return _smirnoff(molecule, force_field) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: probably not worth lazy-loading here