Skip to content

Commit 6eee380

Browse files
Merge pull request #191 from lambda-feedback/tr167-legacy-mode-for-unit-parsing
Added legacy mode for old content involving units
2 parents c632e1e + be2135c commit 6eee380

File tree

4 files changed

+258
-13
lines changed

4 files changed

+258
-13
lines changed

Diff for: app/docs/user.md

+147-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,147 @@ When `physical_quantity` the evaluation function will generate feedback based on
6363

6464
![A flowchart describing the criteria that are checked for physical quantities and what feedback they produce.](quantity_comparison_graph.svg)
6565

66+
## `strictness`
67+
68+
Constrols the conventions used when parsing physical quantities.
69+
70+
**Remark:** If `physical_quantity` is set to `false`, this parameter will be ignored.
71+
72+
There are three possible values: `strict`, `natural` and `legacy`. If `strict` is chosen then quantities will be parsed according to the conventions described in 5.1, 5.2, 5.3.2, 5.3.3 in https://www.bipm.org/documents/20126/41483022/si_brochure_8.pdf and 5.2, 5.3, 5.4.2 and 5.4.3 in https://www.bipm.org/documents/20126/41483022/SI-Brochure-9-EN.pdf. If `natural` is chosen then less restrictive conventions are used.
73+
74+
**Remark:** The default setting is `natural`.
75+
76+
**Remark:** The `legacy` setting should not be used and is only there to allow compatibility with content designed for use with older versions of the evaluation function. If you encounter a question using the `legacy` setting is recommended that it is changed to another setting and the answer is redefined to match the chosen conventions.
77+
78+
## `units_string`
79+
80+
Controls what sets of units are used. There are three values `SI`, `common` and `imperial`.
81+
82+
If `SI` is chosen then only units from the tables `Base SI units` and `Derived SI units` (below) are allowed (in combinations with prefixes). If `common` is chosen then all the units allowed by `SI` as well as those listed in the tables for `Common non-SI units`. If `imperial` is chosen the base SI units and the units listed in the `Imperial units` table are allowed.
83+
84+
**Remark:** The different settings can also be combine, e.g. `SI common imperial` will allow all units.
85+
86+
### Notation and definition of units
87+
88+
#### Table: Base SI units
89+
90+
SI base units based on Table 2 in https://www.bipm.org/documents/20126/41483022/SI-Brochure-9-EN.pdf
91+
92+
Note that gram is used as a base unit instead of kilogram.
93+
94+
| SI base unit | Symbol | Dimension name |
95+
|--------------|:-------|:--------------------|
96+
| metre | m | length |
97+
| gram | g | mass |
98+
| second | s | time |
99+
| ampere | A | electriccurrent |
100+
| kelvin | k | temperature |
101+
| mole | mol | amountofsubstance |
102+
| candela | cd | luminousintensity |
103+
104+
#### Table: SI prefixes
105+
106+
SI prefixes based on Table 7 in https://www.bipm.org/documents/20126/41483022/SI-Brochure-9-EN.pdf
107+
108+
| SI Prefix | Symbol | Factor | | SI Prefix | Symbol | Factor |
109+
|-----------|:-------|:-----------|-|-----------|:-------|:-----------|
110+
| yotta | Y | $10^{24}$ | | deci | d | $10^{-1}$ |
111+
| zetta | Z | $10^{21}$ | | centi | c | $10^{-2}$ |
112+
| exa' | E | $10^{18}$ | | milli | m | $10^{-3}$ |
113+
| peta | P | $10^{15}$ | | micro | mu | $10^{-6}$ |
114+
| tera | T | $10^{12}$ | | nano | n | $10^{-9}$ |
115+
| giga | G | $10^{9}$ | | pico | p | $10^{-12}$ |
116+
| mega | M | $10^{6}$ | | femto | f | $10^{-15}$ |
117+
| kilo | k | $10^{3}$ | | atto | a | $10^{-18}$ |
118+
| hecto | h | $10^{2}$ | | zepto | z | $10^{-21}$ |
119+
| deka | da | $10^{1}$ | | yocto | y | $10^{-24}$ |
120+
121+
#### Table: Derived SI units
122+
123+
Derived SI based on Table 4 in https://www.bipm.org/documents/20126/41483022/SI-Brochure-9-EN.pdf
124+
125+
Note that the function treats radians and steradians as dimensionless values.
126+
127+
| Unit name | Symbol | Expressed in base SI units |
128+
|-----------|:-------|:---------------------------------------------------------------------------------|
129+
| radian | r | $(2\pi)^{-1}$ |
130+
| steradian | sr | $(4\pi)^{-1}$ |
131+
| hertz | Hz | $\mathrm{second}^{-1}$ |
132+
| newton | N | $\mathrm{metre}~\mathrm{kilogram}~\mathrm{second}^{-2}$ |
133+
| pascal | Pa | $\mathrm{metre}^{-1}~\mathrm{kilogram}~\mathrm{second}^{-2}$ |
134+
| joule | J | $\mathrm{metre}^2~\mathrm{kilogram~second}^{-2}$ |
135+
| watt | W | $\mathrm{metre}^2~\mathrm{kilogram~second}^{-3}$ |
136+
| coulomb | C | $\mathrm{second~ampere}$ |
137+
| volt | V | $\mathrm{metre}^2~\mathrm{kilogram second}^{-3}~\mathrm{ampere}^{-1}$ |
138+
| farad | F | $\mathrm{metre}^{-2}~\mathrm{kilogram}^{-1}~\mathrm{second}^4~\mathrm{ampere}^2$ |
139+
| ohm | O | $\mathrm{metre}^2~\mathrm{kilogram second}^{-3}~\mathrm{ampere}^{-2}$ |
140+
| siemens | S | $\mathrm{metre}^{-2}~\mathrm{kilogram}^{-1}~\mathrm{second}^3~\mathrm{ampere}^2$ |
141+
| weber | Wb | $\mathrm{metre}^2~\mathrm{kilogram~second}^{-2}~\mathrm{ampere}^{-1}$ |
142+
| tesla | T | $\mathrm{kilogram~second}^{-2} \mathrm{ampere}^{-1}$ |
143+
| henry | H | $\mathrm{metre}^2~\mathrm{kilogram~second}^{-2}~\mathrm{ampere}^{-2}$ |
144+
| lumen | lm | $\mathrm{candela}$ |
145+
| lux | lx | $\mathrm{metre}^{-2}~\mathrm{candela}$ |
146+
| becquerel | Bq | $\mathrm{second}^{-1}$ |
147+
| gray | Gy | $\mathrm{metre}^2~\mathrm{second}^{-2}$ |
148+
| sievert | Sv | $\mathrm{metre}^2~\mathrm{second}^{-2}$ |
149+
| katal | kat | $\mathrm{mole~second}^{-1}$ |
150+
151+
#### Table: Common non-SI units
152+
153+
Commonly used non-SI units based on Table 8 in https://www.bipm.org/documents/20126/41483022/SI-Brochure-9-EN.pdf and Tables 7 and 8 in https://www.bipm.org/documents/20126/41483022/si_brochure_8.pdf
154+
Note that the function treats angles, neper and bel as dimensionless values.
155+
156+
Note that only the first table in this section has short form symbols defined, the second table does not, this is done to minimize ambiguities when writing units.
157+
158+
| Unit name | Symbol | Expressed in SI units |
159+
|-------------------|:-------|:-------------------------------------------|
160+
| minute | min | $60~\mathrm{second}$ |
161+
| hour | h | $3600~\mathrm{second}$ |
162+
| degree | deg | $\frac{1}{360}$ |
163+
| liter | l | $10^{-3}~\mathrm{metre}^3$ |
164+
| metric_ton | t | $10^3~\mathrm{kilogram}$ |
165+
| neper | Np | $1$ |
166+
| bel | B | $\frac{1}{2}~\ln(10)$ |
167+
| electronvolt | eV | $1.60218 \cdot 10^{-19}~\mathrm{joule}$ |
168+
| atomic_mass_unit | u | $1.66054 \cdot 10^{-27}~\mathrm{kilogram}$ |
169+
| angstrom | å | $10^{-10}~\mathrm{metre}$ |
170+
171+
| Unit name | Expressed in SI units |
172+
|------------------|:-----------------------------------------------------|
173+
| day | $86400~\mathrm{second}$ |
174+
| angleminute | $\frac{\pi}{10800}$ |
175+
| anglesecond | $\frac{\pi}{648000}$ |
176+
| astronomicalunit | $149597870700~\mathrm{metre}$ |
177+
| nauticalmile | $1852~\mathrm{metre}$ |
178+
| knot | $\frac{1852}{3600}~\mathrm{metre~second}^{-1}$ |
179+
| are | $10^2~\mathrm{metre}^2$ |
180+
| hectare | $10^4~\mathrm{metre}^2$ |
181+
| bar | $10^5~\mathrm{pascal}$ |
182+
| barn | $10^{-28}~\mathrm{metre}$ |
183+
| curie | $3.7 \cdot 10^{10}~\mathrm{becquerel} |
184+
| roentgen | $2.58 \cdot 10^{-4}~\mathrm{kelvin~(kilogram)}^{-1}$ |
185+
| rad | $10^{-2}~\mathrm{gray}$ |
186+
| rem | $10^{-2}~\mathrm{sievert}$ |
187+
188+
#### Table: Imperial units
189+
190+
Commonly imperial units taken from https://en.wikipedia.org/wiki/Imperial_units
191+
192+
| Unit name | Symbol | Expressed in SI units |
193+
|-------------------|:-------|:----------------------------------------------|
194+
| inch | in | $0.0254~\mathrm{metre}$ |
195+
| foot | ft | $0.3048~\mathrm{metre}$ |
196+
| yard | yd | $0.9144~\mathrm{metre}$ |
197+
| mile | mi | $1609.344~\mathrm{metre}$ |
198+
| fluid ounce | fl oz | $28.4130625~\mathrm{millilitre}$ |
199+
| gill | gi | $142.0653125~\mathrm{millilitre}$ |
200+
| pint | pt | $568.26125~\mathrm{millilitre}$ |
201+
| quart | qt | $1.1365225~\mathrm{litre}$ |
202+
| gallon | gal | $4546.09~\mathrm{litre}$ |
203+
| ounce | oz | $28.349523125~\mathrm{gram}$ |
204+
| pound | lb | $0.45359237~\mathrm{kilogram}$ |
205+
| stone | st | $6.35029318~\mathrm{kilogram}$ |
206+
66207
## `plus_minus` and `minus_plus`
67208

