Units and Conventions#

physicskit doesn’t force one unit system on every field. Each subpackage computes in the units its own literature uses, so its formulas look like the ones in the papers you are checking against: \(r_\text{ISCO} = 6M\) rather than \(6GM/c^2\), and \(E_n = n + \tfrac12\) rather than \((n + \tfrac12)\hbar\omega\). The cost is that a bare number such as 6.0 only has a physical meaning once you say what the unit is. This page lists the convention of each subpackage, and shows how physicskit.units makes the missing scale choice explicit, converts results to SI and back, and keeps the units attached when results are saved.

Conventions by subpackage#

Subpackage

Convention

What you still have to choose

astro

gravitational, \(G = 1\)

a length and a mass scale (e.g. 1 pc and 1 \(M_\odot\)); astro_units()

relativity

geometrized, \(G = c = 1\)

one scale, conventionally the mass \(M\); geometrized_units(). The black-hole classes use \(M = 1\); BinaryMerger uses 1 m as the length unit (geometrized_units(length_m=1.0)); FLRWCosmology works in km/s/Mpc and Gyr.

quantum

\(\hbar = 1\) (usually with \(m = 1\))

a mass and a length (or energy) scale; quantum_units()

statphys

\(k_B = 1\), couplings \(J = 1\)

an energy (equivalently temperature) scale; also a length and mass for molecular dynamics; statphys_units()

plasma

SI for single-particle, MHD and wave functions; normalized (\(\omega_{pe} = \lambda_D = 1\)) for the PIC code

nothing for the SI functions; a density and temperature to give the PIC units physical meaning

fluids

any consistent system you pass in; idealized problems are non-dimensional

your own units

rmt, chaos

dimensionless

nothing

classical, condensed, fields, optics, particle, semiclassical

constants are explicit parameters (hbar=1.0, g=9.81, …) or units are stated per function (e.g. GeV in parts of particle)

read the function’s docstring

physicskit.constants is the single source of SI values (CODATA, via scipy.constants) that all of this is built on.

Choosing the scale: physicskit.units#

Setting constants to 1 leaves some scales free: \(G = c = 1\) leaves one and \(G = 1\) leaves two. Each preset in physicskit.units takes the free scales as required keyword arguments. None of them has a default, because a default would pick a physical system for you without saying so:

import physicskit.constants as const
from physicskit.units import geometrized_units

sun = geometrized_units(mass_kg=const.SOLAR_MASS_KG)     # M = 1 solar mass
print(f"{sun.scale('length'):.1f} m, {sun.scale('time') * 1e6:.3f} us")
# 1476.7 m, 4.926 us

print(f"ISCO: {sun.to_si(6.0, 'length') / 1e3:.2f} km")  # r = 6M
# ISCO: 8.86 km

print(f"1 ms = {sun.from_si(1.0e-3, 'time'):.1f} M")
# 1 ms = 203.0 M

The same \(r = 6M\) is 8.86 km for a solar-mass black hole and about 58 billion km (385 au) for the \(6.5\times10^9\,M_\odot\) black hole at the centre of M87. The code is identical; only the scale changes.

A dimension the scales don’t determine raises an error instead of returning a number. With \(k_B = 1\) and only an energy scale, an Ising-model temperature converts but a length doesn’t:

from physicskit.units import statphys_units

ising = statphys_units(temperature_k=300.0)   # coupling J = k_B * 300 K
print(f"T_c = {ising.to_si(2.269, 'temperature'):.1f} K")
# T_c = 680.7 K

ising.to_si(1.0, "length")
# ValueError: unit system 'statphys (k_B=1)' does not fix the scale of dimension L^1;
# supply another scale (e.g. length_m or mass_kg) when building it

Choosing the electron mass and the Bohr radius for \(\hbar = 1\) gives Hartree atomic units, so a ground-state energy of \(-1/2\) becomes the hydrogen binding energy:

from physicskit.units import quantum_units

au = quantum_units(mass_kg=const.ELECTRON_MASS, length_m=5.29177210903e-11)
print(f"{const.joules_to_ev(au.to_si(0.5, 'energy')):.3f} eV")
# 13.606 eV

Any other combination can be built with natural_units(), for example particle-physics units with \(\hbar = c = 1\) and energies in GeV:

from physicskit.units import natural_units

