Multi-dimensional data arrays with labeled dimensions for scientific computing
—
Comprehensive physical unit system with predefined units, unit conversion, arithmetic operations, and alias management. Scipp's unit system provides automatic unit propagation through all operations, ensuring dimensional consistency and preventing common scientific computing errors.
Core unit representation with arithmetic operations and string parsing.
class Unit:
"""Physical unit with arithmetic operations and parsing"""
def __init__(self, unit_string):
"""
Create Unit from string representation
Args:
unit_string (str): Unit string (e.g., 'm', 'kg*m/s^2', 'mm/s')
Examples:
Unit('m') # meter
Unit('kg*m/s^2') # force unit (Newton)
Unit('mm') # millimeter
Unit('1/s') # frequency unit
"""
def __mul__(self, other):
"""Multiply units: m * s -> m*s"""
def __truediv__(self, other):
"""Divide units: m / s -> m/s"""
def __pow__(self, exponent):
"""Raise unit to power: m^2 for area"""
def __eq__(self, other):
"""Check unit equality"""
def __str__(self):
"""String representation of unit"""
def __repr__(self):
"""Detailed string representation"""Functions for converting between compatible units and checking dimensional consistency.
def to_unit(x, unit):
"""
Convert variable to specified unit
Args:
x (Variable or DataArray): Input with units
unit (Unit or str): Target unit
Returns:
Variable or DataArray: Converted to target unit
Raises:
UnitError: If units are incompatible
"""Common physical units available as module constants.
# Dimensionless
dimensionless: Unit # Dimensionless quantity
one: Unit # Alias for dimensionless
# Length units
m: Unit # meter
mm: Unit # millimeter
angstrom: Unit # angstrom (10^-10 m)
# Time units
s: Unit # second
us: Unit # microsecond
ns: Unit # nanosecond
# Mass units
kg: Unit # kilogram
# Temperature units
K: Unit # kelvin
# Angle units
rad: Unit # radian
deg: Unit # degree
# Energy units
meV: Unit # millielectronvolt
# Count units
counts: Unit # count unit for discrete quantities
# Special units
default_unit: Unit # Marker for default unit behaviorManagement system for custom unit aliases and shortcuts.
class UnitAliases:
"""Manager for unit aliases and shortcuts"""
def __setitem__(self, alias, unit):
"""
Define a new unit alias
Args:
alias (str): Alias name
unit (str or Unit or Variable): Unit definition
Raises:
ValueError: If unit already has an alias
"""
def __delitem__(self, alias):
"""Remove an existing alias"""
def clear(self):
"""Remove all aliases"""
def scoped(self, **kwargs):
"""
Context manager for temporary aliases
Args:
**kwargs: Mapping from alias names to units
Returns:
Context manager for temporary aliases
"""
def keys(self):
"""Iterator over alias names"""
def values(self):
"""Iterator over aliased units"""
def items(self):
"""Iterator over (alias, unit) pairs"""
# Global alias manager instance
aliases: UnitAliasesimport scipp as sc
# Create variables with units
length = sc.scalar(5.0, unit='m')
time = sc.scalar(2.0, unit='s')
# Unit arithmetic through operations
velocity = length / time # Automatically gets 'm/s' unit
area = length * length # Automatically gets 'm^2' unit
# Check units
print(velocity.unit) # 'm/s'
print(area.unit) # 'm^2'
# Unit compatibility checking
try:
length + time # Will raise UnitError - incompatible units
except sc.UnitError as e:
print(f"Cannot add length and time: {e}")# Use predefined units
distance = sc.array(dims=['x'], values=[1, 2, 3], unit=sc.units.m)
temperature = sc.array(dims=['sensor'], values=[273, 298, 373], unit=sc.units.K)
# Angle calculations with proper units
angles = sc.linspace('angle', 0, 2*3.14159, 100, unit=sc.units.rad)
angles_deg = sc.linspace('angle', 0, 360, 100, unit=sc.units.deg)
# Convert between angle units
angles_converted = sc.to_unit(angles_deg, sc.units.rad)
# Energy units for scientific applications
energies = sc.array(dims=['detector'], values=[1, 5, 10, 50], unit=sc.units.meV)
# Counting statistics
detector_counts = sc.array(dims=['bin'], values=[100, 150, 80], unit=sc.units.counts)# Convert between compatible units
length_m = sc.scalar(1000, unit='mm')
length_converted = sc.to_unit(length_m, 'm') # Result: 1.0 m
# Convert complex units
force = sc.scalar(10, unit='kg*m/s^2') # Newton
force_in_base = sc.to_unit(force, 'kg*m/s^2') # Same unit
# Temperature conversion (if supported)
temp_celsius = sc.scalar(25, unit='degC')
# temp_kelvin = sc.to_unit(temp_celsius, 'K') # Would be 298.15 K# Define custom aliases for convenience
sc.units.aliases['speed'] = 'm/s'
sc.units.aliases['pressure'] = 'kg/(m*s^2)'
# Use aliases in calculations
velocity = sc.scalar(10, unit='speed')
print(velocity.unit) # Will display as 'speed'
# Temporary aliases with context manager
with sc.units.aliases.scoped(temp='K', dist='mm'):
temperature = sc.scalar(300, unit='temp')
position = sc.scalar(5.2, unit='dist')
# Aliases are automatically removed after context# Create complex derived units
acceleration = sc.scalar(9.81, unit='m/s^2')
mass = sc.scalar(2.0, unit='kg')
force = mass * acceleration # Result: kg*m/s^2 (Newton)
# Work with dimensionless quantities
ratio = sc.scalar(0.75, unit=sc.units.dimensionless)
percentage = ratio * 100 # Still dimensionless
# Unit exponentiation
volume = sc.pow(length, 3) # m^3
print(volume.unit) # 'm^3'
# Complex unit expressions
power = force * velocity # kg*m^2/s^3 (Watt)
energy = power * time # kg*m^2/s^2 (Joule)import numpy as np
# Neutron scattering data with proper units
wavelength = sc.array(dims=['detector'], values=[1.8, 2.0, 2.2], unit=sc.units.angstrom)
energy = sc.array(dims=['detector'], values=[25.3, 20.4, 16.7], unit=sc.units.meV)
# Scattering angle calculation
scattering_angles = sc.linspace('angle', 0, 180, 181, unit=sc.units.deg)
q_vector = 4 * sc.sin(scattering_angles / 2) / wavelength # Momentum transfer
# Time-of-flight calculations
distance = sc.scalar(10.0, unit=sc.units.m) # Sample-detector distance
velocity = sc.sqrt(2 * energy / sc.scalar(1.67e-27, unit='kg')) # Neutron velocity
time_of_flight = distance / velocity # Automatically in seconds
# Statistical analysis with count data
raw_counts = sc.array(dims=['bin'], values=[120, 98, 156, 89], unit=sc.units.counts)
measurement_time = sc.scalar(300, unit=sc.units.s)
count_rate = raw_counts / measurement_time # counts/s
# Uncertainty propagation with units
count_uncertainty = sc.sqrt(raw_counts) # Poisson statistics
rate_uncertainty = count_uncertainty / measurement_time # Propagated uncertainty# Unit compatibility checking
try:
length = sc.scalar(5, unit='m')
time = sc.scalar(2, unit='s')
invalid = length + time # Incompatible units
except sc.UnitError as e:
print(f"Unit error: {e}")
# Dimensionless function requirements
try:
angle_with_units = sc.scalar(1.57, unit='rad')
result = sc.exp(angle_with_units) # Should fail - exp requires dimensionless
except sc.UnitError as e:
print(f"Function requires dimensionless input: {e}")
# Correct usage
dimensionless_value = sc.scalar(1.57, unit=sc.units.dimensionless)
result = sc.exp(dimensionless_value) # Works correctlyInstall with Tessl CLI
npx tessl i tessl/pypi-scipp