Skip to content

Compiled functions: branching in FastExpression, and compiled forms for the newer nodes #2195

Description

@Rafael-SOWNet

A harness, compilecheck, compiles a sample of every node that has a numeric or boolean value through both compilers, calls it at sample points, and compares the result with Substitute then EvalNumerical. Measured on master ea08214. Every compiled value agrees with EvalNumerical. What is missing are nodes that are refused.

compiler compiles
FastExpression, Compile("x") 43 of 71 numeric nodes, and 0 of 12 boolean
LINQ, Compile<Complex, Complex> 42 of 71
LINQ, Compile<double, double> 44 of 71
LINQ, Compile<double, bool> 10 of 12 boolean

What is missing, grouped by what it would take:

  1. FastExpression has no booleans and no branch. So piecewise, provided, iverson and every comparison are refused. The fix needs comparison and connective instructions that push 1 or 0, and a select instruction. The instructions run in a single foreach, so a jump would also need an indexed loop. A comparison of a value that is not real should come out false, so that x provided x > 0 is NaN at i, as EvalNumerical has it.
  2. LINQ over Complex refuses comparisons. The message is The binary operator GreaterThan is not defined for the types 'System.Numerics.Complex'. So piecewise and provided compile over double but not over Complex, and mod fails the same way. A fix would compile a comparison of complex operands as a comparison of real parts when both operands are real, and as false otherwise.
  3. These nodes have no compiled form in either compiler:
    • conj, arg, Re and Im: one System.Numerics.Complex call each.
    • iverson: a conditional in LINQ.
    • binomial, fallingfactorial, risingfactorial, n!!, gamma(a, z) and polygamma: these need numerical implementations over double and Complex.
    • The integer functions prime, valuation, gcd, lcm, mobius, divisorsigma, primenu, primeomega, primepi and divides, and membership x in [0; 1]. These could compile over double at whole numbers and give NaN elsewhere, or stay refused.
  4. The calculus nodes and binders are refused by both: derivative, the definite integral, limit, sum, product and apply(lambda(…)). Compiling expr.Evaled first gives the computed form. Whether Compile should do that itself is a decision, because it would make compiling as slow as evaluating.
