Skip to content

Commit

Permalink
refactor: add unitless context for error reporting for values that ne…
Browse files Browse the repository at this point in the history
…ed to be unitless

Currently used for exponents, will be used for matrix indices and the arguments to range()
  • Loading branch information
mgreminger committed Oct 23, 2024
1 parent 29ffe31 commit 01c1d9d
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 27 deletions.
40 changes: 24 additions & 16 deletions public/dimensional_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,15 @@ class ImplicitParameter(TypedDict):
# generated on the fly in evaluate_statements function, does in exist in incoming json
class UnitlessSubExpressionName(TypedDict):
name: str
unitlessContext: str

class UnitlessSubExpression(TypedDict):
type: Literal["assignment"]
name: str
sympy: str
params: list[str]
isUnitlessSubExpression: Literal[True]
unitlessContext: str
isFunctionArgument: Literal[False]
isFunction: Literal[False]
unitlessSubExpressions: list['UnitlessSubExpression | UnitlessSubExpressionName']
Expand Down Expand Up @@ -2199,8 +2201,7 @@ def get_scatter_plot_result(combined_scatter: CombinedExpressionScatter,
if not x_values_all_real_and_finite:
return get_scatter_error_object("One or more x values does not evaluate to a finite real value")

if len(x_units_check) > 1 or \
(("Dimension Error" in x_units_check) or ("Exponent Not Dimensionless" in x_units_check)):
if len(x_units_check) > 1 or "Dimension Error" in x_units_check:
return get_scatter_error_object("One or more of the x values has inconsistent units or a dimension error")

y_values: list[float] = []
Expand All @@ -2224,8 +2225,7 @@ def get_scatter_plot_result(combined_scatter: CombinedExpressionScatter,
if not y_values_all_real_and_finite:
return get_scatter_error_object("One or more y values does not evaluate to a finite real value")

if len(y_units_check) > 1 or \
(("Dimension Error" in y_units_check) or ("Exponent Not Dimensionless" in y_units_check)):
if len(y_units_check) > 1 or "Dimension Error" in y_units_check:
return get_scatter_error_object("One or more of the y values has inconsistent units or a dimension error")

return {"plot": True, "data": [{"isScatter": True, "asLines": combined_scatter["asLines"],
Expand All @@ -2247,7 +2247,7 @@ def get_scatter_plot_result(combined_scatter: CombinedExpressionScatter,
if not is_real_and_finite(cast(Result | FiniteImagResult, scatter_x_values)):
return get_scatter_error_object("x value does not evaluate to a finite real value")

if cast(Result, scatter_x_values)["units"] == "Dimension Error" or cast(Result, scatter_x_values)["units"] == "Exponent Not Dimensionless":
if "Dimension Error" in cast(Result, scatter_x_values)["units"]:
return get_scatter_error_object("x value dimension error")

x_values = [float(cast(Result, scatter_x_values)["value"])]
Expand All @@ -2260,7 +2260,7 @@ def get_scatter_plot_result(combined_scatter: CombinedExpressionScatter,
if not is_real_and_finite(cast(Result | FiniteImagResult, scatter_y_values)):
return get_scatter_error_object("y value does not evaluate to a finite real value")

if cast(Result, scatter_y_values)["units"] == "Dimension Error" or cast(Result, scatter_y_values)["units"] == "Exponent Not Dimensionless":
if "Dimension Error" in cast(Result, scatter_y_values)["units"]:
return get_scatter_error_object("y value dimension error")

y_values = [float(cast(Result, scatter_y_values)["value"])]
Expand Down Expand Up @@ -2433,8 +2433,10 @@ def get_result(evaluated_expression: ExprWithAssumptions, dimensional_analysis_e
custom_units_latex = ""

if not all([unitless_sub_expression_dimensionless[local_item["name"]] for local_item in unitless_sub_expressions]):
dim = "Exponent Not Dimensionless"
dim_latex = "Exponent Not Dimensionless"
context_set = {local_item["unitlessContext"] for local_item in unitless_sub_expressions if not unitless_sub_expression_dimensionless[local_item["name"]]}
context_combined = ", ".join(context_set)
dim = f"Dimension Error: {context_combined} Not Dimensionless"
dim_latex = f"Dimension Error: {context_combined} Not Dimensionless"
elif isRange:
# a separate unitsQuery function is used for plots, no need to perform dimensional analysis before subs are made
dim = ""
Expand Down Expand Up @@ -2524,6 +2526,7 @@ def evaluate_statements(statements: list[InputAndSystemStatement],
unitless_sub_expression_subs: dict[str, Expr | float] = {}
unit_sub_expression_dimensionless: dict[str, bool] = {}
function_unitless_sub_expression_replacements: dict[str, dict[Symbol, Symbol]] = {}
function_unitless_sub_expression_context: dict[str, str] = {}
for i, statement in enumerate(expanded_statements):
if statement["type"] == "local_sub" or statement["type"] == "blank":
continue
Expand Down Expand Up @@ -2560,19 +2563,21 @@ def evaluate_statements(statements: list[InputAndSystemStatement],
# sub equations into each other in topological order if there are more than one
function_name = ""
unitless_sub_expression_name = ""
unitless_sub_expression_context = ""
if statement["isFunction"] is True:
is_function = True
function_name = statement["name"]
is_unitless_sub_expression = False
elif statement["isUnitlessSubExpression"] is True:
is_unitless_sub_expression = True
unitless_sub_expression_name = statement["name"]
unitless_sub_expression_context = statement["unitlessContext"]
is_function = False
else:
is_unitless_sub_expression = False
is_function = False
dependency_unitless_sub_expressions = statement["unitlessSubExpressions"]
new_function_unitless_sub_expressions = {}
new_function_unitless_sub_expressions: dict[str, Expr] = {}
final_expression = statement["expression"]
for sub_statement in reversed(temp_statements[0:-1]):
if (sub_statement["type"] == "assignment" or ((is_function or is_unitless_sub_expression) and sub_statement["type"] == "local_sub")) \
Expand Down Expand Up @@ -2605,14 +2610,15 @@ def evaluate_statements(statements: list[InputAndSystemStatement],
function_unitless_sub_expression_replacements.setdefault(current_function_name, {}).update(
{symbols(unitless_sub_expression_name): symbols(unitless_sub_expression_name+current_function_name)}
)
function_unitless_sub_expression_context[unitless_sub_expression_name] = unitless_sub_expression_context

new_function_unitless_sub_expressions[''] = final_expression

for current_function_name, final_expression in new_function_unitless_sub_expressions.items():
while(True):
available_exonponent_subs = set(function_unitless_sub_expression_replacements.get(current_function_name, {}).keys()) & \
available_unitless_subs = set(function_unitless_sub_expression_replacements.get(current_function_name, {}).keys()) & \
final_expression.free_symbols
if len(available_exonponent_subs) == 0:
if len(available_unitless_subs) == 0:
break
final_expression = subs_wrapper(final_expression, function_unitless_sub_expression_replacements[current_function_name])
final_expression = subs_wrapper(final_expression, unitless_sub_expression_subs)
Expand Down Expand Up @@ -2640,17 +2646,19 @@ def evaluate_statements(statements: list[InputAndSystemStatement],

elif is_function:
while(True):
available_exonponent_subs = set(function_unitless_sub_expression_replacements.get(function_name, {}).keys()) & \
available_unitless_subs = set(function_unitless_sub_expression_replacements.get(function_name, {}).keys()) & \
final_expression.free_symbols
if len(available_exonponent_subs) == 0:
if len(available_unitless_subs) == 0:
break
final_expression = subs_wrapper(final_expression, function_unitless_sub_expression_replacements[function_name])
statement["unitlessSubExpressions"].extend([{"name": str(function_unitless_sub_expression_replacements[function_name][key])} for key in available_exonponent_subs])
statement["unitlessSubExpressions"].extend([{"name": str(function_unitless_sub_expression_replacements[function_name][key]),
"unitlessContext": function_unitless_sub_expression_context[str(key)]} for key in available_unitless_subs])
final_expression = subs_wrapper(final_expression, unitless_sub_expression_subs)
if function_name in function_unitless_sub_expression_replacements:
for unitless_sub_expression_i, unitless_sub_expression in enumerate(statement["unitlessSubExpressions"]):
if symbols(unitless_sub_expression["name"]) in function_unitless_sub_expression_replacements[function_name]:
statement["unitlessSubExpressions"][unitless_sub_expression_i] = UnitlessSubExpressionName(name = str(function_unitless_sub_expression_replacements[function_name][symbols(unitless_sub_expression["name"])]))
statement["unitlessSubExpressions"][unitless_sub_expression_i] = UnitlessSubExpressionName(name = str(function_unitless_sub_expression_replacements[function_name][symbols(unitless_sub_expression["name"])]),
unitlessContext = unitless_sub_expression["unitlessContext"])
statement["expression"] = final_expression

elif statement["type"] == "query":
Expand Down Expand Up @@ -2830,7 +2838,7 @@ def evaluate_statements(statements: list[InputAndSystemStatement],
# set should have length of 1 if there is no error (LHS and RHS are the same and there isn't an error)
units = set(units)
if len(units) == 1:
if "Dimension Error" not in units and "Exponent Not Dimensionless" not in units:
if "Dimension Error" not in units:
error = False
else:
error = True
Expand Down
4 changes: 2 additions & 2 deletions src/PlotCell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -633,12 +633,12 @@
</TooltipIcon>
{:else if !unitsValid($results[index][i].data[0].displayInputUnits)}
<TooltipIcon direction="right" align="end">
<span slot="tooltipText">X-axis upper and/or lower limit dimension error{$results[index][i].data[0].asciiInputUnits === "Exponent Not Dimensionless" ? ": Exponent Not Dimensionless": ""}</span>
<span slot="tooltipText">X-axis upper and/or lower limit dimension error{$results[index][i].data[0].asciiInputUnits.startsWith("Dimension Error:") ? $results[index][i].data[0].asciiInputUnits.slice(15) : ""}</span>
<Error class="error"/>
</TooltipIcon>
{:else if !unitsValid($results[index][i].data[0].displayOutputUnits)}
<TooltipIcon direction="right" align="end">
<span slot="tooltipText">Y-axis dimension error{$results[index][i].data[0].asciiOutputUnits === "Exponent Not Dimensionless" ? ": Exponent Not Dimensionless": ""}</span>
<span slot="tooltipText">Y-axis dimension error{$results[index][i].data[0].asciiOutputUnits.startsWith("Dimension Error:") ? $results[index][i].data[0].asciiOutputUnits.slice(15) : ""}</span>
<Error class="error"/>
</TooltipIcon>
{:else if $results[index][i].data[0].unitsMismatch}
Expand Down
1 change: 1 addition & 0 deletions src/parser/LatexToSympy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1157,6 +1157,7 @@ export class LatexToSympy extends LatexParserVisitor<string | Statement | UnitBl
sympy: exponent,
params: this.params.slice(cursor),
isUnitlessSubExpression: true,
unitlessContext: "Exponent",
isFunctionArgument: false,
isFunction: false,
unitlessSubExpressions: []
Expand Down
1 change: 1 addition & 0 deletions src/parser/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export type UserFunctionRange = Omit<UserFunction, "isRange"> & {

export type UnitlessSubExpression = Omit<BaseAssignmentStatement, "isUnitlessSubExpression"> & {
isUnitlessSubExpression: true;
unitlessContext: string;
isFunctionArgument: false;
isFunction: false;
};
Expand Down
2 changes: 1 addition & 1 deletion src/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export function getArraySI(values: string[], units: string): number[] {
}

export function unitsValid(units: string): boolean {
return (units !== "Exponent Not Dimensionless" && units !== "Dimension Error");
return !units.includes("Dimension Error");
}


Expand Down
16 changes: 8 additions & 8 deletions tests/test_basic.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -577,23 +577,23 @@ test('Test basic functionality', async () => {
content = await page.textContent('#result-value-2');
expect(parseLatexFloat(content)).toBeCloseTo(100, precision);

await expect(page.locator('#cell-3 >> text=Exponent Not Dimensionless')).toBeVisible();
await expect(page.locator('#cell-3 >> text=Dimension Error: Exponent Not Dimensionless')).toBeVisible();

await expect(page.locator('#cell-4 >> text=Exponent Not Dimensionless')).toBeVisible();
await expect(page.locator('#cell-4 >> text=Dimension Error: Exponent Not Dimensionless')).toBeVisible();

content = await page.textContent('#result-units-5');
expect(content).toBe('miles^2');
content = await page.textContent('#result-value-5');
expect(parseLatexFloat(content)).toBeCloseTo(100, precision);

await expect(page.locator('#cell-6 >> text=Exponent Not Dimensionless')).toBeVisible();
await expect(page.locator('#cell-6 >> text=Dimension Error: Exponent Not Dimensionless')).toBeVisible();

content = await page.textContent('#result-units-8');
expect(content).toBe('')
content = await page.textContent('#result-value-8');
expect(parseLatexFloat(content)).toBeCloseTo(100, precision);

await expect(page.locator('#cell-10 >> text=Exponent Not Dimensionless')).toBeVisible();
await expect(page.locator('#cell-10 >> text=Dimension Error: Exponent Not Dimensionless')).toBeVisible();

content = await page.textContent('#result-units-11');
expect(content).toBe('')
Expand Down Expand Up @@ -745,8 +745,8 @@ test('Test function notation with exponents and units', async () => {
expect(parseLatexFloat(content)).toBeCloseTo(512, precision-1);
content = await page.textContent('#result-value-3');
expect(parseLatexFloat(content)).toBeCloseTo(32, precision);
await expect(page.locator('#cell-4 >> text=Exponent Not Dimensionless')).toBeVisible();
await expect(page.locator('#cell-5 >> text=Exponent Not Dimensionless')).toBeVisible();
await expect(page.locator('#cell-4 >> text=Dimension Error: Exponent Not Dimensionless')).toBeVisible();
await expect(page.locator('#cell-5 >> text=Dimension Error: Exponent Not Dimensionless')).toBeVisible();

});

Expand Down Expand Up @@ -1700,9 +1700,9 @@ test.skip('Test zero placeholder', async () => {
content = await page.textContent('#result-units-5');
expect(content).toBe('');

await expect(page.locator('#cell-6 >> text=Exponent Not Dimensionless')).toBeVisible();
await expect(page.locator('#cell-6 >> text=Dimension Error: Exponent Not Dimensionless')).toBeVisible();

await expect(page.locator('#cell-7 >> text=Exponent Not Dimensionless')).toBeVisible();
await expect(page.locator('#cell-7 >> text=Dimension Error: Exponent Not Dimensionless')).toBeVisible();

content = await page.textContent('#result-value-8');
expect(content).toBe(String.raw`\begin{bmatrix} 1\left\lbrack m\right\rbrack \\ 0\left\lbrack m\right\rbrack \end{bmatrix}`);
Expand Down

0 comments on commit 01c1d9d

Please sign in to comment.