Skip to content

Inconsistent use of mapped SMILES breaks internal consistency assumptions #206

Closed

Description

@mattwthompson

This error crops up from deep in Parsl compute graphs, working on a slimmer reproduction:

1792498085.267638 2026-10-20 07:08:05 MainProcess-95220 HTEX-Result-Queue-Thread-13754871808 parsl.dataflow.dflow:390 handle_exec_update ERROR: Task 41 failed after 0 retry attempts
Traceback (most recent call last):
  File "/Users/mattthompson/software/tyff/.pixi/envs/dev/lib/python3.12/site-packages/parsl/dataflow/dflow.py", line 348, in handle_exec_update
    res = self._unwrap_remote_exception_wrapper(future)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mattthompson/software/tyff/.pixi/envs/dev/lib/python3.12/site-packages/parsl/dataflow/dflow.py", line 578, in _unwrap_remote_exception_wrapper
    result.reraise()
  File "/Users/mattthompson/software/tyff/.pixi/envs/dev/lib/python3.12/site-packages/parsl/app/errors.py", line 114, in reraise
    raise v
  File "/Users/mattthompson/software/tyff/.pixi/envs/dev/lib/python3.12/site-packages/parsl/app/errors.py", line 138, in wrapper
    return func(*args, **kwargs)
    ^^^^^^^^^^^^^^^^^
  File "/Users/mattthompson/software/tyff/tyff/compute/apps.py", line 95, in create_jacobian
    return _get_ensemble_average_and_jacobian(
      ^^^^^^^^^^^^^^^^^
  File "/Users/mattthompson/software/tyff/tyff/compute/_jacobian.py", line 130, in _get_ensemble_average_and_jacobian
    worker_force_field = _gather_from_reference(local_force_field, reference_worker_force_field)
      ^^^^^^^^^^^^^^^^^
  File "/Users/mattthompson/software/tyff/tyff/compute/_jacobian.py", line 28, in _gather_from_reference
    idx = torch.tensor([ref_idx[key] for key in potential.parameter_keys])  # KeyError: unseen parameter
    ^^^^^^^^^^^^^^^^^
KeyError: PotentialKey associated with handler 'NAGLChargesHandler' with id '[H:11][c:5]1[c:4]([c:3]([c:2]([c:1]([c:6]1[H:12])[H:7])[H:8])[H:9])[H:10]', mult 0

It's possible that it stems from different mapped SMILES being generated by a given molecule (#191)

Activity

  1. mattwthompson commented on Oct 20, 2026

    @mattwthompson
    MemberAuthor

    This doesn't trigger when calling _gather_from_reference on identical force fields

    I'm able to pinpoint it to chemically equivalent SMILES being written slightly differently at some point, but the provenance of how these two strings got onto my local files is lost to the sands of time. I don't think I can easily pin it to OEChem vs. RDKit differences:

     ipdb> {key.id for key in ref.parameter_keys if key.associated_handler == "NAGLChargesHandler" and len(key.id) == 73}
    {'[H:9][c:3]1[c:2]([c:1]([c:6]([c:5]([c:4]1[H:10])[H:11])[H:12])[H:7])[H:8]'}
    ipdb> {key.id for key in potential.parameter_keys if key.associated_handler == "NAGLChargesHandler" and len(key.id) == 73}
    {'[H:11][c:5]1[c:4]([c:3]([c:2]([c:1]([c:6]1[H:12])[H:7])[H:8])[H:9])[H:10]'}
    ipdb> from openff.toolkit.utils import *
    ipdb> openff.toolkit.Molecule.from_smiles("c1ccccc1").to_smiles(mapped=True, toolkit_registry=OpenEyeToolkitWrapper())
    '[H:9][c:3]1[c:2]([c:1]([c:6]([c:5]([c:4]1[H:10])[H:11])[H:12])[H:7])[H:8]'
    ipdb> openff.toolkit.Molecule.from_smiles("c1ccccc1").to_smiles(mapped=True, toolkit_registry=RDKitToolkitWrapper())
    '[c:1]1([H:7])[c:2]([H:8])[c:3]([H:9])[c:4]([H:10])[c:5]([H:11])[c:6]1[H:12]'
  2. mattwthompson commented on Oct 20, 2026

    @mattwthompson
    MemberAuthor

    Okay so it's probably not OECHem vs. RDKit differences or Isomeric vs. non-isomeric SMILES (via the toolkit), but I do notice that the map_smiles function in this project produces a different string:

    >>> smiles = 'c1ccccc1'
    >>> mapped_smiles = '[H:11][c:5]1[c:4]([c:3]([c:2]([c:1]([c:6]1[H:12])[H:7])[H:8])[H:9])[H:10]'
    >>> import openff.toolkit
    >>> from openff.toolkit.utils import OpenEyeToolkitWrapper, RDKitToolkitWrapper
    >>> openff.toolkit.Molecule.from_mapped_smiles(mapped_smiles).to_smiles(mapped=True, isomeric=True, explicit_hydrogens=True, toolkit_registry=OpenEyeToolkitWrapper())
    '[H:9][c:3]1[c:2]([c:1]([c:6]([c:5]([c:4]1[H:10])[H:11])[H:12])[H:7])[H:8]'
    >>> openff.toolkit.Molecule.from_mapped_smiles(mapped_smiles).to_smiles(mapped=True, isomeric=False, explicit_hydrogens=True, toolkit_registry=OpenEyeToolkitWrapper())
    '[H:9][c:3]1[c:2]([c:1]([c:6]([c:5]([c:4]1[H:10])[H:11])[H:12])[H:7])[H:8]'
    >>> openff.toolkit.Molecule.from_mapped_smiles(mapped_smiles).to_smiles(mapped=True, isomeric=True, explicit_hydrogens=True, toolkit_registry=RDKitToolkitWrapper())
    '[c:1]1([H:7])[c:2]([H:8])[c:3]([H:9])[c:4]([H:10])[c:5]([H:11])[c:6]1[H:12]'
    >>> from tyff.utils.molecule import map_smiles
    >>> map_smiles(smiles)
    '[c:1]1([H:7])[c:2]([H:8])[c:3]([H:9])[c:4]([H:10])[c:5]([H:11])[c:6]1[H:12]'
    >>> mapped_smiles
    '[H:11][c:5]1[c:4]([c:3]([c:2]([c:1]([c:6]1[H:12])[H:7])[H:8])[H:9])[H:10]'

    This difference is relevant because openff.interchange.models.PotentialKey has its .id attribute set (as a simple Python string) by a separate Molecule.to_smiles call. That should all be self-consistent, but we cannot assume that the result of tyff.utils.molecule.map_smiles (or other to_smiles calls1) will produce identical strings:

    >>> mapped_smiles
    '[H:11][c:5]1[c:4]([c:3]([c:2]([c:1]([c:6]1[H:12])[H:7])[H:8])[H:9])[H:10]'
    >>> {key.id for key in openff.toolkit.ForceField("openff-2.3.0.offxml").create_interchange(Molecule.from_mapped_smiles(mapped_smiles).to_topology())['Electrostatics'].potentials}
    {'[H:9][c:3]1[c:2]([c:1]([c:6]([c:5]([c:4]1[H:10])[H:11])[H:12])[H:7])[H:8]'}
    >>> Molecule.from_mapped_smiles(mapped_smiles).to_smiles(mapped=True)
    '[H:9][c:3]1[c:2]([c:1]([c:6]([c:5]([c:4]1[H:10])[H:11])[H:12])[H:7])[H:8]'

    It'll take me a second to find where this mismatch happens, but I think it's likely to be somewhere inside of tyff

    Footnotes

    1. Molecule.from_mapped_smiles(...).to_smiles(mapped=True) returning something different than the input took me by surprise before thinking about it - I'm pretty sure that strings being character-for-character equivalent through the toolkit isn't a reasonable expectation ↩

  3. changed the title [-]`gather_from_reference` chokes with `NAGLChargesHandler`[/-] [+]Inconsistent use of mapped SMILES breaks internal consistency assumptions[/+] on Oct 20, 2026
  4. mattwthompson commented on Oct 20, 2026

    @mattwthompson
    MemberAuthor

    I fixed this somewhere in #190

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions