Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ test/

# test csv which should be user generated
notebooks/pandas_test.csv

# dask stuff
dask-worker-space
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Pint Changelog
0.15 (unreleased)
-----------------

- Change `Quantity` and `Unit` HTML (i.e., Jupyter notebook) repr away from LaTeX to a
simpler, more performant pretty-text and table based repr inspired by Sparse and Dask.
(Issue #654)
- Implement Dask collection interface to support Pint Quantity wrapped Dask arrays.
- Started automatically testing examples in the documentation
- Fixed right operand power for dimensionless Quantity to reflect numpy behavior. (Issue #1136)
Expand Down
8 changes: 4 additions & 4 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,10 @@ Pint also supports `f-strings`_ from python>=3.6 :
>>> print(f'The str is {accel:~.3e}')
The str is 1.300e+00 m / s ** 2
>>> print(f'The str is {accel:~H}') # HTML format (displays well in Jupyter)
The str is \[1.3\ m/{s}^{2}\]
The str is 1.3 m/s<sup>2</sup>

But Pint also extends the standard formatting capabilities for unicode and
LaTeX representations:
But Pint also extends the standard formatting capabilities for unicode, LaTeX, and HTML
representations:

.. doctest::

Expand All @@ -335,7 +335,7 @@ LaTeX representations:
'The LaTeX representation is 1.3\\ \\frac{\\mathrm{meter}}{\\mathrm{second}^{2}}'
>>> # HTML print - good for Jupyter notebooks
>>> 'The HTML representation is {:H}'.format(accel)
'The HTML representation is \\[1.3\\ meter/{second}^{2}\\]'
'The HTML representation is 1.3 meter/second<sup>2</sup>'

If you want to use abbreviated unit names, prefix the specification with `~`:

Expand Down
8 changes: 2 additions & 6 deletions pint/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def _pretty_fmt_exponent(num):
"single_denominator": True,
"product_fmt": r" ",
"division_fmt": r"{}/{}",
"power_fmt": "{{{}}}^{{{}}}", # braces superscript whole exponent
"power_fmt": r"{}<sup>{}</sup>",
"parentheses_fmt": r"({})",
},
"": { # Default format.
Expand Down Expand Up @@ -270,12 +270,8 @@ def format_unit(unit, spec, **kwspec):
(r"\mathrm{{{}}}".format(u.replace("_", r"\_")), p) for u, p in unit.items()
]
return formatter(rm, **fmt).replace("[", "{").replace("]", "}")
elif spec == "H":
# HTML (Jupyter Notebook)
rm = [(u.replace("_", r"\_"), p) for u, p in unit.items()]
return formatter(rm, **fmt)
else:
# Plain text
# HTML and Text
return formatter(unit.items(), **fmt)


Expand Down
12 changes: 4 additions & 8 deletions pint/measurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def __format__(self, spec):
if "L" in newspec and "S" in newspec:
mag = mag.replace("(", r"\left(").replace(")", r"\right)")

if "L" in newspec or "H" in spec:
if "L" in newspec:
space = r"\ "
else:
space = " "
Expand All @@ -158,14 +158,10 @@ def __format__(self, spec):

if "H" in spec:
# Fix exponential format
mag = re.sub(r"\)e\+0?(\d+)", r")×10^{\1}", mag)
mag = re.sub(r"\)e-0?(\d+)", r")×10^{-\1}", mag)
mag = re.sub(r"\)e\+0?(\d+)", r")×10<sup>\1</sup>", mag)
mag = re.sub(r"\)e-0?(\d+)", r")×10<sup>-\1</sup>", mag)

assert ustr[:2] == r"\["
assert ustr[-2:] == r"\]"
return r"\[" + mag + space + ustr[2:]
else:
return mag + space + ustr
return mag + space + ustr


_Measurement = Measurement
Expand Down
79 changes: 52 additions & 27 deletions pint/quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
from .formatting import (
_pretty_fmt_exponent,
ndarray_to_latex,
ndarray_to_latex_parts,
remove_custom_flags,
siunitx_format_unit,
)
Expand All @@ -66,6 +65,7 @@
SharedRegistryObject,
UnitsContainer,
infer_base_unit,
iterable,
logger,
to_units_container,
)
Expand Down Expand Up @@ -311,48 +311,72 @@ def __format__(self, spec):

spec = spec or self.default_format

if "L" in spec:
allf = plain_allf = r"{}\ {}"
else:
allf = plain_allf = "{} {}"

# If Compact is selected, do it at the beginning
if "#" in spec:
spec = spec.replace("#", "")
obj = self.to_compact()
else:
obj = self

