Skip to content

Complex and Fuzzy number improvements and bug fixes

Choose a tag to compare

@rchillyard rchillyard released this 17 Mar 11:43
· 27 commits to main since this release
de18e3f

Number Library — Release Notes since 1.8.1


Version 1.9.3

Work Item 8 — Complex Logarithm

  • Implemented log(z) for complex z via the identity log(r·e^(iθ)) = log(r) + iθ
  • IsEuler extractor introduced for structural matching of Euler-form expressions
  • Complex log rules placed in UniFunction.identitiesMatcher to avoid infinite
    expansion loops
  • Special angle identities: exp(iπ) = -1, exp(iπ/2) = i, etc.
  • Euler special-value cases with .normalize on Literal(Angle) to handle all
    canonical angle representations

Work Item 9 — Complex Trig/Hyperbolic Structural Rules

  • Structural simplification rules for complex trig and hyperbolic functions:
    cos(ix) = cosh(x), sin(ix) = i·sinh(x), cosh(ix) = cos(x),
    sinh(ix) = i·sin(x)
  • IsImaginaryExpression extractor for matching expressions of the form n·i
  • operandsMatcher guards prevent eager evaluation of trig arguments containing
    I before structural rules fire
  • Special angle identities for concrete values: sin(π/6) = 1/2, etc.

Work Item 10 — Complex Approximation

  • approximationComplex path added to materialize for approximate evaluation
    of complex expressions
  • applyComplex implemented on ExpressionMonoFunction
  • approximationComplex on Approximate and subtypes: ValueExpression,
    UniFunction, BiFunction, Expression
  • materialize falls back via .orElse(approximationComplex) when symbolic
    simplification cannot fully reduce an expression
  • IsImaginaryExpression extractor generalised; n*i rule added as bonus
  • Known limitation: fuzzy.toDouble returns modulus for complex values

