Repository navigation
insert_rule: find the rule to replace through an index, not by a full scan - #2001
tombouctou wants to merge 1 commit into
Conversation
… scan When a new unconditional rule is added, insert_rule looked for a rule with the same pattern by calling sameQ on every rule already stored, so building a definition of n rules took O(n^2) time. A package of ~90000 assignments did not load in 15 minutes; 1000 assignments to a symbol that already has 4000 DownValues took 19 s. Each Definition now keeps, for each rule list, an index of its unconditional rules by a fingerprint of their patterns: the structure of the expression with integers, strings and symbols, all other atoms lumped together, so that SameQ patterns always share a fingerprint. sameQ is then called only for the rules in that bucket. The index belongs to the list object it was built for and is rebuilt when the list is replaced (ClearAll, DownValues[f] = ...); rules removed by Unset are dropped from it when met. The precedence key cannot serve as this index: the same left-hand side gets a different key when its head, or an inner head, gets attributes like Flat between two assignments. Behaviour is unchanged; the 1000 assignments now take 0.45 s. Fixes Mathics3#1994. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| replacement rules are conditional if the replace attribute is | ||
| a conditional expression. | ||
| """ | ||
| return not hasattr(rule, "replace") or rule.replace.has_form(SymbolCondition, 2) |
There was a problem hiding this comment.
Notice that it would also happen that Condition wraps the pattern too.
There was a problem hiding this comment.
Right. This PR only moves the old inline is_conditional out of insert_rule; which rules count as conditional is the same as on master. I checked the three places a Condition can sit, on master and on this branch, with the same results on both:
| definitions | rules kept |
|---|---|
f[x_ /; x > 0] = 1; f[x_ /; x > 0] = 2 (inside an argument) |
1, replaced |
f[x_] /; x > 0 := 1; f[x_] /; x > 0 := 2 (wrapping the whole left-hand side) |
2 |
f[x_] := 1 /; x > 0; f[x_] := 2 /; x > 0 (on the right-hand side) |
2 |
In the first case the condition is just part of the pattern, so sameQ and the fingerprint both see it, and a repeated definition replaces the old one. In the other two the rule is treated as conditional and never replaced, before and after this PR. As far as I know, WMA replaces a definition whose left-hand side and condition are both the same, so the last two rows may be worth changing. But that would change behavior, so I'd keep it out of this PR, which is meant to be a pure speed-up.
|
With this PR, and #1994, I would like to wait a little bit. We have a problem with the ordering of rules and with the (lack of a) dispatch rules mechanism. I am working on that problem in a separate branch. However, it is worth getting some fresh ideas on this topic. |
|
Sure, no hurry. Some background on where this and our other recent PRs come from. We are trying to run Neil Strickland's Mathematica package for Toda's tables of unstable homotopy groups of spheres (strickland1.org/toda) unmodified in Mathics3:
Each Mathics3 problem we hit we turn into a small, self-contained fix with its own tests, and we report it here. This PR is the With all our open fixes applied,
If your dispatch branch replaces this mechanism, that's fine with us. We're glad to run these files against it as a benchmark when it's ready: they make a fairly demanding test for rule ordering and lookup. And if it helps, the fingerprint here (head/argument structure, keeping integers, strings and symbols) could be one building block for dispatching. |
Thanks for the background. It is awesome that you are doing this; I am grateful you selected Mahics3. If you or Dr. Strickland want to create a repository in the Mathics3 organization with any of you as an owner, I'd be happy to set that up. |
Fixes #1994.
When an unconditional rule is added,
insert_rulelooks for an existing rule with the same pattern so that it can be replaced. It does this by callingsameQagainst every rule already stored. Building a definition of n rules therefore takes O(n²) time, and loading data files written bySavebecomes impractical:Change. Each
Definitionnow keeps an index of the unconditional rules in each of its rule lists, keyed by a fingerprint of the rule's pattern.SameQalways get the same fingerprint (Reals of different precision may beSameQ, which is why they share one).sameQis called only on the rules in the new rule's bucket, and the rule found is then located in the list by binary search on its own precedence key.ClearAll,DownValues[f] = {...}). Rules removed byUnsetare dropped from it when they are met.Definitions pickled before this change get an index on first use.Why not use the precedence key itself? I tried that first. The key of a left-hand side such as
f[1]depends on theFlat/OneIdentityattributes off, and of any inner heads, at the time the rule is created. Sof[1] = a; SetAttributes[f, Flat]; f[1] = bwould have kept both rules.Behaviour is unchanged. I added a temporary check to
insert_rulethat compared, on every insertion, the rule found through the index with the one the old full scan finds. It reported no difference over the whole pytest suite and the doctests. The new tests intest/core/test_definitions.pycover replacement, replacement after the head or an inner head gets attributes, replacement afterUnset, lists set as a whole in an order of their own, and conditional rules, which are never replaced. They pass on master as well. Another test countssameQcalls when adding a rule to a definition of 500 rules; it fails on master.Timings:
Save)pytest and the doctests give the same results as on master. The only failures are environmental ones, which also fail on master: XML/ZIP/JPEG
FileFormat, and the CLI tests that needmathicson the PATH. mypy is clean onmathics/core/definitions.py.Unrelated to this PR, but noticed while testing:
DownValues[f] = {HoldPattern[f[1]] :> c}stores the left-hand side asHoldPattern[HoldPattern[f[1]]]. So a laterf[1] = dadds a second rule instead of replacing it. This happens on master too.🤖 Generated with Claude Code