Added support for siunitx formatting.

Added support for generating siunitx commands in the __format__
functions for Quantity, Measurment, and Unit classes.

This adds a new code, 'Lx', to the formatting spec, that if present will
trigger the LaTeX siunitx formatting. All other formatting options in
the spec string are be passed through.

Currently does not accept siunit options.
This commit is contained in:
C.D. Clark III
2015-12-07 16:51:32 -06:00
parent b869d7c6ec
commit dcb3df3ce2
8 changed files with 82 additions and 3 deletions

View File

@@ -190,6 +190,43 @@ def format_unit(unit, spec):
return result
def siunitx_format_unit(units):
'''Returns LaTeX code for the unit that can be put into an siunitx command.'''
# NOTE: unit registry is required to identify unit prefixes.
registry = units._REGISTRY
def _tothe(power):
if power == 1:
return ''
elif power == 2:
return r'\squared'
elif power == 3:
return r'\cubed'
else:
return r'\tothe{%d}' % (power)
l = []
# loop through all units in the container
for unit, power in sorted(units._units.items()):
# remove unit prefix if it exists
# siunit supports \prefix commands
prefix = None
for p in registry._prefixes.values():
p = str(p)
if len(p) > 0 and unit.find(p) == 0:
prefix = p
unit = unit.replace( prefix, '', 1 )
if power < 0:
l.append(r'\per')
if not prefix is None:
l.append(r'\{0}'.format(prefix))
l.append(r'\{0}'.format(unit))
l.append(r'{0}'.format(_tothe(abs(power))))
return ''.join(l)
def remove_custom_flags(spec):
for flag in _KNOWN_TYPES:
if flag:

View File

@@ -10,7 +10,7 @@
from __future__ import division, unicode_literals, print_function, absolute_import
from .compat import ufloat
from .formatting import _FORMATS
from .formatting import _FORMATS, siunitx_format_unit
MISSING = object()
@@ -65,6 +65,22 @@ class _Measurement(object):
return '{0}'.format(self)
def __format__(self, spec):
# special cases
if 'Lx' in spec: # the LaTeX siunitx code
# the uncertainties module supports formatting
# numbers in value(unc) notation (i.e. 1.23(45) instead of 1.23 +/- 0.45),
# which siunitx actually accepts as input. we just need to give the 'S'
# formatting option for the uncertainties module.
spec = spec.replace('Lx','S')
# todo: add support for extracting options
opts = 'separate-uncertainty=true'
mstr = format( self.magnitude, spec )
ustr = siunitx_format_unit(self.units)
ret = r'\SI[%s]{%s}{%s}'%( opts, mstr, ustr )
return ret
# standard cases
if 'L' in spec:
newpm = pm = r' \pm '
pars = _FORMATS['L']['parentheses_fmt']

View File

@@ -15,7 +15,7 @@ import operator
import functools
import bisect
from .formatting import remove_custom_flags
from .formatting import remove_custom_flags, siunitx_format_unit
from .errors import (DimensionalityError, OffsetUnitCalculusError,
UndefinedUnitError)
from .definitions import UnitDefinition
@@ -118,6 +118,18 @@ class _Quantity(SharedRegistryObject):
def __format__(self, spec):
spec = spec or self.default_format
# special cases
if 'Lx' in spec: # the LaTeX siunitx code
spec = spec.replace('Lx','')
# todo: add support for extracting options
opts = ''
mstr = format(self.magnitude,spec)
ustr = siunitx_format_unit(self.units)
ret = r'\SI[%s]{%s}{%s}'%( opts, mstr, ustr )
return ret
# standard cases
if '#' in spec:
spec = spec.replace('#', '')
obj = self.to_compact()

View File

