%load_ext autoreload
%autoreload 2

PyUnitWizard Showcase: A Cross-Library Tour#

Welcome to the PyUnitWizard showcase! This notebook walks you through the essentials of the library so you can work comfortably with physical quantities across different Python unit systems. Whether you prefer Pint, OpenMM units, unyt, or Astropy units, PyUnitWizard gives you a single API to inspect, convert, and compare them.

What you will learn#

  • How to configure PyUnitWizard for the unit libraries you already use.

  • How to create and inspect quantities in the default unit system.

  • How to convert values, units, and whole quantities across forms.

  • How to keep calculations consistent with dimensional analysis and standardized units.

  • How to work with NumPy arrays, parse textual quantities, and access built-in constants.

Before you start#

PyUnitWizard itself is lightweight, but you will want to install the unit libraries you plan to interoperate with. A typical environment created with pip may look like:

pip install pyunitwizard pint openmm unyt astropy

Once the packages are available, we can import PyUnitWizard and NumPy.

import pyunitwizard as puw
import numpy as np
puw.__print_version__()
PyUnitWizard version 0.16.0+1.g927d49b.dirty

Loading libraries and setting defaults#

PyUnitWizard can talk to several backends, but it only enables what you load explicitly. The configure module lets you explore what is supported and make the choices that fit your project.

supported_libs = puw.configure.get_libraries_supported()
supported_parsers = puw.configure.get_parsers_supported()
print('Supported libraries:', supported_libs)
print('Supported parsers:', supported_parsers)
Supported libraries: ['pint', 'openmm.unit', 'unyt', 'astropy.units']
Supported parsers: ['pint', 'openmm.unit', 'unyt', 'astropy.units']
# Start from a clean slate for reproducibility in this notebook
puw.configure.reset()

# Load all available libraries so we can demonstrate conversions among them
puw.configure.load_library(['pint', 'openmm.unit', 'unyt', 'astropy.units'])

print('Loaded libraries:', puw.configure.get_libraries_loaded())
Loaded libraries: ['pint', 'openmm.unit', 'unyt', 'astropy.units']
# Choose Pint as the default form for new quantities
puw.configure.set_default_form('pint')
puw.configure.set_default_parser('pint')

# Pick a consistent set of standard units for later comparisons
puw.configure.set_standard_units(['nanometer', 'picosecond', 'kcal/mol', 'kelvin'])

print('Default form:', puw.configure.get_default_form())
print('Default parser:', puw.configure.get_default_parser())
print('Standards:', puw.configure.get_standard_units())
Default form: pint
Default parser: pint
Standards: {'nanometer': {'[L]': 1, '[M]': 0, '[T]': 0, '[K]': 0, '[mol]': 0, '[A]': 0, '[Cd]': 0}, 'picosecond': {'[L]': 0, '[M]': 0, '[T]': 1, '[K]': 0, '[mol]': 0, '[A]': 0, '[Cd]': 0}, 'kcal/mol': {'[L]': 2, '[M]': 1, '[T]': -2, '[K]': 0, '[mol]': -1, '[A]': 0, '[Cd]': 0}, 'kelvin': {'[L]': 0, '[M]': 0, '[T]': 0, '[K]': 1, '[mol]': 0, '[A]': 0, '[Cd]': 0}}

Creating and inspecting quantities#

The quantity constructor produces an object in the default form (Pint in our configuration). PyUnitWizard keeps track of both value and unit metadata.

distance = puw.quantity(2.5, 'nanometers')
print(distance)
print('Form:', puw.get_form(distance))
print('Value:', puw.get_value(distance))
print('Unit:', puw.get_unit(distance))
2.5 nanometer
Form: pint
Value: 2.5
Unit: nanometer

You can obtain the value and unit separately in different forms. For instance, converting the value to ångströms while requesting the unit in OpenMM notation.

value_angstrom, unit_openmm = puw.get_value_and_unit(distance, to_unit='angstrom', to_form='openmm.unit')
print('Value in Å:', value_angstrom)
print('Unit in OpenMM form:', unit_openmm)
Value in Å: 25.0
Unit in OpenMM form: angstrom

Converting across forms and units#

Quantities can be translated into any loaded library or expressed with different units. The convert helper drives both operations.

distance_openmm = puw.convert(distance, to_form='openmm.unit')
distance_picometers = puw.convert(distance, to_unit='picometer')

print('As an OpenMM quantity:', distance_openmm)
print('In picometers:', distance_picometers)
print('Form after conversion:', puw.get_form(distance_openmm))
As an OpenMM quantity: 2.5 nm
In picometers: 2500.0000000000005 picometer
Form after conversion: openmm.unit

Dimensional analysis and compatibility checks#

PyUnitWizard can read the dimensionality of a quantity and tell you whether two objects are compatible or close within tolerances.

