Seed the flipped reaction with a family so the reverse-discovery retry can map it - #1023
Seed the flipped reaction with a family so the reverse-discovery retry can map it#1023kfir4444 wants to merge 2 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1023 +/- ##
==========================================
- Coverage 64.76% 64.75% -0.02%
==========================================
Files 119 119
Lines 40039 40061 +22
Branches 10350 10352 +2
==========================================
+ Hits 25932 25942 +10
- Misses 11129 11141 +12
Partials 2978 2978
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| forward_dicts = [pd for pd in product_dicts if not pd.get('discovered_in_reverse')] | ||
| product_dicts = forward_dicts or product_dicts | ||
| if product_dicts: | ||
| flipped.product_dicts = product_dicts |
There was a problem hiding this comment.
flipped.family is taken from product_dicts[0], but the whole list is assigned and get_reaction_family_products concatenates matches across all 99 families with no grouping. If two families amtch, map_rxn's pdi retry pairs family A's recipe with family B's label map then reads product_dicts[pdi]['r_label_map']).
Both use *1/*2/*3, so it succeeds against the wrong atoms and check_atom_map_and_return only checks it's a permutation.
There was a problem hiding this comment.
Confirmed, and thank you — this is a genuine defect, not a nit. prepare_flipped_reaction assigns the whole list but takes family from product_dicts[0], so map_rxn's pdi retry can pair one family's recipe with another's r_label_map, and since both use *1/*2/*3 it succeeds against the wrong atoms exactly as you describe.
Measured over the 452-reaction corpus: 7 of 335 flipped reactions carry product_dicts spanning more than one family — e.g. C10H11 <=> C10H11-2 matching Intra_Diels_alder_monocyclic + Intra_R_Add_Endocyclic + Intra_R_Add_Exocyclic.
restrict_product_dicts_to_family() from #978 is precisely the right fix. Two options: I add an interim single-family restriction here so this can land independently, or I rebase onto #978 and call your method. I'd prefer the latter — one implementation, not two. Your call on ordering.
| flipped = rxn.flip_reaction() | ||
| try: | ||
| product_dicts = flipped.product_dicts or list() | ||
| except (ValueError, KeyError, AttributeError): |
There was a problem hiding this comment.
Could we log these at debug? get_reaction_family_products logs its onw skips that way so it'd match the neighbourhoo and right now a failed widening is indistinguishable from "this reaction has no family" until it surfaces as a generic could not be atom mapped much later.
There was a problem hiding this comment.
Agreed — a failed widening should be distinguishable from "no family at all". Moving to debug to match get_reaction_family_products.
| product_dicts = flipped.product_dicts or list() | ||
| except (ValueError, KeyError, AttributeError): | ||
| product_dicts = list() | ||
| if not any(not pd.get('discovered_in_reverse') for pd in product_dicts): |
There was a problem hiding this comment.
not any(not x) is all(x), empty list included:
if all(pd.get('discovered_in_reverse') for pd in product_dicts):There was a problem hiding this comment.
Correct, and equivalent on the empty list too. Simplifying to all(...).
| ARCSpecies(label='HCl', smiles='Cl')]) | ||
| rxn.product_dicts = rxn.get_product_dicts(rmg_family_set='all') | ||
| flipped = prepare_flipped_reaction(rxn) | ||
| self.assertIsNotNone(flipped.family) |
There was a problem hiding this comment.
Worth asserting the label? assertEqual(flipped.family, 'XY_Addition_MultipleBond') would catch the 'all' search settling on a different family, which assertIsNotNone can't distinguish from success.
There was a problem hiding this comment.
Agreed, assertIsNotNone is too weak — it can't tell success from 'all' settling on a different family. Adding assertEqual(flipped.family, 'XY_Addition_MultipleBond').
|
@calvinp0 — all four addressed in fb9ca52, thank you. The first one was a real defect, and I measured it before fixing: over the 452-reaction corpus, 7 of 335 flipped reactions carry I've restricted to the chosen family inline, with a Reading #978 properly: these two are converging on the same defect from opposite ends, and I think they should be sequenced rather than raced. Same root cause. Your §4 finding — " Your determinism fix shows up in my data. I hit The Convergent insight worth unifying. You use fewest formed/broken/changed bonds as a family tie-break, and your Proposed order: #978 first — it is larger and more foundational — then #1023 rebases onto it and drops the inline restriction in favour of your method, then #1024. Worth flagging that your direction gate sits directly upstream of #1024: |
fb9ca52 to
71a8239
Compare
Problem
7885a456("Map reactions in the direction their family was discovered in") correctly recognizes areaction whose every template was discovered in reverse and routes it to the flip retry:
The detection is right — such a template's
productsare isomorphic to the reaction's reactants, andboth
get_template_product_orderandreorder_p_label_mapcompare template products againstrxn.p_species, so a forward attempt is doomed.But the flip retry it hands off to cannot map them:
ARCReaction.flip_reactiondeletesfamilyfrom the reaction dict (reset_keys), so the flipped copyre-derives its product dictionaries lazily with the default family set. A reaction whose family was
only matched by a broader set — the usual situation for a template discovered in reverse — comes back with
zero product dictionaries and no family, and the retry has nothing to map with. The branch above returns
unconditionally, so the forward attempt is not tried either.
Concretely, for
C2H4F2 <=> CH2CHF + HF:Measured over the 452 reactions of
testing/errs_no_li.yml, 43 have a family that is discovered only inreverse and is not its own reverse. On
main, 6 of those 43 map.Fix
Give the flipped reaction a usable, forward-discovered template before mapping it. New
prepare_flipped_reactionwidens the family search when the lazy default finds nothing usable, prefers atemplate that describes the flipped reaction forward, and seeds
family/family_own_reverse/product_dictsonto the copy.The detection block from
7885a456is unchanged; this only makes the path it delegates to work. Nothingelse in the mapping pipeline is touched, and the forward path is byte-identical.
Test
Two new tests in
arc/mapping/driver_test.py:test_prepare_flipped_reaction_seeds_a_forward_template— the flipped reaction gets a family, itstemplate is forward-discovered, and its template products are isomorphic to its own products.
test_map_reaction_with_a_reverse_discovered_template—C2H5Cl <=> C2H4 + HCl(
XY_Addition_MultipleBond, discovered in reverse) maps to a valid permutation.Before (this branch's tests against
main'sarc/mapping/driver.py):After:
Full suites:
arc/mapping/andarc/reaction/→133 passed.Corpus effect
Same script over all 452 reactions of
testing/errs_no_li.yml,map_reactionwithrmg_family_set='all', 45 s per reaction:main(c4c2db9)All 43 reactions whose template is discovered only in reverse now map, and no reaction that mapped before
stops mapping. The families recovered are
XY_Addition_MultipleBond(32), plushalocarbene_recombinationandR_Addition_MultipleBondcases — HX eliminations such asC2H5Cl <=> C2H4 + HClandC2H3F3 <=> CH2CF2 + HF, which RMG matches only as additions.