Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/fixtures/latex-equal/symbolic/exponentials-PD-4649.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export default {
mode: "symbolic",
tests: [
// Cases where symbolic validation failed before

{
target: "B=500(\\frac{3}{4})^n",
ne: ["B=500(\\frac{1}{4})^{n-1}"],
},
{
target: "y=500(\\frac{3}{4})^n",
ne: ["y=500(\\frac{1}{4})^{n-1}"],
},
{
target: "a=500(\\frac{3}{4})^d",
ne: ["a=(\\frac{1}{4})^{d-1}"],
},
{
target: "b=500(\\frac{3}{4})^d",
ne: ["b=(\\frac{1}{4})^{d-1}"],
},
{
target: "f=4^g",
ne: ["f=4^{g-1}"],
},
{
target: "h=4^g",
ne: ["h=4^{g-1}"],
},

// Control test — should NOT be equal when using multiplication instead of exponentiation
{
target: "a=4*g",
ne: ["a=4*(g-1)"],
},

// Valid symbolic equivalence (should return true)
{
target: "x=3^n",
eq: ["x=3^n"],
},
{
target: "z=2^y",
eq: ["z=2^y"],
},
{
target: "v=(\\frac{5}{2})^x",
eq: ["v=(\\frac{5}{2})^x"],
},
{
target: "m=(\\frac{3}{4})^a",
eq: ["m=(\\frac{3}{4})^a"],
},
{
target: "k=(1.5)^p",
eq: ["k=(1.5)^p"],
},
],
};
132 changes: 132 additions & 0 deletions src/fixtures/latex-equal/symbolic/exponentials-PD-4896.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
export default {
mode: "symbolic",
tests: [
{
target: "\\left(\\frac{1}{2}\\right)^n",
eq: ["\\left(\\frac{10}{20}\\right)^n"],
},
{
target: "\\left(\\frac{1}{2}\\right)^n",
eq: ["\\left(0.5\\right)^n"],
},
{
target: "\\left(\\frac{1}{2}\\right)^n",
eq: ["\\left(\\frac{2}{4}\\right)^n"],
},
{
target: "\\left(\\frac{10}{20}\\right)^n",
eq: ["\\left(\\frac{1}{2}\\right)^n"],
},
{
target: "\\left(0.5\\right)^n",
eq: ["\\left(\\frac{1}{2}\\right)^n"],
},
{
target: "\\left(\\frac{3}{2}\\right)^a",
eq: ["\\left(\\frac{6}{4}\\right)^a"],
},
{
target: "\\left(\\frac{3}{2}\\right)^a",
eq: ["\\left(1.5\\right)^a"],
},
{
target: "\\left(\\frac{1}{2}\\right)^n",
eq: ["\\left(1\\div2\\right)^n"],
},

// Division written with \left and \right — also should be equivalent

{
target: "\\left(1\\div2\\right)^n",
eq: ["\\left(10\\div20\\right)^n"],
},
{
target: "\\left(1\\div2\\right)^n",
eq: ["\\left(0.5\\right)^n"],
},
{
target: "\\left(1\\div2\\right)^n",
eq: ["\\left(2\\div4\\right)^n"],
},
{
target: "\\left(10\\div20\\right)^n",
eq: ["\\left(1\\div2\\right)^n"],
},
{
target: "\\left(0.5\\right)^n",
eq: ["\\left(1\\div2\\right)^n"],
},
{
target: "\\left(3\\div2\\right)^n",
eq: ["\\left(6\\div4\\right)^n"],
},
{
target: "\\left(3\\div2\\right)^n",
eq: ["\\left(1.5\\right)^n"],
},
// All of the following SHOULD be considered equivalent (eq)

{
target: "(\\frac{1}{2})^n",
eq: ["(\\frac{10}{20})^n"],
},
{
target: "(\\frac{1}{2})^n",
eq: ["(0.5)^n"],
},
{
target: "(\\frac{1}{2})^n",
eq: ["(\\frac{2}{4})^n"],
},
{
target: "(\\frac{10}{20})^n",
eq: ["(\\frac{1}{2})^n"],
},
{
target: "(0.5)^n",
eq: ["(\\frac{1}{2})^n"],
},
{
target: "(\\frac{3}{2})^a",
eq: ["(\\frac{6}{4})^a"],
},
{
target: "(\\frac{3}{2})^a",
eq: ["(1.5)^a"],
},
{
target: "(\\frac{1}{2})^n",
eq: ["(1\\div2)^n"],
},

// Control set – same expressions using division
{
target: "(1\\div2)^n",
eq: ["(10\\div20)^n"],
},
{
target: "(1\\div2)^n",
eq: ["(0.5)^n"],
},
{
target: "(1\\div2)^n",
eq: ["(2\\div4)^n"],
},
{
target: "(10\\div20)^n",
eq: ["(1\\div2)^n"],
},
{
target: "(0.5)^n",
eq: ["(1\\div2)^n"],
},
{
target: "(3\\div2)^n",
eq: ["(6\\div4)^n"],
},
{
target: "(3\\div2)^n",
eq: ["(1.5)^n"],
},
],
};
15 changes: 15 additions & 0 deletions src/symbolic/compare-equations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
transformEqualityInExpression,
expressionsCanBeCompared,
solveQuadraticEquation,
hasSymbolicExponent,
areNumericallyEquivalent,
} from "./utils";

const m: any = mathjs;
Expand Down Expand Up @@ -49,6 +51,19 @@ export const compareEquations = (
let firstEquationCoefficients: number[];
let secondEquationCoefficients: number[];

if (
hasSymbolicExponent(firstExpression) ||
hasSymbolicExponent(secondExpression)
) {
const isEquivalent = areNumericallyEquivalent(
firstExpression,
secondExpression,
firstEquationVariablesName
);

return isEquivalent;
}

if (firstEquationVariablesName.length === 1) {
firstEquationCoefficients = getCoefficients(
firstExpression,
Expand Down
47 changes: 45 additions & 2 deletions src/symbolic/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export const expressionsCanBeCompared = (

firstEquation.traverse(function (node, path, parent) {
if (node.isSymbolNode) {
firstSymbolNode = true
seriesNode = seriesNode || node.name.includes("[")
firstSymbolNode = true;
seriesNode = seriesNode || node.name.includes("[");
}
noFunctionOrArray =
noFunctionOrArray || node.isFunctionNode || node.isArrayNode;
Expand All @@ -46,6 +46,49 @@ export const expressionsCanBeCompared = (
return noFunctionOrArray && symbolNode && !seriesNode;
};

export const hasSymbolicExponent = (node: MathNode): boolean => {
let found = false;

node.traverse((n) => {
if (
n.isOperatorNode &&
n.op === "^" &&
!n.args[1]?.isConstantNode // Exponent is symbolic or compound
) {
found = true;
}
});

return found;
};

export const areNumericallyEquivalent = (
exprA: MathNode,
exprB: MathNode,
variables: string[],
tolerance: number = 1e-10
): boolean => {
const compiledA = m.compile(exprA.toString());
const compiledB = m.compile(exprB.toString());

const testValues = [1, 2, 3, 4, 5];

return testValues.every((val) => {
const scope = variables.reduce((acc, v) => {
acc[v] = val;
return acc;
}, {} as Record<string, number>);

try {
const resultA = compiledA.evaluate(scope);
const resultB = compiledB.evaluate(scope);
return Math.abs(resultA - resultB) < tolerance;
} catch {
return false;
}
});
};

// move the terms of the equations to the left hand side
export const transformEqualityInExpression = (equality: MathNode) =>
// remove added/subtracted numbers/variables from both sides of the equation
Expand Down