gev = natural_units("hbar", "c", energy_j=1e9 * const.ELECTRONVOLT)
print(f"1/GeV = {gev.to_si(1.0, 'length') * 1e15:.4f} fm = {gev.to_si(1.0, 'time'):.3e} s")
# 1/GeV = 0.1973 fm = 6.582e-25 s

How it works: a UnitSystem stores anchors, pairs of a Dimension and the SI size of one unit. Each constant set to 1 is one anchor and each chosen scale is another. Solving the anchors in log space gives the SI size of one unit of any dimension they determine. Anchors that contradict each other, such as \(c = 1\) together with unrelated length and time scales, are rejected when the system is built.

Keeping units with results#

physicskit.results.Result holds a time series (or any array output) together with its metadata, the dimension of each array, and the unit system they are expressed in. The from_* adapters in physicskit.results wrap the result types the subpackages already return (integrator tuples, the classical SimulationResult, quantum EigenResult, rmt Spectrum, chaos map orbits). You can also build one directly:

import numpy as np
import physicskit.relativity as rel
from physicskit.results import Result

bh = rel.SchwarzschildBlackHole(M=1.0)
orbit = bh.integrate_geodesic(bh.circular_orbit_initial_state(10.0), dtau=0.5, n_steps=2000)

result = Result(
    times=orbit["t"],
    states=np.column_stack([orbit["r"], orbit["phi"]]),
    metadata={"r0": 10.0},
    units={"times": "time"},
    unit_system=sun,
)
print(f"{result.times[-1]:.1f} M = {result.to_si().times[-1] * 1e3:.3f} ms")
# 1195.2 M = 5.887 ms

physicskit.io.save() and load() write and read the whole thing, unit system included, as .npz or (with pip install "physicskit[hdf5]") HDF5:

from physicskit import io

io.save(result, "orbit.npz")
back = io.load("orbit.npz")
print(back, back.unit_system == sun)
# Result(times=(2001,), states=(2001, 2), unit_system='relativity (G=c=1)') True

Interoperating with pint#

If you already use pint, install physicskit[units] and convert at the boundary with to_pint() and from_pint(). The dimension of a pint quantity is read from the quantity itself:

import pint
from physicskit.units import from_pint, to_pint

ureg = pint.UnitRegistry()
print(to_pint(6.0, "length", sun, registry=ureg).to("km"))
# 8.860018146200808 kilometer

print(f"{from_pint(1.0 * ureg.millisecond, sun):.1f}")
# 203.0

Relation to older helpers#

A few conversion helpers predate physicskit.units and remain supported: physicskit.constants.gravitational_unit_system() (the same numbers as astro_units()), energy_to_temperature() and the eV converters, and physicskit.relativity.utils.constants (solar_masses_to_geometrized and friends, for the 1-metre geometrized units used by BinaryMerger). For anything else, physicskit.units is the general tool.

Reference#

Natural-unit systems and conversions to and from SI.

Each physicskit subpackage computes in the unit system natural to its own field: physicskit.astro sets \(G = 1\), physicskit.relativity sets \(G = c = 1\), physicskit.quantum sets \(\hbar = 1\), and physicskit.statphys sets \(k_B = 1\). Setting a constant to 1 alone does not fix a unit system: \(G = c = 1\) still leaves one scale free (conventionally a mass \(M\)), and \(G = 1\) leaves two. This module makes those remaining choices explicit. Every preset has keyword arguments you must supply for the free scales; none of them has a hidden default.

How it works. A UnitSystem is defined by a set of anchors: pairs of a Dimension and the SI value of one unit of that dimension. A constant set to 1 is an anchor (for \(c = 1\), one unit of velocity is \(c_{SI}\) m/s), and so is each user-chosen scale (one unit of mass is \(M_\odot\)). Writing the unknown SI sizes of the base units as \([L], [M], [T], [\Theta]\), each anchor with dimension \(L^a M^b T^c \Theta^d\) and SI value \(v\) gives one linear equation in log space,

\[a \ln[L] + b \ln[M] + c \ln[T] + d \ln[\Theta] = \ln v .\]

A dimension is convertible exactly when its exponent vector lies in the span of the anchors’ exponent vectors. So statphys_units() with only an energy scale can convert energies and temperatures but refuses to convert a length, rather than returning a meaningless number.

SI values come from physicskit.constants. Converting to and from pint quantities is supported when pint is installed (pip install "physicskit[units]"); see to_pint() and from_pint().

Examples

The geometrized (\(G = c = 1\)) length of one solar mass is \(GM_\odot/c^2 \approx 1476.6\) m (to the precision of physicskit.constants.SOLAR_MASS_KG):

