TotalDepth.RP66V1.core.Units¶
Handles RP66V1 units.
There are two approaches:
Standard Based Parser¶
Formal but slow and does not appreciate that there are a load of non-standard implementations out there.
- References:
- [RP66V1 Appendix b, B.27 Code UNITS: Units Expression] [“Energistics Unit Symbol Grammar Specification” Section 2.2 Unit Symbol Construction Grammar]
UnitSymbol ::= [ Multiplier ' ' ] FactorExpression .
FactorExpression ::= OneOrMore |
( '1' | OneOrMore | Division ) '/' (
Division ::= '(' OneOrMore '/' Divisor ')'
OneOrMore ::= Factor | Factors .
Divisor ::= Factor | '(' Factors ')' .
Factors ::= Factor '.' Factor { '.' Factor
Factor ::= UnitComponent [ Exponent ] .
UnitComponent ::= PrefixedAtom | Atom | SpecialAtom [ Qualifier ] .
PrefixedAtom ::= ( SIPrefix | BinaryPrefix ) Atom .
Atom ::= Letter { Letter } [ Qualifier ] .
SpecialAtom ::= '%' | 'inH2O' | 'cmH2O' .
Qualifier ::= '[' [ AT ] QualPart { COMMA QualPart } ']' .
AT ::= '@' .
COMMA ::= ',' .
QualPart ::= LetterOrDigit { LetterOrDigit
LetterOrDigit ::= Letter | Digit .
Letter ::= E | LTTR .
LTTR ::= 'A'|'B'|'C' | 'D' | 'L' | 'M' | 'N' | 'O' | 'W' | 'X' | 'Y' | 'Z' | 'a' | 'b' | 'c' | 'd' | 'l' | 'm' | 'n'
| 'o' | 'w' | 'x' | 'y' | 'z' . 'P' | 'e' | 'p' | } .
} .
'F'|'G'|'H' 'Q' | 'R' | 'S'
'f' | 'g' | 'h'
'q' | 'r' | 's'
| 'I' | 'J' | 'K' |
| 'T' | 'U' | 'V' |
| 'i' | 'j' | 'k' |
| 't' | 'u' | 'v' |
Exponent ::= GtOneDigit | '(' ( NonZeroInt '.' FractionalPart | '0' '.' FractionalPart ) ')' .
Multiplier ::= '1' E PowerOfTen [ '/' GtOneInt ] | '1' '/' GtOneInt |
Number [ E PowerOfTen ][ '/' GtOneInt ] .
E ::= 'E' .
PowerOfTen ::= [ '-' ] GtOneInt .
Number ::= GtOneInt |
NonZeroInt '.' FractionalPart | '0' '.' FractionalPart .
GtOneInt ::= GtOneDigit | NonZeroDigit Digit { Digit } .
FractionalPart ::= { Digit } NonZeroDigit .
NonZeroInt ::= NonZeroDigit { Digit } .
Digit ::= '0' | NonZeroDigit .
GtOneDigit ::= '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' .
NonZeroDigit ::= '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' .
SIPrefix ::= 'y' | 'z' | 'a' | 'f' | 'p' | 'n' | 'u' | 'm' | 'c' | 'd' |
'da' | 'h' | 'k' | 'M' | 'G' | 'T' | 'P' | 'E' | 'Z' | 'Y' .
BinaryPrefix ::= 'Ki' | 'Mi' | 'Gi' | 'Ti' | 'Pi' | 'Ei' | 'Zi' | 'Yi' .
Something like this:
import re
import typing
# Token = collections.namedtuple('Token', ['type', 'value', 'line', 'column'])
class Token(typing.NamedTuple):
type: str
value: str
line: int
column:int
def tokenise_units(code: str) -> typing.Sequence[Token]:
# See also [RP66V1 Appendix B, B.27 Code UNITS: Units Expression]
token_specification = [
('BinaryPrefix', r'Ki|Mi|Gi|Ti|Pi|Ei|Zi|Yi'),
('SiPrefix', r'y|z|a|f|p|n|u|m|c|d|da|h|k|M|G|T|P|E|Z|Y'),
('NonZeroDigit', r'1|2|3|4|5|6|7|8|9'),
('Digit', r'0|1|2|3|4|5|6|7|8|9'),
('GtOneDigit', r'2|3|4|5|6|7|8|9'),
('E', r'E'),
('Letter', r'[A-Za-z]'),
('COMMA', r','),
('AT', r'\@'),
('SpecialAtom', r'%|inH2O|cmH2O'),
('PARENLEFT', r'\('),
('PARENRIGHT', r'\)'),
('MULTIPLY', r'\*'),
('DIVIDE', r'/'),
('BLANK', r' '),
('DOT', r'.'),
('HYPHEN', r'-'),
('NEWLINE', r'\n'),
('ID', r'[A-Za-z]+'),
('MISSMATCH', r'.'),
]
tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification)
line_num = 1
line_start = 0
for mo in re.finditer(tok_regex, code):
kind = mo.lastgroup
value = mo.group()
column = mo.start() - line_start
if kind == 'NUMBER':
value = float(value) if '.' in value else int(value)
# elif kind == 'ID' and value in keywords:
# kind = value
# elif kind == 'NEWLINE':
# line_start = mo.end()
# line_num += 1
# continue
# elif kind == 'SKIP':
# continue
elif kind == 'MISMATCH':
raise RuntimeError(f'{value!r} unexpected on line {line_num}')
yield Token(kind, value, line_num, column)
for token in tokenise_units('0.1m/s2'):
for token in tokenise_units('627264E5/15499969 m2'):
print(token)
This is likely to be slow, some pre-processing may help.
Lookup¶
This uses online or offline data structures. The primary source is Schlumberger’s Oilfield Services Data Dictionary (OSDD): https://www.apps.slb.com/cmd/units.aspx It is quick and largely respects existing implementations.
Other data providers (by PRODUCER-CODE) may have alternate mappings that can be put into PRODUCER_CODE_MAPPING_OF_UNIT_CODE.
See src/TotalDepth/RP66V1/util/XMLReadUnits.py for some analysis.
-
exception
TotalDepth.RP66V1.core.Units.
ExceptionRP66V1Units
¶ Base class exception for this module.
-
TotalDepth.RP66V1.core.Units.
PRODUCER_CODE_MAPPING_OF_UNIT_CODE
= {280: {b'gapi': b'GAPI', b'ltrs': b'dm3', b'sec': b'SEC'}}¶ This permits different producer codes to map into Schlumberger’s Oilfield Services Data Dictionary (OSDD). This is just an example, see src/TotalDepth/RP66V1/util/XMLReadUnits.py for some analysis of test files.
-
TotalDepth.RP66V1.core.Units.
convert
(value: float, unit_from: bytes, unit_to: bytes, producer_code: int = 0) → float¶ Converts a value from one unit to another. This uses TotalDepth.common.units with an additional producer code mapping.
Examples:
Code Name Standard Form Dimension Scale Offset DEGC 'degree celsius' degC Temperature 1 -273.15 DEGF 'degree fahrenheit' degF Temperature 0.555555555555556 -459.67
So conversion from, say DEGC to DEGF is:
((value - DEGC.offset) * DEGC.scale) / DEGF.scale + DEGF.offset ((0.0 - -273.15) * 1.0) / 0.555555555555556 + -459.67 == 32.0
-
TotalDepth.RP66V1.core.Units.
convert_function
(unit_from: TotalDepth.common.units.Unit, unit_to: TotalDepth.common.units.Unit, producer_code: int = 0) → Callable¶ Return a partial function to convert from one RP66V1 units to another.