Skip to content

Units & Tensors

The phaethon.units module is the mathematical core of the Phaethon framework. At its center is the BaseUnit—a highly optimized, dimensionally-aware tensor that deeply integrates with NumPy's C-API.

While BaseUnit is the underlying engine, you will never instantiate it directly. Instead, you interact with its concrete descendants (like u.Meter, u.Newton, or u.Decibel). Every physical entity you instantiate inherits a massive arsenal of properties, array mechanics, formatting tools, and casting methods from this foundational class.

(Note: For documentation on how these units interact mathematically via operators like + or *, please refer to the Dimensional Algebra section).

BaseUnit (NumPy) vs. PTensor (PyTorch)

The BaseUnit class documented here is a pure NumPy subclass meant for high-speed CPU computations and Data Engineering. If you are building Physics-Informed Neural Networks (PINNs) and require autograd/GPU tracking, do not use BaseUnit. Instead, use the specialized PyTorch equivalent: PTensor.


Unit Instantiation

The most direct way to create a physical entity in Phaethon is by calling the unit's class constructor directly. Because Phaethon is built on top of NumPy, these constructors natively accept scalars, standard Python lists, or raw multidimensional arrays.

Example Usage:

import phaethon.units as u
import numpy as np

# 1. Scalar Instantiation
temperature = u.Kelvin(300.5)

# 2. Vector / List Instantiation
velocities = u.MeterPerSecond([10.0, 15.5, 20.0])

# 3. Wrapping Raw NumPy Tensors
raw_grid = np.random.rand(10, 10)
pressure_field = u.Pascal(raw_grid)

print(pressure_field.shape)
# Output: (10, 10)

Native Array Proxies (NumPy Protocol)

Legacy unit libraries use slow, high-level "wrappers" that degrade performance. Phaethon takes a hyper-optimized approach: it implements the NumPy Array Protocol (__array_ufunc__ and __array_function__).

While structurally storing the array, BaseUnit acts as a Zero-Overhead Native Proxy. It completely bypasses Python-level bottlenecks by directly intercepting math operations at the NumPy C-API layer, ensuring your arrays retain their physical DNA without sacrificing raw C-speed.

phaethon.array

A fast, physics-aware tensor constructor. It seamlessly converts Python lists, scalars, or existing NumPy ndarray objects directly into a Phaethon NumPy subclass, permanently fusing the specified physical dimension into the array.

Arguments:

object Any
The raw data. Can be a scalar, a nested Python list, or a NumPy ndarray.
unit str | type[BaseUnit]
The physical dimension to attach. Can be a string alias (e.g., 'kg') or a class.
dtype Any | None
The desired data type. Defaults to the most efficient continuous type.

Returns:

BaseUnit
A new physics tensor wrapping the array data.

Example Usage:

import phaethon as ptn
import phaethon.units as u

velocity = ptn.array([[10.5, 20.1], [5.0, 9.8]], unit=u.MeterPerSecond, dtype="float32")
print(velocity.shape)
# Output: (2, 2)

phaethon.asarray

Converts the input to an array, attaching physics without copying the underlying memory if the input is already a compatible ndarray. Essential for wrapping massive simulation grids with zero overhead.

Arguments:

a Any
Input data, in any form that can be converted to an array.
unit str | type[BaseUnit]
The physical dimension to safely wrap around the memory pointer.

Returns:

BaseUnit
A physics tensor sharing memory with the original array (if compatible).

Example Usage:

import numpy as np
import phaethon as ptn

# A massive 1GB numerical grid
massive_grid = np.zeros((1000, 1000, 125))

# Wraps the pointer instantly without duplicating the 1GB memory
physical_grid = ptn.asarray(massive_grid, unit='J')

phaethon.asanyarray

Similar to asarray, but allows subclasses of ndarray (like numpy.ma.MaskedArray) to pass through without losing their subclass identity. Phaethon preserves the subclass behavior entirely inside the physics envelope.

Arguments:

a Any
Input data or array subclass.
unit str | type[BaseUnit]
The physical dimension to attach.

Returns:

BaseUnit
A physics tensor wrapping the array or array subclass.

Example Usage:

import numpy.ma as ma
import phaethon as ptn

