Skip to content

Commit 79adc27

Browse files
committed
feat: fallback to numeric comparison for symbolic exponents in equations comparison, added test cases PD-4890
1 parent cd4d4be commit 79adc27

3 files changed

Lines changed: 253 additions & 0 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
export default {
2+
mode: "symbolic",
3+
tests: [
4+
// Cases where symbolic validation failed before
5+
6+
{
7+
target: "B=500(\\frac{3}{4})^n",
8+
ne: ["B=500(\\frac{1}{4})^{n-1}"],
9+
},
10+
{
11+
target: "y=500(\\frac{3}{4})^n",
12+
ne: ["y=500(\\frac{1}{4})^{n-1}"],
13+
},
14+
{
15+
target: "a=500(\\frac{3}{4})^d",
16+
ne: ["a=(\\frac{1}{4})^{d-1}"],
17+
},
18+
{
19+
target: "b=500(\\frac{3}{4})^d",
20+
ne: ["b=(\\frac{1}{4})^{d-1}"],
21+
},
22+
{
23+
target: "f=4^g",
24+
ne: ["f=4^{g-1}"],
25+
},
26+
{
27+
target: "h=4^g",
28+
ne: ["h=4^{g-1}"],
29+
},
30+
31+
// Control test — should NOT be equal when using multiplication instead of exponentiation
32+
{
33+
target: "a=4*g",
34+
ne: ["a=4*(g-1)"],
35+
},
36+
37+
// Valid symbolic equivalence (should return true)
38+
{
39+
target: "x=3^n",
40+
eq: ["x=3^n"],
41+
},
42+
{
43+
target: "z=2^y",
44+
eq: ["z=2^y"],
45+
},
46+
{
47+
target: "v=(\\frac{5}{2})^x",
48+
eq: ["v=(\\frac{5}{2})^x"],
49+
},
50+
{
51+
target: "m=(\\frac{3}{4})^a",
52+
eq: ["m=(\\frac{3}{4})^a"],
53+
},
54+
{
55+
target: "k=(1.5)^p",
56+
eq: ["k=(1.5)^p"],
57+
},
58+
],
59+
};
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
export default {
2+
mode: "symbolic",
3+
tests: [
4+
{
5+
target: "\\left(\\frac{1}{2}\\right)^n",
6+
eq: ["\\left(\\frac{10}{20}\\right)^n"],
7+
},
8+
{
9+
target: "\\left(\\frac{1}{2}\\right)^n",
10+
eq: ["\\left(0.5\\right)^n"],
11+
},
12+
{
13+
target: "\\left(\\frac{1}{2}\\right)^n",
14+
eq: ["\\left(\\frac{2}{4}\\right)^n"],
15+
},
16+
{
17+
target: "\\left(\\frac{10}{20}\\right)^n",
18+
eq: ["\\left(\\frac{1}{2}\\right)^n"],
19+
},
20+
{
21+
target: "\\left(0.5\\right)^n",
22+
eq: ["\\left(\\frac{1}{2}\\right)^n"],
23+
},
24+
{
25+
target: "\\left(\\frac{3}{2}\\right)^a",
26+
eq: ["\\left(\\frac{6}{4}\\right)^a"],
27+
},
28+
{
29+
target: "\\left(\\frac{3}{2}\\right)^a",
30+
eq: ["\\left(1.5\\right)^a"],
31+
},
32+
{
33+
target: "\\left(\\frac{1}{2}\\right)^n",
34+
eq: ["\\left(1\\div2\\right)^n"],
35+
},
36+
37+
// Division written with \left and \right — also should be equivalent
38+
39+
{
40+
target: "\\left(1\\div2\\right)^n",
41+
eq: ["\\left(10\\div20\\right)^n"],
42+
},
43+
{
44+
target: "\\left(1\\div2\\right)^n",
45+
eq: ["\\left(0.5\\right)^n"],
46+
},
47+
{
48+
target: "\\left(1\\div2\\right)^n",
49+
eq: ["\\left(2\\div4\\right)^n"],
50+
},
51+
{
52+
target: "\\left(10\\div20\\right)^n",
53+
eq: ["\\left(1\\div2\\right)^n"],
54+
},
55+
{
56+
target: "\\left(0.5\\right)^n",
57+
eq: ["\\left(1\\div2\\right)^n"],
58+
},
59+
{
60+
target: "\\left(3\\div2\\right)^n",
61+
eq: ["\\left(6\\div4\\right)^n"],
62+
},
63+
{
64+
target: "\\left(3\\div2\\right)^n",
65+
eq: ["\\left(1.5\\right)^n"],
66+
},
67+
// All of the following SHOULD be considered equivalent (eq)
68+
69+
{
70+
target: "(\\frac{1}{2})^n",
71+
eq: ["(\\frac{10}{20})^n"],
72+
},
73+
{
74+
target: "(\\frac{1}{2})^n",
75+
eq: ["(0.5)^n"],
76+
},
77+
{
78+
target: "(\\frac{1}{2})^n",
79+
eq: ["(\\frac{2}{4})^n"],
80+
},
81+
{
82+
target: "(\\frac{10}{20})^n",
83+
eq: ["(\\frac{1}{2})^n"],
84+
},
85+
{
86+
target: "(0.5)^n",
87+
eq: ["(\\frac{1}{2})^n"],
88+
},
89+
{
90+
target: "(\\frac{3}{2})^a",
91+
eq: ["(\\frac{6}{4})^a"],
92+
},
93+
{
94+
target: "(\\frac{3}{2})^a",
95+
eq: ["(1.5)^a"],
96+
},
97+
{
98+
target: "(\\frac{1}{2})^n",
99+
eq: ["(1\\div2)^n"],
100+
},
101+
102+
// Control set – same expressions using division
103+
{
104+
target: "(1\\div2)^n",
105+
eq: ["(10\\div20)^n"],
106+
},
107+
{
108+
target: "(1\\div2)^n",
109+
eq: ["(0.5)^n"],
110+
},
111+
{
112+
target: "(1\\div2)^n",
113+
eq: ["(2\\div4)^n"],
114+
},
115+
{
116+
target: "(10\\div20)^n",
117+
eq: ["(1\\div2)^n"],
118+
},
119+
{
120+
target: "(0.5)^n",
121+
eq: ["(1\\div2)^n"],
122+
},
123+
{
124+
target: "(3\\div2)^n",
125+
eq: ["(6\\div4)^n"],
126+
},
127+
{
128+
target: "(3\\div2)^n",
129+
eq: ["(1.5)^n"],
130+
},
131+
],
132+
};

