Skip to main content

Physities

banner.png

CI codecov PyPI Python License Docs

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 / Second creates 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)

  1. BaseDimension: Enum of 7 SI base dimensions
  2. Dimension: Frozen dataclass combining base dimensions into composite physical dimensions
  3. Scale: Frozen dataclass with dimension + conversion factors (uses Kobject for validation)
  4. 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.

Metadata

Release files for physities 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for physities 0.2.1
File
physities-0.2.1-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details
physities-0.2.1-cp312-cp312-manylinux_2_34_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.34+ x86-64 Details
physities-0.2.1-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details
physities-0.2.1-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details
physities-0.2.1-cp311-cp311-manylinux_2_34_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.34+ x86-64 Details
physities-0.2.1-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details

Total release size: 2.0 MB

Release files / physities-0.2.1-cp312-cp312-win_amd64.whl

Download URL physities-0.2.1-cp312-cp312-win_amd64.whl
Size 244.9 kB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
2a5abe88521d211d61023d885a66b7c12da127bce353cdda52246159e70cee90
BLAKE2b-256 checksum
How to use checksums
10ecfcebcee1ef58736b6fea9ceb0ba129f09d8371651f24eafa387c56b51dbd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / physities-0.2.1-cp312-cp312-manylinux_2_34_x86_64.whl

Download URL physities-0.2.1-cp312-cp312-manylinux_2_34_x86_64.whl
Size 409.9 kB
Tags CPython 3.12 Linux glibc 2.34+ x86-64
SHA-256 checksum
How to use checksums
f7b7d30bb0d1b6fc37a2e7ad59122e4d1f27435316d003f6023455538c5536bd
BLAKE2b-256 checksum
How to use checksums
df9641871f911d8cb553b6016f384b0104044b4d6353fcf3161df983912ce32a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / physities-0.2.1-cp312-cp312-macosx_11_0_arm64.whl

Download URL physities-0.2.1-cp312-cp312-macosx_11_0_arm64.whl
Size 360.8 kB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
1e4f7359162acfefbdd848b868333b0912a868fc6578b093c700a8cfed0bf5bf
BLAKE2b-256 checksum
How to use checksums
b334173d0a52232cc473bd93604182b1e1d56073b77380d29627a94ef519ea96
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / physities-0.2.1-cp311-cp311-win_amd64.whl

Download URL physities-0.2.1-cp311-cp311-win_amd64.whl
Size 247.2 kB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
46b48211a1b4c392d4b6c14b27bb018873446fc57b2487f1aba04be5e8652855
BLAKE2b-256 checksum
How to use checksums
39c95b0b5325465693030f0d0b8e170a7abcf0374c3a0ec33d06e170915a2c14
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / physities-0.2.1-cp311-cp311-manylinux_2_34_x86_64.whl

Download URL physities-0.2.1-cp311-cp311-manylinux_2_34_x86_64.whl
Size 413.4 kB
Tags CPython 3.11 Linux glibc 2.34+ x86-64
SHA-256 checksum
How to use checksums
8df35a25a3ddeb431d6f4c27995d90aa5753189be1f9c0e2ad4991f8e7d192c6
BLAKE2b-256 checksum
How to use checksums
1992cef707013fee4a3a47063371d3f9a1477b8044d87f3b241c810734a38106
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / physities-0.2.1-cp311-cp311-macosx_11_0_arm64.whl

Download URL physities-0.2.1-cp311-cp311-macosx_11_0_arm64.whl
Size 363.7 kB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
51857cabbcb2566e01df209a1faf2a95248d3166fafbdf8777c1479cdda82d76
BLAKE2b-256 checksum
How to use checksums
8f9197964e9708a183783435a7b3c310d7dbf63b320fa98eb9f0961073bcbb0b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Security for physities 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page