forallpeople

repository·main·Indexed 18 days ago

https://github.com/connorferster/forallpeople

A Python library for units-aware calculations using the SI system and its derivatives, including US customary and Imperial units. It features immutable Physical instances, automatic reduction of physical quantities during arithmetic, and dynamic unit environments for derived units. The library supports automatic SI prefix scaling, integration with NumPy arrays, and custom unit environments defined via JSON.

Tokens
11.7K
Snippets
42
Records
54
Agent score
63%

What's inside forallpeople

  1. Handling dimensionally inconsistent calculations

    main

    Some formulas require 'hidden dimensions' (e.g., $\sqrt{MPa}$ resulting in $MPa$ instead of $MPa^{0.5}$). forallpeople handles this via the __float__ method.

    When float(physical_instance) is called, it returns the numerical portion of the auto-prefixed unit representation rather than the raw SI base value. This allows standard math functions (like math.sqrt) to work with the 'visible' number.

    Warning: If you need to perform calculations on the actual SI base unit value, use .value explicitly.

    import forallpeople as si
    from math import sqrt
    si.environment('default')
    
    MPA = 1e6 * si.Pa
    f_c = 35 * MPA
    
    # Using the auto-prefixed value (matches visual representation)
    result = sqrt(f_c) * MPA  # 5.916 MPa
    
    # Using the actual SI base value
    result_base = sqrt(f_c.value) # 5916.079...
  2. How auto-prefixing works

    main

    By default, forallpeople automatically selects the most conventional SI prefix for representing a unit.

    When auto-prefixing is triggered:

    1. The unit is an SI base unit (m, kg, s, A, cd, K, or mol).
    2. The unit is a derived unit defined in the environment with a .factor == 1.

    When auto-prefixing is NOT triggered:

    1. The unit is defined in the environment with a non-unit factor (e.g., lb).
    2. The unit is a compound unit not explicitly defined in the environment.

    Note on Powers: When a unit is raised to a power, the prefix is also squared/cubed. For example, (5000 * si.m)**2 results in 25.000 km² because the 'kilo' prefix is part of the base unit being squared.

    >>> current = 0.5 * A
    >>> current
    500.000 mA # 'current' is auto-prefixed to 500 milliamperes
    >>> resistance = 1200 * Ohm
    >>> resistance
    1.200 kΩ # 'resistance' is auto-prefixed to kilo-ohms
    >>> voltage = current * resistance
    >>> voltage
    600.000 V # 'voltage' does not have a prefix because its value is above 1 V but less than 1000 V
  3. How `Physical` instances work

    main

    The core abstraction in forallpeople is the Physical class, which represents physical quantities. Physical instances are immutable; any arithmetic operation returns a new instance.

    Each Physical instance has the following attributes:

    • .value: A float representing the numerical value in SI base units.
    • .dimensions: A Dimensions NamedTuple describing the dimensionality in terms of SI base units.
    • .factor: A float used for alternate unit systems (e.g., US customary) that are linearly based on SI.
    • .precision: An int for decimal places in .__repr__() (defaults to 3).
    • .prefixed: A str representing an SI prefix abbreviation (e.g., "k" for kilo).

    Dimensionality is tracked using vectors via the Dimensions class (a NamedTuple), which uses the tuplevector library for vector arithmetic.

  4. Arithmetic on `Physical` instances

    main

    Arithmetic operations on Physical instances follow standard physical logic:

    • Addition/Subtraction: Works if dimensions are equal. Adding a number (int or float) to a Physical instance assumes the units of that instance.
    • Multiplication: Combines dimensions. Multiplying an instance by a number assumes the units of the instance.
    • Division (True Division):
      • Dividing two instances results in a new instance with the difference of their dimension vectors.
      • Dividing two instances of the same dimension results in a float (units are cancelled; there is no 'dimensionless' Physical type).
      • Dividing an instance by a number assumes the units of the instance.
    • Floor Division: Not implemented to avoid ambiguity with unit factors. Use true division and cast to int() if needed (note: this returns an int, not a Physical instance).
    • Power: You can raise a Physical instance to a numeric power (int or float). You cannot raise a Physical instance to the power of another Physical instance.
    • Abs/Neg: abs(instance) returns the absolute value; neg(instance) is equivalent to instance * -1.
  5. Use the top-level namespace for shorter syntax

    main

    If you want to use units directly without the si. prefix (e.g., 5 * N instead of 5 * si.N), load the environment with top_level=True. This 'pushes' the units defined in the environment's JSON file into the global/top-level namespace.

    import forallpeople as si
    
    # Push units to the top-level namespace
    si.environment('default', [top_level=True])
    
    # Now you can use units directly
    a = 5 * N
  6. Define custom environments

    main

    You can define custom unit environments using a JSON document. Each entry in the JSON must follow this structure:

    "Name": {
        "Dimension": [0,0,0,0,0,0,0],
        "Value": 1,
        "Factor": 1,
        "Symbol": ""
    }
    • Dimension: An array of integers representing the SI base unit dimensions.
    • Value: The numerical value.
    • Factor: A float or an arithmetic expression (e.g., "1/0.45359237/9.80665") that is evaluated to define the unit relative to SI.
    • Symbol: The string symbol for the unit.

    Security Note: The Factor field is regex-validated to allow only numbers and arithmetic symbols to prevent arbitrary code execution.

    {
        "kPa": {
            "Dimension": [1,-1,-2,0,0,0,0],
            "Value": 1000
        },
        "lb-f": {
            "Dimension": [1, 1, -2, 0, 0, 0, 0],
            "Factor": "1/0.45359237/9.80665",
            "Symbol": "lb"
        }
    }
  7. Basic usage: Module-level namespace

    main

    The standard way to use the library is to import it as a module (commonly aliased as si). Upon import, the SI base units are instantiated and available as variables within that namespace.

    Available SI base units include:

    • si.m - meter
    • si.kg - kilogram
    • si.s - second
    • si.A - ampere
    • si.cd - candela
    • si.K - kelvin
    • si.mol - mole

    Note that Physical instances are immutable.

    import forallpeople as si
    
    # Example usage
    a = 5 * si.m
  8. Load an environment for derived units

    main

    By default, only SI base units are available. To access SI derived units (like Newton, Pascal, Joule, etc.), you must load an environment using si.environment().

    Loading an environment does two things:

    1. It changes the repr() of Physical instances to match the dimensioned units defined in that environment.
    2. It instantiates the units defined in the environment as Physical objects in the si namespace.

    Important: Regardless of the environment loaded, Physical instances always store their underlying .value in SI base units.

    import forallpeople as si
    
    # Load the 'default' environment to get derived units like si.N, si.Pa, etc.
    si.environment('default', [top_level=False])
    
    area = 3*si.m * 4*si.m
    force = 2500 * si.N
    pressure = force / area
    # pressure will now represent 208.333 Pa
  9. Overview of forallpeople features

    main

    Core Capabilities

    • Units-aware arithmetic: Perform calculations that respect physical dimensions.
    • Environment-based auto-reduction: Units automatically scale or reduce based on the active unit environment (e.g., converting 1100 Hz to 1.1 kHz).
    • Unit Conversion: Easily convert between different units defined within your current environment.
    • Custom Environments: Define specific environments for US Customary, Imperial, or derived SI unit options.
    • Rich Representations: Supports HTML, LaTeX, and plain-text output, making it ideal for Jupyter notebooks.
    • Immutable & Hashable: Physical instances are immutable and can be used in sets or as dictionary keys.
    • Interactive Imports: Supports pushing unit names directly into the top-level namespace for cleaner syntax.
  10. Understand auto-scaling of units

    main

    The library automatically scales base and derived units for display purposes (e.g., converting 5000 * si.kg to 5 Mg).

    Limitations to auto-scaling:

    • Undefined products: Products of base units that are not explicitly defined in the current environment will not be scaled (e.g., si.A * si.kg).
    • Factored units: Instances of units that have a .factor attribute not equal to 1 (such as US customary units like si.lbf) are not scaled.
    disp(5000 * si.kg)        # Auto-scales to Mg
    disp(4_000_000_000 / si.s) # Auto-scales to GHz
    disp(0.000000112 * si.m)  # Auto-scales to nm
    
    # No auto-scaling for undefined products
    disp(40000 * si.A * si.kg)
    
    # No auto-scaling for factored units (e.g. in us_customary)
    si.environment('us_customary')
    disp(40000 * si.lbf)
  11. How unit resolution works

    main

    When representing a quantity, forallpeople determines the unit based on a specific lookup order involving Dimension, Factor, and Prefix.

    Lookup Logic:

    1. Dimension Match: The system first looks for definitions with a matching SI base unit vector.
    2. Factor Match:
      • If a definition matches both Dimension and Factor, it is treated as a defined unit and given priority.
      • If Dimension matches but the Factor does not (and Factor != 1), the system falls back to the derived definition unless a Default unit has been specified in the environment.
      • If Dimension matches and Factor == 1, the derived representation is prioritized.
    3. Fallback: If no dimension match is found, the system falls back to a compound string of SI base units (e.g., kg^1 * m^1 * s^-2).

    Note on Prefixes: Prefixes are only applied to SI base units and SI derived units. They are never applied to defined units.

  12. Load a units environment

    main

    You can use si.environment(name) to load specific unit definitions (e.g., from a JSON file). Loading an environment performs two actions:

    1. It pushes the additional unit definitions into the module namespace as variables.
    2. It modifies the representation of the units to be displayed according to the definitions in that environment.

    Example of loading the 'default' environment:

    si.environment('default')
    import forallpeople as si
    
    # Load the default environment
    si.environment('default')
    
    # Units are now available and formatted according to 'default'
    mass = 5.25 * si.kg