Skip to main content

A Python library for representing and working with physical quantities and units

Project description

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.

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

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

physities-0.2.1-cp312-cp312-win_amd64.whl (244.9 kB view details)

Uploaded CPython 3.12Windows x86-64

physities-0.2.1-cp312-cp312-manylinux_2_34_x86_64.whl (409.9 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ x86-64

physities-0.2.1-cp312-cp312-macosx_11_0_arm64.whl (360.8 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

physities-0.2.1-cp311-cp311-win_amd64.whl (247.2 kB view details)

Uploaded CPython 3.11Windows x86-64

physities-0.2.1-cp311-cp311-manylinux_2_34_x86_64.whl (413.4 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.34+ x86-64

physities-0.2.1-cp311-cp311-macosx_11_0_arm64.whl (363.7 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

File details

Details for the file physities-0.2.1-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: physities-0.2.1-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 244.9 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

Hashes for physities-0.2.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 2a5abe88521d211d61023d885a66b7c12da127bce353cdda52246159e70cee90
MD5 40679eae8faf97c42762d2c5d39b8d67
BLAKE2b-256 10ecfcebcee1ef58736b6fea9ceb0ba129f09d8371651f24eafa387c56b51dbd

See more details on using hashes here.

File details

Details for the file physities-0.2.1-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for physities-0.2.1-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 f7b7d30bb0d1b6fc37a2e7ad59122e4d1f27435316d003f6023455538c5536bd
MD5 81722f741a95ed6ef9e472f3367d5cb9
BLAKE2b-256 df9641871f911d8cb553b6016f384b0104044b4d6353fcf3161df983912ce32a

See more details on using hashes here.

File details

Details for the file physities-0.2.1-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for physities-0.2.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1e4f7359162acfefbdd848b868333b0912a868fc6578b093c700a8cfed0bf5bf
MD5 7c0d55b6c1b5aee9cd9b02919637a9f7
BLAKE2b-256 b334173d0a52232cc473bd93604182b1e1d56073b77380d29627a94ef519ea96

See more details on using hashes here.

File details

Details for the file physities-0.2.1-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: physities-0.2.1-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

Hashes for physities-0.2.1-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 46b48211a1b4c392d4b6c14b27bb018873446fc57b2487f1aba04be5e8652855
MD5 b924035d8337c872d4f3cc9269821ab7
BLAKE2b-256 39c95b0b5325465693030f0d0b8e170a7abcf0374c3a0ec33d06e170915a2c14

See more details on using hashes here.

File details

Details for the file physities-0.2.1-cp311-cp311-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for physities-0.2.1-cp311-cp311-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 8df35a25a3ddeb431d6f4c27995d90aa5753189be1f9c0e2ad4991f8e7d192c6
MD5 2a58f7c9283607f25451bf32b70396a9
BLAKE2b-256 1992cef707013fee4a3a47063371d3f9a1477b8044d87f3b241c810734a38106

See more details on using hashes here.

File details

Details for the file physities-0.2.1-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for physities-0.2.1-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 51857cabbcb2566e01df209a1faf2a95248d3166fafbdf8777c1479cdda82d76
MD5 c8103a63e3556242fbd2f6176eacef53
BLAKE2b-256 8f9197964e9708a183783435a7b3c310d7dbf63b320fa98eb9f0961073bcbb0b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page