22# IMPORTS
33# -------
44
5+ import re
56from copy import deepcopy
67from ..utility .physical_quantity_utilities import (
78 SLR_quantity_parser ,
1920 units_sets_dictionary ,
2021 set_of_SI_prefixes ,
2122 set_of_SI_base_unit_dimensions ,
23+ set_of_derived_SI_units_in_SI_base_units ,
24+ set_of_common_units_in_SI ,
25+ set_of_very_common_units_in_SI ,
26+ set_of_imperial_units ,
2227)
2328from ..utility .slr_parsing_utilities import create_node , infix , operate , catch_undefined , SLR_Parser
2429from sympy import Symbol
@@ -67,22 +72,6 @@ def generate_criteria_parser(reserved_expressions):
6772 end_symbol = "END"
6873 null_symbol = "NULL"
6974
70- def compare (comparison ):
71- comparison_dict = {
72- "=" : lambda inputs : bool ((inputs [0 ] - inputs [1 ]).cancel ().simplify ().simplify () == 0 ),
73- "<=" : lambda inputs : bool ((inputs [0 ] - inputs [1 ]).cancel ().simplify ().simplify () <= 0 ),
74- ">=" : lambda inputs : bool ((inputs [0 ] - inputs [1 ]).cancel ().simplify ().simplify () >= 0 ),
75- "<" : lambda inputs : bool ((inputs [0 ] - inputs [1 ]).cancel ().simplify ().simplify () < 0 ),
76- ">" : lambda inputs : bool ((inputs [0 ] - inputs [1 ]).cancel ().simplify ().simplify () > 0 ),
77- }
78-
79- def wrap (inputs ):
80- if inputs [0 ] is not None and inputs [1 ] is not None :
81- return comparison_dict [comparison ](inputs )
82- else :
83- return False
84- return wrap
85-
8675 criteria_operations = {
8776 "matches" ,
8877 "dimension" ,
@@ -487,7 +476,70 @@ def feedback_procedure_generator(parameters_dict):
487476 return graphs
488477
489478
490- def expression_preprocess (expr , name , parameters ):
479+ def expression_preprocess (name , expr , parameters ):
480+ if parameters .get ("strictness" , "natural" ) == "legacy" :
481+ prefix_data = {(p [0 ], p [1 ], tuple (), p [3 ]) for p in set_of_SI_prefixes }
482+ prefixes = []
483+ for prefix in prefix_data :
484+ prefixes = prefixes + [prefix [0 ]] + list (prefix [- 1 ])
485+ prefix_short_forms = [prefix [1 ] for prefix in prefix_data ]
486+ unit_data = set_of_SI_base_unit_dimensions \
487+ | set_of_derived_SI_units_in_SI_base_units \
488+ | set_of_common_units_in_SI \
489+ | set_of_very_common_units_in_SI \
490+ | set_of_imperial_units
491+ unit_long_forms = prefixes
492+ for unit in unit_data :
493+ unit_long_forms = unit_long_forms + [unit [0 ]] + list (unit [- 2 ]) + list (unit [- 1 ])
494+ unit_long_forms = "(" + "|" .join (unit_long_forms )+ ")"
495+ # Rewrite any expression on the form "*UNIT" (but not "**UNIT") as " UNIT"
496+ # Example: "newton*metre" ---> "newton metre"
497+ search_string = r"(?<!\*)\* *" + unit_long_forms
498+ match_content = re .search (search_string , expr [1 :])
499+ while match_content is not None :
500+ expr = expr [0 :match_content .span ()[0 ]+ 1 ]+ match_content .group ().replace ("*" , " " )+ expr [match_content .span ()[1 ]+ 1 :]
501+ match_content = re .search (search_string , expr [1 :])
502+ prefixes = "(" + "|" .join (prefixes )+ ")"
503+ # Rewrite any expression on the form "PREFIX UNIT" as "PREFIXUNIT"
504+ # Example: "kilo metre" ---> "kilometre"
505+ search_string = prefixes + " " + unit_long_forms
506+ match_content = re .search (search_string , expr )
507+ while match_content is not None :
508+ expr = expr [0 :match_content .span ()[0 ]]+ " " + "" .join (match_content .group ().split ())+ expr [match_content .span ()[1 ]:]
509+ match_content = re .search (search_string , expr )
510+ unit_short_forms = [u [1 ] for u in unit_data ]
511+ short_forms = "(" + "|" .join (list (set (prefix_short_forms + unit_short_forms )))+ ")"
512+ # Add space before short forms of prefixes or unit names if they are preceded by numbers or multiplication
513+ # Example: "100Pa" ---> "100 Pa"
514+ search_string = r"[0-9\*\(\)]" + short_forms
515+ match_content = re .search (search_string , expr )
516+ while match_content is not None :
517+ expr = expr [0 :match_content .span ()[0 ]+ 1 ]+ " " + expr [match_content .span ()[0 ]+ 1 :]
518+ match_content = re .search (search_string , expr )
519+ # Remove space after prefix short forms if they are preceded by numbers, multiplication or space
520+ # Example: "100 m Pa" ---> "100 mPa"
521+ prefix_short_forms = "(" + "|" .join (prefix_short_forms )+ ")"
522+ search_string = r"[0-9\*\(\) ]" + prefix_short_forms + " "
523+ match_content = re .search (search_string , expr )
524+ while match_content is not None :
525+ expr = expr [0 :match_content .span ()[0 ]+ 1 ]+ match_content .group ()[0 :- 1 ]+ expr [match_content .span ()[1 ]:]
526+ match_content = re .search (search_string , expr )
527+ # Remove multiplication and space after prefix short forms if they are preceded by numbers, multiplication or space
528+ # Example: "100 m* Pa" ---> "100 mPa"
529+ search_string = r"[0-9\*\(\) ]" + prefix_short_forms + "\* "
530+ match_content = re .search (search_string , expr )
531+ while match_content is not None :
532+ expr = expr [0 :match_content .span ()[0 ]+ 1 ]+ match_content .group ()[0 :- 2 ]+ expr [match_content .span ()[1 ]:]
533+ match_content = re .search (search_string , expr )
534+ # Replace multiplication followed by space before unit short forms with only spaces if they are preceded by numbers or space
535+ # Example: "100* Pa" ---> "100 Pa"
536+ unit_short_forms = "(" + "|" .join (unit_short_forms )+ ")"
537+ search_string = r"[0-9\(\) ]\* " + unit_short_forms
538+ match_content = re .search (search_string , expr )
539+ while match_content is not None :
540+ expr = expr [0 :match_content .span ()[0 ]]+ match_content .group ().replace ("*" ," " )+ expr [match_content .span ()[1 ]:]
541+ match_content = re .search (search_string , expr )
542+
491543 prefixes = set (x [0 ] for x in set_of_SI_prefixes )
492544 fundamental_units = set (x [0 ] for x in set_of_SI_base_unit_dimensions )
493545 units_string = parameters .get ("units_string" , "SI common imperial" )
@@ -499,7 +551,7 @@ def expression_preprocess(expr, name, parameters):
499551 dimensions = set (x [2 ] for x in set_of_SI_base_unit_dimensions )
500552 unsplittable_symbols = list (prefixes | fundamental_units | valid_units | dimensions )
501553 preprocess_parameters = deepcopy (parameters )
502- # TODO: find better way to add reserved keywords for physical quantity criteria added to prevent preprocessing to mangle them
554+ # TODO: find better way to prevent preprocessing from mangling reserved keywords for physical quantity criteria
503555 preprocess_parameters .update ({"reserved_keywords" : preprocess_parameters .get ("reserved_keywords" , [])+ unsplittable_symbols + ['matches' ]})
504556 expr = substitute_input_symbols (expr .strip (), preprocess_parameters )[0 ]
505557 success = True
@@ -516,6 +568,14 @@ def feedback_string_generator(tags, graph, parameters_dict):
516568 return
517569
518570
571+ def parsing_parameters_generator (params , unsplittable_symbols = tuple (), symbol_assumptions = tuple ()):
572+ parsing_parameters = create_sympy_parsing_params (params )
573+ parsing_parameters .update ({
574+ "strictness" : params .get ("strictness" , "natural" )
575+ })
576+ return parsing_parameters
577+
578+
519579context = {
520580 "expression_preview" : preview_function ,
521581 "generate_criteria_parser" : generate_criteria_parser ,
@@ -524,5 +584,5 @@ def feedback_string_generator(tags, graph, parameters_dict):
524584 "default_criteria" : {"response matches answer" },
525585 "feedback_procedure_generator" : feedback_procedure_generator ,
526586 "feedback_string_generator" : feedback_string_generator ,
527- "parsing_parameters_generator" : create_sympy_parsing_params ,
587+ "parsing_parameters_generator" : parsing_parameters_generator ,
528588}
0 commit comments