Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP fix calculus substitution bugs #156 and #244 #245

Merged
merged 5 commits into from
Mar 24, 2024
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
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');

});
Loading