speed = puw.quantity(5.0, 'meter/second')
acceleration = puw.quantity(1.2, 'meter/second**2')
print('Speed dimensionality:', puw.get_dimensionality(speed))
print('Acceleration dimensionality:', puw.get_dimensionality(acceleration))
print('Are speed and acceleration compatible?', puw.are_compatible(speed, acceleration))
Speed dimensionality: {'[L]': 1, '[M]': 0, '[T]': -1, '[K]': 0, '[mol]': 0, '[A]': 0, '[Cd]': 0}
Acceleration dimensionality: {'[L]': 1, '[M]': 0, '[T]': -2, '[K]': 0, '[mol]': 0, '[A]': 0, '[Cd]': 0}
Are speed and acceleration compatible? False
speed_mph = puw.convert(speed, to_unit='mile/hour')
print('Converted speed:', speed_mph)
print('Are the two speed representations close?', puw.are_close(speed, speed_mph, rtol=1e-9))
Converted speed: 11.184681460272012 mile / hour
Are the two speed representations close? True

Working with arrays of values#

Under the hood PyUnitWizard can store NumPy arrays. This is convenient when your simulation or experiment delivers vectorized data.

trajectory = puw.quantity(np.linspace(0, 1, 5), 'micrometers')
print('Trajectory values:', puw.get_value(trajectory))
print('Converted to nanometers:', puw.get_value(trajectory, to_unit='nanometer'))
Trajectory values: [0.   0.25 0.5  0.75 1.  ]
Converted to nanometers: [   0.  250.  500.  750. 1000.]
# Standardize the array so it follows the project-wide conventions
trajectory_std = puw.standardize(trajectory)
print('Standardized trajectory unit:', puw.get_unit(trajectory_std))
print('Values in standards:', puw.get_value(trajectory_std))
Standardized trajectory unit: nanometer
Values in standards: [   0.  250.  500.  750. 1000.]

Parsing from strings and manipulating units#

When ingesting data from configuration files or user input, quantities often arrive as strings. Use the default parser (or specify one) to turn them into rich objects.

string_quantity = puw.convert('42 kilocalorie/mole', to_form='pint')
print('Parsed quantity:', string_quantity)
print('Is it recognized as a quantity?', puw.is_quantity(string_quantity))
Parsed quantity: 42.0 kilocalorie / mole
Is it recognized as a quantity? True
# You can also inspect plain units without attaching a value
force_unit = puw.unit('kilojoule/(nanometer*mole)', form='pint')
print('Unit object:', force_unit)
print('Is it a unit?', puw.is_unit(force_unit))
Unit object: kilojoule / mole / nanometer
Is it a unit? True

If a value needs to be updated while keeping all metadata intact, change_value does the job.

updated_distance = puw.change_value(distance, 3.1)
print('Updated distance:', updated_distance)
Updated distance: 3.1 nanometer

Maintaining consistency with standards#

In collaborative settings it is common to enforce a canonical representation for reporting. standardize converts any compatible quantity to the configured standard units and default form.

energy = puw.quantity(-12.5, 'kilojoule/mole')
energy_std = puw.standardize(energy)
print('Original energy:', energy)
print('Standardized energy:', energy_std)
print('As a string:', puw.to_string(energy_std))
Original energy: -12.5 kilojoule / mole
Standardized energy: -2.987571701720841 kilocalorie / mole
As a string: -2.987571701720841 kilocalorie / mole

The check helper raises an informative error if a quantity does not match an expected dimensionality. It is a lightweight guard for functions that demand specific units.

try:
    puw.check(speed, dimensionality={'[L]': 1, '[T]': -1})
    print('Speed passes the dimensionality check.')
except Exception as exc:
    print('Check failed:', exc)
Speed passes the dimensionality check.
try:
    puw.check(acceleration, dimensionality={'[L]': 1, '[T]': -1})
except Exception as exc:
    print('Acceleration check result:', exc)

Accessing physical constants#

PyUnitWizard bundles a few frequently used physical constants. They are expressed as regular quantities, so you can convert them like any other value.

from pyunitwizard import constants
constants_catalog = constants.show_constants()
for names, text in constants_catalog.items():
    print(', '.join(names), '->', text)
Avogadro, NA -> 6.02214076e+23 1/mole
Universal gas, R, Molar gas -> 8.13446261815324 J/(kelvin*mole)
Boltzmann, KB -> 1.380649e-23 J/kelvin
avogadro_entry = constants_catalog[('Avogadro', 'NA')]
avogadro = puw.convert(avogadro_entry, parser='pint')
print('Avogadro constant:', avogadro)
print('Unit form:', puw.get_unit(avogadro))
Avogadro constant: 6.02214076e+23 / mole
Unit form: 1 / mole

Summary#

  • Configure PyUnitWizard once and reuse the same API with multiple unit toolkits.

  • Generate quantities from numbers, arrays, or strings and interrogate their form, value, and dimensionality.

  • Convert seamlessly across forms, enforce project-wide standards, and validate inputs with helper checks.

  • Tap into bundled constants or extend the approach for your own domain-specific catalog.

You now have a tour of the core features that make PyUnitWizard a flexible companion when juggling different unit systems.