The full table
node sample FastExpression LINQ Complex LINQ double
Variable x ok ok ok
Integer 3 ok ok ok
Rational 1/2 ok ok ok
Real (root is Rational) 0.5 ok ok ok
Complex (root is Sumf) 2 + 3i ok ok refused
Constant pi ok ok ok
Sumf x + 3 ok ok ok
Minusf x - (x^2) ok ok ok
Mulf x * (x + 1) ok ok ok
Divf x / (x + 2) ok ok ok
Powf x ^ 3 ok ok ok
Logf log(2, x) ok ok ok
Sinf sin(x) ok ok ok
Cosf cos(x) ok ok ok
Tanf tan(x) ok ok ok
Cotanf cotan(x) ok ok ok
Secantf sec(x) ok ok ok
Cosecantf cosec(x) ok ok ok
Arcsinf arcsin(x) ok ok ok
Arccosf arccos(x) ok ok ok
Arctanf arctan(x) ok ok ok
Arccotanf arccotan(x) ok ok ok
Arcsecantf arcsec(x) ok ok ok
Arccosecantf arccosec(x) ok ok ok
Absf abs(x) ok ok ok
Signumf sgn(x) ok ok ok
Conjugatef conj(x) refused refused refused
Argf arg(x) refused refused refused
Ref Re(x) refused refused refused
Imf Im(x) refused refused refused
Floorf floor(x) ok ok ok
Ceilf ceil(x) ok ok ok
Roundf round(x) ok ok ok
Modf x mod 3 ok refused ok
Maxf max(x, 1) ok ok ok
Minf min(x, 1) ok ok ok
Factorialf x! ok ok ok
DoubleFactorialf x!! refused refused refused
Binomialf binomial(x, 3) refused refused refused
FallingFactorialf fallingfactorial(x, 3) refused refused refused
RisingFactorialf risingfactorial(x, 3) refused refused refused
IncompleteGammaf gamma(2, x) refused refused refused
Polygammaf polygamma(1, x) refused refused refused
Erff erf(x) ok ok ok
Erfcf erfc(x) ok ok ok
Erfif erfi(x) ok ok ok
Eif Ei(x) ok ok ok
Lif li(x) ok ok ok
Sif Si(x) ok ok ok
Cif Ci(x) ok ok ok
Shif Shi(x) ok ok ok
Chif Chi(x) ok ok ok
Phif phi(x) ok ok ok
Primef prime(x) refused refused refused
Valuationf valuation(x, 2) refused refused refused
Gcdf gcd(x, 4) refused refused refused
Lcmf lcm(x, 4) refused refused refused
Mobiusf mobius(x) refused refused refused
DivisorSigmaf divisorsigma(1, x) refused refused refused
PrimeNuf primenu(x) refused refused refused
PrimeOmegaf primeomega(x) refused refused refused
PrimePif primepi(x) refused refused refused
Iversonf iverson(x > 0) refused refused refused
Providedf x provided x > 0 refused refused ok
Piecewise piecewise(x provided x > 0, -x provided x <= 0) refused refused ok
Derivativef derivative(x^3, x) refused refused refused
Integralf integral(t x, t, 0, 1) refused refused refused
Limitf limit(sin(t x)/t, t, 0) refused refused refused
Summationf sum(k x, k, 1, 3) refused refused refused
Productf product(x + k, k, 1, 3) refused refused refused
Application apply(lambda(t, t^2), x) refused refused refused
node sample FastExpression LINQ double -> bool
Equalsf x = 2.5 refused ok
Greaterf x > 1 refused ok
GreaterOrEqualf x >= 1 refused ok
Lessf x < 1 refused ok
LessOrEqualf x <= 1 refused ok
Andf x > 0 and x < 2 refused ok
Orf x < 0 or x > 2 refused ok
Xorf x > 0 xor x > 1 refused ok
Notf not x > 0 refused ok
Impliesf x > 0 -> x > 1 refused ok
Dividesf 3 divides x refused refused
Inf x in [0; 1] refused refused

Related: #2145 and #2182, which added most of the nodes in item 3.

