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:
@@ -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:
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 ± 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 ± 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 ± 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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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__)
|
||||
|
||||
Reference in New Issue
Block a user