Store#

A store is the in-memory state of a Traveler model. It gathers the data a model needs under stable names so that model steps can receive one coherent object instead of a long collection of loosely related arguments.

Traveler provides the AbstractStore base class. An application normally subclasses it and calls the concrete class Store, because different applications work with different collections of tables.

Note

A store is not a database or a file format. Loading and saving data are separate concerns; the store represents the data while a model is running.

Why use a store?#

A plain dictionary can group objects, but a store adds model-specific behavior:

  • data has consistent names across model steps;

  • values can be accessed by key or as attributes;

  • an application can register typed tables and validate them together;

  • metadata and completed-step information travel with the model state;

  • the whole object is a JAX pytree and can participate in JAX-oriented workflows.

This leads to simple step interfaces. A function can ask for a Store and retrieve the particular data it needs:

def average_auto_ownership(store):
    return store.households.autos.mean()

The function does not need separate households, persons, configuration, and metadata arguments. Other steps can use the same store while accessing different contents.

Creating a simple store#

A minimal application only needs a concrete subclass. Named objects passed to the constructor become the store’s data:

import jax.numpy as jnp

from traveler import AbstractStore, JaxTable


class Store(AbstractStore):
    """Data available to this model."""


households = JaxTable(
    {
        "household_id": jnp.array([1, 2]),
        "autos": jnp.array([0, 1]),
    }
)

store = Store(
    data={
        "households": households,
        "settings": {"income_growth": 1.03},
    },
    metadata={"scenario": "base"},
)

Store data supports both mapping-style and attribute-style access:

store["households"] is store.households  # True
store.households.autos                   # Array([0, 1], dtype=int32)
store["settings"]["income_growth"]      # 1.03
store.metadata["scenario"]              # "base"

Use data entries for information that model calculations consume. Use metadata for descriptive or bookkeeping information such as a scenario name, provenance, or which steps have completed.

What can a store contain?#

A store commonly contains:

  • JaxTable objects containing model records;

  • JAX arrays and categorical arrays;

  • supporting mappings, parameters, lookup values, or other model inputs;

  • application-specific table objects that describe relationships and derived columns;

  • metadata associated with the model run.

The contents do not all need to have the same shape. For example, a household table and a person table can have different row counts, while a settings mapping may have no rows at all. Their names and relationships are what make them part of the same model state.

Typed stores#

Larger applications declare the tables a store is expected to contain. A typed table provides a view of the underlying data and can define validation, relationships, and lazily computed attributes. The structure looks like this:

from traveler import AbstractStore

from .tables import Households, Persons


class Store(AbstractStore):
    households: Households
    persons: Persons

These annotations register Households and Persons as the table types for those names. store["households"] accesses the underlying table data, while store.households returns the application’s Households view connected to the store. Typed tables are declared with annotations alone; they do not have class-level default values. Calling store.validate() checks registered tables together, including constraints that depend on shared categorical values.

The typed attribute is a read-only view. Replace its underlying data with item assignment, such as store["households"] = new_table, or use a helper such as assign_on() to produce an updated store.

Producing updated state#

Helpers such as assign_on make it convenient to derive a new store while leaving the original table unchanged:

updated = store.assign_on(
    "households",
    autos=store.households.autos + 1,
)

store.households.autos    # Array([0, 1], dtype=int32)
updated.households.autos  # Array([1, 2], dtype=int32)

This style makes the flow of model state explicit: load or construct a store, pass it through model steps, and receive a store containing the resulting tables and metadata.