Skip to content

Commit

Permalink
fix: integral substitution bug, fixes #244
Browse files Browse the repository at this point in the history
nth order derivative units bug was introduced in last commit, this has also been fixed.  New tests added for nth order derivatives and numerical integral limits with compatible and incompatible units.
  • Loading branch information
mgreminger committed Mar 24, 2024
1 parent 66fb59d commit 69aad74
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 8 deletions.
30 changes: 25 additions & 5 deletions public/dimensional_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,14 +940,33 @@ def custom_norm(expression: Matrix):
def custom_dot(exp1: Matrix, exp2: Matrix):
return exp1.dot(exp2)

def custom_derivative(expr: Expr, dummy_diff_var: Symbol, diff_var: Expr, order=None):
def custom_derivative(expr: Expr, dummy_diff_var: Symbol, diff_var: Expr, order: int | None = None):
if order is not None:
return Derivative(expr, dummy_diff_var, order, evaluate=True).subs({dummy_diff_var: diff_var})
else:
return Derivative(expr, dummy_diff_var, evaluate=True).subs({dummy_diff_var: diff_var})

def custom_derivative_dims(expr: Expr, dummy_diff_var: Symbol, diff_var: Expr, order=None):
return expr.subs({dummy_diff_var: diff_var}) / diff_var # type: ignore
def custom_derivative_dims(expr: Expr, dummy_diff_var: Symbol, diff_var: Expr, order: int | None = None):
if order is None:
order = 1
return expr.subs({dummy_diff_var: diff_var}) / diff_var**order # type: ignore

def custom_integral(expr: Expr, dummy_integral_var: Symbol, integral_var: Expr,
lower_limit: Expr | None = None, upper_limit: Expr | None = None,
lower_limit_dims: Expr | None = None, upper_limit_dims: Expr | None = None):
if lower_limit is not None and upper_limit is not None:
return Integral(expr, (dummy_integral_var, lower_limit, upper_limit)).subs({dummy_integral_var: integral_var})
else:
return Integral(expr, dummy_integral_var).subs({dummy_integral_var: integral_var})

def custom_integral_dims(expr: Expr, dummy_integral_var: Symbol, integral_var: Expr,
lower_limit: Expr | None = None, upper_limit: Expr | None = None,
lower_limit_dims: Expr | None = None, upper_limit_dims: Expr | None = None):
if lower_limit is not None and upper_limit is not None:
ensure_dims_all_compatible(lower_limit_dims, upper_limit_dims)
return expr.subs({dummy_integral_var: lower_limit_dims}) * lower_limit_dims # type: ignore
else:
return expr.subs({dummy_integral_var: integral_var}) * integral_var # type: ignore

placeholder_map: dict[Function, PlaceholderFunction] = {
cast(Function, Function('_StrictLessThan')) : {"dim_func": ensure_dims_all_compatible, "sympy_func": StrictLessThan},
Expand Down Expand Up @@ -980,7 +999,8 @@ def custom_derivative_dims(expr: Expr, dummy_diff_var: Symbol, diff_var: Expr, o
cast(Function, Function('_ceil')) : {"dim_func": ensure_unitless_in, "sympy_func": ceiling},
cast(Function, Function('_floor')) : {"dim_func": ensure_unitless_in, "sympy_func": floor},
cast(Function, Function('_round')) : {"dim_func": ensure_unitless_in, "sympy_func": custom_round},
cast(Function, Function('_Derivative')) : {"dim_func": custom_derivative_dims, "sympy_func": custom_derivative}
cast(Function, Function('_Derivative')) : {"dim_func": custom_derivative_dims, "sympy_func": custom_derivative},
cast(Function, Function('_Integral')) : {"dim_func": custom_integral_dims, "sympy_func": custom_integral}
}

placeholder_set = set(placeholder_map.keys())
Expand Down Expand Up @@ -1696,7 +1716,7 @@ def combine_plot_results(results: list[Result | FiniteImagResult | PlotResult |


def subs_wrapper(expression: Expr, subs: dict[str, str] | dict[str, Expr | float] | dict[Symbol, Symbol]) -> Expr:
if len(expression.atoms(Integral, Derivative, Subs)) > 0:
if len(expression.atoms(Subs)) > 0:
# must use slower subs when substituting parameters that may be in a integral or derivative
# subs automatically delays substitution by wrapping integral or derivative in a subs function
return cast(Expr, expression.subs(subs))
Expand Down
8 changes: 6 additions & 2 deletions src/parser/LatexToSympy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,8 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
this.insertTokenCommand('mathrm', child.id(0).children[0] as TerminalNode);
}
const variableOfIntegration = this.mapVariableNames(this.visitId(child.id(1)));
return `Integral(${this.visit(child.expr())}, ${variableOfIntegration})`;
this.params.push(variableOfIntegration);
return `_Integral(Subs(${this.visit(child.expr())}, ${variableOfIntegration}, ${variableOfIntegration}__dummy_var), ${variableOfIntegration}__dummy_var, ${variableOfIntegration})`;
}
}

