The QuantityParser and related functions

NOTES:

Defines the classes
as well as the functions
  • parse() - parse string using a new instance of QuantityParser and a blank history
  • set_preferred_product() - uses the QuantityParser to set the preferred product for a Quantity, called by Quantity.set_preferred()

This module can be used outside Sage and it uses true divison (i.e. 3/2 is 1.5) rather than the Python standard (3/2 is 1).

AUTHORS:

  • David Bate (2008): initial version
  • Miriam Backens (2009): fixed a few bugs, added set_preferred_product()

EXAMPLE:

sage: from sage.dimpy import *
sage: p = QuantityParser()
sage: h = ParseHistory()
sage: p.parse('airspeed = 11.7 m/s', h)
[11.7 m s^-1, 'airspeed = 11.7*meter/second', 'airspeed']
sage: p.parse('airspeed in mi/h', h)
[26.172154617 * (0.44704 m s^-1),
 'airspeed = 26.172154617 * mile/hour',
 'airspeed']
sage: print h.prnt()
0: airspeed = 11.7 m/s
1: airspeed in mi/h
exception sage.dimpy.quantity_parser.ParseError
class sage.dimpy.quantity_parser.ParseHistory

Stores the most recent 200 parser requests for recall.

ATTRIBUTES:

  • self.history - a list of the most recent strings. An entry with a smaller index is older.
  • self.defined_variables - a dictionary of variable_name:variable_value pairs so that we can have different variables for each user
add(string)
Add a string to the end of self.history.
copy()
Return a copy of self.
delete(pos)
Delete the entry with index position.
length()
Return the number of entries.
prnt()
Return a string representation of the entries.
read(pos)
Read the entry at position.
class sage.dimpy.quantity_parser.QuantityParser(html=False, on_the_fly=True, false_requests=False)

A parser for the language of quantities.

Creating a new QuantityParser:

INPUT:

  • html - a bool, whether to use hml formatting in the output (default: False)
  • on_the_fly - a bool, whether to allow on-the-fly defining of variables (default: True)
  • false_requests – whether to allow dimensionally false requests like “3 miles in meter/second” (default: False)

ATTRIBUTES:

  • QuantityParser.TOK_... - tokens for the parser
  • QuantityParser.SI_PREFIXES, SI_PREFIXES_SHORT - dictionaries, store the SI prefixes
  • QuantityParser.WORD_NUMBERS - a dictionary. Stores some words and their values
  • QuantityParser.ALL_UNITS - a dictionary. Contains the pre-defined Quantities from the quantity and additional_units modules and their values
  • QuantityParser.UNIT_SYMBOLS - a dictionary of unit symbol:unit name (as strings)
  • self.history - the ParseHistory we should use during parsing
  • self.string - the string to be parsed
  • self.pos - an integer, the current character to be parsed in self.string
  • self.output_string - the interpretation of self.string i.e. if self.string is "3 meters", self.output_string = "3*meter", one is generated for each expression in a query.
  • self.fly_variables - the variables created during a parse are stored in this as name:value pairs
  • self.dict - the contents of all the other dictionaries are copied into this
  • self.false_requests - bool, if we should allow things like “3 meters in miles per hour” (default: False)
  • self.on_the_fly - bool, allow on the fly-defining of variables (default: True)
  • self.html - bool, format output using html (default: False)

NOTES:

This is an infix parser that will also handle terms that look like 3m, 3m**2, 3m^2, 3m2 (to mean 3m^2), 3millimeter, 3mm, ten thousand. Accepts “per” to mean divide and “in” to mean a conversion (“meters in miles”).

Be aware that there are two types of multiplication:

  • The * operator has the same precedence as a standard infix calculator.

  • A space between words i.e. “meter second” or “ten million” is parsed first. This is important when asking for a division:

    1/ten*million = (1/ten)*million
    1/ten million = 1/(ten*million)

Grammar (BNF):

query      := expression (ws query_op expression)?
query_op   := [in,=]
expression := term (ws [+-] ws term)*
term       := factor (ws [*/] ws factor)*
factor     := powterm (ws ('**'|'^') ws powterm)*
powterm    := '(' ws expression ws ')' | [+-] ws powterm | number | (number)? ws sentence | '#' number
sentence   := (word ws (number|('**'|'^') ws number)?)+
word       := an alphabetic string (i.e. all characters pass isalpha())
number     := '-'? (integer | real)
integer    := digit+
real       := ((digit+ ('.' digit*)?) | ('.' digit+)) ([eE] [+-]? digit+)?
digit      := [0-9]
ws         := [ \t\r\n]*
check_alpha()
Check if the next non whitespace character is alphabetic.
check_number()

Check if the next non-whitespace character is a number.

number := integer | real

check_plus()
Check if the next non-whitespace character is + or -.
check_power()
Check if the next non-whitespace character is ** or ^.
check_query_op()

Check whether the next non-whitespace part of the string is a query operator.

query_op := [in,=]

