Skip to content

Stringize drops a node's Codomain, so domain(x, ZZ) prints as x and does not round trip #1022

Description

@Rafael-SOWNet

Stringize prints no node's Codomain, so a node that carries one prints as though it did not, and
parsing the result gives a different node back. Same shape as #873, which was the Rational/Divf
case, and the last one I can find.

Measured on v2.3.0 (6b93b40):

var d = MathS.FromString("domain(x, ZZ)");
d.GetType()          // Entity.Variable
d.Codomain           // Integer
d.Stringize()        // "x"
MathS.FromString(d.Stringize()).Codomain   // Any
d == MathS.FromString(d.Stringize())       // False

The parser has the syntax already — domain(expr, SET) maps straight onto WithCodomain, and it
works for every node, not only variables:

domain(x + 1, ZZ)     -> Sumf,     Codomain = Integer
domain(sqrt(-1), RR)  -> Powf,     Codomain = Real
domain([1, 2], RR)    -> Matrix,   Codomain = Real

so this is only the printing half being absent.

It is not confined to a node somebody wrote domain(...) around, because the codomain travels with
the subnode:

var e = MathS.Sin(MathS.Var("x").WithCodomain(Domain.Integer)) + MathS.Var("y").WithCodomain(Domain.Real);
e.Stringize()   // "sin(x) + y" -- both annotations gone

Codomain is not decoration: it decides evaluation. sqrt(-1) is i, and the same expression with
Codomain = Real is NaN — the example in Domains.Definition.cs says exactly this, and prints the
same string for both.

Why it matters beyond the round trip: the printed form is the library's exchange format, so anything
that writes an expression out — a file, a database column, EntityJsonConverter (#323) — loses the
annotation silently, and a caller who narrowed a domain gets it back widened with no error anywhere.

StringizeRoundTripsToTheSameNode in EveryNodeSurvivesEveryPipelineTest does not catch it because
the samples it builds all carry the default codomain for their type. A node with a narrowed one is
worth adding there whichever way this is fixed.

Two ways to fix, and they are not equivalent:

  1. print domain(<inner>, SET) wherever a node's codomain is not the one the parser would give the
    same text. Round trip becomes an identity; output for the ordinary case is unchanged, since the
    ordinary case has the default.
  2. decide that Codomain is not part of a node's identity and take it out of record equality — which
    is roughly Unify Codomain with a "provided ... in RR" condition #721's direction, where the constraint becomes a provided ... in RR condition that
    is printed.

The first is the smaller fix and does not prejudge #721.

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