# the LaTeX siunitx code
if "L" in spec:
allf = plain_allf = r"{}\ {}"
elif "H" in spec:
allf = plain_allf = "{} {}"
if iterable(obj.magnitude):
# Use HTML table instead of plain text template for array-likes
allf = (
"<table><tbody>"
"<tr><th>Magnitude</th>"
"<td style='text-align:left;'>{}</td></tr>"
"<tr><th>Units</th><td style='text-align:left;'>{}</td></tr>"
"</tbody></table>"
)
else:
allf = plain_allf = "{} {}"

if "Lx" in spec:
# the LaTeX siunitx code
spec = spec.replace("Lx", "")
# TODO: add support for extracting options
opts = ""
ustr = siunitx_format_unit(obj.units)
allf = r"\SI[%s]{{{}}}{{{}}}" % opts
elif "H" in spec:
ustr = format(obj.units, spec)
assert ustr[:2] == r"\["
assert ustr[-2:] == r"\]"
ustr = ustr[2:-2]
allf = r"\[{}\ {}\]"
else:
# Hand off to unit formatting
ustr = format(obj.units, spec)

mspec = remove_custom_flags(spec)
if isinstance(self.magnitude, ndarray):
if "H" in spec:
# HTML formatting
if hasattr(obj.magnitude, "_repr_html_"):
# If magnitude has an HTML repr, nest it within Pint's
mstr = obj.magnitude._repr_html_()
else:
if isinstance(self.magnitude, ndarray):
# Use custom ndarray text formatting with monospace font
formatter = "{{:{}}}".format(mspec)
with printoptions(formatter={"float_kind": formatter.format}):
mstr = (
"<pre>"
+ format(obj.magnitude).replace("\n", "<br>")
+ "</pre>"
)
elif not iterable(obj.magnitude):
# Use plain text for scalars
mstr = format(obj.magnitude, mspec)
else:
# Use monospace font for other array-likes
mstr = (
"<pre>"
+ format(obj.magnitude, mspec).replace("\n", "<br>")
+ "</pre>"
)
elif isinstance(self.magnitude, ndarray):
if "L" in spec:
# Use ndarray LaTeX special formatting
mstr = ndarray_to_latex(obj.magnitude, mspec)
elif "H" in spec:
allf = r"\[{} {}\]"
# this is required to have the magnitude and unit in the same line
parts = ndarray_to_latex_parts(obj.magnitude, mspec)

if len(parts) > 1:
return "\n".join(allf.format(part, ustr) for part in parts)

mstr = parts[0]
else:
# Use custom ndarray text formatting
formatter = "{{:{}}}".format(mspec)
with printoptions(formatter={"float_kind": formatter.format}):
mstr = format(obj.magnitude).replace("\n", "")
Expand All @@ -361,13 +385,14 @@ def __format__(self, spec):

if "L" in spec:
mstr = self._exp_pattern.sub(r"\1\\times 10^{\2\3}", mstr)
elif "H" in spec:
mstr = self._exp_pattern.sub(r"\1×10^{\2\3}", mstr)
elif "P" in spec:
elif "H" in spec or "P" in spec:
m = self._exp_pattern.match(mstr)
_exp_formatter = (
_pretty_fmt_exponent if "P" in spec else lambda s: f"<sup>{s}</sup>"
)
if m:
exp = int(m.group(2) + m.group(3))
mstr = self._exp_pattern.sub(r"\1×10" + _pretty_fmt_exponent(exp), mstr)
mstr = self._exp_pattern.sub(r"\1×10" + _exp_formatter(exp), mstr)