check_times()
Check if the next non-whitespace character is * or /.
get_exponent()
Used when calculating factors of the form 2^{2^{2^{2}}}.
imperial()

Quick fix so that there are a variety of ways to enter ‘Imperial gallon’ etc.

NOTES:

This fixes issues of capitals vs. non capital letters and periods in the abbreviations. If neither imperial nor US is specified, it will assume imperial.

parse(string, history)

Parse a string input using an infix notation.

INPUT:
  • string - the string to parse
  • history - a ParseHistory object
OUTPUT: a list with 3 entries
  1. the value of the parsed input, i.e. a Quantity, Flydim or number
  2. a standard format string representation of what is parsed
  3. the string that replaces a history reference, this string can be used directly in an eval or exec statement, unless it is a variable name

EXAMPLE:

sage: p = QuantityParser()
sage: h = ParseHistory()
sage: p.parse("3 meters in miles", h)
[0.00186411357671 * (1.609344 km), '3*meter = 0.00186411357671 * mile', '3*meter']
sage: h.prnt()
'0: 3 meters in miles'

Checking that bug fixes work. Numbers with zeros right after the decimal point:

sage: p.parse('1.00002', h)
[1.0000199999999999, '1.00002 = 1.00002', '1.00002']

Negative exponents:

sage: p.parse('m s^-2', h)
[1.0 m s^-2, '(meter*(second)**-2) = 1.0 m s^-2', '(meter*(second)**-2)']

Removed spurious carets in output:

sage: p.parse('meter^2', h)
[1 m^2, '(meter)**2 = 1 m^2', '(meter)**2']
parse_expression()

Parse an expression.

expression := term (ws [+-] ws term)*

parse_factor()

Parse a factor.

factor := powterm (ws ('**'|'^') ws powterm)*

parse_integer()

Parse an integer.

integer := digit+

digit   := [0-9]

parse_number(add_to_string=True)

Parse a number.

number := '-'? (integer | real)

real   := ((digit+ ('.' digit*)?) | ('.' digit+)) ([eE] [+-]? digit+)?

parse_operator(increment=True, add_to_string=True)

Parse arithmetic operators.

These are +, -, *, /, ** and ^.

parse_powterm()

Parse a powterm.

powterm := '(' ws expression ws ')' | [+-] ws powterm | number | (number)? ws sentence | '#' number

parse_query()

Parse a query.

query := expression (ws query_op expression)?

OUTPUT: a list [value, formatted_string, history_string], where value is the required value, formatted_string is printed to the string, and history_string is put into formatted_string when the parser finds a history request.

See documentation for QuantityParser.parse().

parse_query_op(increment=True)

Parse the main operator of the query.

query_op := [in,=]

parse_sentence()

Parse a sentence.

sentence := (word ws (number|('**'|'^') ws number)?)+

parse_term()

Parse a term.

term := factor (ws [*/] ws factor)*

parse_word()

Parse a word.

OUTPUT: a tuple (value, formatted_string)

word := an alphabetic string (i.e. all characters pass isalpha(), underscores also allowed)

skip_whitespace()

Move forward to the next non-whitespace character.

ws := [ \t\r\n]*

sage.dimpy.quantity_parser.parse(string)

Parse string and return the value, but do not consider a history.

INPUT:

  • string - a parseable string

OUTPUT: a Quantity or number, the value of the parsed input

EXAMPLES:

sage: parse('200 pF/mm^2')
0.0002 m^-4 kg^-1 s^4 A^2
sage: parse('one hundred')
100
sage: type(_)
<type 'int'>
sage.dimpy.quantity_parser.set_preferred_product(quantity, string, **kwargs)

An easy way of defining new units that are products of old ones.

INPUT:

  • quantity - the Quantity whose preferred unit is set
  • string - a string, will be parsed to create the unit and also used as the display name for the new unit
  • **kwargs - a dictionary of name_in_string=name_of_unit pairs of all self-defined Quantities and Flydims used in the string

NOTES:

The argument should be a string and this is what the new unit will print as. The string is parsed to find the value and dimensions associated with the unit. For further use, the unit is saved under a name derived from string by removing all exponentiations (^) and replacing multiplications (* and ‘ ‘) with one underscore, divisions (/) with double underscores minus signs with ‘neg’ and parentheses ((, )) with triple underscores, i.e. ‘kWh/d’ will become ‘kWh__d’, ‘m/s^2’ will become ‘m__s2’ and ‘J/(kg l)’ will become ‘J_____kg_l___’.

Any self-defined units and their string representations have to be given to the function as name_in_string=name_of_unit pairs.

EXAMPLES:

sage: x = 1 * kWh / day
sage: x
41.6666666667 W
sage: set_preferred_product(x, "kWh/d")
sage: x
1.0 kWh/d

sage: calorie = 4186.8*joule
sage: y = 2000*calorie/day
sage: y.set_preferred("kcal/d", kcal=calorie)
sage: y
2000.00000000000 kcal/d

sage: (5*meter/second).in_unit('mi h^-1')
11.1846814603 mi h^-1