|
| 1 | +# Licensed under a 3-clause BSD style license, see LICENSE. |
| 2 | +""" |
| 3 | +Conversion routines between CLHEP, the HEP System of Units, and Pint units. |
| 4 | +
|
| 5 | +CLHEP adopts the approach where all the quantities are stored in the base unit |
| 6 | +system, effectively dimensionless. Pint offers to store both the magnitude and |
| 7 | +the dimensionality of the unit, which is helpful in deducing and/or validating |
| 8 | +the resulting unit of formulas. This module offers conversion routines between |
| 9 | +Pint's default base unit system and CLHEP. |
| 10 | +""" |
| 11 | + |
| 12 | +from __future__ import annotations |
| 13 | + |
| 14 | +try: |
| 15 | + import pint |
| 16 | +except ImportError as exc: |
| 17 | + msg = "Pint is required to use hepunits.pint." |
| 18 | + raise ImportError(msg) from exc |
| 19 | + |
| 20 | +# TODO: support more unit conversions |
| 21 | +_clhep_base_units = { |
| 22 | + "[length]": "millimeter", |
| 23 | + "[time]": "nanosecond", |
| 24 | + "[mass]": "MeV * millimeter**-2 * nanosecond**2", |
| 25 | + "[current]": "elementary_charge / nanosecond", |
| 26 | +} |
| 27 | + |
| 28 | + |
| 29 | +def _unit_from(val: pint.Quantity | pint.Unit) -> pint.Unit: |
| 30 | + """Extract the dimensionality from a Pint Quantity or Unit.""" |
| 31 | + # Grabbing the type is a quick way to be in the correct unit registry |
| 32 | + # see e.g. https://github.com/hgrecco/pint/issues/2207 |
| 33 | + unit = type(val.units) if isinstance(val, pint.Quantity) else type(val) |
| 34 | + out = unit("dimensionless") |
| 35 | + for dim, exponent in val.dimensionality.items(): |
| 36 | + if dim not in _clhep_base_units: |
| 37 | + msg = f"Unsupported dimension {dim} in {val}" |
| 38 | + raise ValueError(msg) |
| 39 | + out *= unit(_clhep_base_units[dim]) ** exponent |
| 40 | + |
| 41 | + return out |
| 42 | + |
| 43 | + |
| 44 | +def to_clhep(val: pint.Quantity | pint.Unit) -> float: |
| 45 | + """ |
| 46 | + Convert a Pint Quantity or Unit to CLHEP base units. |
| 47 | +
|
| 48 | + Parameters |
| 49 | + ---------- |
| 50 | + val : pint.Quantity or pint.Unit |
| 51 | + The value to convert. |
| 52 | +
|
| 53 | + Returns |
| 54 | + ------- |
| 55 | + float |
| 56 | + The value in CLHEP base units (dimensionless). |
| 57 | +
|
| 58 | + Examples |
| 59 | + -------- |
| 60 | + >>> ureg = pint.UnitRegistry() |
| 61 | + >>> g = 9.8 * ureg.meter / ureg.second**2 |
| 62 | + >>> g |
| 63 | + <Quantity(9.8, 'meter / second ** 2')> |
| 64 | + >>> to_clhep(g) |
| 65 | + 9.800000000000001e-15 |
| 66 | + """ |
| 67 | + clhep_unit = _unit_from(val) |
| 68 | + q = pint.Quantity(1.0, val) if isinstance(val, pint.Unit) else val |
| 69 | + return q.to(clhep_unit).magnitude # type: ignore[no-any-return] |
| 70 | + |
| 71 | + |
| 72 | +def from_clhep(val: float, unit: pint.Unit) -> pint.Quantity: |
| 73 | + """ |
| 74 | + Convert a value in CLHEP base units to a Pint Quantity. |
| 75 | +
|
| 76 | + Parameters |
| 77 | + ---------- |
| 78 | + val : float |
| 79 | + The value in CLHEP base units (dimensionless). |
| 80 | + unit : pint.Unit |
| 81 | + The desired output unit. |
| 82 | +
|
| 83 | + Returns |
| 84 | + ------- |
| 85 | + pint.Quantity |
| 86 | + The value in the desired unit. |
| 87 | +
|
| 88 | + Examples |
| 89 | + -------- |
| 90 | + >>> ureg = pint.UnitRegistry() |
| 91 | + >>> from_clhep(hepunits.c_light, ureg.meter / ureg.second) |
| 92 | + <Quantity(299792458.0, 'meter / second')> |
| 93 | + """ |
| 94 | + clhep_unit = _unit_from(unit) |
| 95 | + return pint.Quantity(val, clhep_unit).to(unit) |
0 commit comments