68209
The $\pm$ and $\mp$ symbols can be represented in the answer or response by `plus_minus` and `minus_plus` respectively.
@@ -81,15 +222,20 @@ If `strict_syntax` is set to false, then `*` can be omitted and `^` used instead
81222

82223
By default `strict_syntax` is set to true.
83224

225+
## `strictness`
226+
This parameter is only used when `physical_quantity` is set to `true`. It accepts three possible values: `strict`, `natural` and `legacy`.
227+
`strict`:
228+
84229
## `symbol_assumptions`
85230

86231
This input parameter allows the author to set an extra assumption each symbol. Each assumption should be written on the form `('symbol','assumption name')` and all pairs concatenated into a single string.
87232

88-
The possible assumption `constant`, `function` as well as those listed here:
233+
The possible assumptions are: `constant`, `function` as well as those listed here:
89234
[`SymPy Assumption Predicates`](https://docs.sympy.org/latest/guides/assumptions.html#predicates)
90235

91236
**Note:** Writing a symbol which denotes a function without its arguments, e.g. `T` instead of `T(x,t)`, is prone to cause errors.
92237

238+
93239
## Examples
94240

95241
Implemented versions of these examples can be found in the module 'Examples: Evaluation Functions'.

Diff for: app/quantity_comparison_evaluation_tests.py

+33
Original file line numberDiff line numberDiff line change
@@ -263,5 +263,38 @@ def test_print_floating_point_approximation_of_very_large_numbers(self):
263263
result = evaluation_function(res, ans, params, include_test_data=True)
264264
assert result["is_correct"] is False
265265

266+
def test_legacy_strictness(self):
267+
ans = "100*kilo*pascal*ohm"
268+
res = "100 kilopascal ohm"
269+
params = {
270+
'strict_syntax': False,
271+
'physical_quantity': True,
272+
'strictness': 'legacy',
273+
}
274+
result = evaluation_function(res, ans, params, include_test_data=True)
275+
assert result["is_correct"] is True
276+
ans = "8650*watt"
277+
res = "8.65kW"
278+
result = evaluation_function(res, ans, params, include_test_data=True)
279+
assert result["is_correct"] is True
280+
res = "8650W"
281+
result = evaluation_function(res, ans, params, include_test_data=True)
282+
assert result["is_correct"] is True
283+
res = "8650*W"
284+
result = evaluation_function(res, ans, params, include_test_data=True)
285+
assert result["is_correct"] is True
286+
res = "8.65 k W"
287+
result = evaluation_function(res, ans, params, include_test_data=True)
288+
assert result["is_correct"] is True
289+
res = "8.65 k*W"
290+
result = evaluation_function(res, ans, params, include_test_data=True)
291+
assert result["is_correct"] is True
292+
res = "(8.65)kW"
293+
result = evaluation_function(res, ans, params, include_test_data=True)
294+
assert result["is_correct"] is True
295+
res = "(8650)W"
296+
result = evaluation_function(res, ans, params, include_test_data=True)
297+
assert result["is_correct"] is True
298+
266299
if __name__ == "__main__":
267300
pytest.main(['-xk not slow', "--tb=line", os.path.abspath(__file__)])

Diff for: app/slr_quantity.py

+66
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,72 @@ def quantity_comparison(response, answer, parameters, eval_response):
461461

462462
eval_response.is_correct = False
463463

464+
if parameters.get("strictness", "natural") == "legacy":
465+
466+
def compensate_for_legacy_quirks(expr):
467+
prefix_data = {(p[0], p[1], tuple(), p[3]) for p in set_of_SI_prefixes}
468+
prefixes = []
469+
for prefix in prefix_data:
470+
prefixes = prefixes+[prefix[0]] + list(prefix[-1])
471+
prefix_short_forms = [prefix[1] for prefix in prefix_data]
472+
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
473+
unit_long_forms = prefixes
474+
for unit in unit_data:
475+
unit_long_forms = unit_long_forms+[unit[0]] + list(unit[-2]) + list(unit[-1])
476+
unit_long_forms = "("+"|".join(unit_long_forms)+")"
477+
# Rewrite any expression on the form "*UNIT" (but not "**UNIT") as " UNIT"
478+
# Example: "newton*metre" ---> "newton metre"
479+
search_string = r"(?<!\*)\* *"+unit_long_forms
480+
match_content = re.search(search_string, expr[1:])
481+
while match_content is not None:
482+
expr = expr[0:match_content.span()[0]+1]+match_content.group().replace("*"," ")+expr[match_content.span()[1]+1:]
483+
match_content = re.search(search_string, expr[1:])
484+
prefixes = "("+"|".join(prefixes)+")"
485+
# Rewrite any expression on the form "PREFIX UNIT" as "PREFIXUNIT"
486+
# Example: "kilo metre" ---> "kilometre"
487+
search_string = prefixes+" "+unit_long_forms
488+
match_content = re.search(search_string, expr)
489+
while match_content is not None:
490+
expr = expr[0:match_content.span()[0]]+" "+"".join(match_content.group().split())+expr[match_content.span()[1]:]
491+
match_content = re.search(search_string, expr)
492+
unit_short_forms = [u[1] for u in unit_data]
493+
short_forms = "("+"|".join(list(set(prefix_short_forms+unit_short_forms)))+")"
494+
# Add space before short forms of prefixes or unit names if they are preceded by numbers or multiplication
495+
# Example: "100Pa" ---> "100 Pa"
496+
search_string = r"[0-9\*\(\)]"+short_forms
497+
match_content = re.search(search_string, expr)
498+
while match_content is not None:
499+
expr = expr[0:match_content.span()[0]+1]+" "+expr[match_content.span()[0]+1:]
500+
match_content = re.search(search_string, expr)
501+
# Remove space after prefix short forms if they are preceded by numbers, multiplication or space
502+
# Example: "100 m Pa" ---> "100 mPa"
503+
prefix_short_forms = "("+"|".join(prefix_short_forms)+")"
504+
search_string = r"[0-9\*\(\) ]"+prefix_short_forms+" "
505+
match_content = re.search(search_string, expr)
506+
while match_content is not None:
507+
expr = expr[0:match_content.span()[0]+1]+match_content.group()[0:-1]+expr[match_content.span()[1]:]
508+
match_content = re.search(search_string, expr)
509+
# Remove multiplication and space after prefix short forms if they are preceded by numbers, multiplication or space
510+
# Example: "100 m* Pa" ---> "100 mPa"
511+
search_string = r"[0-9\*\(\) ]"+prefix_short_forms+"\* "
512+
match_content = re.search(search_string, expr)
513+
while match_content is not None:
514+
expr = expr[0:match_content.span()[0]+1]+match_content.group()[0:-2]+expr[match_content.span()[1]:]
515+
match_content = re.search(search_string, expr)
516+
# Replace multiplication followed by space before unit short forms with only spaces if they are preceded by numbers or space
517+
# Example: "100* Pa" ---> "100 Pa"
518+
unit_short_forms = "("+"|".join(unit_short_forms)+")"
519+
search_string = r"[0-9\(\) ]\* "+unit_short_forms
520+
match_content = re.search(search_string, expr)
521+
while match_content is not None:
522+
expr = expr[0:match_content.span()[0]]+match_content.group().replace("*"," ")+expr[match_content.span()[1]:]
523+
match_content = re.search(search_string, expr)
524+
return expr
525+
526+
response = compensate_for_legacy_quirks(response)
527+
answer = compensate_for_legacy_quirks(answer)
528+
parameters["strictness"] = "natural"
529+
464530
quantity_parser = SLR_quantity_parser(parameters)
465531
quantity_parsing = SLR_quantity_parsing
466532

0 commit comments

Comments
 (0)