MCPcopy Create free account
hub / github.com/cortex-js/compute-engine / simplify

Function simplify

src/compute-engine/boxed-expression/simplify.ts:99–232  ·  view source on GitHub ↗
(
  expr: Expression,
  options?: Partial<InternalSimplifyOptions>,
  steps?: RuleSteps
)

Source from the content-addressed store, hash-verified

97 // subexpression with no free unknowns must NOT be folded when its
98 // constant-ness comes from an assigned value — `9 - w²` with `w := 5` stays
99 // symbolic, not `-72`, and a binder body `Add(x,1)` with a global `x := 5`
100 // stays `x + 1`. `simplify()` never substitutes assigned values; that is
101 // `.evaluate()`'s job. The `hasAssignedVariable` check is ordered LAST (after
102 // the cheap `unknowns`/operator gates) so it runs only on fold candidates.
103
104 // If purely numeric (no unknowns), evaluate the whole expression.
105 if (
106 expr.unknowns.length === 0 &&
107 BASIC_ARITHMETIC.includes(expr.operator) &&
108 !hasAssignedVariable(expr)
109 ) {
110 const evaluated = expr.evaluate();
111 if (isNumber(evaluated)) return evaluated;
112 }
113
114 // Constant logarithms are folded to their exact value even when they are
115 // not BASIC_ARITHMETIC and are buried inside another operand. This closes
116 // the Divide-context gap where `(ln(e)·y)/x` kept its `ln(e)` factor: the
117 // lazy-Multiply branch simplifies bare `Ln` operands, but the Divide
118 // branch only runs this numeric fold on its operands, so a constant `Ln`
119 // in a Divide numerator/denominator was never reduced. The exactness
120 // contract keeps genuinely-symbolic logs (Ln(2)) as non-literal, so only
121 // closed-form constants (Ln(e) -> 1, Log(1000,10) -> 3) fold here.
122 if (
123 (expr.operator === 'Ln' || expr.operator === 'Log') &&
124 expr.unknowns.length === 0 &&
125 !hasAssignedVariable(expr)
126 ) {
127 const evaluated = expr.evaluate();
128 if (isNumber(evaluated) && !evaluated.isSame(expr)) return evaluated;
129 }
130
131 // Otherwise, recursively process operands
132 const newOps = expr.ops.map((op) => evaluateNumericSubexpressions(op));
133
134 // Check if anything changed
135 const changed = newOps.some((op, i) => op !== expr.ops[i]);
136 if (!changed) return expr;
137
138 // Reconstruct with _fn to avoid re-canonicalization
139 return expr.engine._fn(expr.operator, newOps);
140}
141
142/**
143 * Cheap structural pre-check: is there a product or power that expansion could
144 * act on at all? Keeps the trial expansion below from calling `expand()` on
145 * every expression that reaches the fixpoint.
146 */
147function mightExpand(expr: Expression): boolean {
148 if (!isFunction(expr)) return false;
149 if (
150 expr.operator === 'Multiply' &&
151 expr.ops.some((x) => isFunction(x, 'Add'))
152 )
153 return true;
154 if (expr.operator === 'Power' && isFunction(expr.op1, 'Add')) return true;
155 return expr.ops.some(mightExpand);
156}

Callers 5

explainExpressionFunction · 0.90
simplifyMethod · 0.90
simplifyMethod · 0.90
simplifyMethod · 0.90
simplifyOperandsFunction · 0.70

Calls 12

expToTrigFunction · 0.90
hasSeenFunction · 0.85
costFnFunction · 0.85
simplifyExpressionFunction · 0.85
warnMethod · 0.80
_fuAlgorithmMethod · 0.80
simplifyMethod · 0.65
isSameMethod · 0.65
rulesMethod · 0.65
getRuleSetMethod · 0.65
atMethod · 0.65
sliceMethod · 0.65

Tested by

no test coverage detected