src/symbolic/compare-equations.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,68 @@ export const compareEquations = (
4949
let firstEquationCoefficients: number[];
5050
let secondEquationCoefficients: number[];
5151

52+
const hasSymbolicExponent = (node: MathNode): boolean => {
53+
let found = false;
54+
55+
node.traverse((n) => {
56+
if (
57+
n.isOperatorNode &&
58+
n.op === "^" &&
59+
!n.args[1]?.isConstantNode // Exponent is symbolic or compound
60+
) {
61+
found = true;
62+
}
63+
});
64+
65+
return found;
66+
};
67+
68+
const areNumericallyEquivalent = (
69+
exprA: MathNode,
70+
exprB: MathNode,
71+
variables: string[],
72+
tolerance: number = 1e-10
73+
): boolean => {
74+
const compiledA = m.compile(exprA.toString());
75+
const compiledB = m.compile(exprB.toString());
76+
77+
const testValues = [1, 2, 3, 4, 5];
78+
79+
return testValues.every((val) => {
80+
const scope = variables.reduce((acc, v) => {
81+
acc[v] = val;
82+
return acc;
83+
}, {} as Record<string, number>);
84+
85+
try {
86+
const resultA = compiledA.evaluate(scope);
87+
const resultB = compiledB.evaluate(scope);
88+
return Math.abs(resultA - resultB) < tolerance;
89+
} catch {
90+
return false;
91+
}
92+
});
93+
};
94+
95+
if (
96+
hasSymbolicExponent(firstExpression) ||
97+
hasSymbolicExponent(secondExpression)
98+
) {
99+
const isEquivalent = areNumericallyEquivalent(
100+
firstExpression,
101+
secondExpression,
102+
firstEquationVariablesName
103+
);
104+
console.log(
105+
"Symbolic exponent detected, fallback to numeric equivalence:",
106+
firstExpression.toString(),
107+
secondExpression.toString(),
108+
"=>",
109+
isEquivalent
110+
);
111+
return isEquivalent;
112+
}
113+
52114
if (firstEquationVariablesName.length === 1) {
53115
firstEquationCoefficients = getCoefficients(
54116
firstExpression,

0 commit comments

Comments
 (0)