Skip to content

V1 9 0#195

Open
rchillyard wants to merge 36 commits intomainfrom
V1_9_0
Open

V1 9 0#195
rchillyard wants to merge 36 commits intomainfrom
V1_9_0

Conversation

@rchillyard
Copy link
Owner

@rchillyard rchillyard commented Mar 7, 2026

Resolves #189, #192, #149

Added ComplexFunctionSpec, mostly pending.
Approximate: Changed return type of fuzzy to Eager;
Eager:
* added imaginary(Scalar) method;
* added Imaginary with unapply method;
InversePower: added create and asComplex methods;
Scalar:
* added compare(Eager,Eager) override;
* fixed bug in mulitplying two Angles together;
Real: power method now returns None instead of Some(NaN)
Fix complex function simplifications and canonicalize log/exp forms

- Add UniFunction(I, Reciprocal) → -I identity (1/i = -i)
- Add IPi extractor for expressions of the form ±iπ in Extractors.scala
- Remove exp(-x) → reciprocal(exp(x)) rewrite, making e^-x canonical
- Add reciprocal(exp(x)) → e^-x rewrite to normalize to canonical form
- Fix BiFunction(E, x, Log) → UniFunction(x, Ln) canonicalization (was backwards)
- Fix e^(log_e(x)) = x identity case for BiFunction(E, x, Log) form
- Fix Log operand ordering inconsistency (partial - further work needed)
- Activate sinh(iπ/2) = i test in ComplexFunctionSpec

In Eager:
- add IntToImaginary
In Approximate:
- rework declaration of fuzzy;
In Expression:
- refactor fuzzy;
Fix Log operand ordering and clean up Power identity cases

- Fix BiFunction(a, b, Log) convention: a is value, b is base (log_b(a))
- Fix toString for Log to render correctly as log_b(a)
- Fix unreachable cases in matchBiFunctionIdentitiesPower by moving
  iπ and ComplexPolar MinusOne cases before the (E, x) catch-all
- Add comment to (E, x) catch-all noting it must remain last among (E, _) cases
- Fix test using wrong Log operand order (BiFunction(E, Pi, Log) → BiFunction(Pi, E, Log))
- Add // TESTME to cases that are not firing in coverage (lines 903, 907)
Split up CompositeExpression;
Add new class: Euler.
Add simplification rules in UniFunction and BiFunction.
Add additional extractors in ExpressionExtractors
feat(expression): implement Euler lazy complex expression with full simplification pipeline

Add Euler(r, θ) as the canonical lazy representation of r·e^(iθ) in the
expression module, materialising to ComplexPolar on evaluation. Implements
all work items from LazyPolarExpressionDesign.md for milestone 1.8.

New files:
- Euler.scala: CompositeExpression subtype with identitiesMatcher,
  structuralMatcher, operandsMatcher, and evaluate via valuableToMaybeField
- ExpressionExtractors.scala: Sin, Cos, Exp, Sinh, Cosh unapply extractors
- EulerSpec.scala: 46 tests covering materialisation, identity simplification,
  de Moivre arithmetic, Euler recognition, and end-to-end multi-step proofs

Modified files:
- BiFunction.scala: add de Moivre rules, HasEuler guard, EulerSumCommutative,
  EulerProductCommutative, SumExpression, i^n cycling rule (period 4),
  fix ExpressionComplementaryCommutative extractLeft to exclude negated forms
- Matchers.scala: add Match.of (by-name, exception-safe), rethrow
  MatcherException in tryMatch, add commutative matcher combinator
- Extractors.scala: make IsZero structural-only to avoid ClassCastException
  when expressions contain I (InversePower)
- shouldStaySymbolic: propagate symbolicity if ANY term is symbolic (was ALL)
- InversePower: remove CanMultiplyAndDivide to fix ClassCastException

All 3763 tests passing, 0 failures, 21 pre-existing ignores.
refactor(expression): clean up dead code and clarify Euler product matching
Remove EulerProductCommutative object and its case from BiFunction.structuralMatcher
since exp(i*θ) is always simplified to Euler(1, θ) before structuralMatcher runs,
making the commutative extractor approach unworkable. The explicit cases
BiFunction(r, Euler(One, θ), Product) and BiFunction(Euler(One, θ), r, Product)
handle both orderings instead. Add explanatory comment.

Remove duplicate I^n cycling rule from BiFunction.identitiesMatcher since
structuralMatcher always handles it first. Cases 0-3 (mod 4) remain in
structuralMatcher where they correctly fire.

All 3763 tests passing, 0 failures, 21 pre-existing ignores.
This was SNAPSHOT version for testing and development.
— add complex/hyperbolic parsing to LaTeXParser

LaTeXParser changes:
- Add 'i' as a mathSymbol mapping to I (imaginary unit)
- Add sinh, cosh to fnName and tupleToFunctionExpression
- Fix implicitMul to use power.* instead of symbolicAtom.* so that
  expressions like 2\e^{i\pi/2} keep base and exponent together