>>> import physicskit.constants as const
>>> from physicskit.units import geometrized_units
>>> sun = geometrized_units(mass_kg=const.SOLAR_MASS_KG)
>>> abs(sun.scale("length") - 1476.6) < 0.1
True
>>> round(sun.to_si(10.0, "time") * 1e6, 2)  # 10 M in microseconds
49.26
physicskit.units.DIMENSIONS: dict[str, Dimension] = {'acceleration': Dimension(length=1.0, mass=0.0, time=-2.0, temperature=0.0), 'action': Dimension(length=2.0, mass=1.0, time=-1.0, temperature=0.0), 'angular_momentum': Dimension(length=2.0, mass=1.0, time=-1.0, temperature=0.0), 'area': Dimension(length=2.0, mass=0.0, time=0.0, temperature=0.0), 'density': Dimension(length=-3.0, mass=1.0, time=0.0, temperature=0.0), 'dimensionless': Dimension(length=0.0, mass=0.0, time=0.0, temperature=0.0), 'energy': Dimension(length=2.0, mass=1.0, time=-2.0, temperature=0.0), 'entropy': Dimension(length=2.0, mass=1.0, time=-2.0, temperature=-1.0), 'force': Dimension(length=1.0, mass=1.0, time=-2.0, temperature=0.0), 'frequency': Dimension(length=-0.0, mass=-0.0, time=-1.0, temperature=-0.0), 'length': Dimension(length=1, mass=0.0, time=0.0, temperature=0.0), 'mass': Dimension(length=0.0, mass=1, time=0.0, temperature=0.0), 'momentum': Dimension(length=1.0, mass=1.0, time=-1.0, temperature=0.0), 'number_density': Dimension(length=-3.0, mass=-0.0, time=-0.0, temperature=-0.0), 'power': Dimension(length=2.0, mass=1.0, time=-3.0, temperature=0.0), 'pressure': Dimension(length=-1.0, mass=1.0, time=-2.0, temperature=0.0), 'temperature': Dimension(length=0.0, mass=0.0, time=0.0, temperature=1), 'time': Dimension(length=0.0, mass=0.0, time=1, temperature=0.0), 'velocity': Dimension(length=1.0, mass=0.0, time=-1.0, temperature=0.0), 'volume': Dimension(length=3.0, mass=0.0, time=0.0, temperature=0.0)}#

Named dimensions accepted wherever a dimension argument is taken.

class physicskit.units.Dimension(length=0.0, mass=0.0, time=0.0, temperature=0.0)[source]#

Bases: object

Physical dimension \(L^a M^b T^c \Theta^d\) as an exponent vector.

Dimensions multiply, divide and raise to powers like the quantities they describe, so derived dimensions can be built from base ones.

Parameters:
  • length (float) – Exponents of length, mass, time and temperature.

  • mass (float) – Exponents of length, mass, time and temperature.

  • time (float) – Exponents of length, mass, time and temperature.

  • temperature (float) – Exponents of length, mass, time and temperature.

Examples

>>> L, M, T = Dimension(length=1), Dimension(mass=1), Dimension(time=1)
>>> M * L**2 / T**2 == DIMENSIONS["energy"]
True
as_vector()[source]#

Return the exponents as a (4,) array (length, mass, time, temperature).

Return type:

NDArray[double]

Returns:

ndarray of float, shape (4,)

length: float = 0.0#
mass: float = 0.0#
temperature: float = 0.0#
time: float = 0.0#
physicskit.units.NATURAL_CONSTANTS: dict[str, tuple[Dimension, float]] = {'G': (Dimension(length=3.0, mass=-1.0, time=-2.0, temperature=0.0), 6.6743e-11), 'c': (Dimension(length=1.0, mass=0.0, time=-1.0, temperature=0.0), 299792458.0), 'hbar': (Dimension(length=2.0, mass=1.0, time=-1.0, temperature=0.0), 1.0545718176461565e-34), 'k_B': (Dimension(length=2.0, mass=1.0, time=-2.0, temperature=-1.0), 1.380649e-23)}#

Constants that a natural-unit system can set to 1, as name -> (dimension, SI value). SI values are from physicskit.constants (CODATA values via scipy.constants).