# A masked array containing invalid/missing sensor data
sensor_data = ma.masked_array([10.5, -999.0, 12.0], mask=[0, 1, 0])

# Phaethon preserves the mask inside the physics tensor
safe_temp = ptn.asanyarray(sensor_data, unit='degC')

print(safe_temp.mag.mask)
# Output: [False  True False]

Properties & Array Mechanics

Every instantiated physical tensor automatically exposes NumPy-native properties and methods. These methods route calculations directly to the underlying C-engine while perfectly preserving their physical dimension.

Core Properties

.mag float | ndarray
Magnitude. Returns the raw numerical value without physical metadata.
.dimension str
Returns the canonical name of the physical dimension (e.g., "energy").
.shape tuple[int, ...]
Tuple of array dimensions. Returns an empty tuple () for scalars.
.ndim int
Number of array dimensions (0 for scalars, 1 for vectors, 2 for matrices, etc.).
.symbol str | None
Retrieve the symbol of a unit as a string.
.aliases list[str] | None
Retrieve a list of strings containing all aliases for a unit.
.T BaseUnit
Returns the transposed physical tensor.

Shaping & Reductions

.reshape(shape) BaseUnit
Gives a new shape to the physical tensor without changing its data.
.flatten() BaseUnit
Return a copy of the tensor collapsed into one dimension.
.sum(axis=None, keepdims=False) BaseUnit
Sum of tensor elements over a given axis. Physical dimension remains identical.
.mean(axis=None, keepdims=False) BaseUnit
Compute the arithmetic mean along the specified axis.
.max() / .min() BaseUnit
Returns the maximum or minimum value along a given axis.

NumPy Subclassing & Delegation

Phaethon's BaseUnit acts as a highly optimized NumPy proxy. It intercepts NumPy's C-API calls via __array_ufunc__ and __array_function__, allowing you to use native NumPy functions directly on physical tensors.

np.sum / np.max / np.mean
Standard mathematical reductions natively return a Phaethon tensor preserving the original physical dimension.
np.abs / np.negative / np.rint
Unary universal functions (ufuncs) are strictly routed through the physics engine.
np.sqrt / np.cbrt / np.square
These functions are intercepted and transformed into dimensional algebra (e.g., np.sqrt(Area) automatically returns Length).

Example Usage:

import numpy as np
import phaethon.units as u
import phaethon as ptn

# instantiating a negative velocity
with ptn.using(axiom_strictness_level='ignore'):
    velocities = u.MeterPerSecond([-10.0, 5.0, -2.5])

# Absolute value is safely passed to the NumPy C-Engine
absolute_v = np.abs(velocities)
print(absolute_v)
# Output: <MeterPerSecond Array: [10.   5.   2.5] m/s>

# NumPy square triggers Dimensional Synthesis
squared = np.square(velocities)
print(squared)
# Output: <JoulePerKilogram Array: [100, 25, 6.25]>

print(squared.dimension)
# Output: 'specific_energy'

Direct Attribute Delegation: If a specific NumPy method is not natively overridden by Phaethon, the tensor will automatically search the underlying NumPy ndarray and delegate the call, wrapping the result back into the physics envelope if applicable.

# Using a native NumPy attribute directly on a Phaethon tensor
print(velocities.size)
# Output: 3

Diagnostics & Formatting

Phaethon units provide powerful built-in methods to inspect their underlying SI structure or format their output for UIs.

.decompose()

Returns a string representation of the unit's absolute, canonical SI base structure (its fundamental DNA). It breaks down complex derived units (like Watts or Pascals) into their fundamental SI exponents (Kilograms, Meters, Seconds, etc.).

Returns:

str
The SI mathematical signature.

Example Usage:

import phaethon.units as u

pressure = u.Pascal(100)

print(pressure.decompose())
# Output: kg/(m·s²)

.format()

Applies precise structural and numeric formatting to the magnitude using pure float formatting, bypassing the default __str__ behavior.

Arguments:

prec int
Number of decimal places to round to (Default: 4).
sigfigs int | None
Overrides precision to strictly enforce Significant Figures.
scinote bool
Force the output into Scientific Notation.
delim bool | str
Adds thousands separators (e.g., True for ",", or supply a custom string like ".").
tag bool
Whether to append the unit symbol at the end of the string (Default: True).