UniFunction.structuralMatcher changes:
- Add recognition rules for exp((i·θ)*k) and exp(k*(i·θ)) — the parser
  produces this flattened form for inputs like \e^{i\pi/2}
- Add recognition rule for exp(Aggregate{*, i, ...terms}) since
  operandsMatcher flattens (i*π)*½ to Aggregate before structuralMatcher runs

Aggregate:
- Add contains() method used by the new Euler recognition rule

LaTeXParserSpec:
- Add tests for i, sinh, cosh, Cartesian a+bi, polar r*e^(iθ) parsing
- Add end-to-end simplification tests for Euler's identity and e^(iπ/2)=i

All 3763 tests passing, 0 failures, 21 pre-existing ignores.
Euler.scala:
- Add top-level IsEuler extractor (unapply returning Option[Euler])
  recognising MinusOne → Euler(1,π), I → Euler(1,π/2), and
  recursively -x → Euler(r,-θ) for any IsEuler(x)

UniFunction.scala:
- Add case to identitiesMatcher: UniFunction(IsEuler(Euler(r,θ)), Ln)
  → (ln(r) + i·θ).simplify  (complex logarithm principal value)

ComplexFunctionSpec.scala:
- Un-pending and rewrite log(-1)=iπ and log(i)=iπ/2 tests
  using symbolic equality rather than Literal(Eager.iPi)

ExpressionMatchersSpec (or wherever):
- Update "simplify ln(-1)" expected value from Literal(ComplexPolar(...))
  to (I * Pi).simplify, reflecting the new symbolic result

All 3,772 tests passing, 0 failures, 19 pre-existing ignores.
UniFunction.operandsMatcher:
- Restore && u.x.contains(I) guard (had been accidentally dropped)
  so that only trig functions with imaginary BiFunction arguments are
  deferred, not all BiFunction arguments

IsMinusOne.unapply:
- Add case MinusOne => Some(MinusOne) so that the MinusOne case object
  itself is matched, not just ValueExpression(WholeNumber(-1)) and
  UniFunction(IsUnity(_), Negate)

IsEuler.unapply:
- Use IsMinusOne(_) instead of literal MinusOne for generality

Noop:
- Return Error only for Noop("Error tester") to support Error propagation test
- Return Miss for all other cases (restoring original behaviour)

Expression.simplify:
- Add case em.Error(e) => throw ExpressionException(...) to propagate
  errors from the matcher pipeline

Expression:
- Add debug method using productIterator for unambiguous structural
  display in test failure messages

All 3,777 tests passing, 0 failures; 15 ignored
I think everything here is good but it doesn't actually solve any of the ten failures in CompleFunctionSpec.
In general, more defs have been changed to lazy vals.
Eager: rename Imaginary as HasImaginary;
InversePower:
* Add squareRoot and cubeRoot
* Fix an error in normalization (for Real objects);
* Add IsSquareRoot and IsImaginary extractors;
* removed unnecessary (and dangerous) normalization in apply;
Real: added specific negate method;
 infrastructure (Layer 1)

core/numerical/Operations.scala:
- Add MonadicOperationSinh, MonadicOperationCosh, MonadicOperationTanh
  using MonadicOperationFunc with correct derivative for fuzz propagation

core/numerical/Number.scala:
- Add sinh, cosh, tanh delegating to new MonadicOperations

core/numerical/Real.scala:
- Add sinh, cosh, tanh delegating to Number
- Add negate returning Real (avoids cast in InversePower extractor)

core/numerical/Field.scala:
- Declare sinh, cosh, tanh as abstract methods

core/numerical/BaseComplex.scala:
- Add sinh, cosh (complex versions using exponential decomposition)
- Add tanh = sinh / cosh
- Remove debug println statements from exp

expression/expr/BiFunction.scala:
- Guard InversePowerTimesNumberCommutative with ip.number.signum >= 0
  to prevent i*2 being collapsed to InversePower(2,-4) = √(-4)

expression/expr/Expression.scala:
- Add debug method using productIterator for unambiguous structural display
- Fix render on CompositeExpression to use simplify rather than matchSimpler

expression/expr/Euler.scala:
- IsEuler.unapply: use IsMinusOne(_) instead of literal MinusOne

expression/expr/ValueExpression.scala:
- IsMinusOne.unapply: add case MinusOne => Some(MinusOne)
- ValueExpression.equals: short-circuit with eq before ===

core/numerical/InversePower (various):
- Add extractor objects for positive and imaginary square roots
- Fix unapply for imaginary case using Real.negate

