diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 8499ea4..e92b622 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -248,6 +248,15 @@ If you want to use abbreviated unit names, prefix the specification with `~`: The same is true for latex (`L`) and HTML (`H`) specs. +Pint also supports the LaTeX siunitx package: + +.. doctest:: + + >>> accel = 1.3 * ureg['meter/second**2'] + >>> # siunitx Latex print + >>> print('The siunitx representation is {:Lx}'.format(accel)) + The siunitx representation is \SI[]{1.3}{\meter\per\second\squared} + Finally, you can specify a default format specification: >>> 'The acceleration is {}'.format(accel) diff --git a/pint/formatting.py b/pint/formatting.py index 2a70710..8d7f8e6 100644 --- a/pint/formatting.py +++ b/pint/formatting.py @@ -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: diff --git a/pint/measurement.py b/pint/measurement.py index e82591d..d0d239f 100644 --- a/pint/measurement.py +++ b/pint/measurement.py @@ -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'] diff --git a/pint/quantity.py b/pint/quantity.py index b0fade5..07dcdd5 100644 --- a/pint/quantity.py +++ b/pint/quantity.py @@ -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() diff --git a/pint/testsuite/test_measurement.py b/pint/testsuite/test_measurement.py index bf1487c..b7f2a50 100644 --- a/pint/testsuite/test_measurement.py +++ b/pint/testsuite/test_measurement.py @@ -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) second2') 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) second2') 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) second2') 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() diff --git a/pint/testsuite/test_quantity.py b/pint/testsuite/test_quantity.py index d701364..3ed98a2 100644 --- a/pint/testsuite/test_quantity.py +++ b/pint/testsuite/test_quantity.py @@ -104,6 +104,7 @@ class TestQuantity(QuantityTestCase): ('{0:P~}', '4.12345678 kg·m²/s'), ('{0:H~}', '4.12345678 kg m2/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) diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py index edbb2fa..d170c90 100644 --- a/pint/testsuite/test_unit.py +++ b/pint/testsuite/test_unit.py @@ -32,6 +32,7 @@ class TestUnit(QuantityTestCase): ('{0:P}', 'kilogram·meter²/second'), ('{0:H}', 'kilogram meter2/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'), diff --git a/pint/unit.py b/pint/unit.py index bbf9dfb..98aaa0f 100644 --- a/pint/unit.py +++ b/pint/unit.py @@ -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: if self.dimensionless: diff --git a/pint/util.py b/pint/util.py index 31e8206..d0bd14f 100644 --- a/pint/util.py +++ b/pint/util.py @@ -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__)