Skip to content

Drop Pydantic v1#93

Open
mattwthompson wants to merge 15 commits into
mainfrom
no-pydantic-v1
Open

Drop Pydantic v1#93
mattwthompson wants to merge 15 commits into
mainfrom
no-pydantic-v1

Conversation

@mattwthompson

@mattwthompson mattwthompson commented May 27, 2026

Copy link
Copy Markdown
Member

Description

Fixes #91

Status

  • Ready to go

@codecov

codecov Bot commented Jun 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.44444% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.51%. Comparing base (a822950) to head (26b22b1).

Files with missing lines Patch % Lines
descent/train.py 94.44% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #93      +/-   ##
==========================================
+ Coverage   97.66%   99.51%   +1.85%     
==========================================
  Files          11       11              
  Lines        1071     1038      -33     
==========================================
- Hits         1046     1033      -13     
+ Misses         25        5      -20     
Flag Coverage Δ
unittests 99.51% <94.44%> (+1.85%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mattwthompson
mattwthompson marked this pull request as ready for review June 3, 2026 13:24
@jameseastwood jameseastwood assigned j-wags and unassigned mattwthompson Jul 7, 2026
@read-the-docs-community

read-the-docs-community Bot commented Jul 10, 2026

Copy link
Copy Markdown

Documentation build overview

📚 descent-fitting | 🛠️ Build #33536795 | 📁 Comparing a9c860f against latest (3d23214)

  🔍 Preview build  

37 files changed · ± 37 modified

± Modified

mattwthompson and others added 3 commits July 10, 2026 12:30
Keys have historically matched on (id, mult, associated_handler,
bond_order) only. After dropping the pydantic v1 shim, matching uses
PotentialKey's full-field equality, which also compares virtual_site_type
and cosmetic_attributes -- fields a user cannot reconstruct from a SMIRKS.
Such keys are then silently ignored in exclude (and would raise KeyError
in include).

Covers both vulnerable fields via the exclude path: virtual_site_type
(a v-site key) and cosmetic_attributes (any handler; here a cosmetic
attribute on a Bonds parameter).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@j-wags j-wags left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I didn't find any problems myself, but I let claude do a once-over and it spotted a sneaky regression wrt vsites and cosmetic attributes. I worked with claude to generate minimal tests expressing this behavior.

I lean slightly toward favoring fixing this regression (since it could cause user surprise) but I'd also be fine with A) just listing it in the release notes (and removing the new tests I committed) or B) erroring in cases where it could make a difference (though this could be hard to detect)

Comment on lines +486 to +489
excluded = openff.interchange.models.PotentialKey(
id="[#1:2]-[#8X2H2+0:1]-[#1:3] EP once",
associated_handler="VirtualSites",
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is now how as-applied virtual site parameters are stored in Interchange. There is a separate class for virtual sites that intentionally stores relevant fields:

In [1]: from openff.toolkit import Molecule, ForceField

In [2]: virtual_site_keys = [*ForceField("opc.offxml").create_interchange(Molecule.from_smiles("O").
      ⋮ to_topology())['VirtualSites'].key_map]

In [3]: virtual_site_keys
Out[3]: [SMIRNOFFVirtualSiteKey with orientation atom indices (0, 1, 2)]

In [4]: type(virtual_site_keys)
Out[4]: list

In [5]: [type(key) for key in virtual_site_keys]
Out[5]: [openff.interchange.models.SMIRNOFFVirtualSiteKey]

In [6]: virtual_site_keys[-1].dict()
<ipython-input-6-9d453a18a1da>:1: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.13/migration/
  virtual_site_keys[-1].dict()
Out[6]:
{'atom_indices': None,
 'orientation_atom_indices': (0, 1, 2),
 'type': 'DivalentLonePair',
 'name': 'EP',
 'match': 'once'}

This approach, be it by Claude or our scientists, of smooshing together 3 parameters into a single string is not encouraged

Comment thread descent/train.py
Comment on lines +352 to +355
all_keys = [
openff.interchange.models.PotentialKey(**key.dict())
for key in force_field.v_sites.keys
]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ideally switch these over to VirtualSiteKeys

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Apologies for wading in here, but I'd like to understand this. My understanding is: we want the keys to match force field parameters independent of which atoms they're applied to, but VirtualSiteKey is a subclass of TopologyKey, so identifies where parameters are matched rather than the parameters themselves. PotentialKeys identify parameters, independent of where they're applied. So how can we replace PotentialKeys with VirtualSiteKeys?

Also, from above, is the "smooshing together" of parameters not a fairly old feature of Interchange? https://github.com/openforcefield/openff-interchange/blob/100e7f09923c8f42a64672dade85a5e9aa256a2c/openff/interchange/smirnoff/_virtual_sites.py#L121

Thanks!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You're right - I confused myself with these classes and what they do. I'll take a stab at fixing this regression - would you be up for reviewing a fix for this? ETA mid next week

I do think there could be a better solution than stringifying the relevant fields together, but that's what is done in Interchange and ought to be what this code builds off of.

Comment thread descent/train.py
key_to_row = {key: row_idx for row_idx, key in enumerate(all_keys)}
assert len(key_to_row) == len(all_keys), "duplicate keys found"

return {key_to_row[key] for key in unfrozen_keys if key not in excluded_keys}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Note to self: this is the point of failure. It's not actually related to Pydantic API changes, it's related to Interchange behavior changes over some period of years. The ~vendored code here has different behavior than upstream.

If we want exclude a fairly generic parameter

ipdb> [e.dict() for e in excluded_keys]
[{'id': '[#6X4:1]-[#6X4:2]', 'mult': None, 'associated_handler': 'Bonds', 'bond_order': None, 'virtual_site_type': None, 'cosmetic_attributes': {}}]

but a similar-looking parameter also has cosmetic attributes:

ipdb> [u.dict() for u in unfrozen_keys if u.cosmetic_attributes]
[{'id': '[#6X4:1]-[#6X4:2]', 'mult': None, 'associated_handler': 'Bonds', 'bond_order': None, 'virtual_site_type': None, 'cosmetic_attributes': {'foo': 'bar'}}]

then nothing is excluded (probably one parameter should be):

ipdb> len(unfrozen_keys)
2
ipdb> len({key_to_row[key] for key in unfrozen_keys if key not in excluded_keys})
2

Optionally add a breakpoint before returning and run something like

$ pixi run -e dev python -m pytest -v descent/tests/test_train.py::TestTrainable::test_init_exclude_key_without_cosmetic_attributes --pdb -s

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.

Remove pydantic v1 support

3 participants