diff --git a/public/dimensional_analysis.py b/public/dimensional_analysis.py index bb03db12..a9c5d40e 100644 --- a/public/dimensional_analysis.py +++ b/public/dimensional_analysis.py @@ -69,6 +69,8 @@ Integer ) +from sympy import S + class ExprWithAssumptions(Expr): is_finite: bool is_integer: bool @@ -867,7 +869,22 @@ def custom_latex(expression: Expr) -> str: _range = Function("_range") -def ensure_dims_all_compatible(*args): +def dimensional_dependencies_equal(input1, input2): + keys1 = set(input1.keys()) + keys2 = set(input2.keys()) + + for key in keys1 ^ keys2: # symmetric difference + if input1.get(key, S.Zero).round(EXP_NUM_DIGITS) != S.Zero or \ + input2.get(key, S.Zero).round(EXP_NUM_DIGITS) != S.Zero: + return False + + for key in keys1 & keys2: # union + if input1[key].round(EXP_NUM_DIGITS) != input2[key].round(EXP_NUM_DIGITS): + return False + + return True + +def ensure_dims_all_compatible(*args, error_message=None): if args[0].is_zero: if all(arg.is_zero for arg in args): first_arg = sympify('0') @@ -880,10 +897,14 @@ def ensure_dims_all_compatible(*args): return first_arg first_arg_dims = custom_get_dimensional_dependencies(first_arg) - if all(custom_get_dimensional_dependencies(arg) == first_arg_dims for arg in args[1:]): + + if all(dimensional_dependencies_equal(custom_get_dimensional_dependencies(arg), first_arg_dims) for arg in args[1:]): return first_arg - raise TypeError('All input arguments to function need to have compatible units') + if error_message is None: + raise TypeError('All input arguments to function need to have compatible units') + else: + raise TypeError(error_message) def ensure_dims_all_compatible_scalar_or_matrix(*args): if len(args) == 1 and is_matrix(args[0]): @@ -1161,7 +1182,8 @@ def custom_integral_dims(local_expr: Expr, global_expr: Expr, dummy_integral_var return global_expr * integral_var # type: ignore def custom_add_dims(*args: Expr): - return Add(*[Abs(arg) for arg in args]) + return ensure_dims_all_compatible(*[Abs(arg) for arg in args], + error_message="Only equivalent dimensions can be added or subtracted.") def custom_pow(base: Expr, exponent: Expr): large_rational = False diff --git a/tests/test_basic.spec.mjs b/tests/test_basic.spec.mjs index 95ea83d8..192a1110 100644 --- a/tests/test_basic.spec.mjs +++ b/tests/test_basic.spec.mjs @@ -780,6 +780,28 @@ test('Test zero canceling bug with exponent', async () => { expect(content).toBe('m'); }); +test('Test floating point exponent rounding', async () => { + + await page.setLatex(0, String.raw`1\left\lbrack m\right\rbrack+1\left\lbrack\frac{N^{\frac13}}{m^{\frac23}}\right\rbrack\cdot1\left\lbrack\frac{m^{\frac53}}{N^{\frac13}}\right\rbrack=`); + await page.click('#add-math-cell'); + await page.setLatex(1, String.raw`1\left\lbrack N\cdot s^{.0000000000001}\right\rbrack+2\left\lbrack N\right\rbrack=`); + await page.click('#add-math-cell'); + await page.setLatex(2, String.raw`1\left\lbrack N\cdot s^{.000000000001}\right\rbrack+2\left\lbrack N\right\rbrack=`); + + await page.waitForSelector('text=Updating...', {state: 'detached'}); + + let content = await page.textContent('#result-value-0'); + expect(parseLatexFloat(content)).toBeCloseTo(2, precision); + content = await page.textContent('#result-units-0'); + expect(content).toBe('m'); + + content = await page.textContent('#result-value-1'); + expect(parseLatexFloat(content)).toBeCloseTo(3, precision); + content = await page.textContent('#result-units-1'); + expect(content).toBe('N'); + + await expect(page.locator('#cell-2 >> text=Dimension Error')).toBeVisible(); +}); test('Test function notation with integrals', async () => {