diff --git a/app/context/physical_quantity.py b/app/context/physical_quantity.py index ecd4f51..28348de 100644 --- a/app/context/physical_quantity.py +++ b/app/context/physical_quantity.py @@ -6,7 +6,7 @@ from copy import deepcopy from ..utility.physical_quantity_utilities import ( SLR_quantity_parser, - SLR_quantity_parsing + SLR_quantity_parsing, expression_preprocess ) from ..preview_implementations.physical_quantity_preview import preview_function from ..feedback.physical_quantity import feedback_string_generators as physical_quantity_feedback_string_generators @@ -476,107 +476,6 @@ def feedback_procedure_generator(parameters_dict): graphs.update({label: graph}) return graphs -def preprocess_legacy(expr, parameters): - prefix_data = {(p[0], p[1], tuple(), p[3]) for p in set_of_SI_prefixes} - prefixes = [] - for prefix in prefix_data: - prefixes = prefixes + [prefix[0]] + list(prefix[-1]) - prefix_short_forms = [prefix[1] for prefix in prefix_data] - unit_data = set_of_SI_base_unit_dimensions \ - | set_of_derived_SI_units_in_SI_base_units \ - | set_of_common_units_in_SI \ - | set_of_very_common_units_in_SI \ - | set_of_imperial_units - unit_long_forms = prefixes - for unit in unit_data: - unit_long_forms = unit_long_forms + [unit[0]] + list(unit[-2]) + list(unit[-1]) - unit_long_forms = "(" + "|".join(unit_long_forms) + ")" - # Rewrite any expression on the form "*UNIT" (but not "**UNIT") as " UNIT" - # Example: "newton*metre" ---> "newton metre" - search_string = r"(? "kilometre" - search_string = prefixes + " " + unit_long_forms - match_content = re.search(search_string, expr) - while match_content is not None: - expr = expr[0:match_content.span()[0]] + " " + "".join(match_content.group().split()) + expr[ - match_content.span()[ - 1]:] - match_content = re.search(search_string, expr) - unit_short_forms = [u[1] for u in unit_data] - short_forms = "(" + "|".join(list(set(prefix_short_forms + unit_short_forms))) + ")" - # Add space before short forms of prefixes or unit names if they are preceded by numbers or multiplication - # Example: "100Pa" ---> "100 Pa" - search_string = r"[0-9\*\(\)]" + short_forms - match_content = re.search(search_string, expr) - while match_content is not None: - expr = expr[0:match_content.span()[0] + 1] + " " + expr[match_content.span()[0] + 1:] - match_content = re.search(search_string, expr) - # Remove space after prefix short forms if they are preceded by numbers, multiplication or space - # Example: "100 m Pa" ---> "100 mPa" - prefix_short_forms = "(" + "|".join(prefix_short_forms) + ")" - search_string = r"[0-9\*\(\) ]" + prefix_short_forms + " " - match_content = re.search(search_string, expr) - while match_content is not None: - expr = expr[0:match_content.span()[0] + 1] + match_content.group()[0:-1] + expr[match_content.span()[1]:] - match_content = re.search(search_string, expr) - # Remove multiplication and space after prefix short forms if they are preceded by numbers, multiplication or space - # Example: "100 m* Pa" ---> "100 mPa" - search_string = r"[0-9\*\(\) ]" + prefix_short_forms + "\* " - match_content = re.search(search_string, expr) - while match_content is not None: - expr = expr[0:match_content.span()[0] + 1] + match_content.group()[0:-2] + expr[match_content.span()[1]:] - match_content = re.search(search_string, expr) - # Replace multiplication followed by space before unit short forms with only spaces if they are preceded by numbers or space - # Example: "100* Pa" ---> "100 Pa" - unit_short_forms = "(" + "|".join(unit_short_forms) + ")" - search_string = r"[0-9\(\) ]\* " + unit_short_forms - match_content = re.search(search_string, expr) - while match_content is not None: - expr = expr[0:match_content.span()[0]] + match_content.group().replace("*", " ") + expr[ - match_content.span()[1]:] - match_content = re.search(search_string, expr) - - return expr - -def transform_prefixes_to_standard(expr): - """ - Transform ONLY alternative prefix spellings to standard prefix names. - Ensure there's exactly one space after the prefix before the unit. - Works for both attached (e.g. 'km') and spaced (e.g. 'k m') forms. - """ - - for prefix_name, symbol, power, alternatives in set_of_SI_prefixes: - for alt in alternatives: - if not alt: - continue - - # Match the alternative prefix either attached to or followed by spaces before a unit - # Examples matched: "km", "k m", "microsecond", "micro second" - pattern = rf'(? Result: unit_sympy = res_parsed.unit.content_string() if unit is not None else "" sympy_out = value_sympy+separator_sympy+unit_sympy else: - res_parsed = quantity_parsing(response, params, parser, "response") + _, res_pre_processed, _ = expression_preprocess("response", response, params) + res_parsed = quantity_parsing(res_pre_processed, params, parser, "response") latex_out = res_parsed.latex_string sympy_out = response diff --git a/app/preview_tests.py b/app/preview_tests.py index c758d26..0ba4b28 100644 --- a/app/preview_tests.py +++ b/app/preview_tests.py @@ -93,7 +93,6 @@ def test_natural_logarithm_notation(self): ("e * x", True, False, "e * x", "E*x"), ("E", True, False, "E", "E",), ("ER_2", True, False, "ER_2", "E*R_2",), - # TODO: add exp (0), (1), (2) and (x) ("exp(1)", False, True, "e^{1}", "exp(1)"), ("e**1", False, True, "e^{1}", "E**1"), ("e^{1}", True, True, "e^{1}", "E"), diff --git a/app/tests/physical_quantity_evaluation_tests.py b/app/tests/physical_quantity_evaluation_tests.py index 43ff30f..ba1bed5 100644 --- a/app/tests/physical_quantity_evaluation_tests.py +++ b/app/tests/physical_quantity_evaluation_tests.py @@ -378,8 +378,8 @@ def test_answer_zero_value(self): "ans,res", [ ("10 ohm", "10 Ω"), - ("10 micro A", "10 μA"), - ("10 micro A", "10 μ A"), + ("10 microA", "10 μA"), + ("10 microA", "10 μ A"), ("30 degree", "30 °"), ] ) diff --git a/app/utility/physical_quantity_utilities.py b/app/utility/physical_quantity_utilities.py index b902bed..33a074d 100644 --- a/app/utility/physical_quantity_utilities.py +++ b/app/utility/physical_quantity_utilities.py @@ -273,6 +273,13 @@ def SLR_generate_unit_dictionaries(units_string, strictness): prefixes_long_to_short[prefix]+units_long_to_short[unit]: prefix+units[unit] } ) + if prefix + units_long_to_short[unit] not in units_short_to_long.keys(): + prefixed_units.update( + { + prefix + units_long_to_short[unit]: prefix + units[unit] + } + ) + prefixed_units_end = {**units_end} for unit in units_end.keys(): @@ -486,3 +493,104 @@ def SLR_quantity_parsing(expr, parameters, parser, name): tag_handler = set_tags(parameters.get("strictness", "strict")) return PhysicalQuantity(name, parameters, quantity[0], parser, messages=[], tag_handler=tag_handler) + +def expression_preprocess(name, expr, parameters): + if parameters.get("strictness", "natural") == "legacy": + expr = preprocess_legacy(expr, parameters) + return True, expr, None + + expr = transform_prefixes_to_standard(expr) + + return True, expr, None + +def preprocess_legacy(expr, parameters): + prefix_data = {(p[0], p[1], tuple(), p[3]) for p in set_of_SI_prefixes} + prefixes = [] + for prefix in prefix_data: + prefixes = prefixes + [prefix[0]] + list(prefix[-1]) + prefix_short_forms = [prefix[1] for prefix in prefix_data] + unit_data = set_of_SI_base_unit_dimensions \ + | set_of_derived_SI_units_in_SI_base_units \ + | set_of_common_units_in_SI \ + | set_of_very_common_units_in_SI \ + | set_of_imperial_units + unit_long_forms = prefixes + for unit in unit_data: + unit_long_forms = unit_long_forms + [unit[0]] + list(unit[-2]) + list(unit[-1]) + unit_long_forms = "(" + "|".join(unit_long_forms) + ")" + # Rewrite any expression on the form "*UNIT" (but not "**UNIT") as " UNIT" + # Example: "newton*metre" ---> "newton metre" + search_string = r"(? "kilometre" + search_string = prefixes + " " + unit_long_forms + match_content = re.search(search_string, expr) + while match_content is not None: + expr = expr[0:match_content.span()[0]] + " " + "".join(match_content.group().split()) + expr[ + match_content.span()[ + 1]:] + match_content = re.search(search_string, expr) + unit_short_forms = [u[1] for u in unit_data] + short_forms = "(" + "|".join(list(set(prefix_short_forms + unit_short_forms))) + ")" + # Add space before short forms of prefixes or unit names if they are preceded by numbers or multiplication + # Example: "100Pa" ---> "100 Pa" + search_string = r"[0-9\*\(\)]" + short_forms + match_content = re.search(search_string, expr) + while match_content is not None: + expr = expr[0:match_content.span()[0] + 1] + " " + expr[match_content.span()[0] + 1:] + match_content = re.search(search_string, expr) + # Remove space after prefix short forms if they are preceded by numbers, multiplication or space + # Example: "100 m Pa" ---> "100 mPa" + prefix_short_forms = "(" + "|".join(prefix_short_forms) + ")" + search_string = r"[0-9\*\(\) ]" + prefix_short_forms + " " + match_content = re.search(search_string, expr) + while match_content is not None: + expr = expr[0:match_content.span()[0] + 1] + match_content.group()[0:-1] + expr[match_content.span()[1]:] + match_content = re.search(search_string, expr) + # Remove multiplication and space after prefix short forms if they are preceded by numbers, multiplication or space + # Example: "100 m* Pa" ---> "100 mPa" + search_string = r"[0-9\*\(\) ]" + prefix_short_forms + "\* " + match_content = re.search(search_string, expr) + while match_content is not None: + expr = expr[0:match_content.span()[0] + 1] + match_content.group()[0:-2] + expr[match_content.span()[1]:] + match_content = re.search(search_string, expr) + # Replace multiplication followed by space before unit short forms with only spaces if they are preceded by numbers or space + # Example: "100* Pa" ---> "100 Pa" + unit_short_forms = "(" + "|".join(unit_short_forms) + ")" + search_string = r"[0-9\(\) ]\* " + unit_short_forms + match_content = re.search(search_string, expr) + while match_content is not None: + expr = expr[0:match_content.span()[0]] + match_content.group().replace("*", " ") + expr[ + match_content.span()[1]:] + match_content = re.search(search_string, expr) + + return expr + +def transform_prefixes_to_standard(expr): + """ + Transform ONLY alternative prefix spellings to standard prefix names. + Ensure there's exactly one space after the prefix before the unit. + Works for both attached (e.g. 'km') and spaced (e.g. 'k m') forms. + """ + + for prefix_name, symbol, power, alternatives in set_of_SI_prefixes: + for alt in alternatives: + if not alt: + continue + + # Match the alternative prefix either attached to or followed by spaces before a unit + # Examples matched: "km", "k m", "microsecond", "micro second" + pattern = rf'(?