physicskit.units.SI = UnitSystem(name='SI', anchors=((Dimension(length=1, mass=0.0, time=0.0, temperature=0.0), 1.0), (Dimension(length=0.0, mass=1, time=0.0, temperature=0.0), 1.0), (Dimension(length=0.0, mass=0.0, time=1, temperature=0.0), 1.0), (Dimension(length=0.0, mass=0.0, time=0.0, temperature=1), 1.0)))#

one unit of every dimension is its SI unit. Converting to it is the identity.

Type:

The SI system itself

physicskit.units.SUBPACKAGE_CONVENTIONS: dict[str, dict[str, Any]] = {'astro': {'constants': ('G',), 'preset': 'astro_units'}, 'quantum': {'constants': ('hbar',), 'preset': 'quantum_units'}, 'relativity': {'constants': ('G', 'c'), 'preset': 'geometrized_units'}, 'statphys': {'constants': ('k_B',), 'preset': 'statphys_units'}}#

Which constants each subpackage sets to 1, and the preset that builds a matching UnitSystem once the remaining free scales are chosen.

class physicskit.units.UnitSystem(name, anchors)[source]#

Bases: object

A system of units fixed by a set of anchors.

Most users should build one with a preset (astro_units(), geometrized_units(), quantum_units(), statphys_units()) or with natural_units().

Parameters:
  • name (str) – Human-readable label, e.g. "relativity (G=c=1)".

  • anchors (tuple[tuple[Dimension, float], ...]) – Each anchor states that one unit of dimension in this system equals value in SI. The anchors must be mutually consistent.

Raises:

ValueError – If an anchor value is not a positive finite number, or redundant anchors disagree.

Examples

A plain “centimetre-gram-second” mechanical system:

>>> cgs = UnitSystem("cgs", ((Dimension(length=1), 1e-2), (Dimension(mass=1), 1e-3), (Dimension(time=1), 1.0)))
>>> round(cgs.to_si(1.0, "energy"), 15)  # 1 erg in J
1e-07
anchors: tuple[tuple[Dimension, float], ...]#
classmethod from_dict(data)[source]#

Rebuild a UnitSystem from to_dict() output.

Parameters:

data (Mapping[str, Any]) – Output of to_dict().

Return type:

UnitSystem

Returns:

UnitSystem

Examples

>>> u = geometrized_units(mass_kg=const.SOLAR_MASS_KG)
>>> UnitSystem.from_dict(u.to_dict()) == u
True
from_si(value, dimension)[source]#

Convert value from SI to this system.

Parameters:
  • value (ArrayLike) – Quantity in SI units.

  • dimension (str | Dimension) – The quantity’s dimension.

Return type:

Any

Returns:

float or ndarray – The same quantity in this system’s units.

Examples

>>> u = geometrized_units(mass_kg=const.SOLAR_MASS_KG)
>>> round(u.from_si(const.C, "velocity"), 12)
1.0
is_determined(dimension)[source]#

Whether the anchors fix the SI size of one unit of dimension.

Parameters:

dimension (str | Dimension) – A key of DIMENSIONS or a Dimension.

Return type:

bool

Returns:

bool

Examples

>>> u = statphys_units(energy_j=1.0e-21)
>>> u.is_determined("temperature"), u.is_determined("length")
(True, False)
name: str#
scale(dimension)[source]#

SI value of one unit of dimension in this system.

Parameters:

dimension (str | Dimension) – A key of DIMENSIONS or a Dimension.

Return type:

float

Returns:

float

Raises:

ValueError – If the system’s anchors do not determine this dimension.

Examples

>>> u = quantum_units(mass_kg=const.ELECTRON_MASS, length_m=5.29177210903e-11)
>>> print(f"{u.scale('time'):.4e}")  # atomic unit of time, s
2.4189e-17
scales()[source]#

SI value of one unit for every named dimension this system fixes.

Return type:

dict[str, float]

Returns:

dict of str to float – Keys are the entries of DIMENSIONS that are determined.

Examples

>>> sorted(statphys_units(temperature_k=300.0).scales())
['dimensionless', 'energy', 'entropy', 'temperature']
to_dict()[source]#

Serialize to a JSON-compatible dict (inverse of from_dict()).

Return type:

dict[str, Any]

Returns:

dict

to_si(value, dimension)[source]#

Convert value from this system to SI.

Parameters:
  • value (ArrayLike) – Quantity expressed in this system’s units.

  • dimension (str | Dimension) – The quantity’s dimension.

Return type:

Any

Returns:

float or ndarray – The same quantity in SI units.

Examples

