Skip to content

Consolidate scalar and t2s mul × mul merge into a generic n_ary_mul_mul_dispatch #104

Description

@petlenz

Summary

Scalar and tensor-to-scalar both implement the (mul) * (mul) merge that promotes like factors to powers, but using different approaches:

Domain Source Approach
Scalar src/numsim_cas/scalar/simplifier/scalar_simplifier_mul.cpp:100 (n_ary_mul::dispatch(scalar_mul)) Operator-chain: copy lhs into a new mul, then iterate rhs children and apply * via operator*. Each * re-enters the visitor pipeline and triggers full simplification.
T2s src/numsim_cas/tensor_to_scalar/simplifier/tensor_to_scalar_simplifier_mul.cpp:99 (mul_base::dispatch(tensor_to_scalar_mul)) Inlined merge via a domain-local push_or_combine helper (with try_fold_numeric_pow).

Both produce correct results for the cases verified in tests/CoreBugFixTest.h::MulMulMergesLikeFactors{Scalar,T2s}. The architectural concern is drift: the two implementations can diverge in subtle ways (handling of zero children, pow promotion rules, etc.) over time, repeating the bug class that #91 was an instance of.

Surfaced during the #97 investigation — see PR #103.

What a consolidation would look like

Lift the merge into a generic n_ary_mul_mul_dispatch<Traits> in core/simplifier/simplifier_mul.h, paralleling n_ary_add_dispatch. The two approaches need to be reconciled:

  • Scalar's operator-chain is conceptually simpler (delegates to existing simplification rules) but slower (O(N × simplification_cost) per merge).
  • T2s's push_or_combine is more efficient (O(N) with map lookups) but inlines logic that the operator-chain gets for free.

A unified n_ary_mul_mul_dispatch would need to either:

  1. Generalize push_or_combine to be domain-agnostic — needs a Traits::combine(child, existing) hook.
  2. Stick with the operator-chain and accept the perf trade-off.

Either way, the rules that push_or_combine inlines (numeric pow folding, identity-multiplied terms) need explicit handling.

Acceptance criteria

  • Generic n_ary_mul_mul_dispatch<Traits> in core/simplifier/simplifier_mul.h.
  • Scalar wrapper uses it (removes the operator-chain code in n_ary_mul::dispatch(scalar_mul)).
  • T2s wrapper uses it (removes the push_or_combine helper, or moves it into the generic layer).
  • MulMulMergesLikeFactors{Scalar,T2s} continue to pass.
  • Microbenchmark to verify scalar's perf isn't regressed (the operator-chain approach may actually be slower than the unified push_or_combine).

Not blocking

The functional behavior is correct today; this is a code-organization refactor that closes a drift surface. Worth pursuing alongside #95's broader simplifier-unification work but not urgent.

References

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