Skip to content

NumPy Array Support

ucon provides NumberArray for working with numpy arrays of dimensioned quantities.

Installation

pip install ucon[numpy]

Creating NumberArrays

From lists or numpy arrays

from ucon import units
import numpy as np

# From list - uses callable syntax
heights = units.meter([1.7, 1.8, 1.9, 2.0])

# From numpy array
data = np.array([100, 200, 300])
distances = units.kilometer(data)

# Explicit construction
from ucon.numpy import NumberArray
temps = NumberArray([20, 21, 22, 23], unit=units.celsius)

With uncertainty

# Uniform uncertainty (same for all elements)
measurements = units.meter([1.0, 2.0, 3.0], uncertainty=0.01)

# Per-element uncertainty
errors = np.array([0.01, 0.02, 0.015])
measurements = NumberArray([1.0, 2.0, 3.0], unit=units.meter, uncertainty=errors)

Vectorized Conversion

heights = units.meter([1.7, 1.8, 1.9])

# Convert to feet
heights_ft = heights.to(units.foot)
print(heights_ft)  # <[5.577, 5.906, 6.234] ft>

# Uncertainty is propagated
temps = units.celsius([20, 21, 22], uncertainty=0.5)
temps_f = temps.to(units.fahrenheit)
# Uncertainty is scaled appropriately

Arithmetic Operations

All arithmetic preserves units and propagates uncertainty.

a = units.meter([1, 2, 3])
b = units.meter([4, 5, 6])

# Addition/subtraction (same unit required)
c = a + b  # <[5, 7, 9] m>
d = b - a  # <[3, 3, 3] m>

# Multiplication/division
area = a * b  # units are combined: m^2
speed = a / units.second([1, 2, 3])  # m/s

# Scalar operations
doubled = a * 2  # <[2, 4, 6] m>

Broadcasting

NumPy broadcasting is supported:

# (2, 3) + (3,) broadcasting
matrix = NumberArray([[1, 2, 3], [4, 5, 6]], unit=units.meter)
row = NumberArray([10, 20, 30], unit=units.meter)
result = matrix + row  # [[11, 22, 33], [14, 25, 36]] m

Comparison Operators

Comparisons return boolean arrays for filtering:

heights = units.meter([1.5, 1.7, 1.8, 2.0, 2.1])

# Compare with scalar
tall = heights > 1.8
# array([False, False, False, True, True])

# Compare with Number
threshold = units.meter(1.75)
above_threshold = heights >= threshold

# Use for filtering
filtered = heights.quantities[heights > 1.8]

Indexing and Iteration

heights = units.meter([1.7, 1.8, 1.9])

# Scalar index returns Number
first = heights[0]  # <1.7 m>

# Slice returns NumberArray
subset = heights[1:]  # <[1.8, 1.9] m>

# Iterate as Numbers
for h in heights:
    print(h)  # prints each as <Number>

Reduction Operations

heights = units.meter([1.7, 1.8, 1.9, 2.0])

total = heights.sum()   # <7.4 m>
avg = heights.mean()    # <1.85 m>
std = heights.std()     # <0.129... m>
minimum = heights.min() # <1.7 m>
maximum = heights.max() # <2.0 m>

Uncertainty in Reductions

# With uniform uncertainty
data = units.meter([1, 2, 3, 4], uncertainty=0.1)

total = data.sum()
# uncertainty: 0.1 * sqrt(4) = 0.2

avg = data.mean()
# uncertainty: 0.1 / sqrt(4) = 0.05

N-Dimensional Arrays

NumberArray supports arrays of any dimension:

# 2D array
grid = NumberArray(
    [[1, 2, 3],
     [4, 5, 6]],
    unit=units.meter
)
print(grid.shape)  # (2, 3)
print(grid.ndim)   # 2

# Index by row
row = grid[0]  # <[1, 2, 3] m>

# Index by element
element = grid[0, 1]  # <2 m> (Number)

Integration with NumPy

import numpy as np

heights = units.meter([1.7, 1.8, 1.9])

# Convert to raw numpy array
arr = np.asarray(heights)

# Use numpy functions on quantities
mean_val = np.mean(heights.quantities)

Performance Tips

NumberArray is designed for correctness first, but performs well for batch operations.

What's Fast

Operation Performance
Creation from ndarray Essentially free (~0.001ms) - just wraps the array
Addition/subtraction Excellent - often faster than raw NumPy at scale
Reductions (sum, mean) Excellent - comparable to NumPy
Throughput 100M+ elements/sec for conversions

What Has Fixed Costs

Conversions have small fixed costs for unit checking and graph lookup. These are cached after the first conversion, so repeated conversions of the same unit pair are faster (~1.5x speedup from cache).

Best Practices

Pass ndarrays, not lists:

# Slower - must convert list to array
heights = units.meter([1.7, 1.8, 1.9, 2.0])

# Faster - wraps existing array directly
data = np.array([1.7, 1.8, 1.9, 2.0])
heights = units.meter(data)

Pre-convert units before loops:

# Slower - converts on every iteration
for sample in samples:
    value_ft = sample.to(units.foot)
    process(value_ft)

# Faster - convert once, iterate on result
samples_ft = samples.to(units.foot)
for value in samples_ft:
    process(value)

Batch operations over element-wise:

# Slower - N separate operations
results = [x.to(units.foot) for x in measurements]

# Faster - single vectorized operation
results = measurements.to(units.foot)

Uncertainty Propagation Cost

Multiplication with uncertainty is more expensive due to quadrature error propagation. If you don't need per-operation uncertainty, consider:

# Propagate uncertainty only at final result
a = NumberArray(data_a, unit=units.meter)  # no uncertainty
b = NumberArray(data_b, unit=units.meter)  # no uncertainty
result = a * b

# Add uncertainty estimate at the end if needed
final = NumberArray(result.quantities, unit=result.unit, uncertainty=estimated_error)

Example: Scientific Data Analysis

import numpy as np
from ucon import units

# Experimental measurements with uncertainty
measurements = units.meter(
    [10.2, 10.5, 10.3, 10.4, 10.6],
    uncertainty=0.1
)

# Statistical analysis
mean = measurements.mean()
std = measurements.std()

print(f"Mean: {mean}")
print(f"Std Dev: {std}")

# Convert to different units
measurements_cm = measurements.to(units.centimeter)

# Filter outliers
mask = abs(measurements.quantities - mean.quantity) < 2 * std.quantity
filtered = measurements.quantities[mask]