Dimensional analysis arithmetic for SI quantities. Named after Percy Bridgman.
Bridgman works with dimension dictionaries whose keys are SI base-dimension symbols and whose values are integer exponents:
force = {"M": 1, "L": 1, "T": -2}uv add bridgmanInstall the symbolic API with SymPy support:
uv add "bridgman[sympy]"Dimensions:dict[str, int]type alias.mul_dims(d1, d2): multiply quantities by adding exponents.div_dims(d1, d2): divide quantities by subtracting exponents.pow_dims(d, n): raise dimensions to an integer power.nmust be anint;booland non-integer exponents raiseTypeError.dims_equal(d1, d2): compare after removing zero exponents.is_dimensionless(d): return true when all exponents are zero or absent.format_dims(d): produce display text such asM L T⁻², using Unicode superscripts. Dimensionless values render as1.dims_signature(d): produce a canonical, zero-stripped signature such asM:1,L:1,T:-2, with dimensionless values represented as1.parse_dims_signature(signature): parse a signature produced bydims_signature.canonicalize_dims(d): normalize dimension keys.Theta, uppercase theta, and lowercase theta all canonicalize toTheta.
The symbolic API requires SymPy. Its canonical checking entry point is
verify_expr.
dims_of_expr(expr, dim_map): compute dimensions for a SymPy expression.verify_expr(eq, dim_map): verify a SymPy equality or inequality by comparing the dimensions of both sides.explain_expr(eq, dim_map): return a structured dimension-onlyCheckResultexposingok,lhs_dimensions,rhs_dimensions,reason, andsteps.DimensionalError: raised for proven dimensional inconsistency or unsupported symbolic constructs.SympyRequiredError: raised by symbolic APIs when SymPy is not installed.
Supported expression forms are symbols, numbers, multiplication, powers,
addition, Abs, Min, Max, and equality or inequality through verify_expr.
Addition, Min, and Max require every term to share dimensions. Abs
preserves the argument dimensions. Powers of dimensioned quantities require
exact integer or rational exponents; SymPy Float exponents are rejected
because they are not exact dimensional claims. Dimensionless bases may be raised
to arbitrary symbolic or floating exponents because the result remains
dimensionless.
The following functions require dimensionless arguments and return
dimensionless results: sin, cos, tan, exp, log, sinh, cosh,
and tanh. If any argument has dimensions, Bridgman raises
DimensionalError. atan2(y, x) is different: it requires y and x to have
equal dimensions, and returns a dimensionless result.
Unsupported SymPy nodes, including derivatives, integrals, piecewise
expressions, Kronecker deltas, and nested relational expressions, raise
DimensionalError rather than being silently accepted.
The Pi API works directly on dimension dictionaries and integer exponents. It checks and generates dimensionless monomial products without adding value-bearing quantities, unit conversion, code generation, or third-party dependencies.
from bridgman import count_pi_groups, is_dimensionless_product, pi_groups
rho = {"M": 1, "L": -3}
velocity = {"L": 1, "T": -1}
length = {"L": 1}
dynamic_viscosity = {"M": 1, "L": -1, "T": -1}
quantities = {
"rho": rho,
"v": velocity,
"L": length,
"mu": dynamic_viscosity,
}
assert count_pi_groups(quantities) == 1
assert is_dimensionless_product(
quantities,
{"rho": 1, "v": 1, "L": 1, "mu": -1},
)
assert pi_groups(quantities) == ({"rho": 1, "v": 1, "L": 1, "mu": -1},)PiError: raised when product names or quantity labels are invalid.is_dimensionless_product(quantities, exponents): checks whether a user-authored integer power product is dimensionless.count_pi_groups(quantities): returns the Buckingham countn - rank(A).pi_groups(quantities): returns Bridgman's deterministic integer basis for dimensionless power products.
Generated bases are useful diagnostics, not semantic identity surfaces. Different valid bases can span the same dimensionless space, so downstream systems should store original quantities plus checked authored products when they need stable artifacts. Pi groups are dimension-only; they do not replace the kind layer and cannot distinguish dimensional twins such as energy and torque.
Dimensions say whether an equation is dimensionally possible. They do not say
whether two dimensionally identical quantities mean the same thing. Energy and
torque both have dimensions {"M": 1, "L": 2, "T": -2}; pressure and energy
density also collide; angle and plain unitless values are both dimensionless.
The semantic kind layer keeps the dimension dictionaries as the arithmetic core and adds named quantity kinds above them:
import sympy as sp
from bridgman import KindRegistry, OperationRule, QuantityKind, verify_expr_kinds
E, F, d, tau = sp.symbols("E F d tau")
energy = {"M": 1, "L": 2, "T": -2}
force = {"M": 1, "L": 1, "T": -2}
length = {"L": 1}
registry = KindRegistry(
kinds=[
QuantityKind("Energy", energy),
QuantityKind("Torque", energy),
QuantityKind("Force", force),
QuantityKind("Length", length),
],
rules=[
OperationRule(
"Force",
"mul",
"Length",
"Energy",
commutative=True,
rationale="Work: W = Fd",
),
],
)
assert verify_expr_kinds(
sp.Eq(E, F * d),
registry=registry,
kind_map={"E": "Energy", "F": "Force", "d": "Length"},
)
assert not verify_expr_kinds(
sp.Eq(E, tau),
registry=registry,
kind_map={"E": "Energy", "tau": "Torque"},
)QuantityKind(name, dimensions): declares a semantic kind. Names are arbitrary non-empty strings and do not need to be valid Python identifiers. Dimensions are canonicalized at construction.OperationRule(left_kind, op, right_kind, result_kind, commutative=False, rationale=None): declares a semantic"mul"or"div"rule. Setcommutative=Trueto register both argument orders for multiplication (division rules cannot be commutative).rationaleis an optional human string surfaced inCheckResult.steps.KindRegistry(kinds=[...], rules=[...]): validates kind definitions and operation rules. Operation rules are checked for dimensional consistency at registration; dimensionally invalid rules raiseInvalidOperationRuleError. Introspection methods:kind_dimensions(name),result_kind(left, op, right),operation_rule(left, op, right),kinds_with_dimensions(d),unique_kind_with_dimensions(d), andambiguous_kinds(d).kind_of_expr(expr, registry=..., kind_map=...): infers the semantic kind of a SymPy expression.verify_expr_kinds(eq, registry=..., kind_map=...): verifies both dimensions and semantic kind.explain_expr_kinds(eq, registry=..., kind_map=...): returns a structuredCheckResultwith kinds, dimensions, reason text, and operation steps.
All kind errors derive from KindError:
DuplicateKindError: same kind name registered twice.DuplicateOperationRuleError: same(left, op, right)registered twice (including the implicit reverse direction of a commutative rule).UnknownKindError: a referenced kind name is not in the registry, or no registered kind has the supplied dimensions.InvalidOperationRuleError: an operation rule uses an unsupportedopor is dimensionally inconsistent with its declared result.MissingOperationRuleError(subclass ofInvalidOperationRuleError): no rule exists for a requested(left, op, right)triple encountered during expression evaluation.AmbiguousKindError: dimensions match more than one registered kind when a unique kind is required.KindMismatchError: incompatible kinds combined in an operation that requires equal kinds (e.g. addition,Min,Max).
Kind-aware checking is stricter than dimension-only checking. Every kind-accepted equation should also be dimensionally accepted, but dimensionally accepted equations over semantic twins can still be rejected by kind-aware verification.
import sympy as sp
from bridgman import verify_expr
F, m, a = sp.symbols("F m a")
dim_map = {
"F": {"M": 1, "L": 1, "T": -2},
"m": {"M": 1},
"a": {"L": 1, "T": -2},
}
assert verify_expr(sp.Eq(F, m * a), dim_map)MIT