if allf == plain_allf and ustr.startswith("1 /"):
# Write e.g. "3 / s" instead of "3 1 / s"
Expand Down
16 changes: 8 additions & 8 deletions pint/testsuite/test_measurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ def test_format(self):
("{!r}", "<Measurement(4.0, 0.1, second ** 2)>"),
("{:P}", "(4.00 ± 0.10) second²"),
("{:L}", r"\left(4.00 \pm 0.10\right)\ \mathrm{second}^{2}"),
("{:H}", r"\[(4.00 &plusmn; 0.10)\ {second}^{2}\]"),
("{:H}", "(4.00 &plusmn; 0.10) second<sup>2</sup>"),
("{:C}", "(4.00+/-0.10) second**2"),
("{:Lx}", r"\SI{4.00 +- 0.10}{\second\squared}"),
("{:.1f}", "(4.0 +/- 0.1) second ** 2"),
("{:.1fP}", "(4.0 ± 0.1) second²"),
("{:.1fL}", r"\left(4.0 \pm 0.1\right)\ \mathrm{second}^{2}"),
("{:.1fH}", r"\[(4.0 &plusmn; 0.1)\ {second}^{2}\]"),
("{:.1fH}", "(4.0 &plusmn; 0.1) second<sup>2</sup>"),
("{:.1fC}", "(4.0+/-0.1) second**2"),
("{:.1fLx}", r"\SI{4.0 +- 0.1}{\second\squared}"),
):
Expand All @@ -70,7 +70,7 @@ def test_format_paru(self):
("{:.3uS}", "0.2000(100) second ** 2"),
("{:.3uSP}", "0.2000(100) second²"),
("{:.3uSL}", r"0.2000\left(100\right)\ \mathrm{second}^{2}"),
("{:.3uSH}", r"\[0.2000(100)\ {second}^{2}\]"),
("{:.3uSH}", "0.2000(100) second<sup>2</sup>"),
("{:.3uSC}", "0.2000(100) second**2"),
):
with self.subTest(spec):
Expand All @@ -84,7 +84,7 @@ def test_format_u(self):
("{:.3u}", "(0.2000 +/- 0.0100) second ** 2"),
("{:.3uP}", "(0.2000 ± 0.0100) second²"),
("{:.3uL}", r"\left(0.2000 \pm 0.0100\right)\ \mathrm{second}^{2}"),
("{:.3uH}", r"\[(0.2000 &plusmn; 0.0100)\ {second}^{2}\]"),
("{:.3uH}", "(0.2000 &plusmn; 0.0100) second<sup>2</sup>"),
("{:.3uC}", "(0.2000+/-0.0100) second**2"),
("{:.3uLx}", r"\SI{0.2000 +- 0.0100}{\second\squared}",),
("{:.1uLx}", r"\SI{0.20 +- 0.01}{\second\squared}"),
Expand All @@ -101,7 +101,7 @@ def test_format_percu(self):
("{:.1u%}", "(20 +/- 1)% second ** 2"),
("{:.1u%P}", "(20 ± 1)% second²"),
("{:.1u%L}", r"\left(20 \pm 1\right) \%\ \mathrm{second}^{2}"),
("{:.1u%H}", r"\[(20 &plusmn; 1)%\ {second}^{2}\]"),
("{:.1u%H}", "(20 &plusmn; 1)% second<sup>2</sup>"),
("{:.1u%C}", "(20+/-1)% second**2"),
):
with self.subTest(spec):
Expand All @@ -117,7 +117,7 @@ def test_format_perce(self):
"{:.1ueL}",
r"\left(2.0 \pm 0.1\right) \times 10^{-1}\ \mathrm{second}^{2}",
),
("{:.1ueH}", r"\[(2.0 &plusmn; 0.1)×10^{-1}\ {second}^{2}\]"),
("{:.1ueH}", "(2.0 &plusmn; 0.1)×10<sup>-1</sup> second<sup>2</sup>"),
("{:.1ueC}", "(2.0+/-0.1)e-01 second**2"),
):
with self.subTest(spec):
Expand All @@ -132,7 +132,7 @@ def test_format_exponential_pos(self):
("{!r}", "<Measurement(4e+20, 1e+19, second ** 2)>"),
("{:P}", "(4.00 ± 0.10)×10²⁰ second²"),
("{:L}", r"\left(4.00 \pm 0.10\right) \times 10^{20}\ \mathrm{second}^{2}"),
("{:H}", r"\[(4.00 &plusmn; 0.10)×10^{20}\ {second}^{2}\]"),
("{:H}", "(4.00 &plusmn; 0.10)×10<sup>20</sup> second<sup>2</sup>"),
("{:C}", "(4.00+/-0.10)e+20 second**2"),
("{:Lx}", r"\SI{4.00 +- 0.10 e+20}{\second\squared}"),
):
Expand All @@ -149,7 +149,7 @@ def test_format_exponential_neg(self):
"{:L}",
r"\left(4.00 \pm 0.10\right) \times 10^{-20}\ \mathrm{second}^{2}",
),
("{:H}", r"\[(4.00 &plusmn; 0.10)×10^{-20}\ {second}^{2}\]"),
("{:H}", "(4.00 &plusmn; 0.10)×10<sup>-20</sup> second<sup>2</sup>"),
("{:C}", "(4.00+/-0.10)e-20 second**2"),
("{:Lx}", r"\SI{4.00 +- 0.10 e-20}{\second\squared}"),
):
Expand Down
25 changes: 17 additions & 8 deletions pint/testsuite/test_quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,15 @@ def test_quantity_format(self):
r"4.12345678\ \frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}",
),
("{:P}", "4.12345678 kilogram·meter²/second"),
("{:H}", r"\[4.12345678\ kilogram\ {meter}^{2}/second\]"),
("{:H}", "4.12345678 kilogram meter<sup>2</sup>/second"),
("{:C}", "4.12345678 kilogram*meter**2/second"),
("{:~}", "4.12345678 kg * m ** 2 / s"),
(
"{:L~}",
r"4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}",
),
("{:P~}", "4.12345678 kg·m²/s"),
("{:H~}", r"\[4.12345678\ kg\ {m}^{2}/s\]"),
("{:H~}", "4.12345678 kg m<sup>2</sup>/s"),
("{:C~}", "4.12345678 kg*m**2/s"),
("{:Lx}", r"\SI[]{4.12345678}{\kilo\gram\meter\squared\per\second}"),
):
Expand Down Expand Up @@ -176,6 +176,15 @@ def test_quantity_array_format(self):
),
("{:.2f~P}", "[0.00 1.00 10000000.00 1000000000000.00 nan inf] kg·m²"),
("{:g~P}", "[1e-16 1 1e+07 1e+12 nan inf] kg·m²"),
(
"{:.2f~H}",
(
"<table><tbody><tr><th>Magnitude</th><td style='text-align:left;'>"
"<pre>[0.00 1.00 10000000.00 1000000000000.00 nan inf]</pre></td></tr>"
"<tr><th>Units</th><td style='text-align:left;'>kg m<sup>2</sup></td></tr>"
"</tbody></table>"
),
),
):
with self.subTest(spec):
self.assertEqual(spec.format(x), result)
Expand Down Expand Up @@ -209,12 +218,12 @@ def test_default_formatting(self):
r"4.12345678\ \frac{\mathrm{kilogram} \cdot \mathrm{meter}^{2}}{\mathrm{second}}",
),
("P", "4.12345678 kilogram·meter²/second"),
("H", r"\[4.12345678\ kilogram\ {meter}^{2}/second\]"),
("H", "4.12345678 kilogram meter<sup>2</sup>/second"),
("C", "4.12345678 kilogram*meter**2/second"),
("~", "4.12345678 kg * m ** 2 / s"),
("L~", r"4.12345678\ \frac{\mathrm{kg} \cdot \mathrm{m}^{2}}{\mathrm{s}}"),
("P~", "4.12345678 kg·m²/s"),
("H~", r"\[4.12345678\ kg\ {m}^{2}/s\]"),
("H~", "4.12345678 kg m<sup>2</sup>/s"),
("C~", "4.12345678 kg*m**2/s"),
):
with self.subTest(spec):
Expand All @@ -224,12 +233,12 @@ def test_default_formatting(self):
def test_exponent_formatting(self):
ureg = UnitRegistry()
x = ureg.Quantity(1e20, "meter")
self.assertEqual(f"{x:~H}", r"\[1×10^{20}\ m\]")
self.assertEqual(f"{x:~H}", r"1×10<sup>20</sup> m")
self.assertEqual(f"{x:~L}", r"1\times 10^{20}\ \mathrm{m}")
self.assertEqual(f"{x:~P}", r"1×10²⁰ m")