@@ -51,11 +51,13 @@ class TestMeasurement(QuantityTestCase):
self.assertEqual('{0:L}'.format(m), r'\left(4.00 \pm 0.10\right) second^{2}')
self.assertEqual('{0:H}'.format(m), '(4.00 &plusmn; 0.10) second<sup>2</sup>')
self.assertEqual('{0:C}'.format(m), '(4.00+/-0.10) second**2')
self.assertEqual('{0:Lx}'.format(m), r'\SI[separate-uncertainty=true]{4.00(10)}{\second\squared}')
self.assertEqual('{0:.1f}'.format(m), '(4.0 +/- 0.1) second ** 2')
self.assertEqual('{0:.1fP}'.format(m), '(4.0 ± 0.1) second²')
self.assertEqual('{0:.1fL}'.format(m), r'\left(4.0 \pm 0.1\right) second^{2}')
self.assertEqual('{0:.1fH}'.format(m), '(4.0 &plusmn; 0.1) second<sup>2</sup>')
self.assertEqual('{0:.1fC}'.format(m), '(4.0+/-0.1) second**2')
self.assertEqual('{0:.1fLx}'.format(m), '\SI[separate-uncertainty=true]{4.0(1)}{\second\squared}')
def test_format_paru(self):
v, u = self.Q_(0.20, 's ** 2'), self.Q_(0.01, 's ** 2')
@@ -75,6 +77,8 @@ class TestMeasurement(QuantityTestCase):
self.assertEqual('{0:.3uL}'.format(m), r'\left(0.2000 \pm 0.0100\right) second^{2}')
self.assertEqual('{0:.3uH}'.format(m), '(0.2000 &plusmn; 0.0100) second<sup>2</sup>')
self.assertEqual('{0:.3uC}'.format(m), '(0.2000+/-0.0100) second**2')
self.assertEqual('{0:.3uLx}'.format(m), '\SI[separate-uncertainty=true]{0.2000(100)}{\second\squared}')
self.assertEqual('{0:.1uLx}'.format(m), '\SI[separate-uncertainty=true]{0.20(1)}{\second\squared}')
def test_format_percu(self):
self.test_format_perce()

View File

@@ -104,6 +104,7 @@ class TestQuantity(QuantityTestCase):
('{0:P~}', '4.12345678 kg·m²/s'),
('{0:H~}', '4.12345678 kg m<sup>2</sup>/s'),
('{0:C~}', '4.12345678 kg*m**2/s'),
('{0:Lx}', r'\SI[]{4.12345678}{\kilo\gram\meter\squared\per\second}'),
):
self.assertEqual(spec.format(x), result)

View File

@@ -32,6 +32,7 @@ class TestUnit(QuantityTestCase):
('{0:P}', 'kilogram·meter²/second'),
('{0:H}', 'kilogram meter<sup>2</sup>/second'),
('{0:C}', 'kilogram*meter**2/second'),
('{0:Lx}', r'\si[]{\kilo\gram\meter\squared\per\second}'),
('{0:~}', 'kg * m ** 2 / s'),
('{0:L~}', r'\frac{kg \cdot m^{2}}{s}'),
('{0:P~}', 'kg·m²/s'),

View File

@@ -30,6 +30,7 @@ from .util import (logger, pi_theorem, solve_dependencies, ParserHelper,
find_shortest_path, UnitsContainer, _is_dim,
SharedRegistryObject, to_units_container)
from .compat import tokenizer, string_types, NUMERIC_TYPES, long_type, zip_longest
from .formatting import siunitx_format_unit
from .definitions import (Definition, UnitDefinition, PrefixDefinition,
DimensionDefinition)
from .converters import ScaleConverter
@@ -99,6 +100,13 @@ class _Unit(SharedRegistryObject):
def __format__(self, spec):
spec = spec or self.default_format
# special cases
if 'Lx' in spec: # the LaTeX siunitx code
opts = ''
ustr = siunitx_format_unit(self)
ret = r'\si[%s]{%s}'%( opts, ustr )
return ret
if '~' in spec:
units = UnitsContainer(dict((self._REGISTRY._get_symbol(key),

View File

@@ -22,7 +22,7 @@ from token import STRING, NAME, OP, NUMBER
from tokenize import untokenize
from .compat import string_types, tokenizer, lru_cache, NullHandler, maketrans, NUMERIC_TYPES
from .formatting import format_unit
from .formatting import format_unit,siunitx_format_unit
from .pint_eval import build_eval_tree
logger = logging.getLogger(__name__)