Expand All @@ -1251,6 +1252,7 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
this.insertTokenCommand('mathrm', child.id(0).children[0] as TerminalNode);
}
const variableOfIntegration = this.mapVariableNames(this.visitId(child.id(1)));
this.params.push(variableOfIntegration);

let lowerLimit: string;
let upperLimit: string;
Expand All @@ -1275,7 +1277,7 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
upperLimit = child.CARET_SINGLE_CHAR_NUMBER().toString()[1];
}

return `Integral(${integrand}, (${variableOfIntegration}, ${lowerLimit}, ${upperLimit}))`;
return `_Integral(Subs(${integrand}, ${variableOfIntegration}, ${variableOfIntegration}__dummy_var), ${variableOfIntegration}__dummy_var, ${variableOfIntegration}, Subs(${lowerLimit}, ${variableOfIntegration}, ${variableOfIntegration}__dummy_var), Subs(${upperLimit}, ${variableOfIntegration}, ${variableOfIntegration}__dummy_var), ${lowerLimit}, ${upperLimit})`;
}
}

Expand All @@ -1295,6 +1297,7 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
this.insertTokenCommand('mathrm', child.id(1).children[0] as TerminalNode);
}
const variableOfDifferentiation = this.mapVariableNames(this.visitId(child.id(2)));
this.params.push(variableOfDifferentiation);
return `_Derivative(Subs(${this.visit(child.expr())}, ${variableOfDifferentiation}, ${variableOfDifferentiation}__dummy_var), ${variableOfDifferentiation}__dummy_var, ${variableOfDifferentiation})`;
}
}
Expand Down Expand Up @@ -1339,6 +1342,7 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
this.insertTokenCommand('mathrm', child.id(1).children[0] as TerminalNode);
}
const variableOfDifferentiation = this.mapVariableNames(this.visitId(child.id(2)));
this.params.push(variableOfDifferentiation);
return `_Derivative(Subs(${this.visit(child.expr())}, ${variableOfDifferentiation}, ${variableOfDifferentiation}__dummy_var), ${variableOfDifferentiation}__dummy_var, ${variableOfDifferentiation}, ${exp1})`;
}
}
Expand Down
46 changes: 45 additions & 1 deletion tests/test_calc.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ test('Test derivative substitution bug #156', async () => {
});


test.skip('Test integral substitution bug #244', async () => {
test('Test integral substitution bug #244', async () => {

await page.setLatex(0, String.raw`c=a\left(b=1\right)`);

Expand All @@ -214,4 +214,48 @@ test.skip('Test integral substitution bug #244', async () => {
let content = await page.textContent('#result-value-1');
expect(parseLatexFloat(content)).toBeCloseTo(0.5, precision);

});


test('Test definite integral with numerical limits that have units', async () => {

await page.setLatex(0, String.raw`\int_{0\left\lbrack m\right\rbrack}^{1\left\lbrack m\right\rbrack}\left(x\right)\mathrm{d}\left(x\right)=`);

await page.keyboard.press('Shift+Enter');
await page.setLatex(1, String.raw`\int_0^{1\left\lbrack m\right\rbrack}\left(x\right)\mathrm{d}\left(x\right)=`);

await page.waitForSelector('.status-footer', {state: 'detached'});

let content = await page.textContent('#result-value-0');
expect(parseLatexFloat(content)).toBeCloseTo(0.5, precision);
content = await page.textContent('#result-units-0');
expect(content).toBe('m^2');

await expect(page.locator("#cell-1 >> text=Dimension Error")).toBeAttached();

});


test('Test units for nth order derivatives', async () => {

await page.setLatex(0, String.raw`\frac{\mathrm{d}^2}{\mathrm{d}\left(y\right)^2}\left(1\right)=`);

await page.keyboard.press('Shift+Enter');
await page.setLatex(1, String.raw`\frac{\mathrm{d}^2}{\mathrm{d}\left(y\right)^2}\left(y^3\right)=`);

await page.keyboard.press('Shift+Enter');
await page.setLatex(2, String.raw`y=1\left\lbrack m\right\rbrack`);

await page.waitForSelector('.status-footer', {state: 'detached'});

let content = await page.textContent('#result-value-0');
expect(parseLatexFloat(content)).toBeCloseTo(0, precision);
content = await page.textContent('#result-units-0');
expect(content).toBe('m^-2');

content = await page.textContent('#result-value-1');
expect(parseLatexFloat(content)).toBeCloseTo(6, precision);
content = await page.textContent('#result-units-1');
expect(content).toBe('m');

});

0 comments on commit 69aad74

Please sign in to comment.