Skip to content

Commit

Permalink
feat: add automatic decimal to fraction conversion sheet setting
Browse files Browse the repository at this point in the history
This has been the default but there are cases where sheets may fail to solve because of the excessive length of some of the rational representations. When these situations occur, the error messages have been updated to recommend changing this setting to solve the issues.
  • Loading branch information
mgreminger committed Apr 28, 2024
1 parent 7ec3af3 commit e35d8f2
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 31 deletions.
71 changes: 46 additions & 25 deletions public/dimensional_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ class StatementsAndSystems(TypedDict):
systemDefinitions: list[SystemDefinition]
customBaseUnits: NotRequired[CustomBaseUnits]
simplifySymbolicExpressions: bool
convertFloatsToFractions: bool

def is_code_function_query_statement(statement: InputAndSystemStatement) -> TypeGuard[CodeFunctionQueryStatement]:
return statement.get("isCodeFunctionQuery", False)
Expand Down Expand Up @@ -787,8 +788,12 @@ def custom_latex(expression: Expr) -> str:
try:
result_latex = latex(new_expression)
except ValueError as e:
result_latex = f"\\text{{Error generating symbolic result: {e}}}"

result_latex = """
\\begin{split}
&\\text{Error generating symbolic result.} \\\\
&\\text{Try disabling the "Automatically Convert Decimal Values to Fractions" sheet setting.}
\\end{split}
"""

result_latex = result_latex.replace('_{as variable}','')

Expand Down Expand Up @@ -1245,10 +1250,10 @@ def as_int_if_int(expr: Expr | float) -> Expr | float:
return expr


def get_parameter_subs(parameters: list[ImplicitParameter]):
def get_parameter_subs(parameters: list[ImplicitParameter], convert_floats_to_fractions: bool):
# sub parameter values
parameter_subs: dict[Symbol, Expr] = {
symbols(param["name"]): sympify(param["si_value"], rational=True)
symbols(param["name"]): sympify(param["si_value"], rational=convert_floats_to_fractions)
for param in parameters
if param["si_value"] is not None
}
Expand All @@ -1259,16 +1264,16 @@ def get_parameter_subs(parameters: list[ImplicitParameter]):


def sympify_statements(statements: list[Statement] | list[EqualityStatement],
sympify_exponents=False):
sympify_exponents=False, convert_floats_to_fractions=True):
for i, statement in enumerate(statements):
statement["index"] = i
if statement["type"] != "local_sub" and statement["type"] != "blank" and \
statement["type"] != "scatterQuery":
try:
statement["expression"] = sympify(statement["sympy"], rational=True)
statement["expression"] = sympify(statement["sympy"], rational=convert_floats_to_fractions)
if sympify_exponents:
for exponent in cast(list[Exponent], statement["exponents"]):
exponent["expression"] = sympify(exponent["sympy"], rational=True)
exponent["expression"] = sympify(exponent["sympy"], rational=convert_floats_to_fractions)
except SyntaxError:
print(f"Parsing error for equation {statement['sympy']}")
raise ParsingError
Expand All @@ -1279,11 +1284,13 @@ def remove_implicit_and_exponent(input_set: set[str]) -> set[str]:
if not variable.startswith( ("implicit_param__", "exponent__") )}


def solve_system(statements: list[EqualityStatement], variables: list[str]):
def solve_system(statements: list[EqualityStatement], variables: list[str],
convert_floats_to_fractions: bool):
parameters = get_all_implicit_parameters(statements)
parameter_subs = get_parameter_subs(parameters)
parameter_subs = get_parameter_subs(parameters, convert_floats_to_fractions)

sympify_statements(statements, sympify_exponents=True)
sympify_statements(statements, sympify_exponents=True,
convert_floats_to_fractions=convert_floats_to_fractions)

# give all of the statements an index so that they can be re-ordered
for i, statement in enumerate(statements):
Expand Down Expand Up @@ -1360,11 +1367,13 @@ def solve_system(statements: list[EqualityStatement], variables: list[str]):


def solve_system_numerical(statements: list[EqualityStatement], variables: list[str],
guesses: list[str], guess_statements: list[GuessAssignmentStatement]):
guesses: list[str], guess_statements: list[GuessAssignmentStatement],
convert_floats_to_fractions: bool):
parameters = get_all_implicit_parameters([*statements, *guess_statements])
parameter_subs = get_parameter_subs(parameters)
parameter_subs = get_parameter_subs(parameters, convert_floats_to_fractions)

