From f046a5c052ffada16991e1d8833a8a7264686df9 Mon Sep 17 00:00:00 2001 From: mgreminger Date: Wed, 3 Apr 2024 16:05:44 -0500 Subject: [PATCH 1/2] fix: implement integrals and derivatives for piecewise functions --- public/dimensional_analysis.py | 27 +++++++----- src/parser/LatexToSympy.ts | 11 +++-- tests/test_calc.spec.mjs | 81 +++++++++++++++++++++++++++++++++- 3 files changed, 102 insertions(+), 17 deletions(-) diff --git a/public/dimensional_analysis.py b/public/dimensional_analysis.py index e1c7ee28..09945e42 100644 --- a/public/dimensional_analysis.py +++ b/public/dimensional_analysis.py @@ -940,33 +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: int | None = None): +def custom_derivative(local_expr: Expr, global_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}) + return Derivative(local_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}) + return Derivative(local_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): +def custom_derivative_dims(local_expr: Expr, global_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 + return global_expr / diff_var**order # type: ignore -def custom_integral(expr: Expr, dummy_integral_var: Symbol, integral_var: Expr, +def custom_integral(local_expr: Expr, global_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}) + return Integral(local_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}) + return Integral(local_expr, dummy_integral_var).subs({dummy_integral_var: integral_var}) -def custom_integral_dims(expr: Expr, dummy_integral_var: Symbol, integral_var: Expr, +def custom_integral_dims(local_expr: Expr, global_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 + return global_expr * lower_limit_dims # type: ignore else: - return expr.subs({dummy_integral_var: integral_var}) * integral_var # type: ignore + return global_expr * integral_var # type: ignore placeholder_map: dict[Function, PlaceholderFunction] = { cast(Function, Function('_StrictLessThan')) : {"dim_func": ensure_dims_all_compatible, "sympy_func": StrictLessThan}, @@ -1004,6 +1004,7 @@ def custom_integral_dims(expr: Expr, dummy_integral_var: Symbol, integral_var: E } placeholder_set = set(placeholder_map.keys()) +dummy_var_placeholder_set = (Function('_Derivative'), Function('_Integral')) placeholder_inverse_map = { value["sympy_func"]: key for key, value in reversed(placeholder_map.items()) } placeholder_inverse_set = set(placeholder_inverse_map.keys()) @@ -1043,7 +1044,9 @@ def replace_placeholder_funcs(expr: Expr, func_key: Literal["dim_func"] | Litera if len(expr.args) == 0: return expr - if expr.func in placeholder_set: + if expr.func in dummy_var_placeholder_set and func_key == "dim_func": + return cast(Expr, cast(Callable, placeholder_map[expr.func][func_key])(*(replace_placeholder_funcs(cast(Expr, arg), func_key) if index > 0 else arg for index, arg in enumerate(expr.args)))) + elif expr.func in placeholder_set: return cast(Expr, cast(Callable, placeholder_map[expr.func][func_key])(*(replace_placeholder_funcs(cast(Expr, arg), func_key) for arg in expr.args))) elif func_key == "dim_func" and (expr.func is Mul or expr.func is MatMul): processed_args = [replace_placeholder_funcs(cast(Expr, arg), func_key) for arg in expr.args] diff --git a/src/parser/LatexToSympy.ts b/src/parser/LatexToSympy.ts index b4d3a8da..858705b9 100644 --- a/src/parser/LatexToSympy.ts +++ b/src/parser/LatexToSympy.ts @@ -1236,7 +1236,8 @@ export class LatexToSympy extends LatexParserVisitor { content = await page.textContent('#result-units-1'); expect(content).toBe('m'); -}); \ No newline at end of file +}); + +test('Test unitless calculus when operand contains placeholder function', async () => { + + await page.click('#delete-0'); + + await page.click('#add-piecewise-cell'); + + await page.locator('#piecewise-parameter-0 math-field.editable').type('y'); + await page.locator('#piecewise-expression-0-0 math-field.editable').type('x+1'); + await page.locator('#piecewise-expression-0-1 math-field.editable').type('1-x'); + await page.locator('#piecewise-condition-0-0 math-field.editable').type('x<0'); + + await page.keyboard.press('Shift+Enter'); + await page.setLatex(1, String.raw`y_{prime}=\frac{\mathrm{d}}{\mathrm{d}\left(x\right)}\left(y\right)=`); + + await page.keyboard.press('Shift+Enter'); + await page.setLatex(2, String.raw`\int_{-1}^1\left(y\right)\mathrm{d}\left(x\right)=`); + + await page.keyboard.press('Shift+Enter'); + await page.setLatex(3, String.raw`y_{prime}\left(x=-.5\right)=`); + + await page.waitForSelector('.status-footer', {state: 'detached'}); + + let content = await page.textContent('#result-value-2'); + expect(parseLatexFloat(content)).toBeCloseTo(1, precision); + content = await page.textContent('#result-units-2'); + expect(content).toBe(''); + + content = await page.textContent('#result-value-3'); + expect(parseLatexFloat(content)).toBeCloseTo(1, precision); + content = await page.textContent('#result-units-3'); + expect(content).toBe(''); + +}); + +test('Test calculus units when operand contains placeholder function', async () => { + + await page.click('#delete-0'); + + await page.click('#add-piecewise-cell'); + + await page.locator('#piecewise-parameter-0 math-field.editable').type('y'); + await page.locator('#piecewise-expression-0-0 math-field.editable').type('x+1[m]'); + await page.locator('#piecewise-expression-0-1 math-field.editable').type('1[m]-x'); + await page.locator('#piecewise-condition-0-0 math-field.editable').type('x<0[m]'); + + await page.keyboard.press('Shift+Enter'); + await page.setLatex(1, String.raw`y_{prime}=\frac{\mathrm{d}}{\mathrm{d}\left(x\right)}\left(y^2\right)=`); + + await page.keyboard.press('Shift+Enter'); + await page.setLatex(2, String.raw`\int_{-1\left\lbrack m\right\rbrack}^{1\left\lbrack m\right\rbrack}\left(y\right)\mathrm{d}\left(x\right)=`); + + await page.keyboard.press('Shift+Enter'); + await page.setLatex(3, String.raw`y_{prime}\left(x=-.5\left\lbrack m\right\rbrack\right)=`); + + await page.keyboard.press('Shift+Enter'); + await page.setLatex(4, String.raw`y_{int}=\int\left(y\right)\mathrm{d}\left(x\right)=`); + + await page.keyboard.press('Shift+Enter'); + await page.setLatex(5, String.raw`y_{int}\left(x=1\left\lbrack m\right\rbrack\right)=`); + + await page.waitForSelector('.status-footer', {state: 'detached'}); + + let content = await page.textContent('#result-value-2'); + expect(parseLatexFloat(content)).toBeCloseTo(1, precision); + content = await page.textContent('#result-units-2'); + expect(content).toBe('m^2'); + + content = await page.textContent('#result-value-3'); + expect(parseLatexFloat(content)).toBeCloseTo(1, precision); + content = await page.textContent('#result-units-3'); + expect(content).toBe('m'); + + content = await page.textContent('#result-value-5'); + expect(parseLatexFloat(content)).toBeCloseTo(0.5, precision); + content = await page.textContent('#result-units-5'); + expect(content).toBe('m^2'); + +}); From 546f4ce239fc77c1573a9529376f0aafb37d1627 Mon Sep 17 00:00:00 2001 From: mgreminger Date: Thu, 4 Apr 2024 09:53:34 -0500 Subject: [PATCH 2/2] Bump version and document changes --- src/App.svelte | 2 +- src/Updates.svelte | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/App.svelte b/src/App.svelte index c9480e05..3572ac09 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -90,7 +90,7 @@ const apiUrl = window.location.origin; - const currentVersion = 20240324; + const currentVersion = 20240404; const tutorialHash = "fFjTsnFoSQMLwcvteVoNtL"; const termsVersion = 20240110; diff --git a/src/Updates.svelte b/src/Updates.svelte index 5ffae423..31686199 100644 --- a/src/Updates.svelte +++ b/src/Updates.svelte @@ -16,6 +16,17 @@ } +April 4, 2024 +

Differentiation and Integration Improvements

+

+ Integration and differentiation now support piecewise functions. These updates + will also fix situations that led to dimension errors when performing + integration and differentiation on expressions that used certain builtin functions + such as the min and max functions. +

+ +
+ March 24, 2024

Differentiation and Integration Bug Fixes