{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Core Concepts\n", "\n", "This guide explains the fundamental concepts behind StateTracker's design." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## States and States\n", "\n", "A **State** is an object that can take on a finite number of discrete states, numbered from 0 to `num_states - 1`. The simplest state is a \"leaf\" state created directly with a specified number of states:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2026-01-17T20:59:33.510634Z", "iopub.status.busy": "2026-01-17T20:59:33.510442Z", "iopub.status.idle": "2026-01-17T20:59:34.399921Z", "shell.execute_reply": "2026-01-17T20:59:34.399400Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 1, 2, 3, 4]\n" ] } ], "source": [ "from statetracker import Manager, State\n", "\n", "with Manager():\n", " A = State(num_values=5, name=\"A\")\n", " print(list(A))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "States can be **iterated** to cycle through all their states, and their current state can be read or set via the `state` property." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Manager Context\n", "\n", "All states must be created within a `Manager` context. The Manager tracks all states and their relationships:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2026-01-17T20:59:34.419163Z", "iopub.status.busy": "2026-01-17T20:59:34.418989Z", "iopub.status.idle": "2026-01-17T20:59:34.421581Z", "shell.execute_reply": "2026-01-17T20:59:34.420994Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['A', 'B']\n" ] } ], "source": [ "with Manager() as mgr:\n", " A = State(num_values=3, name=\"A\")\n", " B = State(num_values=2, name=\"B\")\n", "\n", " # Manager tracks all states\n", " print(mgr.get_all_names())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Creating states outside a Manager context raises a `RuntimeError`.\n", "\n", "## State Composition\n", "\n", "The power of StateTracker comes from **composing** states using operations. When you combine states, you create a new \"derived\" state that depends on its \"parent\" states:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2026-01-17T20:59:34.423147Z", "iopub.status.busy": "2026-01-17T20:59:34.423010Z", "iopub.status.idle": "2026-01-17T20:59:34.425884Z", "shell.execute_reply": "2026-01-17T20:59:34.425439Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C has 6 states\n" ] } ], "source": [ "from statetracker import product\n", "\n", "with Manager():\n", " A = State(num_values=2, name=\"A\")\n", " B = State(num_values=3, name=\"B\")\n", "\n", " # C is derived from A and B via product\n", " C = product([A, B], name=\"C\") # 6 states (2 × 3)\n", "\n", " print(f\"C has {C.num_values} states\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This creates a **directed acyclic graph (DAG)** of state dependencies. You can visualize this structure using `print_dag()`:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2026-01-17T20:59:34.427258Z", "iopub.status.busy": "2026-01-17T20:59:34.427161Z", "iopub.status.idle": "2026-01-17T20:59:34.430185Z", "shell.execute_reply": "2026-01-17T20:59:34.429781Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C (counter, io=0, n=6)\n", "└── [op=Product]\n", " ├── A (counter, io=0, n=2)\n", " └── B (counter, io=0, n=3)\n" ] } ], "source": [ "with Manager():\n", " A = State(num_values=2, name=\"A\")\n", " B = State(num_values=3, name=\"B\")\n", " C = product([A, B], name=\"C\")\n", "\n", " C.print_dag()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Unidirectional State Propagation\n", "\n", "StateTracker uses **unidirectional state propagation**: when you set the state of a derived state, the states of all its parent states are automatically computed and updated." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2026-01-17T20:59:34.431562Z", "iopub.status.busy": "2026-01-17T20:59:34.431471Z", "iopub.status.idle": "2026-01-17T20:59:34.433621Z", "shell.execute_reply": "2026-01-17T20:59:34.433268Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A.state = 1\n", "B.state = 2\n" ] } ], "source": [ "with Manager():\n", " A = State(num_values=2, name=\"A\")\n", " B = State(num_values=3, name=\"B\")\n", " C = product([A, B])\n", "\n", " C.value = 5 # Set the derived state's state\n", "\n", " # Parent states are automatically computed\n", " print(f\"A.value = {A.value}\") # 1\n", " print(f\"B.value = {B.value}\") # 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the key insight: you iterate over the **derived** state, and the **parent** states automatically track along.\n", "\n", "## Active vs Inactive States\n", "\n", "A state's state can be either:\n", "\n", "- **Active**: An integer from 0 to `num_states - 1`\n", "- **Inactive**: `None`\n", "\n", "Inactive states arise with operations like `stack` where only one parent is \"active\" at a time:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2026-01-17T20:59:34.434965Z", "iopub.status.busy": "2026-01-17T20:59:34.434883Z", "iopub.status.idle": "2026-01-17T20:59:34.437425Z", "shell.execute_reply": "2026-01-17T20:59:34.436900Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "C=0, A=0, B=None\n", "C=1, A=1, B=None\n", "C=2, A=None, B=0\n", "C=3, A=None, B=1\n", "C=4, A=None, B=2\n" ] } ], "source": [ "from statetracker import stack\n", "\n", "with Manager():\n", " A = State(num_values=2, name=\"A\")\n", " B = State(num_values=3, name=\"B\")\n", " C = stack([A, B]) # 5 states total\n", "\n", " for state in C:\n", " print(f\"C={state}, A={A.value}, B={B.value}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use `is_active` to check if a state is active. You can also use `print_states()` to conveniently display all states at once, or `print_states(include_inactive=False)` to show only active states.\n", "\n", "## Conflict Detection\n", "\n", "When a state appears in multiple branches of the DAG, StateTracker detects **conflicting state assignments**. This happens when two different paths would assign different values to the same parent state." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2026-01-17T20:59:34.439120Z", "iopub.status.busy": "2026-01-17T20:59:34.439020Z", "iopub.status.idle": "2026-01-17T20:59:34.442678Z", "shell.execute_reply": "2026-01-17T20:59:34.442274Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error: ConflictingStateAssignmentError\n" ] } ], "source": [ "with Manager():\n", " A = State(num_values=3, name=\"A\")\n", " B = A[0:2] # B=0 → A=0, B=1 → A=1\n", " C = A[1:3] # C=0 → A=1, C=1 → A=2\n", "\n", " # Using product tries to activate BOTH B and C simultaneously\n", " D = product([B, C])\n", "\n", " # D state 0 means B=0 (A=0) and C=0 (A=1)\n", " # But A can't be both 0 and 1 at the same time!\n", " try:\n", " for state in D:\n", " pass\n", " except Exception as e:\n", " print(f\"Error: {type(e).__name__}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that `stack` does NOT cause conflicts because it only activates one parent at a time. The conflict arises when an operation like `product` tries to activate multiple states that share a common ancestor with incompatible state requirements.\n", "\n", "## Iteration Order and `ordered_product()`\n", "\n", "Sometimes you want certain states to have priority over others when they appear together in Cartesian products. The `ordered_product()` function reads an `iter_order` property from each state to determine ordering. States with lower `iter_order` values iterate \"faster\" (change more frequently in the inner loop)." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2026-01-17T20:59:34.443964Z", "iopub.status.busy": "2026-01-17T20:59:34.443875Z", "iopub.status.idle": "2026-01-17T20:59:34.446340Z", "shell.execute_reply": "2026-01-17T20:59:34.445837Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A=0, B=0\n", "A=1, B=0\n", "A=0, B=1\n", "A=1, B=1\n" ] } ], "source": [ "from statetracker import ordered_product\n", "\n", "with Manager():\n", " A = State(num_values=2, name=\"A\", iter_order=0) # Fast\n", " B = State(num_values=2, name=\"B\", iter_order=1) # Slow\n", "\n", " C = ordered_product([A, B])\n", "\n", " for _ in C:\n", " print(f\"A={A.value}, B={B.value}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The default `iter_order` is 0 for leaf states. Derived states inherit the minimum `iter_order` of their parents.\n", "\n", "Note that the regular `product()` function does not use `iter_order`—it preserves the exact order of states you pass to it.\n", "\n", "## State Identity\n", "\n", "Each state has a unique `id` assigned by the Manager. This is used for:\n", "\n", "- Deduplication in `ordered_product()`\n", "- Tie-breaking when states have the same `iter_order`\n", "\n", "States are compared by identity (`is`), not value, so two states with the same `num_states` are still distinct objects.\n", "\n", "## Copying States\n", "\n", "States support two types of copying:\n", "\n", "- `copy()`: Creates a new state with the same parents (shallow copy)\n", "- `deepcopy()`: Recursively creates a new state with copied ancestors" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2026-01-17T20:59:34.447559Z", "iopub.status.busy": "2026-01-17T20:59:34.447480Z", "iopub.status.idle": "2026-01-17T20:59:34.449948Z", "shell.execute_reply": "2026-01-17T20:59:34.449544Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "B's DAG:\n", "Counter[1] (counter, io=0, n=2)\n", "└── [op=Slice]\n", " └── A (counter, io=0, n=3)\n", "\n", "C's DAG (shallow copy - shares A):\n", "C (counter, io=0, n=2)\n", "└── [op=Slice]\n", " └── A (counter, io=0, n=3)\n", "\n", "D's DAG (deep copy - has own parent):\n", "D (counter, io=0, n=2)\n", "└── [op=Slice]\n", " └── Counter[3] (counter, io=0, n=3)\n" ] } ], "source": [ "with Manager():\n", " A = State(num_values=3, name=\"A\")\n", " B = A[1:3]\n", "\n", " # Shallow copy: C shares parent A with B\n", " C = B.copy(name=\"C\")\n", "\n", " # Deep copy: D has its own copy of the parent\n", " D = B.deepcopy(name=\"D\")\n", "\n", " print(\"B's DAG:\")\n", " B.print_dag()\n", " print(\"\\nC's DAG (shallow copy - shares A):\")\n", " C.print_dag()\n", " print(\"\\nD's DAG (deep copy - has own parent):\")\n", " D.print_dag()" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.16" } }, "nbformat": 4, "nbformat_minor": 2 }