Skip to content

Five decisions whose consequences reach past their own issue #814

Description

@Rafael-SOWNet

Written while working out what should happen before 2.0. Each of these looks self-contained and is not — settling one changes what is possible or cheap elsewhere. Measurements are on master (21f0d16).


1. Whether a new node type is breaking — #248 — gates three other issues

Entity is a public abstract record whose hierarchy is open (InitDirectChildren is protected, so external code can derive). Nothing states whether adding a subtype is a breaking change.

If it is, then none of these may ship in a minor version:

and the roadmap waits on 3.0 for years. Detail and a suggested wording in #248. This is the one to settle first, because three other issues cannot be scheduled until it is.


2. #204 is the simplifier's objective function, not a display choice

Roots versus fractional powers reads like a printing decision. It is not, because Simplify generates candidates and returns the shortest by Complexity, which is node count:

Complexity
sqrt(x) 3
x ^ (1/2) 5
sqrt(x) + sqrt(y) 7
x ^ (1/2) + y ^ (1/2) 11

A radical written as a fractional power costs nearly twice as much, and the gap grows with the number of radicals. So the representation decides which candidate wins, everywhere a radical can appear — not merely how the winner prints. Switching to fractional powers would silently re-rank a large part of the simplifier, and the effect is not visible in any individual rule.

Whatever is decided, it wants measuring against simpsweep and casbench rather than reasoning about, and it should be decided before 2.0 since the printed form of every radical is user-visible.


3. #326 collides with a guarantee 2.0 is about to make

Stringize round-tripping was fixed this cycle — BREAKING-CHANGES.md now promises that powers, lambdas, applications and piecewises parse back from their own output. Changing piecewise syntax after 2.0 ships breaks expressions users have already saved, which is exactly what that guarantee is for.

It is also worse than it looks today: the syntax #326 records as current does not parse, and a near-miss of it crashes with a NullReferenceException rather than a parse error (#813).

So #326 is not a free-standing style question — it is the last cheap moment to move the syntax, and there is a crash sitting on the same grammar rule.


4. #721 and #812 are the same statement told twice

#812 is a wrong answer: abs(x) = -1 returns a non-empty set whose members do not satisfy it, because the inversion is missing an a >= 0 guard.

Expressing that guard needs a decision that is #721's: is "this holds only where the argument is non-negative" a Providedf condition on the node, or a consequence of MathS.Settings.Codomain? Fixing #812 without settling #721 means picking one mechanism by accident and then having two mechanisms for one statement — which is the situation #721 was opened to end.

Fix #812 first if it must be fixed first (it is a wrong answer, so it outranks tidiness), but write down which mechanism was chosen and why, so #721 inherits a decision rather than a mess.


5. The spine's third customer is the polynomial layer, and it is the expensive one

The monoid-algebra spine landed in #778 with two customers: the boolean minimiser and symbolic quantum states. AGENTS.md already records that the polynomial representation "is already a monoid algebra in all but name", and that most of the remaining backlog — #381 characteristic polynomials, #475 Diophantine equations, multivariate GCD, factorisation, #55 — sits behind that layer.

The caveat is also already recorded and is worth repeating here because it decides the order of work: GatherMonomialInformation is on the hot path for solving, long division and simplification. So this is the one place where "unify the representation" cannot be done on the strength of the argument that it is the same structure. Build the benchmark before the change, not after — otherwise there is no way to tell a regression from noise, and the change is large enough that it would not be obvious.


None of these is a request to do the work. They are the couplings I would want known before anything schedules the features on either side of them.

Activity

  1. Rafael-SOWNet commented on Sep 5, 2026

    @Rafael-SOWNet
    MemberAuthor

    Re-measured all five against the tree at 5d802bab. Two no longer hold, one is half true, two
    stand.

    item now verdict
    1 · #248 gates #809/#286/#495 Partly true. #248 is closed without the policy having been written; #809 closed. No node type has been added since v2.0.0. NodeContract.md decided Option A and it is unimplemented — InvertNode, SortHashName, IntrinsicCondition are still private protected abstract, Priority and ToSymPy internal abstract, so no external assembly can derive a node today Re-scope to one sentence: adding a node type is not breaking while the contract stays closed, and becomes breaking only when Option A ships — which is itself the moment to say so. #286 and #495 are then ungated
    2 · #204 is the objective function No longer true. sqrt(x) and x ^ (1/2) are the same entity: == is True, Complexity 3 and 3, SimplifiedRate equal; sqrt(x) + sqrt(y) and the power form are 7 and 7. The 3-vs-5 gap this item rests on came from 1/2 parsing as a Divf before 2.3.0 Drop
    3 · #326 collides with round-trip Half true. Round-trip holds and is tested; #813 is fixed. The "last cheap moment" argument has expired — 2.0 shipped Re-scope: additive grammar in a minor, removal at the major
    4 · #812 and #721 are one statement No longer true. abs(x) = -1 → { }; abs(x) = a → { a * e^(i*r_1) provided r_1 in RR and a >= 0 }, so the guard is a Providedf on the solution. #721 separately chose the parameter reading, DomainConditionIn Drop. Two mechanisms for two different statements, both now recorded
    5 · the polynomial layer Still true. GatherMonomialInformation has nine call sites across Simplificator, long division, the polynomial solver, Limit, the parser and factoring, beside an internal MultivariatePolynomial / IntegerPolynomial layer — two representations coexisting Keep, but not here. Nothing in it is user-visible, so it belongs on #746 tier 1 rather than on a breakages docket

    On item 5's own condition

    It asked for the unification to happen only after benchmarking. That benchmark now exists:
    PerformanceGate fails the build on more than 3% allocation over CommonFunctionsInterVersion, and
    on time only above 3×. Worth knowing that the gate catches allocation and not ordinary time
    regressions — the wide-noise problem on CI runners is why — so a timing claim still needs the desktop
    A/B.

    Not re-measured

    Whether any external consumer switches exhaustively over node types, which is the thing item 1's
    policy would actually protect.

  2. Happypig375 commented on Sep 18, 2026

    @Happypig375
    Member

    We will likely move to C# discriminated unions instead of allowing an outsider to inherit a node. Our algorithms simply cannot handle arbitrary extensions of node types. #204 also needs a redesign for v3

  3. added this to the 3.0 milestone on Sep 27, 2026
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

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions