Skip to content

Commit

Permalink
Merge pull request #245 from mgreminger/calc-fix
Browse files Browse the repository at this point in the history
WIP fix calculus substitution bugs #156 and #244
  • Loading branch information
mgreminger authored Mar 24, 2024
2 parents c104781 + 27406e4 commit 4dde262
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 6 deletions.
32 changes: 31 additions & 1 deletion public/dimensional_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,34 @@ 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: 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: 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},
cast(Function, Function('_LessThan')) : {"dim_func": ensure_dims_all_compatible, "sympy_func": LessThan},
Expand Down Expand Up @@ -971,6 +999,8 @@ def custom_dot(exp1: Matrix, exp2: Matrix):
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('_Integral')) : {"dim_func": custom_integral_dims, "sympy_func": custom_integral}
}

placeholder_set = set(placeholder_map.keys())
Expand Down Expand Up @@ -1686,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)) > 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
2 changes: 1 addition & 1 deletion src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
const apiUrl = window.location.origin;
const currentVersion = 20240321;
const currentVersion = 20240324;
const tutorialHash = "fFjTsnFoSQMLwcvteVoNtL";
const termsVersion = 20240110;
Expand Down
14 changes: 14 additions & 0 deletions src/Updates.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@
}
</style>

<em>March 24, 2024</em>
<h4>Differentiation and Integration Bug Fixes</h4>
<p>
Two bugs that occur in certain situations when performing substitutions
with
<a href="https://github.com/mgreminger/EngineeringPaper.xyz/issues/156" target="_blank">derivatives</a>
and
<a href="https://github.com/mgreminger/EngineeringPaper.xyz/issues/244" target="_blank">integrals</a>
have been fixed. This fix also has the benefit of speeding up calculations for sheets that
make use of derivatives or integrals.
</p>

<br>

<em>March 21, 2024</em>
<h4>Custom Right Click Context Menu Added</h4>
<p>
Expand Down
12 changes: 8 additions & 4 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,7 +1297,8 @@ 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)));
return `Derivative(${this.visit(child.expr())}, ${variableOfDifferentiation}, evaluate=False)`;
this.params.push(variableOfDifferentiation);
return `_Derivative(Subs(${this.visit(child.expr())}, ${variableOfDifferentiation}, ${variableOfDifferentiation}__dummy_var), ${variableOfDifferentiation}__dummy_var, ${variableOfDifferentiation})`;
}
}

Expand Down Expand Up @@ -1339,7 +1342,8 @@ 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)));
return `Derivative(${this.visit(child.expr())}, ${variableOfDifferentiation}, ${exp1}, evaluate=False)`;
this.params.push(variableOfDifferentiation);
return `_Derivative(Subs(${this.visit(child.expr())}, ${variableOfDifferentiation}, ${variableOfDifferentiation}__dummy_var), ${variableOfDifferentiation}__dummy_var, ${variableOfDifferentiation}, ${exp1})`;
}
}

Expand Down
80 changes: 80 additions & 0 deletions tests/test_calc.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,83 @@ test('Test substitution of differential variable', async () => {
expect(content).toBe('');
});


test('Test derivative substitution bug #156', async () => {

await page.setLatex(0, String.raw`y=20\cdot x`);

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

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

let content = await page.textContent('#result-value-1');
expect(content).toBe('40 x');

});


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

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

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

await page.click('#add-piecewise-cell');

await page.locator('#piecewise-parameter-2 math-field.editable').type('a');
await page.locator('#piecewise-expression-2-0 math-field.editable').type('1');
await page.locator('#piecewise-expression-2-1 math-field.editable').type('-1');
await page.locator('#piecewise-condition-2-0 math-field.editable').type('b>=0');

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

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 4dde262

Please sign in to comment.