x /= 1e40
self.assertEqual(f"{x:~H}", r"\[1×10^{-20}\ m\]")
self.assertEqual(f"{x:~H}", r"1×10<sup>-20</sup> m")
self.assertEqual(f"{x:~L}", r"1\times 10^{-20}\ \mathrm{m}")
self.assertEqual(f"{x:~P}", r"1×10⁻²⁰ m")

Expand All @@ -250,7 +259,7 @@ def pretty(cls, data):

ureg = UnitRegistry()
x = 3.5 * ureg.Unit(UnitsContainer(meter=2, kilogram=1, second=-1))
self.assertEqual(x._repr_html_(), r"\[3.5\ kilogram\ {meter}^{2}/second\]")
self.assertEqual(x._repr_html_(), "3.5 kilogram meter<sup>2</sup>/second")
self.assertEqual(
x._repr_latex_(),
r"$3.5\ \frac{\mathrm{kilogram} \cdot "
Expand All @@ -259,7 +268,7 @@ def pretty(cls, data):
x._repr_pretty_(Pretty, False)
self.assertEqual("".join(alltext), "3.5 kilogram·meter²/second")
ureg.default_format = "~"
self.assertEqual(x._repr_html_(), r"\[3.5\ kg\ {m}^{2}/s\]")
self.assertEqual(x._repr_html_(), "3.5 kg m<sup>2</sup>/s")
self.assertEqual(
x._repr_latex_(),
r"$3.5\ \frac{\mathrm{kg} \cdot " r"\mathrm{m}^{2}}{\mathrm{s}}$",
Expand Down
Loading