Skip to content

Commit

Permalink
fix: implement integrals and derivatives for piecewise functions
Browse files Browse the repository at this point in the history
  • Loading branch information
mgreminger committed Apr 3, 2024
1 parent ec7e3e7 commit f046a5c
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 17 deletions.
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
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');

});

0 comments on commit f046a5c

Please sign in to comment.