Give the cost model a name, so a caller can pick one instead of writing one (#746 v2.0) - #949
Merged
Merged
Conversation
…ng one (#746 v2.0) #746 v2.0 asks for "a cost model that is comparable across domains and is *data*, so callers can supply their own (smallest tree, fewest radicals, numerically stablest, most readable to a student)". Half of that already existed and is worth saying plainly before adding anything: ComplexityCriteria has always been a Setting<Func<Entity, double>>, and its documentation carries a worked example of ranking by fewest divisions. Measured, it does exactly what it says -- a / b + b / c stays split by default and becomes (a * c + b ^ 2) / (b * c) under that criterion. What was missing is that a criterion could not be *named*. Two callers wanting the smallest tree each wrote the same lambda, neither could record which one produced an answer, and nothing could list the alternatives. CostModel is that: a name, a description, and the function, with Default, SmallestTree, FewestDivisions and FewestRadicals in All. CostModel.Default is the function the setting already used, moved rather than copied, and ComplexityCriteria now initialises from it. A copy would have passed every test here and drifted later; the test asserts the rate is equal, not merely that the same candidate wins. Every model counts nodes a little, even the ones that are about something else. A criterion counting only its own feature ties constantly, and a tie goes to whichever candidate the search generated first -- an accident rather than a preference, which is the same trap #205 hit when two forms of 1 / (sqrt(3) + 5) rated equal. The node term is small enough not to overturn the feature and large enough to order what the feature cannot separate. Two things measured rather than assumed, both of which I had wrong at first. The default prefers the rationalised form of 1 / (sqrt(3) + 5) at 17 against 19 -- but a hand-written (sqrt(3) - 5) / (-22) costs 21, worse than either, because the negative denominator is itself weighted; the form to compare against is the one Simplify produces. And the models' disagreement is on a / b + b / c: default 24 against 33 for the combined form, fewest-divisions 2.007 against 1.011 the other way. The node counts of the radical pair tie at 7, so that pair shows a weighting difference and not a disagreement. No behaviour changes: the default is the same function, and the suite's answers are unmoved. Measured: suite 7225 passed / 0 failed, 16 of them new. PublicApi.txt records the type. #746 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 15, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#746 v2.0 asks for "a cost model that is comparable across domains and is data, so callers can supply their own (smallest tree, fewest radicals, numerically stablest, most readable to a student)".
Half of it already existed
MathS.Settings.ComplexityCriteriahas always been aSetting<Func<Entity, double>>, and its own documentation carries a worked example of ranking by fewest divisions. Measured, it does exactly what it says:Worth stating plainly before adding anything, because the roadmap item reads as though none of it were built.
What was missing: a criterion could not be named
Two callers wanting the smallest tree each wrote the same lambda, neither could record which one produced an answer, and nothing could list the alternatives.
CostModelis a name, a description and the function, withDefault,SmallestTree,FewestDivisionsandFewestRadicalsinAll.CostModel.Defaultis the function the setting already used — moved, not copied, withComplexityCriterianow initialising from it. A copy would have passed every test here and drifted later, so the test asserts the rate is equal, not merely that the same candidate wins.Every model counts nodes a little, even the ones about something else
A criterion counting only its own feature ties constantly, and a tie goes to whichever candidate the search generated first — an accident rather than a preference. That is the same trap #205 hit, where two forms of
1 / (sqrt(3) + 5)rated equal. The node term is small enough not to overturn the feature and large enough to order what the feature cannot separate.Two things measured rather than assumed — both of which I had wrong first
1 / (sqrt(3) + 5), 17 against 19. But a hand-written(sqrt(3) - 5) / (-22)costs 21, worse than either, because the negative denominator is itself weighted. The form to compare against is the oneSimplifyactually produces.a / b + b / c: default 24 against 33 for the combined form, fewest-divisions 2.007 against 1.011 the other way. The radical pair's node counts tie at 7, so that pair demonstrates a weighting difference, not a disagreement — it gets its own test saying so.Measured
Suite 7225 passed / 0 failed, 16 new. No behaviour changes — the default is the same function and the suite's answers are unmoved.
PublicApi.txtrecords the type; the new file gets a per-file header year in.editorconfig, following the convention documented there.🤖 Generated with Claude Code