Work Item 11 — Pythagorean Identities

  • sin²(z) + cos²(z) = 1 and cosh²(z) - sinh²(z) = 1 for symbolic complex z
  • leaveOperandsAsIs mechanism on CompositeExpression prevents eager expansion
    of hyperbolic functions before identity matchers fire
  • IsHyperbolic extractor introduced; leaveOperandsAsIs extended to protect
    all hyperbolic BiFunction pairs
  • shouldStaySymbolic exempts trig/hyperbolic functions with complex arguments,
    allowing simplifyExpand to fire correctly
  • containsI method added to Expression
  • Deferred: cos²(x) - sin²(x) = cos(2x) symbolic identity (Issue #194)
  • Deferred: Pythagorean identity for fully evaluable concrete numeric x
    (architecturally blocked — requires symbolic variable support)

Work Item 12 — Canonical Ordering of BiFunction Operands

  • given Ordering[Expression] in Expression companion object: primary key
    AtomExpression < CompositeExpression; within atoms by type rank; within
    composites by depth
  • IsCommutative extractor added for Sum and Product BiFunctions
  • structuralMatcher rule swaps non-canonical commutative operands:
    BiFunction(composite, atom, f) → BiFunction(atom, composite, f)
  • BiFunctionTableSpec added with comprehensive table-driven tests for all
    BiFunction matcher rules

Bug fixes uncovered by WI12

  • BiFunction.equals: use === (Cats Eq) in operandsMatch instead of ==
  • matchBiFunctionIdentitiesPower: added missing 1^x → 1 and x^0 → 1 cases
  • isSame (ComplexPolar/Cartesian): convert polar to Cartesian for
    subtraction rather than Cartesian to polar
  • BiFunction.leaveOperandsAsIs: extended to protect all hyperbolic BiFunction
    pairs, not just IsCoshSquared/IsSinhSquared
  • Matchers.scala code review: eitherOr triple-evaluation bug fixed; retype[S]
    method added for phantom-type Miss casts; simplifyLazy renamed to
    simplifyExpand

Known issues deferred from WI12

  • Issue #196: Box/Gaussian fuzz combination too strict in subtraction;
    workaround applied in affected tests using Number("x.xxxx(20)") notation
  • Issue #197: Complex + Real asymmetric addition; IsImaginaryExpressionCommutative
    rule commented out pending fix

Version 1.9.1

Scala 3 Migration of core module

  • core module migrated from Scala 2 to Scala 3
  • given/using replaces implicit val/implicit def throughout
  • New Scala 3 control structure syntax adopted
  • Cross-module consistency achieved: all modules now on Scala 3

Version 1.9.2 (unreleased)

Work Item 13 — Trapezoid Fuzz Shape

  • Trapezoid(a, b) case class added to Shape hierarchy, representing the
    convolution of two Box distributions with half-widths a ≤ b
  • wiggle formula: flat-top region returns b - a; ramp region returns
    (a + b) - 2√(ab·confidence); threshold a/b guards flat-top branch
  • probability formula: flat-top region x/b; ramp region uses quadratic formula
  • sigma = √((a² + b²) / 3) for CLT conversion at n ≥ 3 Box convolutions
  • AbsoluteFuzz.* updated: Box ⊗ Box → Trapezoid(a, b) (or Box if one is
    negligible via negligibleRatio)
  • RelativeFuzz.* similarly updated for Box⊗Box → Trapezoid
  • negligibleRatio = 1E6 introduced to suppress spurious Trapezoid creation

Work Item 14 — Suppress Machine-Epsilon Fuzz

  • maybeAddFuzz in GeneralNumber suppresses machine-epsilon fuzz when real
    fuzz is already present
  • negligibleRatio = 1E6 constant prevents spurious Trapezoid creation from
    double-precision noise

Version 1.9.3 (unreleased — in progress)

Work Item 15 — Fix Fuzz Combination Machinery

The full Box⊗Box subtraction pipeline now correctly produces Trapezoid fuzz
end-to-end. A chain of interconnected issues was identified and fixed:

Root cause fixes

  • maybeAddFuzz restored to only add fuzz when fuzz.isEmpty; intelligence
    for suppression moved entirely into Fuzziness.* via the new three-tier rules.
    Previously, specialize would unconditionally convolve fresh Box fuzz onto
    numbers already carrying Trapezoid fuzz, burying the Trapezoid under Gaussian.
  • combine was gating Box⊗Box convolution on relative == true, causing
    AbsoluteFuzz Box⊗Box to fall through to normalizeShape → Gaussian.
    Guard simplified to f1.shape == Box && f2.shape == Box regardless of style.
  • AbsoluteFuzz.* was missing cross-shape cases (Box⊗Trapezoid,
    Trapezoid⊗Gaussian, Trapezoid⊗Trapezoid). All now handled uniformly via
    sigma conversion.
  • RelativeFuzz.* made fully consistent with AbsoluteFuzz.* for all
    cross-shape combinations.
  • monadicFuzziness now short-circuits for exact linear operations (negation,
    scale by Rational) via op.fuzz: Option[Int], preserving absolute/relative
    style and Box shape through negation. Previously, MonadicOperationNegate
    with fuzz: Int = 0 always introduced double-precision fuzz.

New three-tier combination rules (Fuzziness.applyRules)

Rule 2 (applied first): if larger/smaller > negligibleRatio (9),
        keep larger unchanged — prevents spurious shape promotion.
Rule 1 (applied second): if either magnitude < doublePrecisionFloor (≈ 4.4E-16),
        scale larger by sqrt(2), keep shape — double-precision noise stays noise.
Rule 3: full convolution — Box⊗Box → Trapezoid, all else → Gaussian.
  • negligibleRatio reduced from 1E6 to 9
  • doublePrecisionFloor = DoublePrecisionTolerance * 2 introduced
  • NaN/zero guard added to applyRules to prevent Trapezoid(NaN, NaN)
  • sanitize helper added to Fuzziness companion: strips NaN/infinite
    magnitudes at the boundary of fuzz arithmetic in monadicFuzziness and
    applyCoefficients

Other fixes

  • Shape.effectiveMagnitude(l) added to Shape trait; Trapezoid returns
    sigma, Box and Gaussian return l. Used by AbsoluteFuzz.getQualifiedString
    to fix runaway rendering of Trapezoid-fuzz numbers (was producing
    "3.000...000[]" instead of "3.00(41)").
  • getPowerCoefficients: math.log(math.abs(z)) fixes ancient NaN bug
    for negative base values.
  • Default confidence changed from 0.5 to oneSigma = 0.317, corresponding
    to the standard "1 sigma" statistical threshold (P ≈ 68.3%). Gives
    isProbablyZero a natural, distribution-agnostic interpretation.

Shape promotion table

this \ convolute Box Trapezoid Gaussian
Box Trapezoid Gaussian Gaussian
Trapezoid Gaussian Gaussian Gaussian
Gaussian Gaussian Gaussian Gaussian

(Subject to Rules 1 and 2 suppressing promotion when magnitudes differ greatly.)

Result: 3,825 passing, 4 ignored (net +3 vs prior commit).


Known Deferred Issues (as of WI15)

  • Issue #196: Box/Gaussian fuzz combination too strict in subtraction
  • Issue #197: Complex + Real asymmetric addition
  • Issue #193: Symbolic Pythagorean identities for concrete numeric arguments
    (requires symbolic variable support)
  • Issue #194: cos²(x) - sin²(x) = cos(2x) identity
  • FuzzyNumber with None fuzz should be ExactNumber
  • tan/tanh as first-class functions (currently Sin/Cos division)