Tips and gotchas¶
A grab-bag of practical idioms for everyday use, and the sharp edges that most
often trip people up. The gotchas are not bugs — they follow from physipy's
design (a Quantity wraps a single SI value plus one Dimension) — but they can
surprise you if you don't know them.
Tips¶
Get a plain number out¶
Two idioms, depending on what you want:
from physipy import m, units
q = 3 * units["mm"]
q.value # 0.003 -> magnitude in SI (metres)
q / units["mm"] # 3.0 -> magnitude expressed in a chosen unit
See Getting a plain number back.
Control how a quantity displays — favunit¶
The value and the displayed unit are independent. Set a favourite unit for display without changing the stored value:
See Display is separate from value.
Build arrays of quantities — asqarray¶
np.array([q1, q2]) does not give a dimensioned array. Use asqarray:
from physipy import asqarray, m
asqarray([1 * m, 2 * m, 3 * m]) # a single Quantity wrapping array([1., 2., 3.]) * m
Guard functions with dimensions — decorators¶
physipy ships decorators (in physipy.utils, re-exported at top level) to
enforce/strip dimensions at function boundaries:
| Decorator | What it does |
|---|---|
check_dimension(units_in, units_out) |
validate the dimensions of inputs and outputs |
dimension_and_favunit(inputs, outputs) |
validate dimensions and attach a favunit to outputs |
set_favunit(*favunits_out) |
attach favunits to outputs |
drop_dimension |
strip dimensions and pass the SI magnitudes to the wrapped function (handy to interface with code that only accepts plain numbers) |
Units and constants are just objects¶
There is no registry to instantiate — units and constants are plain Quantity
objects looked up by name:
from physipy import units, imperial_units, constants
units["mm"], imperial_units["mile"], constants["c"] # constants pulls in scipy lazily
Check what numpy is supported¶
physipy implements numpy support function-by-function. Query it at runtime:
import physipy
print(physipy.numpy_coverage()) # summary of implemented / missing / n-a
physipy.supported_numpy_functions(names=True)
See the numpy support page for the full, generated coverage report.
Gotchas¶
Values are always stored in SI¶
A quantity normalises to SI at construction, so .value is not the number you
typed if you used a non-SI unit:
_favunit_value() is display-only — don't compute with it¶
_favunit_value() returns the value expressed in the favunit, not SI. Using it
in numeric code introduces a silent 1/scale error that cancels in ratios and
looks fine in plots. Rule of thumb: .value for computation, q / U for
"value in unit U", _favunit_value() only for display.
See the footgun section.
Dimensionless results collapse to plain numbers¶
When an operation cancels all dimensions, you get a bare Python/numpy number, not
a Quantity:
So don't expect .value / .dimension on the result of a ratio — it's already a
plain number.
Comparisons return plain booleans (but still dimension-check)¶
import numpy as np
(np.arange(3) * m) > (1.5 * m) # array([False, False, True]) — a bool ndarray
(3 * m) > (1 * s) # raises DimensionError
The unit is enforced on the operands but stripped from the boolean result.
Angles (rad, sr) are real dimensions¶
physipy treats plane angle and solid angle as base dimensions. This catches
real errors (mixing an angle with a bare number), but when you interface with
code that expects dimensionless radians you may need to drop the rad
dimension explicitly. Transcendental functions require dimensionless/angle
input:
Some numpy functions can't be implemented¶
Because every element of a Quantity shares one Dimension, functions whose
output would need per-element heterogeneous dimensions have no faithful
representation (e.g. np.vander, the polynomial-coefficient family). See
Functions that cannot be implemented.
Two more numpy specifics:
- Logical ufuncs (
np.logical_and/or/xor/not) are intentionally not implemented — their semantics on dimensioned values are ill-defined. np.arangecan't be overridden the way ufuncs can. Usephysipy.quantity.utils.qarange, or build from a plain range:
np.arange(10 * m) # raises DimensionError
from physipy.quantity.utils import qarange
qarange(2 * m, 5 * m) # array([2., 3., 4.]) * m
np.arange(10) * m # also fine
np.full_like / np.full with a Quantity fill value¶
A Quantity fill_value is not honoured when the template/shape is plain,
because numpy dispatches full_like on its template a and full on like= —
never on fill_value — so physipy's __array_function__ is never consulted.
The two functions even fail differently:
np.full_like(np.arange(3), 3 * m) # raises DimensionError (via internal copyto)
np.full(3, 3 * m) # silently drops the unit -> array([3, 3, 3])
Make the template carry the dimension (then dispatch fires), or multiply a unitless template by the quantity:
np.full_like(np.arange(3) * m, 3 * m) # -> [3 3 3] m (a is a Quantity)
np.ones_like(np.arange(3)) * (3 * m) # -> [3 3 3] m (multiply instead)
matplotlib: activate the unit interface¶
By default matplotlib won't put units on your axes. Turn on the interface once per session:
See the matplotlib support page.
Limited quantity string parsing¶
physipy parses dimension strings (with the optional sympy extra) but not full
quantity strings like pint's "3 m/s". See the
comparison page limitations.
Reporting a gotcha¶
Known gotchas and limitations are tracked on GitHub with the gotcha label.
- Browse the current list:
open
gotchaissues. - Hit a new one? Open an issue
and tag it
gotchaso it shows up in that list (and, eventually, here).