sympify_statements(statements, sympify_exponents=True)
sympify_statements(statements, sympify_exponents=True,
convert_floats_to_fractions=convert_floats_to_fractions)

# give all of the statements an index so that they can be re-ordered
for i, statement in enumerate(statements):
Expand Down Expand Up @@ -1829,7 +1838,8 @@ def get_hashable_matrix_units(matrix_result: MatrixResult) -> tuple[tuple[str, .

def evaluate_statements(statements: list[InputAndSystemStatement],
custom_base_units: CustomBaseUnits | None,
simplify_symbolic_expressions: bool) -> tuple[list[Result | FiniteImagResult | list[PlotResult] | MatrixResult], dict[int,bool]]:
simplify_symbolic_expressions: bool,
convert_floats_to_fractions: bool) -> tuple[list[Result | FiniteImagResult | list[PlotResult] | MatrixResult], dict[int,bool]]:
num_statements = len(statements)

if num_statements == 0:
Expand All @@ -1839,14 +1849,14 @@ def evaluate_statements(statements: list[InputAndSystemStatement],
"cellNum": statement.get("cellNum", -1)} for statement in statements]

parameters = get_all_implicit_parameters(statements)
parameter_subs = get_parameter_subs(parameters)
parameter_subs = get_parameter_subs(parameters, convert_floats_to_fractions)
dimensional_analysis_subs: dict[Symbol, Expr] = {
symbols(param["name"]): get_dims(param["dimensions"]) for param in parameters
}

expanded_statements: list[Statement] = expand_with_sub_statements(statements)

sympify_statements(expanded_statements)
sympify_statements(expanded_statements, convert_floats_to_fractions=convert_floats_to_fractions)

expanded_statements = get_sorted_statements(expanded_statements)

Expand Down Expand Up @@ -2161,13 +2171,16 @@ def evaluate_statements(statements: list[InputAndSystemStatement],

def get_query_values(statements: list[InputAndSystemStatement],
custom_base_units: CustomBaseUnits | None,
simplify_symbolic_expressions: bool):
simplify_symbolic_expressions: bool,
convert_floats_to_fractions: bool):
error: None | str = None

results: list[Result | FiniteImagResult | list[PlotResult] | MatrixResult] = []
numerical_system_cell_errors: dict[int, bool] = {}
try:
results, numerical_system_cell_errors = evaluate_statements(statements, custom_base_units, simplify_symbolic_expressions)
results, numerical_system_cell_errors = evaluate_statements(statements, custom_base_units,
simplify_symbolic_expressions,
convert_floats_to_fractions)
except DuplicateAssignment as e:
error = f"Duplicate assignment of variable {e}"
except ReferenceCycle as e:
Expand All @@ -2178,6 +2191,8 @@ def get_query_values(statements: list[InputAndSystemStatement],
error = "Cannot solve overdetermined system"
except NoSolutionFound as e:
error = "Unable to solve system of equations"
except MemoryError:
error = 'A MemoryError occurred while completing the calculation, try disabling the "Automatically Convert Decimal Values to Fractions" sheet setting'
except Exception as e:
print(f"Unhandled exception: {type(e).__name__}, {e}")
error = f"Unhandled exception: {type(e).__name__}, {e}"
Expand All @@ -2187,7 +2202,7 @@ def get_query_values(statements: list[InputAndSystemStatement],


@lru_cache(maxsize=1024)
def get_system_solution(statements, variables):
def get_system_solution(statements, variables, convert_floats_to_fractions):
statements = cast(list[EqualityStatement], loads(statements))
variables = cast(list[str], loads(variables))

Expand All @@ -2196,7 +2211,7 @@ def get_system_solution(statements, variables):
display_solutions: dict[str, list[str]]

try:
new_statements = solve_system(statements, variables)
new_statements = solve_system(statements, variables, convert_floats_to_fractions)
except (ParameterError, ParsingError) as e:
error = e.__class__.__name__
new_statements = []
Expand Down Expand Up @@ -2227,7 +2242,7 @@ def get_system_solution(statements, variables):


@lru_cache(maxsize=1024)
def get_system_solution_numerical(statements, variables, guesses, guessStatements):
def get_system_solution_numerical(statements, variables, guesses, guessStatements, convert_floats_to_fractions):
statements = cast(list[EqualityStatement], loads(statements))
variables = cast(list[str], loads(variables))
guesses = cast(list[str], loads(guesses))
Expand All @@ -2237,7 +2252,8 @@ def get_system_solution_numerical(statements, variables, guesses, guessStatement
new_statements: list[list[EqualityUnitsQueryStatement | GuessAssignmentStatement]] = []
display_solutions: dict[str, list[str]] = {}
try:
new_statements, display_solutions = solve_system_numerical(statements, variables, guesses, guess_statements)
new_statements, display_solutions = solve_system_numerical(statements, variables, guesses,
guess_statements, convert_floats_to_fractions)
except (ParameterError, ParsingError) as e:
error = e.__class__.__name__
except OverDeterminedSystem as e:
Expand All @@ -2260,6 +2276,7 @@ def solve_sheet(statements_and_systems):
system_definitions = statements_and_systems["systemDefinitions"]
custom_base_units = statements_and_systems.get("customBaseUnits", None)
simplify_symbolic_expressions = statements_and_systems["simplifySymbolicExpressions"]
convert_floats_to_fractions = statements_and_systems["convertFloatsToFractions"]

system_results: list[SystemResult] = []
equation_to_system_cell_map: dict[int,int] = {}
Expand All @@ -2276,7 +2293,8 @@ def solve_sheet(statements_and_systems):
(system_error,
system_solutions,
display_solutions) = get_system_solution(dumps(system_definition["statements"]),
dumps(system_definition["variables"]))
dumps(system_definition["variables"]),
convert_floats_to_fractions)
else:
for statement in system_definition["statements"]:
equation_to_system_cell_map[statement["equationIndex"]] = i
Expand All @@ -2287,7 +2305,8 @@ def solve_sheet(statements_and_systems):
display_solutions) = get_system_solution_numerical(dumps(system_definition["statements"]),
dumps(system_definition["variables"]),
dumps(system_definition["guesses"]),
dumps(system_definition["guessStatements"]))
dumps(system_definition["guessStatements"]),
convert_floats_to_fractions)