Activity

  1. added this to the 2.8 milestone on Oct 29, 2026
  2. Rafael-SOWNet commented on Oct 29, 2026

    @Rafael-SOWNet
    MemberAuthor

    Done in #2196: conj, arg, Re and Im compile in both compilers, and iverson compiles under Compile<TIn, TOut> over the reals. FastExpression now covers 47 of 71 nodes and LINQ over Complex 46, with no disagreements.

    Item 2 is not just a matter of comparing real parts. The interpreter has no truth value for i > 1: a comparison of a non-real number is undecided, and a provided or piecewise whose predicate is undecided has no value. A plain bool can only say false. That makes x provided x > 1 NaN at i, which is right, but not (x > 1) true at i, which is wrong.

    Comparisons over Complex therefore need three-valued logic:

    • comparisons and connectives compile to bool? with Kleene's tables;
    • provided and piecewise give NaN where the predicate is not true.

    The compiled booleans over double have the same gap at a NaN argument, where .NET's comparisons are false.

  3. Rafael-SOWNet commented on Oct 29, 2026

    @Rafael-SOWNet
    MemberAuthor

    Items 1 and 2 are in.

    • A compiled statement with no truth value has none #2208: the LINQ compiler gives a statement the truth value evaluation gives it. A comparison over Complex compiles: it holds or fails where both sides are real, and is null where one is NaN or not real. Over double a comparison with NaN is null too, where it was false, so not (x > 1) is no longer true at NaN. The connectives follow Kleene's tables. A provided has no value where its predicate has none, and a piecewise passes over such a case, as evaluation does. As bool the result is false, and as bool? it is null.
    • FastExpression compiles statements, provided and piecewise #2209: FastExpression compiles statements, provided, piecewise and Iverson brackets. A statement is 1, 0 or NaN on the stack, and the comparisons and connectives are the LINQ compiler's. There is no jump: every case of a piecewise is computed and the one that holds is kept.

    Both are checked against evaluation over a grid of expressions and of points, i and NaN among them.

    Next: mod over Complex in LINQ, through the remainder FastExpression already takes. Then item 3's numerical nodes, starting with binomial. Measuring it turned up binomial(3, 1/2), which evaluation leaves unevaluated where the gamma ratio gives 32/(5 pi); that is fixed separately.

    🤖 Generated with Claude Code

    https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

  4. Rafael-SOWNet commented on Oct 30, 2026

    @Rafael-SOWNet
    MemberAuthor

    Item 3, the numerical nodes. Each compiles in both compilers, over Complex and over double, and is checked against evaluation at a grid of points:

    PR compiles
    #2210 mod over Complex in LINQ, by FastExpression's remainder: the floored one where both sides are real, NaN where either is not
    #2213 binomial, fallingfactorial, risingfactorial and n!!
    #2214 lambertw(k, z)
    #2215 polygamma(n, z) and harmonicnumber(n, m)
    #2216 catalannumber
    #2217 mobius, divisorsigma, primenu, primeomega, valuation, prime and primepi: exact at a whole argument and NaN at any other

    lambertw, harmonicnumber and catalannumber were not in the issue's table.

    Measuring the binomial found two evaluation defects, fixed separately in #2211 and #2212:

    • binomial(3, 1/2) was left unevaluated; it is 32/(5 pi).
    • A binomial at a pole of its denominator, as binomial(1/2, 3/2), was left as written; it is 0.

    Checking the Lambert W kernel against mpmath found a third, fixed in #2214. Evaluation and the kernel shared one start, which put W_0 on the wrong branch on the real axis between -1 and -2/e, where it came out real, and near -1.

    Still refused, by decision:

    • gamma(a, z). For a rational a with an odd denominator, evaluation takes the real root on the negative axis. A compiled a arrives as a double, so 1/3 cannot be told from a decimal near it. The series also needs guard digits that a double does not have. A compiled form would disagree with evaluation.
    • gcd and lcm. Evaluation takes them over the rationals, and every finite double is a rational. A compiled gcd(0.1, 1) would be exact for the double nearest 0.1 and meaningless for 0.1. They could compile at whole arguments only, with NaN elsewhere; that is not decided yet.

    Item 4, the calculus nodes and binders, is untouched.

    🤖 Generated with Claude Code

  5. Rafael-SOWNet commented on Oct 30, 2026

    @Rafael-SOWNet
    MemberAuthor

    Since the last note:

    PR
    #2218 the binomial, the factorial powers, the double factorial and the Catalan numbers are exact at whole arguments. The Catalan numbers had been rounded by (double)BigInteger, which truncates
    #2223 membership and divisibility compile
    #2233 a congruence compiles
    #2234 a quantifier, a subset and a cardinality over a finite set compile
    #2228 a statement asked for as a number is its truth value, 1, 0 or NaN, in the LINQ compiler too; it threw InvalidProtocolProvided
    #2229 a statement where a number is expected, as in sin(x > 1), is refused by both compilers; FastExpression answered it, and LINQ threw KeyNotFoundException
    #2226, #2231 a product, a quotient and a power of reals is real at an infinity. -x at +oo was (-∞, NaN), and x^2 at +oo was (NaN, NaN)

    Measuring these turned up three defects in evaluation:

    work/compilecheck, regenerated on master b8a6a6f, finds no compiled value that disagrees with evaluation:

    • numeric nodes: FastExpression 66 of 75, the LINQ compiler 66 over Complex and 65 over double;
    • statements: 16 of 16 in both compilers.

    Still refused:

    • gamma(a, z), as explained in the last note.
    • A set as a value, which has no compiled form, as opposed to a statement about one.

    Two decisions remain:

    1. gcd and lcm. Evaluation takes them over the rationals, and every finite double is a rational, so a compiled gcd(0.1, 1) would be exact for the double nearest 0.1 and meaningless for 0.1. Should they compile at whole arguments only, with NaN elsewhere, or stay refused?
    2. Item 4, the calculus nodes and the binders: derivative, the definite integral, limit, sum, product and apply(lambda(…)). Compiling expr.Evaled gives the computed form. Should Compile do that itself, at the cost of compiling as slowly as evaluating, or keep refusing, so that the caller decides?

    🤖 Generated with Claude Code

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