Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/releasehistory.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Please note that all releases prior to a version 1.0.0 are considered pre-releas
* #1452 Better manage some caches by invalidate OpenMM export-specific caches at export time
* #1468 Update charge assignment documentation to include NAGL-based charge models.
* #1481 Ensured box vectors and positions were stored internally in nanometers if passed in as other compatible units such as Angstroms.
* #1487 Change charge logging level to `logging.DEBUG`

## 0.5.2 - 2025-03-02

Expand Down
23 changes: 12 additions & 11 deletions docs/using/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ If a molecule gets charges from one method, attempts to match charges for later

After all (mass-bearing) atoms have partial charges assigned, virtual sites are given charges by transferring charge from the atoms they are associated with ("orientation" atoms) according to the parameters of the force field. (The value of these parameters can be 0.0.)

Given this complexity, it may be useful to track how each atom actually got charges assigned. Interchange has opt-in logging to track this behavior. This uses the [standard library `logging` module](https://docs.python.org/3/library/logging.html) at the `INFO` level. The easiest way to get started is by adding something like `logging.basicConfig(level=logging.INFO)` to the beginning of a script or program. For example, this script:
Given this complexity, it may be useful to track how each atom actually got charges assigned. Interchange has opt-in logging to track this behavior. This uses the [standard library `logging` module](https://docs.python.org/3/library/logging.html) at the `logging.DEBUG` level. The easiest way to get started is by adding something like `logging.getLogger("openff.interchange").setLevel(logging.DEBUG)` to the beginning of a script or program. For example, this script:

```python
import logging

from openff.toolkit import ForceField, Molecule

logging.basicConfig(level=logging.INFO)
logging.basicConfig()
logging.getLogger("openff.interchange").setLevel(logging.DEBUG)

ForceField("openff-2.2.0.offxml").create_interchange(
Molecule.from_smiles("CCO").to_topology()
Expand All @@ -30,15 +31,15 @@ ForceField("openff-2.2.0.offxml").create_interchange(
will produce output including something like

```shell
INFO:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bcc, applied to (topology) atom index 0
INFO:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bcc, applied to (topology) atom index 1
INFO:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bcc, applied to (topology) atom index 2
INFO:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bcc, applied to (topology) atom index 3
INFO:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bcc, applied to (topology) atom index 4
INFO:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bcc, applied to (topology) atom index 5
INFO:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bcc, applied to (topology) atom index 6
INFO:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bcc, applied to (topology) atom index 7
INFO:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bcc, applied to (topology) atom index 8
DEBUG:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bccelf10, applied to topology atom index 0
DEBUG:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bccelf10, applied to topology atom index 1
DEBUG:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bccelf10, applied to topology atom index 2
DEBUG:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bccelf10, applied to topology atom index 3
DEBUG:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bccelf10, applied to topology atom index 4
DEBUG:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bccelf10, applied to topology atom index 5
DEBUG:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bccelf10, applied to topology atom index 6
DEBUG:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bccelf10, applied to topology atom index 7
DEBUG:openff.interchange.smirnoff._nonbonded:Charge section ToolkitAM1BCC, using charge method am1bccelf10, applied to topology atom index 8
```

This functionality is only available with SMIRNOFF force fields.
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def map_methods_to_atom_indices(caplog: pytest.LogCaptureFixture) -> dict[str, l
for record in caplog.records:
# skip logged warnings from upstreams/other packages
if record.name.startswith("openff.interchange"):
assert record.levelname == "INFO", "Only INFO logs are expected."
assert record.levelname == "DEBUG", "Only DEBUG logs are expected."
else:
continue

Expand Down Expand Up @@ -282,7 +282,7 @@ def ligand_and_water_and_ions(ligand, water_and_ions) -> Topology:


def test_case0(caplog, sage_no_nagl, ligand):
with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage_no_nagl.create_interchange(ligand.to_topology())

info = map_methods_to_atom_indices(caplog)
Expand All @@ -292,7 +292,7 @@ def test_case0(caplog, sage_no_nagl, ligand):


def test_case1(caplog, sage_no_nagl, ligand_and_water_and_ions):
with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage_no_nagl.create_interchange(ligand_and_water_and_ions)

info = map_methods_to_atom_indices(caplog)
Expand All @@ -307,7 +307,7 @@ def test_case1(caplog, sage_no_nagl, ligand_and_water_and_ions):
def test_case2(caplog, sage_no_nagl, ligand, solvent):
topology = Topology.from_molecules([ligand, solvent, solvent, solvent])

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage_no_nagl.create_interchange(topology)

info = map_methods_to_atom_indices(caplog)
Expand All @@ -322,7 +322,7 @@ def test_case3(caplog, sage_no_nagl, ligand_and_water_and_ions, solvent):

ligand_and_water_and_ions.molecule(0).assign_partial_charges(partial_charge_method="gasteiger")

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage_no_nagl.create_interchange(
ligand_and_water_and_ions,
)
Expand Down Expand Up @@ -350,7 +350,7 @@ def test_cases4_5(caplog, ligand_and_water_and_ions, preset_on_protein):
if preset_on_protein:
complex.molecule(0).assign_partial_charges(partial_charge_method="zeros")

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
if preset_on_protein:
ff.create_interchange(complex, charge_from_molecules=[complex.molecule(0)])
else:
Expand Down Expand Up @@ -381,7 +381,7 @@ def test_case6(caplog, ligand, water):

topology = Topology.from_molecules([ligand, water, water, water])

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
force_field.create_interchange(topology)

info = map_methods_to_atom_indices(caplog)
Expand All @@ -403,7 +403,7 @@ def test_case6(caplog, ligand, water):
def test_case7(caplog, sage_no_nagl, ligand_and_water_and_ions):
ligand_and_water_and_ions.molecule(0).assign_partial_charges(partial_charge_method="gasteiger")

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage_no_nagl.create_interchange(
ligand_and_water_and_ions,
charge_from_molecules=[ligand_and_water_and_ions.molecule(0)],
Expand All @@ -421,7 +421,7 @@ def test_case7(caplog, sage_no_nagl, ligand_and_water_and_ions):
def test_case8(caplog, sage_no_nagl, water_and_ions):
water_and_ions.molecule(0).assign_partial_charges(partial_charge_method="gasteiger")

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage_no_nagl.create_interchange(
water_and_ions,
charge_from_molecules=[water_and_ions.molecule(0)],
Expand All @@ -437,7 +437,7 @@ def test_case8(caplog, sage_no_nagl, water_and_ions):


def test_case9(caplog, sage_with_bond_charge):
with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
_ensure_pre_nagl_sage(sage_with_bond_charge).create_interchange(
Molecule.from_mapped_smiles(
"[H:3][C:1]([H:4])([H:5])[Cl:2]",
Expand All @@ -457,7 +457,7 @@ def test_case10(caplog, sage_with_nagl_chargeincrements, ligand):
pytest.importorskip("openff.nagl")
pytest.importorskip("rdkit")

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
with toolkit_registry_manager(
toolkit_registry=ToolkitRegistry(
toolkit_precedence=[NAGLToolkitWrapper, RDKitToolkitWrapper],
Expand Down Expand Up @@ -486,7 +486,7 @@ def test_case11(caplog, sage, ligand):
"""Test that NAGL charge assignment is properly logged."""
pytest.importorskip("openff.nagl")

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage.create_interchange(ligand.to_topology())

info = map_methods_to_atom_indices(caplog)
Expand All @@ -502,7 +502,7 @@ def test_case12(caplog, sage, water):

topology = Topology.from_molecules([water, water])

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage.create_interchange(topology)

info = map_methods_to_atom_indices(caplog)
Expand All @@ -518,7 +518,7 @@ def test_case13(caplog, sage, ligand, water):

topology = Topology.from_molecules([ligand, water])

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage.create_interchange(topology)

info = map_methods_to_atom_indices(caplog)
Expand All @@ -536,7 +536,7 @@ def test_case14(caplog, sage, ligand):

ligand.assign_partial_charges("gasteiger")

with caplog.at_level(logging.INFO):
with caplog.at_level(logging.DEBUG):
sage.create_interchange(
ligand.to_topology(),
charge_from_molecules=[ligand],
Expand Down
12 changes: 6 additions & 6 deletions openff/interchange/smirnoff/_nonbonded.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def _get_charges(
parameter_value.m,
)

logger.info(
logger.debug(
"Charge section ChargeIncrementModel, applying charge increment from atom "
f"{topology_key.this_atom_index} to atoms {topology_key.other_atom_indices}",
)
Expand Down Expand Up @@ -938,35 +938,35 @@ def store_matches(
# Have this new key (on a duplicate molecule) point to the same potential
# as the old key (on a unique/reference molecule)
if type(new_key) is LibraryChargeTopologyKey:
logger.info(
logger.debug(
"Charge section LibraryCharges applied to topology atom index "
f"{topology_atom_index}",
)

elif type(new_key) is SingleAtomChargeTopologyKey:
if new_key.extras["handler"] == "ToolkitAM1BCCHandler":
logger.info(
logger.debug(
"Charge section ToolkitAM1BCC, using charge method "
f"{new_key.extras['partial_charge_method']}, "
f"applied to topology atom index {topology_atom_index}",
)
elif new_key.extras["handler"] == "NAGLChargesHandler":
logger.info(
logger.debug(
"Charge section NAGLCharges, using NAGL model "
f"{new_key.extras['partial_charge_method']}, "
f"applied to topology atom index {topology_atom_index}",
)

elif new_key.extras["handler"] == "preset":
logger.info(
logger.debug(
f"Preset charges applied to atom index {topology_atom_index}",
)

else:
raise ValueError(f"Unhandled handler {new_key.extras['handler']}")

elif type(new_key) is ChargeModelTopologyKey:
logger.info(
logger.debug(
"Charge section ChargeIncrementModel, using charge method "
f"{new_key.partial_charge_method}, "
f"applied to topology atom index {new_key.this_atom_index}",
Expand Down
2 changes: 1 addition & 1 deletion openff/interchange/smirnoff/_virtual_sites.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def store_potentials( # type: ignore[override]
},
)

logger.info(
logger.debug(
"Charge section VirtualSites applied to virtual site with orientation atoms at topology indices "
f"{virtual_site_key.orientation_atom_indices}",
)
Expand Down