-
Notifications
You must be signed in to change notification settings - Fork 18
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
Add support for None bond anchors #1510
base: master
Are you sure you want to change the base?
Conversation
assert get_num_connected_components(num_atoms, bond_potential.potential.idxs) == 1, ( | ||
"hybrid molecule has multiple connected components" | ||
) | ||
if get_num_connected_components(num_atoms, bond_potential.potential.idxs) > 1: | ||
warnings.warn("Hybrid molecule has multiple connected components") |
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.
Is there some weaker property than num_connected_components == 1
we should still assert here?
I don't know what the correct behavior is, but maybe something like "for every connected_component
in the input topology, assert that num_connected_components(attach_dumy_atoms(connected_component)) == 1
"
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.
I thought about this a bit... and I'm still not sure how to implement something like attach_dumy_atoms
at the "generic" level if we need to support AHFE. I think having something like this being re-implemented later on specifically for RBFE/RHFE code-paths would make a lot of sense
tests/test_dummy.py
Outdated
assert len(anchored_dummy_group_assignments) == 1 | ||
assert anchored_dummy_group_assignments[0] == {1: (2, frozenset({0}))} | ||
|
||
# Test that providing an empty core results in None values for both the bond anchor |
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.
Core is not empty here?
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.
fixed - meant to say:
# Test that providing an empty core and a None bond anchor results in a None angle anchor
anchored_dummy_group_assignments = generate_anchored_dummy_group_assignments(
{None: {0, 1, 2, 3, 4}}, g_a, g_b, [], []
)
(sorry, accidentally hit the "Close with comment" button and reopened) |
core_bonds_c = get_core_bonds(bond_graph_a.edges(), bond_graph_b.edges(), core_atoms_a, core_atoms_b) | ||
c_to_b = {c: b for c, b in enumerate(core_atoms_b)} | ||
core_bonds_b = frozenset(translate_bonds(core_bonds_c, c_to_b)) | ||
|
||
def get_angle_anchors(bond_anchor): | ||
if bond_anchor is None: |
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.
Should the type of the dummy_groups
argument to this function be updated to dict[Optional[int], frozenset[int]]
?
Will the restriction of this representation to only one unanchored dummy group be an issue in practice?
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.
Will the restriction of this representation to only one unanchored dummy group be an issue in practice?
- Multiple disconnected dummy groups (and their atoms) will all be assigned to the "None" dummy group.
def test_multiple_none_dummy_groups():
# Test that if we break a core-core bond, we only have one valid
# choice of the angle anchor
#
# mol_a: H-O-H.H-O-H
# mol_b: H-O-H.H-O-H
g_a = convert_to_nx(Chem.AddHs(Chem.MolFromSmiles("O.O")))
core_a = []
dgas = list(generate_dummy_group_assignments(g_a, core_a))
expected_dga = {None: {0,1,2,3,4,5}}
assert equivalent_assignment(dgas, [expected_dga])
- As a result, their dummy angle anchors will also be assigned to the "None" angle anchor:
def test_multiple_none_dummy_groups():
# Test that if we break a core-core bond, we only have one valid
# choice of the angle anchor
#
# mol_a: H-O-H.H-O-H
# mol_b: H-O-H.H-O-H
g_a = convert_to_nx(Chem.AddHs(Chem.MolFromSmiles("O.O")))
g_b = convert_to_nx(Chem.AddHs(Chem.MolFromSmiles("O.O")))
core_a = []
core_b = []
# Test that providing an empty core and a None bond anchor results in a None angle anchor
anchored_dummy_group_assignments = generate_anchored_dummy_group_assignments(
{None: {0, 1, 2, 3, 4, 5}}, g_a, g_b, core_a, core_b
)
anchored_dummy_group_assignments = list(anchored_dummy_group_assignments)
assert anchored_dummy_group_assignments == [{None: (None, frozenset({0, 1, 2, 3, 4, 5}))}]
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.
Added a test in ade39b7
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.
Multiple disconnected dummy groups (and their atoms) will all be assigned to the "None" dummy group.
Got it. I wanted to be sure that the apparent behavior of unioning into a single group dummy groups that are disconnected (both from the core and each other) was intentional (versus using a different representation that preserves information about them being disconnected; I don't think I'm totally clear on the use case for this, so this may not be a useful comment)
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.
Should the type of the dummy_groups argument to this function be updated to dict[Optional[int], frozenset[int]]?
IIUC, both the type of the dummy_groups
argument and the return type of generate_anchored_dummy_group_assignments
should be updated to indicate that the bond anchor might be None
.
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.
Yep, updated in 727b151
timemachine/fe/dummy.py
Outdated
pass | ||
|
||
|
||
class ZeroBondAnchorWarning(UserWarning): | ||
pass | ||
|
||
|
||
def generate_dummy_group_assignments( | ||
bond_graph: nx.Graph, core_atoms: Collection[int] | ||
) -> Iterator[dict[int, frozenset[int]]]: |
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.
Should this be updated to
) -> Iterator[dict[int, frozenset[int]]]: | |
) -> Iterator[dict[Optional[int], frozenset[int]]]: |
?
Is there an issue if there are multiple unanchored dummy groups? The current behavior seems to be to keep only the last one(?)
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.
Hm I haven't actually thought about how multiple unanchored dummy groups would work in practice - e.g. if we're decoupling 2 separate molecules from the environment, both of which have no anchors?
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.
(See above comment for intended behavior)
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.
I think the return type should still be updated, though? (To reflect that the anchor can be None
; I'm not sure why mypy doesn't complain about this)
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.
Updated type signatures in 727b151
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.
(Looks like the type of find_dummy_groups_and_anchors
also needs to be updated)
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.
Thanks - looks like I may have a mypy version mismatch issue locally again (since it passed the local git precommit).
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.
Might be because the pre-commit git hook only runs on files in the diff by default, while in CI we run with the --all-files
flag
Add test for single topology end-states WIP Add a test for multiple disconnected components. Fix typo in kwarg.
if assert_single_connected_component: | ||
assert len(list(nx.connected_components(bond_graph))) == 1 |
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/optional: noting that this change to an optional assertion reduces the power of static checking to catch errors somewhat, because now the bond anchor is optional in the return type even if we pass assert_single_connected_component=True
(and thus downstream code not intended to handle empty cores or disconnected graphs must assert bond_anchor is not None
).
One way around this could be to instead define a variant generate_dummy_group_assignments_connected
that calls this function with assert_single_connected_component=True
and returns the old type (i.e. asserting in the types that the dummy anchor is not null). Another option could be to use typing.overload
.
(Non-blocking, OK to clean this up later.)
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.
This is temporary - it will be non-optional (i.e. the assertion will default to False after the refactoring is done), and the argument will be removed.
This PR adds support for handling dummy groups that are completely unanchored (i.e. devoid of bond anchors). The previous behavior was to omit the unanchored dummy groups entirely from the output of
generate_dummy_group_assignments
.