A Python library for representing and working with physical quantities and units
Project description
Physities
A high-performance Python library for representing and working with physical quantities and units. Features dimensional analysis, unit conversion, and mathematical operations on physical measurements, powered by a Rust core for optimal performance.
Features
- Type-safe physical quantity operations
- Automatic dimensional analysis
- Unit conversion with compile-time dimension checking
- Elegant operator syntax (
Meter / Secondcreates velocity units) - High-performance Rust backend using ndarray for linear algebra
- UnitArray for batch operations on arrays of values
- NumPy interoperability
- Compact serialization (int64 encoding for dimensions)
- Database-ready serialization (
to_tuple(),to_dict())
Installation
pip install physities
Or with Poetry:
poetry add physities
Building from Source
Requires Rust toolchain and maturin:
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install maturin
pip install maturin
# Build and install
maturin develop --release
Quick Start
from physities.src.unit import Meter, Second, Kilometer, Hour
# Create composite unit types using operator syntax
MetersPerSecond = Meter / Second
KilometersPerHour = Kilometer / Hour
# Create values
v1 = MetersPerSecond(40) # 40 m/s
v2 = KilometersPerHour(144) # 144 km/h
# Convert between units
v3 = v2.convert(MetersPerSecond) # 40 m/s
# Comparison works across compatible units
assert v1 == v2 # True: 40 m/s == 144 km/h
Available Units
Base SI Units
| Dimension | Base Unit |
|---|---|
| Length | Meter |
| Mass | Kilogram |
| Time | Second |
| Temperature | Kelvin |
| Amount | Unity |
| Electric Current | Ampere |
| Luminous Intensity | Candela |
Derived Units
Length
Gigameter, Megameter, Kilometer, Hectometer, Decameter, Decimeter, Centimeter, Millimeter, Micrometer, Nanometer, Foot, Yard, Inch, Mile, Furlong, Rod
Time
Nanosecond, Microsecond, Millisecond, Centisecond, Decisecond, Minute, Hour, Day, Week, Month, Year, Decade, Century, Millennium
Mass
Gigagram, Megagram, Tonne, Hectogram, Decagram, Gram, Decigram, Centigram, Milligram, Microgram, Nanogram, Pound, Ounce, Stone, Carat, Grain, Slug
Electric Current
Gigaampere, Megaampere, Kiloampere, Milliampere, Microampere, Nanoampere
Amount
Dozen, Moles, Pairs, Score
Area
Meter2, Kilometer2, Hectare, Centimeter2, Millimeter2, Foot2, Yard2, Inch2, Mile2, Acre
Volume
Meter3, Liter, Kiloliter, Milliliter, Centimeter3, Foot3, Gallon, Pint, Barrel
Examples
Creating and Using Units
from physities.src.unit import Meter, Second, Kilogram
# Create a velocity unit
Velocity = Meter / Second
v = Velocity(10) # 10 m/s
# Create an acceleration unit
Acceleration = Meter / (Second ** 2)
a = Acceleration(9.8) # 9.8 m/s²
# Create a force unit (Newton)
Newton = Kilogram * Meter / (Second ** 2)
force = Newton(100) # 100 N
Unit Conversion
from physities.src.unit import Kilometer, Mile, Hour
# Create speed units
Kmh = Kilometer / Hour
Mph = Mile / Hour
# Convert between units
speed_kmh = Kmh(100)
speed_mph = speed_kmh.convert(Mph)
Mathematical Operations
from physities.src.unit import Meter, Second
Ms = Meter / Second
v1 = Ms(10)
v2 = Ms(20)
# Addition and subtraction (same units only)
v3 = v1 + v2 # 30 m/s
v4 = v2 - v1 # 10 m/s
# Multiplication and division with scalars
v5 = v1 * 2 # 20 m/s
v6 = v2 / 2 # 10 m/s
# Powers
v7 = v1 ** 2 # 100 m²/s²
Creating Custom Units
from physities.src.unit import Unit
from physities.src.scale import Scale
from physities.src.dimension import Dimension
# Define a custom unit
class Furlong(Unit):
scale = Scale(
dimension=Dimension.new_length(),
from_base_scale_conversions=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
rescale_value=201.168, # 1 furlong = 201.168 meters
)
value = None
# Or derive from existing units
from physities.src.unit import Meter
MyUnit = 201.168 * Meter # Equivalent to Furlong
Batch Operations with UnitArray
For high-performance operations on many values, use UnitArray:
from physities.src.unit import Meter, Kilometer, UnitArray
import numpy as np
# Create array of 10,000 measurements
distances = UnitArray(Meter, np.random.rand(10000) * 1000)
# Batch operations (vectorized, ~100x faster than loops)
total = distances.sum() # Returns a single Meter
average = distances.mean() # Returns a single Meter
doubled = distances * 2 # Returns UnitArray
# Convert all values at once
km_distances = distances.convert(Kilometer)
Database Serialization
Units can be serialized for database storage:
from physities.src.unit import Meter, Second
from physities.src.unit.unit import Unit
velocity = (Meter / Second)(25)
# Compact format: (si_value, dimension_int64)
data = velocity.to_tuple() # (25.0, 61441)
# Store in database: INSERT INTO measurements (value, dimension) VALUES (25.0, 61441)
# Restore from database
restored = Unit.from_tuple(data)
# Full format (preserves original unit)
full_data = velocity.to_dict()
restored = Unit.from_dict(full_data)
Using the Rust Backend Directly
For high-performance operations, you can use the Rust PhysicalScale directly:
from physities._physities_core import PhysicalScale
# Create a velocity scale (length=1, time=-1)
scale = PhysicalScale.from_components(
(1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0), # dimension exponents
(1000.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0), # conversion factors (km)
1.0 # rescale value
)
# Operations
squared = scale.power(2)
product = scale.multiply(scale)
# Serialization
json_str = scale.to_json()
int64_encoded = scale.to_dimension_int64()
# NumPy interop
import numpy as np
arr = scale.as_numpy()
Architecture
Physities uses a hybrid Python/Rust architecture for optimal performance and usability:
graph TB
subgraph Python["Python API Layer"]
Unit["Unit + MetaUnit"]
Scale["Scale"]
Dimension["Dimension"]
BaseDim["BaseDimension"]
Unit --> Scale --> Dimension --> BaseDim
end
subgraph Rust["Rust Core (Performance)"]
PhysicalScale["PhysicalScale"]
NDArray["ndarray (15 × f64)"]
PhysicalScale --> NDArray
end
Python -.->|"optional"| Rust
style Python fill:#3572A5,color:#fff
style Rust fill:#dea584,color:#000
Python Layer (API)
- BaseDimension: Enum of 7 SI base dimensions
- Dimension: Frozen dataclass combining base dimensions into composite physical dimensions
- Scale: Frozen dataclass with dimension + conversion factors (uses Kobject for validation)
- Unit + MetaUnit: MetaUnit metaclass enables operator overloading at the class level
Rust Core (Performance)
The PhysicalScale struct provides high-performance operations:
- Unified data structure: 15-element ndarray (7 dimension exponents + 7 conversion factors + 1 rescale)
- Linear algebra operations: All physical operations implemented as vector math
- SIMD-friendly: Contiguous memory layout enables vectorized operations
- Serialization: Int64 bitwise encoding for compact dimension storage
See docs/ARCHITECTURE.md for detailed documentation.
API Reference
Unit Class
The main class for working with physical quantities.
class Unit:
scale: Scale # The scale/unit definition
value: float # The numeric value
def convert(self, target: Type[Unit]) -> Unit:
"""Convert to another unit of the same dimension."""
def to_si(self) -> Unit:
"""Convert to SI base units."""
def to_tuple(self) -> tuple[float, int]:
"""Serialize to (si_value, dimension_int64) for database storage."""
def to_dict(self) -> dict:
"""Serialize to dictionary with full scale information."""
@classmethod
def from_tuple(cls, data: tuple) -> Unit:
"""Restore from compact tuple format."""
@classmethod
def from_dict(cls, data: dict) -> Unit:
"""Restore from dictionary format."""
UnitArray Class
High-performance batch operations on arrays of values.
class UnitArray:
unit_type: MetaUnit # The unit class (e.g., Meter)
values: np.ndarray # NumPy array of values
scale: Scale # The scale definition
def __init__(self, unit_type: MetaUnit, values: ArrayLike): ...
# Arithmetic (vectorized)
def __add__(self, other: Unit | UnitArray) -> UnitArray: ...
def __mul__(self, scalar: float) -> UnitArray: ...
# Reductions (return single Unit)
def sum(self) -> Unit: ...
def mean(self) -> Unit: ...
def std(self) -> Unit: ...
def min(self) -> Unit: ...
def max(self) -> Unit: ...
# Conversion
def convert(self, target: MetaUnit) -> UnitArray: ...
def to_numpy(self) -> np.ndarray: ...
Scale Class
Defines unit conversion factors.
@dataclass(frozen=True, slots=True)
class Scale:
dimension: Dimension
from_base_scale_conversions: tuple[float, ...]
rescale_value: float
@property
def conversion_factor(self) -> float:
"""Total conversion factor to SI units."""
@property
def is_dimensionless(self) -> bool:
"""Check if scale has no dimensions."""
Dimension Class
Represents physical dimensions.
@dataclass(frozen=True, slots=True)
class Dimension:
dimensions_tuple: tuple[float, ...]
@classmethod
def new_length(cls, power: float = 1) -> Dimension: ...
@classmethod
def new_mass(cls, power: float = 1) -> Dimension: ...
@classmethod
def new_time(cls, power: float = 1) -> Dimension: ...
@classmethod
def new_temperature(cls, power: float = 1) -> Dimension: ...
@classmethod
def new_amount(cls, power: float = 1) -> Dimension: ...
@classmethod
def new_electric_current(cls, power: float = 1) -> Dimension: ...
@classmethod
def new_luminous_intensity(cls, power: float = 1) -> Dimension: ...
@classmethod
def new_dimensionless(cls) -> Dimension: ...
PhysicalScale (Rust)
High-performance scale operations.
class PhysicalScale:
# Properties
length: float
mass: float
temperature: float
time: float
amount: float
electric_current: float
luminous_intensity: float
rescale_value: float
conversion_factor: float
is_dimensionless: bool
# Operations
def multiply(self, other: PhysicalScale) -> PhysicalScale: ...
def divide(self, other: PhysicalScale) -> PhysicalScale: ...
def power(self, exp: float) -> PhysicalScale: ...
def multiply_scalar(self, scalar: float) -> PhysicalScale: ...
# Serialization
def to_json(self) -> str: ...
def to_dimension_int64(self) -> int: ...
@staticmethod
def from_json(json_str: str) -> PhysicalScale: ...
@staticmethod
def from_dimension_int64(encoded: int) -> PhysicalScale: ...
BaseDimension Enum
The 7 SI base dimensions:
class BaseDimension(IntEnum):
LENGTH = 0
MASS = 1
TEMPERATURE = 2
TIME = 3
AMOUNT = 4
ELECTRIC_CURRENT = 5
LUMINOUS_INTENSITY = 6
Development
Prerequisites
- Python 3.11+
- Rust toolchain (for building from source)
- Poetry (optional, for dependency management)
Setup
# Clone repository
git clone https://github.com/CenturyBoys/physities.git
cd physities
# Install dependencies
pip install -e ".[dev]"
# Or with Poetry
poetry install
# Build Rust extension
maturin develop
Running Tests
# Run all tests
pytest tests/ -v
# Run unit tests only
pytest tests/ -v -m unit
# Run with coverage
pytest tests/ --cov=physities
Linting
# Python linting
ruff check .
# Rust linting
cargo clippy
Building
# Development build
maturin develop
# Release build
maturin build --release
# Build wheels for distribution
maturin build --release -o dist
Performance
Physities adds ~20x overhead vs raw Python floats for type safety. For batch operations, use UnitArray to get near-NumPy performance.
| Operation | Plain Python | Physities | Overhead |
|---|---|---|---|
a + b (floats) |
~210 ns | ~25 µs | ~120x |
a * b (floats) |
~210 ns | ~48 µs | ~230x |
| Create value | ~600 ns | ~780 ns | ~1.3x |
| Create type | ~15 µs | ~39 µs | ~2.6x |
Batch operations (UnitArray):
| Operation | Loop (100 units) | UnitArray (100) | Speedup |
|---|---|---|---|
| Add scalar | 2.3 ms | 22 µs | 100x |
See benchmarks for detailed performance data.
License
MIT
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file physities-0.2.0-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: physities-0.2.0-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 245.0 kB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c44fabc90c54a1803ed86c5360805f046f83305afc514bf3a198bf5401deadd6
|
|
| MD5 |
49073e8768ce8ac70f0d2449548bd40e
|
|
| BLAKE2b-256 |
a03d826684c9171cdd2dcb61d586c28192c0c0e84254465705b19d1d32ad1c03
|
File details
Details for the file physities-0.2.0-cp312-cp312-manylinux_2_34_x86_64.whl.
File metadata
- Download URL: physities-0.2.0-cp312-cp312-manylinux_2_34_x86_64.whl
- Upload date:
- Size: 409.9 kB
- Tags: CPython 3.12, manylinux: glibc 2.34+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ce0a28101ce96424335a8a83ce7a50a095cb7d237a3c5586542f9c8f16898f1
|
|
| MD5 |
0afa9ee6609ed588b502ecaed72a0de8
|
|
| BLAKE2b-256 |
3d10cac160037ef6478e3f4c10ac44177b56b35f79764fb08d2f6ef6bc4c072e
|
File details
Details for the file physities-0.2.0-cp312-cp312-macosx_11_0_arm64.whl.
File metadata
- Download URL: physities-0.2.0-cp312-cp312-macosx_11_0_arm64.whl
- Upload date:
- Size: 361.0 kB
- Tags: CPython 3.12, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90485db4930efdb1014b5ed6045145b02136f032cfddb2d5f60e987aaf769b03
|
|
| MD5 |
375b1344bc6e02b8725c292900e7d026
|
|
| BLAKE2b-256 |
057b50b5b4c08cd0ba6910c894400c99ab763c54da9ca9ea74b4fcb03f7151e5
|
File details
Details for the file physities-0.2.0-cp311-cp311-win_amd64.whl.
File metadata
- Download URL: physities-0.2.0-cp311-cp311-win_amd64.whl
- Upload date:
- Size: 247.2 kB
- Tags: CPython 3.11, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6cfb21df35462d877cf85cf72593f4420e8e633d2fb1fa48db5df7e664a1d336
|
|
| MD5 |
cbeba6f6f9c42584d10ecd7a8ecfe693
|
|
| BLAKE2b-256 |
c87a1764bac71ce7f50abc8c8c331de67bb3baa150011c7fc80346ae35096015
|
File details
Details for the file physities-0.2.0-cp311-cp311-manylinux_2_34_x86_64.whl.
File metadata
- Download URL: physities-0.2.0-cp311-cp311-manylinux_2_34_x86_64.whl
- Upload date:
- Size: 413.3 kB
- Tags: CPython 3.11, manylinux: glibc 2.34+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb86a2e89cd84f3efdf7832f7f771603d58f960f1dc988ec33a3ca6942e5068c
|
|
| MD5 |
7bf941a42c6d397cd5404e3b9a1dd178
|
|
| BLAKE2b-256 |
27ccf6e67520fa4a5cdadea7d75aa210edbd0d3451375393a97f40e9bdcb967a
|
File details
Details for the file physities-0.2.0-cp311-cp311-macosx_11_0_arm64.whl.
File metadata
- Download URL: physities-0.2.0-cp311-cp311-macosx_11_0_arm64.whl
- Upload date:
- Size: 363.9 kB
- Tags: CPython 3.11, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4ff0bad5a395e9b3c4bf47f69fbfa5fea67632b001a437c7f2586fb3feb52d9
|
|
| MD5 |
6638c95cd44394ac9bd867edc8e88155
|
|
| BLAKE2b-256 |
3f26121c2452969ea1daae114d3066ad042200190f6978668d136060119ed8f3
|