>>> u = astro_units(length_m=const.ASTRONOMICAL_UNIT_M, mass_kg=const.SOLAR_MASS_KG)
>>> round(u.to_si(2 * np.pi, "time") / 86400 / 365.25, 4)  # Earth's orbit in years
1.0
physicskit.units.astro_units(*, length_m, mass_kg)[source]#

Gravitational units, \(G = 1\), as used by physicskit.astro.

The time unit follows from \(G_{SI} = L^3/(M T^2)\), i.e. \(T = \sqrt{L^3/(G_{SI} M)}\) (same as physicskit.constants.gravitational_unit_system()).

Parameters:
  • length_m (float) – SI length of one length unit, in m.

  • mass_kg (float) – SI mass of one mass unit, in kg.

Return type:

UnitSystem

Returns:

UnitSystem

Examples

With 1 pc and 1 solar mass, one time unit is about 14.9 Myr:

>>> u = astro_units(length_m=const.PARSEC_M, mass_kg=const.SOLAR_MASS_KG)
>>> round(u.scale("time") / (1e6 * 365.25 * 86400), 2)
14.91
physicskit.units.from_pint(quantity, system)[source]#

Convert a pint quantity into system’s units.

The dimension is read from the quantity itself. Requires the optional pint dependency (physicskit[units]).

Parameters:
  • quantity (Any) – Any quantity whose dimensionality is built from length, mass, time and temperature.

  • system (UnitSystem) – The target unit system.

Return type:

Any

Returns:

float or ndarray – The magnitude in system’s units.

Raises:

ValueError – If quantity involves another base dimension (e.g. current).

Examples

>>> import pint
>>> ureg = pint.UnitRegistry()
>>> from_pint(1476.6 * ureg.meter, geometrized_units(mass_kg=const.SOLAR_MASS_KG))
0.99997...
physicskit.units.from_si(value, dimension, system)[source]#

Convert an SI value into system’s units.

Equivalent to system.from_si(value, dimension).

Parameters:
Return type:

Any

Returns:

float or ndarray

Examples

>>> round(from_si(300.0, "temperature", statphys_units(temperature_k=300.0)), 12)
1.0
physicskit.units.geometrized_units(*, mass_kg=None, length_m=None)[source]#

Geometrized units, \(G = c = 1\), as used by physicskit.relativity.

With a mass scale \(M\), one unit of length is \(GM/c^2\) and one unit of time is \(GM/c^3\) (Misner, Thorne & Wheeler, Gravitation, 1973, Box 1.8).

Parameters:
  • mass_kg (float | None) – SI mass of one mass unit (conventionally the black-hole or system mass \(M\)).

  • length_m (float | None) – SI length of one length unit, as an alternative to mass_kg. Supply exactly one of the two.

Return type:

UnitSystem

Returns:

UnitSystem

Examples

>>> u = geometrized_units(mass_kg=const.SOLAR_MASS_KG)
>>> print(f"{u.scale('length'):.5g} m, {u.scale('time') * 1e6:.4g} us")
1476.7 m, 4.926 us
physicskit.units.natural_units(*constants, name=None, length_m=None, mass_kg=None, time_s=None, energy_j=None, temperature_k=None)[source]#

Build a unit system with the given constants set to 1 and explicit scales.

Parameters:
  • *constants (str) – Names from NATURAL_CONSTANTS ("G", "c", "hbar", "k_B") to set equal to 1.

  • name (str | None) – Label for the system; defaults to e.g. "G=c=1".

  • length_m (float | None) – SI size of one unit of length, mass, time, energy or temperature.

  • mass_kg (float | None) – SI size of one unit of length, mass, time, energy or temperature.

  • time_s (float | None) – SI size of one unit of length, mass, time, energy or temperature.

  • energy_j (float | None) – SI size of one unit of length, mass, time, energy or temperature.

  • temperature_k (float | None) – SI size of one unit of length, mass, time, energy or temperature.

Return type:

UnitSystem

Returns:

UnitSystem

Raises:

ValueError – For an unknown constant name, or scales that contradict the constants set to 1 (e.g. \(c = 1\) with both a length and a time scale that are not in ratio \(c\)).

Examples

Particle-physics units \(\hbar = c = 1\) with energies in GeV:

>>> gev = natural_units("hbar", "c", energy_j=1e9 * const.ELECTRONVOLT)
>>> round(gev.scale("length") * 1e15, 4)  # hbar*c / GeV, in fm
0.1973
physicskit.units.quantum_units(*, mass_kg, length_m=None, energy_j=None)[source]#