Returns:

str
A highly formatted string representation of the unit.

Example Usage:

import phaethon.units as u

distance = u.Meter(12500.55)

print(distance.format(delim=True, prec=1))
# Output: 12,500.6 m

print(distance.format(scinote=True, sigfigs=3, tag=False))
# Output: 1.25E+04

Dimensional Casting & Escaping

Phaethon is designed to be strictly typed and physically guarded. However, advanced scientific computing often requires traversing domains, stripping semantic ghosts, or linearizing complex logarithmic scales. Phaethon provides deterministic methods to safely navigate these operations.

.to() (Standard Casting)

Safely converts the unit instance to another physical unit within the same semantic domain. This operation is strictly guarded by the Physics Engine.

Arguments:

target_unit str | type[BaseUnit]
The destination physical unit class or string alias.

Returns:

BaseUnit
A new instance safely cast to the requested unit.

Raises:

TypeError
If the provided target is not a valid Phaethon class or registered alias.
DimensionMismatchError
If you attempt to convert between fundamentally incompatible physics (e.g., Length to Mass).
SemanticMismatchError
If you attempt to cross an Exclusive Domain Lock or trigger a Phantom Collision without explicit algebraic synthesis.

Example Usage:

import phaethon as ptn
import phaethon.units as u

distance = ptn.array([1.5, 3.2], unit='km')
speed = u.MeterPerSecond(150)

meters = distance.to('m') # <Meter Array: [1500.0, 3200.0]>
kmh = speed.to(u.KilometerPerHour) # # <KilometerPerHour: 540.0 km/h>

.si (The Core Extractor / De-Phantomizer)

The semantic escape hatch. It explicitly attacks the DNA of the unit. It strips away all Phantom Units (e.g., cycle, decay, radian), gracefully downgrading the entity to its pure, generic SI blank canvas (multiplier = 1.0).

Use this when you need to intentionally break a domain lock (e.g., extracting raw energy from torque) to perform generalized mathematics.

Property Type: BaseUnit
Returns a newly synthesized, generic BaseUnit free from semantic constraints.

Example Usage:

import phaethon.units as u

# Torque possesses an Exclusive Domain Lock and a Phantom Unit (Angle⁻¹)
torque = u.NewtonMeter(100)  
print(torque.dimension)  
# Output: 'torque'

# Extracting the core strips the Phantom Unit, collapsing it back to basic Energy
raw_energy = torque.si  

print(raw_energy.dimension)
# Output: 'energy'

print(raw_energy)
# Output: <Joule: 100.0 J>

~ (The Base Converter / Linearizer)

The absolute canonical scale converter (triggered using the bitwise NOT ~ operator). Unlike .si which attacks the semantics, ~ attacks the Scale.

It forces any derived multiplier (like kilo), physical constant (like SpeedOfLight), or non-linear shell (like Decibel) to collapse directly into the primary SI Base Unit of its respective dimension, while perfectly preserving its Semantic Domain Lock. It is also the primary tool for instantly linearizing logarithmic physics.

Operator: __invert__
Returns the linearized, SI Base Unit of the current dimensional state without stripping phantom semantics.

Example Usage: Scale Unrolling vs Semantic Extraction

import phaethon.units as u

# 1. Unrolling a Physical Constant
c = u.SpeedOfLight(1)
print(~c) 
# Output: <MeterPerSecond: 2.9979E+08 m/s>

# 2. Preserving the Semantic Domain
torque = u.NewtonMeter(100)
print(~torque) 
# Output: <NewtonMeter: 100.0 N·m> (Notice it remains Torque, unlike .si!)

# 3. Linearizing Logarithmic Scales natively
signal = u.DecibelMilliwatt(30.0) # 30 dBm

# Both approaches yield the linear SI Base (Watt), 
# because stripping the log shell naturally reveals the linear base core.
linear_power = ~signal            
si_power = signal.si              

print(linear_power)
# Output: <Watt: 1.0 W>

print(si_power.to(u.Milliwatt)) 
# Output: <Milliwatt: 1000.0 mW>