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

fix: implement integrals and derivatives for piecewise functions #250

Merged
merged 2 commits into from
Apr 4, 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
27 changes: 15 additions & 12 deletions public/dimensional_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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]
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 = 20240324;
const currentVersion = 20240404;
const tutorialHash = "fFjTsnFoSQMLwcvteVoNtL";

const termsVersion = 20240110;
Expand Down
11 changes: 11 additions & 0 deletions src/Updates.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@
}
</style>

<em>April 4, 2024</em>
<h4>Differentiation and Integration Improvements</h4>
<p>
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.
</p>

<br>

<em>March 24, 2024</em>
<h4>Differentiation and Integration Bug Fixes</h4>
<p>
Expand Down
11 changes: 7 additions & 4 deletions src/parser/LatexToSympy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,8 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
}
const variableOfIntegration = this.mapVariableNames(this.visitId(child.id(1)));
this.params.push(variableOfIntegration);
return `_Integral(Subs(${this.visit(child.expr())}, ${variableOfIntegration}, ${variableOfIntegration}__dummy_var), ${variableOfIntegration}__dummy_var, ${variableOfIntegration})`;
let integrand = this.visit(child.expr());
return `_Integral(Subs(${integrand}, ${variableOfIntegration}, ${variableOfIntegration}__dummy_var), ${integrand}, ${variableOfIntegration}__dummy_var, ${variableOfIntegration})`;
}
}

Expand Down Expand Up @@ -1277,7 +1278,7 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
upperLimit = child.CARET_SINGLE_CHAR_NUMBER().toString()[1];
}

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})`;
return `_Integral(Subs(${integrand}, ${variableOfIntegration}, ${variableOfIntegration}__dummy_var), Subs(${integrand}, ${variableOfIntegration}, ${lowerLimit}), ${variableOfIntegration}__dummy_var, ${variableOfIntegration}, Subs(${lowerLimit}, ${variableOfIntegration}, ${variableOfIntegration}__dummy_var), Subs(${upperLimit}, ${variableOfIntegration}, ${variableOfIntegration}__dummy_var), ${lowerLimit}, ${upperLimit})`;
}
}

Expand All @@ -1298,7 +1299,8 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
}
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})`;
let operand = this.visit(child.expr());
return `_Derivative(Subs(${operand}, ${variableOfDifferentiation}, ${variableOfDifferentiation}__dummy_var), ${operand}, ${variableOfDifferentiation}__dummy_var, ${variableOfDifferentiation})`;
}
}

Expand Down Expand Up @@ -1343,7 +1345,8 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
}
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})`;
let operand = this.visit(child.expr());
return `_Derivative(Subs(${operand}, ${variableOfDifferentiation}, ${variableOfDifferentiation}__dummy_var), ${operand}, ${variableOfDifferentiation}__dummy_var, ${variableOfDifferentiation}, ${exp1})`;
}
}

Expand Down
81 changes: 80 additions & 1 deletion tests/test_calc.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,83 @@ test('Test units for nth order derivatives', async () => {
content = await page.textContent('#result-units-1');
expect(content).toBe('m');

});
});

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');

});
Loading