Units with \(\hbar = 1\), as used by physicskit.quantum.

Choosing a mass \(m\) and a length \(a\) fixes the energy unit \(\hbar^2/(m a^2)\) and time unit \(m a^2/\hbar\). With the electron mass and the Bohr radius these are the Hartree atomic units (CODATA: \(E_h = 27.211\) eV, \(t_{au} = 2.4189\times10^{-17}\) s).

Parameters:
  • mass_kg (float) – SI mass of one mass unit (the particle mass for m = 1 code).

  • length_m (float | None) – SI length of one length unit.

  • energy_j (float | None) – SI energy of one energy unit, as an alternative to length_m. Supply exactly one of the two.

Return type:

UnitSystem

Returns:

UnitSystem

Examples

>>> au = quantum_units(mass_kg=const.ELECTRON_MASS, length_m=5.29177210903e-11)
>>> round(const.joules_to_ev(au.scale("energy")), 3)  # Hartree, eV
27.211
physicskit.units.statphys_units(*, energy_j=None, temperature_k=None, length_m=None, mass_kg=None)[source]#

Units with \(k_B = 1\), as used by physicskit.statphys.

With \(k_B = 1\) an energy scale \(\epsilon\) and a temperature scale \(\epsilon/k_B\) are the same choice, which is enough for lattice models (e.g. Ising with coupling \(J = 1\)). Molecular dynamics additionally needs a length \(\sigma\) and mass \(m\), giving the Lennard-Jones reduced time unit \(\tau = \sigma\sqrt{m/\epsilon}\) (Allen & Tildesley, Computer Simulation of Liquids, 2nd ed., 2017, Appendix B).

Parameters:
  • energy_j (float | None) – SI energy of one energy unit (e.g. the coupling \(J\)).

  • temperature_k (float | None) – SI temperature of one temperature unit, as an alternative to energy_j. Supply exactly one of the two.

  • length_m (float | None) – Length and mass scales, needed only to convert mechanical quantities such as time or pressure.

  • mass_kg (float | None) – Length and mass scales, needed only to convert mechanical quantities such as time or pressure.

Return type:

UnitSystem

Returns:

UnitSystem

Examples

Lennard-Jones argon (\(\sigma = 3.405\) Å, \(\epsilon/k_B = 119.8\) K, \(m = 39.948\) u) has a time unit of about 2.16 ps:

>>> u = statphys_units(temperature_k=119.8, length_m=3.405e-10, mass_kg=39.948 * 1.66053906660e-27)
>>> round(u.scale("time") * 1e12, 2)
2.16
physicskit.units.to_pint(value, dimension, system, registry=None)[source]#

Convert a natural-unit value into a pint quantity in SI base units.

Requires the optional pint dependency (physicskit[units]).

Parameters:
  • value (ArrayLike) – Quantity in system’s units.

  • dimension (str | Dimension) – The quantity’s dimension.

  • system (UnitSystem) – The unit system value is expressed in.

  • registry (Any) – Registry to create the quantity in; defaults to pint.get_application_registry().

Return type:

Any

Returns:

pint.Quantity

Examples

>>> q = to_pint(1.0, "length", geometrized_units(mass_kg=const.SOLAR_MASS_KG))
>>> q.to("km")
<Quantity(1.47662..., 'kilometer')>
physicskit.units.to_si(value, dimension, system)[source]#

Convert value from system’s units to SI.

Equivalent to system.to_si(value, dimension).

Parameters:
  • value (ArrayLike) – Quantity in system’s units.

  • dimension (str | Dimension) – The quantity’s dimension.

  • system (UnitSystem) – The unit system value is expressed in.

Return type:

Any

Returns:

float or ndarray

Examples

>>> to_si(1.0, "length", geometrized_units(mass_kg=const.SOLAR_MASS_KG)) > 1476
True

A shared, unit-aware container for simulation output.

Subpackages return results in several shapes: a (times, states) tuple from the shared physicskit.integrators and the chaos flows, a (times, positions, velocities) tuple from the symplectic integrators, the classical SimulationResult, an orbit array from a chaos map, a quantum EigenResult, and an rmt Spectrum. Result is a single container they can all be converted to, which physicskit.io can save and load and which knows its units.

The container is opt-in. Subpackages keep returning their own types, and the from_* adapters below convert them. The adapters read attributes by name and do not import the subpackages.

Examples

