Skip to content

Added simple rounding error solution #1167

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

pbuslaev
Copy link
Contributor

Description

This is a proposed solution for #1166 on the GROMACS writer side.

If this approach is approved I can add tests/parameters

Checklist

  • Add tests
  • Lint
  • Update docstrings

Copy link
Member

@j-wags j-wags left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some questions about the implementation here. I think also it could be commented a bit better - it took me a while to grok why some intermediates were getting rounded and others not just by reading the code. This new behavior should also be described in the docs.

Comment on lines +232 to +233
def _rounding_error(_arr, _sum, _tolerance):
return numpy.round(numpy.sum(_arr) - _sum, _tolerance)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not blocking) My external perspective here is that needing to come back to this definition repeatedly as this is called in multiple places decreases readability substantially, and that the line length is about the same if numpy.round were called directly on each line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the point, but when initially I coded it without a function the code looked to heavy as for my liking. With the function it looked tidier, so I opted for having this function. I don't have any strong opinion here, so I am happy to get rid of the function if this is more in line with openff codestyle.

Comment on lines 238 to 241
rounded_charges += numpy.round(
-rounding_error / len(charges_to_write),
tolerance,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(blocking) Wouldn't 0 charges be affected here, contradicting the "never adjust 0 charges" comment below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, should be resolved now.

tolerance,
)
diff = _rounding_error(rounded_charges, total_charge, tolerance)
while diff != 0:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not blocking) Under what circumstances would this while loop iterate more than once? I can't think of any.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, since tolerance is exactly the same, then one round should be enough.

@mattwthompson mattwthompson linked an issue Mar 4, 2025 that may be closed by this pull request
Copy link

codecov bot commented Mar 5, 2025

Codecov Report

Attention: Patch coverage is 35.29412% with 11 lines in your changes missing coverage. Please review.

Project coverage is 93.22%. Comparing base (6426579) to head (dd48e2e).
Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
...enff/interchange/interop/gromacs/export/_export.py 31.25% 11 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1167      +/-   ##
==========================================
- Coverage   93.38%   93.22%   -0.17%     
==========================================
  Files          70       70              
  Lines        6036     6050      +14     
==========================================
+ Hits         5637     5640       +3     
- Misses        399      410      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jameseastwood jameseastwood requested review from mattwthompson and j-wags and removed request for j-wags March 19, 2025 21:53
Copy link
Member

@mattwthompson mattwthompson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you're after here and think it's probably a net improvement. I still think most of the cases that would be fixed by this ought to be handled earlier in a workflow, but I see some value in safeguarding against small errors propagating through to GROMACS files on an opt-in basis.

Comment on lines +452 to +454
Charges written to the topology are normalized per molecule to ensure that each molecule has a charge which is
exactly integer. Charges that are exactly 0 are not touched.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is not the default behavior and the opt-in would be private here, I think this information should only live in the argument's description

Comment on lines +357 to +364
_normalize_charges: bool, default = False
If True charges written to the topology are normalized per molecule to ensure that each molecule has a
charge which is exactly integer. Charges that are exactly 0 are not touched.
If False, charges are untouched.
_normalize_charges: bool, default = False
If True charges written to the topology are normalized per molecule to ensure that each molecule has a
charge which is exactly integer. Charges that are exactly 0 are not touched.
If False, charges are untouched.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_normalize_charges: bool, default = False
If True charges written to the topology are normalized per molecule to ensure that each molecule has a
charge which is exactly integer. Charges that are exactly 0 are not touched.
If False, charges are untouched.
_normalize_charges: bool, default = False
If True charges written to the topology are normalized per molecule to ensure that each molecule has a
charge which is exactly integer. Charges that are exactly 0 are not touched.
If False, charges are untouched.
_normalize_charges: bool, default = False
If True charges written to the topology are normalized per molecule to ensure that each molecule has a
charge which is exactly integer. Charges that are exactly 0 are not touched.
If False, charges are untouched.

# placeholder for output charges
rounded_charges = numpy.round(charges, tolerance)
# integer total charge
total_charge = numpy.round(numpy.sum(charges), 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we have formal charges of each atom at this point, right?

@mattwthompson
Copy link
Member

@pbuslaev what do you think about trying to fix this problem earlier by "fixing" charges during Interchange.from_openmm?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Adjust charges written to GROMACS topology
3 participants