ComplexFunctionSpec:
- Mark 12 Tier 3 approximate evaluation tests as pending
  (// TODO Work Item 10 — requires approximationComplex)
- Mark i*2 materialisation test as pending (// TODO Issue #149)
- Fix stale expectations for sinh/cosh structural rule tests
- Fix cosh(iπ/2) = 0 test to call .simplify

UniFunction.scala:
- Restore && u.x.contains(I) guard in operandsMatcher

All 3,778 tests passing, 0 failures, 14 pending.
- Add approximationComplex(force: Boolean) to Approximate trait (default None)
- Add applyComplex to ExpressionMonoFunction for sin/cos/sinh/cosh/exp/negate/reciprocal/ln
- Override approximationComplex in ValueExpression, I, UniFunction, BiFunction
- Update materialize to fall back to approximationComplex after approximation
- Add IsImaginaryExpression extractor for use in BiFunction.identitiesMatcher
- Add Product rule for scalar * imaginary → Complex(0, n) in BiFunction.identitiesMatcher
- Fix EulerSpec and ExpressionSpec test expectations for new canonical Complex form
- Un-pending 6 ComplexFunctionSpec tests; keep 2 pending for Issue C (#193)

Resolves #189, #192, #149
- Add IsSinSquared, IsCosSquared, IsSinhSquared, IsCoshSquared extractors
  to Extractors.scala
- Add SumSymmetricCommutative extractor to BiFunction.scala
- Wire CommutativeExtractor into matchBiFunctionIdentitiesSum
- Add sin²+cos²=1 rule (green)
- Add cosh²-sinh²=1 rule (pending: architectural limitation, see WI-13)
- Add leaveOperandsAsIs to CompositeExpression (default propagation)
- Override in BiFunction for IsCoshSquared/IsSinhSquared base cases
- Fix shouldStaySymbolic instance method: add Nameable case in
  CompositeExpression terms check
- Un-pending cosh²-sinh²=1 test for complex z (now green)
- Add materialize-based test for concrete numeric argument
- Keep symbolic test for concrete numeric argument pending
Use Matchers 1.0.16
Renamed simplifyLazy as simplifyExpand.
Previously used == (Java structural equality) which fails when operands
are mathematically equivalent but differently-constructed expressions
(e.g. MinusOne vs Literal(Number(-1)) vs UniFunction(One, Negate)).
Introduces a canonical ordering rule for commutative BiFunction expressions
so that operands always appear in a defined order, reducing the need for
CommutativeExtractor in downstream identity rules.

Core changes:
- IsCommutative extractor (Extractors.scala): matches Sum and Product BiFunctions,
  exposing all three components
- given Ordering[Expression] (Expression companion): primary key AtomExpression <
  CompositeExpression; within atoms by type rank; within composites by depth
- structuralMatcher rule (BiFunction): swaps non-canonical commutative operands
  using BiFunction(y, x, f)
- BiFunctionTableSpec: comprehensive table-driven tests covering all BiFunction
  matcher rules
Add isProbablyZero at the Numerical level.

Bug fixes uncovered by reordering:
- BiFunction.equals: use === (Cats Eq) in operandsMatch instead of ==
- matchBiFunctionIdentitiesPower: add missing 1^x -> 1 and x^0 -> 1 cases
- isSame (ComplexPolar/Cartesian): convert polar to Cartesian for subtraction,
  not Cartesian to polar
- BiFunction.leaveOperandsAsIs: extend to protect all hyperbolic BiFunction pairs,
  not just IsCoshSquared/IsSinhSquared; introduce IsHyperbolic extractor
- shouldStaySymbolic: exempt trig/hyperbolic functions when argument containsI,
  allowing simplifyExpand to fire for complex arguments; introduce containsI

Known issues deferred:
- 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
- FuzzyNumber with None fuzz should be ExactNumber
- IsImaginary should match any Complex(0,x), not just i

3804 tests green; 3 pending
Add Trapezoid(a, b) as a new Shape representing the convolution of two
Box distributions with half-widths a <= b.

- Trapezoid.wiggle: flat-top (confidence > a/b) returns b-a; ramp
  returns (a+b) - 2√(ab·confidence). Triangle case (a==b) handled by
  `a < b` guard on flat-top branch.
- Trapezoid.probability: flat-top returns x/b; ramp returns
  (b-a)/b + 1/(4ab)[4a² - (a+b-x)²]
- Trapezoid.sigma = √((a²+b²)/3) for conversion to equivalent Gaussian
- AbsoluteFuzz.*: Box⊗Box → Trapezoid; Trapezoid⊗Gaussian normalizes first
- RelativeFuzz.*: Box⊗Box → Trapezoid with Trapezoid shape
- normalizeShape: Trapezoid → Gaussian for both AbsoluteFuzz and RelativeFuzz
- combine: Box⊗Box handled directly without normalizing to Gaussian
- Fix Box.probability: was 2*x/l (incorrect), now x/l (correct)
- Add "Box-like trapezoid" test confirming Trapezoid(0.1, 10) ≈ Box(10)
- Comprehensive tests in FuzzinessSpec and FuzzinessSpec2

Pending: WI14 (suppress operationFuzz when input fuzz is already present)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Complex functions are not behaving correctly

1 participant