axiom

A model of your own · step 3 of 5

Dimensions are checked when you build it

What stops you writing a formula that cannot mean anything?

Every node carries a dimension, and the rules are the ones you would apply by hand: a sum needs its terms to agree, exp needs a dimensionless argument, and the mean has to end up in the dimension of the outcome it predicts.

The point is when this happens. Adding visits to dollars raises at the moment you construct the model — not after a fit, not in a downstream unit conversion, not in the number somebody put on a slide. A model that builds is a model whose arithmetic means something.

step 3
from axiom.core import DimensionError, Likelihood, ModelSpec

sigma = Param(name="sigma", dimension=D.outcome,
              prior=Prior(family="halfnormal", hyper={"sigma": 5.0}))
footfall = Data(name="footfall", dimension=D.outcome)

# the honest mistake: adding a spend to a footfall
try:
    ModelSpec(
        name="wrong", mean=Add(terms=(alpha, spend)), outcome=footfall,
        likelihood=Likelihood(family="normal", scale="sigma"),
        parameters=(alpha, sigma),
    )
except DimensionError as exc:
    print(f"refused at construction: {exc}")

model = ModelSpec(
    name="exponential_approach",
    mean=mean,
    outcome=footfall,
    likelihood=Likelihood(family="normal", scale="sigma"),
    parameters=(alpha, beta, k, sigma),
)
print()
print(f"built    : {model.name}")
print(f"hash     : {model.content_hash()[:16]}")
print(f"params   : {[p.name for p in model.parameters]}")
what it printed
refused at construction: add: terms of a sum must share a dimension; term 0 is Y, term 1 is $

built    : exponential_approach
hash     : d896b064b742c84c
params   : ['alpha', 'beta', 'k', 'sigma']

Every line above was captured by running this code at build time. The steps share one namespace, so the code on this page is the code you would type after the steps before it.