diff --git a/public/dimensional_analysis.py b/public/dimensional_analysis.py index ac7c94bf..e1c7ee28 100644 --- a/public/dimensional_analysis.py +++ b/public/dimensional_analysis.py @@ -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}, @@ -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()) @@ -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)) diff --git a/src/App.svelte b/src/App.svelte index 56e1b82c..326f9735 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -90,7 +90,7 @@ const apiUrl = window.location.origin; - const currentVersion = 20240321; + const currentVersion = 20240324; const tutorialHash = "fFjTsnFoSQMLwcvteVoNtL"; const termsVersion = 20240110; diff --git a/src/Updates.svelte b/src/Updates.svelte index fc621451..5ffae423 100644 --- a/src/Updates.svelte +++ b/src/Updates.svelte @@ -16,6 +16,20 @@ } +March 24, 2024 +

Differentiation and Integration Bug Fixes

+

+ Two bugs that occur in certain situations when performing substitutions + with + derivatives + and + integrals + have been fixed. This fix also has the benefit of speeding up calculations for sheets that + make use of derivatives or integrals. +

+ +
+ March 21, 2024

Custom Right Click Context Menu Added

diff --git a/src/parser/LatexToSympy.ts b/src/parser/LatexToSympy.ts index efd06dcb..b4d3a8da 100644 --- a/src/parser/LatexToSympy.ts +++ b/src/parser/LatexToSympy.ts @@ -1235,7 +1235,8 @@ export class LatexToSympy extends LatexParserVisitor { 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'); + +}); \ No newline at end of file