>>> import numpy as np
>>> from physicskit.results import Result
>>> from physicskit.units import geometrized_units
>>> sun = geometrized_units(mass_kg=1.98847e30)
>>> r = Result(np.linspace(0.0, 10.0, 11), np.zeros((11, 2)), units={"times": "time"}, unit_system=sun)
>>> round(float(r.to_si().times[-1]) * 1e6, 2)  # 10 M in microseconds
49.26
class physicskit.results.Result(times, states, metadata=<factory>, units=<factory>, unit_system=None, arrays=<factory>)[source]#

Bases: object

Time series or other array output, with metadata and units.

Parameters:
  • times (NDArray[Any] | None) – Sample times, shape (n,), or None for output with no time axis (an eigenvalue spectrum, say).

  • states (NDArray[Any]) – The main output array. When times is given, its first axis must have length n.

  • metadata (dict[str, Any]) – JSON-compatible information such as the integrator name, parameters and provenance. It must be JSON-compatible for physicskit.io.save() to store it.

  • units (dict[str, str]) – Maps an array name ("times", "states" or a key of arrays) to a dimension name from physicskit.units.DIMENSIONS. Arrays without an entry are treated as dimensionless or of unknown dimension, and convert() leaves them unchanged.

  • unit_system (UnitSystem | None) – The system the dimensioned arrays are expressed in. None means unspecified; physicskit.units.SI means SI.

  • arrays (dict[str, NDArray[Any]]) – Further named arrays, such as momenta, energies or a spatial grid.

Raises:

ValueError – If lengths disagree, an array name is reserved or invalid, or a units entry names an unknown array or dimension.

Examples

>>> r = Result([0.0, 0.5, 1.0], [[1.0], [0.5], [0.0]], arrays={"energy": [2.0, 2.0, 2.0]})
>>> r.names
['times', 'states', 'energy']
>>> r["energy"].shape
(3,)
arrays: dict[str, NDArray[Any]]#
convert(system)[source]#

Return a copy with every dimensioned array expressed in system.

Arrays listed in units are converted through SI; all other arrays and the metadata are copied unchanged.

Parameters:

system (UnitSystem) – Target unit system.

Return type:

Result

Returns:

Result

Raises:

ValueError – If units is non-empty but unit_system is None, or either system leaves a needed dimension undetermined.

Examples

>>> from physicskit.units import SI, statphys_units
>>> r = Result(None, [1.0, 2.0], units={"states": "temperature"}, unit_system=statphys_units(temperature_k=300.0))
>>> r.convert(SI).states
array([300., 600.])
metadata: dict[str, Any]#
property names: list[str]#

Names of all arrays held, in the order times, states, extras.

Returns:

list of str

states: NDArray[Any]#
times: NDArray[Any] | None#
to_si()[source]#

Return a copy with every dimensioned array in SI units.

Shorthand for convert(physicskit.units.SI).

Return type:

Result

Returns:

Result

unit_system: UnitSystem | None = None#
units: dict[str, str]#
physicskit.results.from_eigen_result(result, *, metadata=None, units=None, unit_system=None)[source]#

Wrap a quantum EigenResult.

There is no time axis. states holds the wavefunctions, shape (n_states, len(x)), and arrays holds x and energies.

Parameters:
Return type:

Result

Returns:

Result

Examples

>>> import numpy as np
>>> from physicskit.quantum.core.eigensolvers import NumerovSolver, infinite_well
>>> eig = NumerovSolver(np.linspace(0.0, 1.0, 201), infinite_well()).solve(n_states=3)
>>> r = from_eigen_result(eig)
>>> r.times is None, r.states.shape, r["energies"].shape
(True, (3, 201), (3,))
physicskit.results.from_integrator(output, *, method=None, metadata=None, units=None, unit_system=None)[source]#

Wrap the tuple returned by a physicskit.integrators function.

Accepts (times, states) from rk4_integrate(), dopri5_integrate() and the chaos flows’ trajectory() methods, or (times, positions, velocities) from leapfrog_integrate() and yoshida4_integrate(). For the latter, states is the phase-space trajectory [positions, velocities] stacked along the last axis, and the two halves are also kept as arrays["positions"] and arrays["velocities"].

Parameters:
Return type:

Result

Returns:

Result

Examples