if system_error is None:
if selected_solution > len(system_solutions) - 1:
Expand All @@ -2304,7 +2323,9 @@ def solve_sheet(statements_and_systems):
error: str | None
results: list[Result | FiniteImagResult | list[PlotResult] | MatrixResult]
numerical_system_cell_errors: dict[int, bool]
error, results, numerical_system_cell_errors = get_query_values(statements, custom_base_units, simplify_symbolic_expressions)
error, results, numerical_system_cell_errors = get_query_values(statements, custom_base_units,
simplify_symbolic_expressions,
convert_floats_to_fractions)

# If there was a numerical solve, check to make sure there were not unit mismatches
# between the lhs and rhs of each equality in the system
Expand Down
14 changes: 11 additions & 3 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,8 @@
statements: statements,
systemDefinitions: systemDefinitions,
customBaseUnits: $config.customBaseUnits,
simplifySymbolicExpressions: $config.simplifySymbolicExpressions
simplifySymbolicExpressions: $config.simplifySymbolicExpressions,
convertFloatsToFractions: $config.convertFloatsToFractions
};
}
Expand Down Expand Up @@ -1103,6 +1104,7 @@ Please include a link to this sheet in the email to assist in debugging the prob
$config = sheet.config ?? getDefaultConfig();
$config.customBaseUnits = $config.customBaseUnits ?? getDefaultBaseUnits(); // customBaseUnits may not exist
$config.simplifySymbolicExpressions = $config.simplifySymbolicExpressions ?? true; // simplifySymboicExpressions may not exist
$config.convertFloatsToFractions = $config.convertFloatsToFractions ?? true; // convertFloatsToFractions may not exist
if (!$history.map(item => item.hash !== "file" ? getSheetHash(new URL(item.url)) : "").includes(getSheetHash(window.location))) {
$history = requestHistory;
Expand Down Expand Up @@ -2644,7 +2646,8 @@ Please include a link to this sheet in the email to assist in debugging the prob
on:click:button--primary={() => modalInfo.modalOpen = false}
on:click:button--secondary={() => {mathCellConfigDialog?.resetDefaults();
baseUnitsConfigDialog?.resetDefaults();
$config.simplifySymbolicExpressions = true;}}
$config.simplifySymbolicExpressions = true;
$config.convertFloatsToFractions = true;}}
bind:open={modalInfo.modalOpen}
>
{#if modalInfo.mathCell}
Expand All @@ -2661,10 +2664,15 @@ Please include a link to this sheet in the email to assist in debugging the prob
<svelte:fragment slot="content">
<TabContent>
<Checkbox
labelText="Automatically Simplify Symbolic Expressions (unchecking will speed up sheet updates)"
labelText="Automatically Simplify Symbolic Expressions (unchecking may speed up sheet updates)"
bind:checked={$config.simplifySymbolicExpressions}
on:check={() => $mathCellChanged = true}
/>
<Checkbox
labelText="Automatically Convert Decimal Values to Fractions (increases precision for decimal numbers, unchecking may speed up sheet updates)"
bind:checked={$config.convertFloatsToFractions}
on:check={() => $mathCellChanged = true}
/>
<MathCellConfigDialog
bind:this={mathCellConfigDialog}
bind:mathCellConfig={$config.mathCellConfig}
Expand Down
7 changes: 5 additions & 2 deletions src/sheet/Sheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type Config = {
mathCellConfig: MathCellConfig;
customBaseUnits?: CustomBaseUnits; // some early sheets won't have this property
simplifySymbolicExpressions?: boolean; // some early sheets won't have this property
convertFloatsToFractions?: boolean; // some early sheets won't have this property
};

type Notation = "auto" | "fixed" | "exponential" | "engineering";
Expand All @@ -43,7 +44,8 @@ export function getDefaultConfig(): Config {
return {
mathCellConfig: getDefaultMathCellConfig(),
customBaseUnits: getDefaultBaseUnits(),
simplifySymbolicExpressions: true
simplifySymbolicExpressions: true,
convertFloatsToFractions: true
};
}

Expand Down Expand Up @@ -82,7 +84,8 @@ export function isDefaultMathConfig(config: MathCellConfig): boolean {
export function isDefaultConfig(config: Config): boolean {
return isDefaultMathConfig(config.mathCellConfig) &&
isDefaultBaseUnits(config.customBaseUnits) &&
config.simplifySymbolicExpressions === true;
config.simplifySymbolicExpressions === true &&
config.convertFloatsToFractions === true;
}

export function copyMathConfig(input: MathCellConfig): MathCellConfig {
Expand Down
2 changes: 1 addition & 1 deletion src/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const defaultTitle = 'New Sheet';
export const unsavedChange = writable(false);
export const autosaveNeeded = writable(false);

export const config = writable(getDefaultConfig())
export const config = writable(getDefaultConfig());
export const cells: Writable<Cell[]> = writable([]);
export const title = writable(defaultTitle);
export const results: Writable<(Result | FiniteImagResult | MatrixResult | PlotResult[])[]> = writable([]);
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type StatementsAndSystems = {
systemDefinitions: SystemDefinition[];
customBaseUnits?: CustomBaseUnits;
simplifySymbolicExpressions: boolean;
convertFloatsToFractions: boolean;
}


32 changes: 32 additions & 0 deletions tests/test_number_format.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -515,3 +515,35 @@ test('Test number input validation', async () => {
expect(content).toBe('0.666666666666667');

});


test('Test disabling automatic fraction conversion', async () => {
// turn off automatic simplification
await page.getByRole('button', { name: 'Sheet Settings' }).click();
await page.locator('label').filter({ hasText: 'Automatically Convert Decimal Values to Fractions' }).click();
await page.getByRole('button', { name: 'Confirm' }).click();

await page.setLatex(0, String.raw`\left(\frac{1.115625000065330001000001000010001}{1.355801000010000100001000010000100010}\right)^{\frac{1}{-.01780001000100010001}}=`);

await page.waitForSelector('text=Updating...', {state: 'detached'});

// check output
let content = await page.textContent('#result-value-0');
expect(parseLatexFloat(content)).toBeCloseTo(57170.5227437832, precision);
});


test('Test default automatic fraction conversion setting', async () => {
await page.setLatex(0, String.raw`.125=`);

// turn on symbolic results
await page.getByRole('button', { name: 'Sheet Settings' }).click();
await page.locator('label').filter({ hasText: 'Display Symbolic Results' }).click();
await page.getByRole('button', { name: 'Confirm' }).click();

await page.waitForSelector('text=Updating...', {state: 'detached'});

// check output
let content = await page.textContent('#result-value-0');
expect(content).toBe(String.raw`\frac{1}{8}`);
});

0 comments on commit e35d8f2

Please sign in to comment.