>>> import numpy as np
>>> r = from_integrator((np.array([0.0, 0.1]), np.ones((2, 1)), np.zeros((2, 1))), method="leapfrog")
>>> r.states.shape, r.metadata["method"]
((2, 2), 'leapfrog')
physicskit.results.from_map_orbit(orbit, *, metadata=None, units=None, unit_system=None)[source]#

Wrap the orbit returned by a chaos map’s trajectory().

Maps are discrete, so times is the iteration index 0, 1, ... and metadata["time_kind"] is "iteration".

Parameters:
Return type:

Result

Returns:

Result

Examples

>>> from physicskit.chaos.systems.maps import LogisticMap
>>> r = from_map_orbit(LogisticMap(r=3.9).trajectory(n_iter=5))
>>> r.times
array([0, 1, 2, 3, 4, 5])
physicskit.results.from_simulation_result(result, *, metadata=None, units=None, unit_system=None)[source]#

Wrap a classical SimulationResult.

t becomes times and y becomes states. q, p and energy, when present, go to arrays. Entries of extra go to arrays if they are arrays and to metadata["extra"] otherwise; values that are not JSON scalars are stored as their repr.

Parameters:
Return type:

Result

Returns:

Result

Examples

>>> from physicskit.classical.systems.hamiltonian import HenonHeilesSystem
>>> sim = HenonHeilesSystem(q0=[0.1, 0.0], p0=[0.0, 0.2]).integrate((0.0, 1.0), dt=0.01)
>>> r = from_simulation_result(sim)
>>> r.names, r.metadata["method"]
(['times', 'states', 'q', 'p', 'energy'], 'yoshida4')
physicskit.results.from_spectrum(spectrum, *, metadata=None)[source]#

Wrap an rmt Spectrum.

There is no time axis. states holds the raw eigenvalues, shape (n_samples, n). n, beta, ensemble and scale go to metadata, and eigenvectors (if sampled) to arrays. Random-matrix spectra are dimensionless, so no units are attached.

Parameters:
  • spectrum (Any) – Output of an ensemble’s sample().

  • metadata (Mapping[str, Any] | None) – Extra metadata, merged over the adapter’s own.

Return type:

Result

Returns:

Result

Examples

>>> import physicskit.rmt as rmt
>>> r = from_spectrum(rmt.ensembles.GOE(n=4, seed=1).sample(n_samples=3))
>>> r.states.shape, r.metadata["ensemble"], r.metadata["beta"]
((3, 4), 'GOE', 1.0)

Save and load Result objects.

Two formats are supported, chosen by file extension:

  • .npz (NumPy’s zipped archive), always available. Loading never unpickles anything (allow_pickle=False), so a file from an untrusted source cannot execute code.

  • .h5 / .hdf5, when h5py is installed (pip install "physicskit[hdf5]"). Arrays become datasets that other HDF5 tools can read directly.

Both store the same thing: the times, states and extra arrays, plus a JSON header with the metadata, units, unit system and a format version. The metadata must therefore be JSON-compatible. NumPy scalars are converted to Python numbers, and tuples come back as lists.

Examples

>>> import numpy as np, tempfile, pathlib
>>> from physicskit.results import Result
>>> from physicskit.io import save, load
>>> r = Result(np.linspace(0, 1, 5), np.eye(5), metadata={"method": "rk4"})
>>> with tempfile.TemporaryDirectory() as d:
...     back = load(save(r, pathlib.Path(d) / "run.npz"))
>>> back.metadata["method"], np.array_equal(back.states, r.states)
('rk4', True)
physicskit.io.FORMAT_VERSION = 1#

Version of the on-disk layout written by save(). load() refuses files written by a newer version.

physicskit.io.load(path, *, format=None)[source]#

Read a Result written by save().

Parameters:
  • path (str | PathLike[str]) – File to read.

  • format (str | None) – Explicit format, overriding the extension.

Return type:

Result

Returns:

Result

Raises:
  • ValueError – If the file is not a physicskit result, or was written by a newer format version.

  • ImportError – For HDF5 when h5py is not installed.

physicskit.io.save(result, path, *, format=None)[source]#

Write result to path, overwriting any existing file.

Parameters:
  • result (Result) – The result to save.

  • path (str | PathLike[str]) – Destination. The extension (.npz, .h5, .hdf5) picks the format unless format is given.

  • format (str | None) – Explicit format, overriding the extension.

Return type:

Path

Returns:

pathlib.Path – The path written.

Raises:
  • TypeError – If the metadata is not JSON-compatible or an array is not numeric or boolean.

  • ImportError – For HDF5 when h5py is not installed.