Source code for prefgraph.datasets._demo

"""Synthetic demo dataset for PrefGraph.

Generates a deterministic dataset of budget-constrained consumers with
varying rationality levels - no downloads, no setup.

Usage:
    from prefgraph.datasets import load_demo
    from prefgraph.engine import Engine

    users = load_demo()
    results = Engine(metrics=["garp", "ccei", "mpi"]).analyze_arrays(users)
"""

from __future__ import annotations

import numpy as np


[docs] def load_demo( n_users: int = 100, n_obs: int = 15, n_goods: int = 5, seed: int = 42, return_panel: bool = False, ) -> list[tuple[np.ndarray, np.ndarray]]: """Load a synthetic demo dataset (offline, zero setup). Generates deterministic budget data with a mix of rational and irrational consumers for testing and demos. The dataset contains three types of consumers: - ~40% perfectly rational (Cobb-Douglas utility maximization) - ~40% noisy rational (perturbations from optimal) - ~20% irrational (random choices) This creates an interesting CCEI distribution for demonstrations. Args: n_users: Number of synthetic users (default 100). n_obs: Observations per user (default 15). n_goods: Number of goods (default 5). seed: Random seed for reproducibility (default 42). return_panel: If True, return a BehaviorPanel instead. Returns: List of (prices T*K, quantities T*K) tuples ready for Engine.analyze_arrays(). If return_panel=True, returns a BehaviorPanel. """ rng = np.random.RandomState(seed) n_rational = int(n_users * 0.4) n_noisy = int(n_users * 0.4) n_irrational = n_users - n_rational - n_noisy users: list[tuple[np.ndarray, np.ndarray]] = [] for i in range(n_users): # Random prices and budgets per observation prices = rng.uniform(0.5, 5.0, size=(n_obs, n_goods)) budgets = rng.uniform(10.0, 100.0, size=n_obs) # Cobb-Douglas utility weights for this user alpha = rng.dirichlet(np.ones(n_goods)) if i < n_rational: # Perfectly rational: exact Cobb-Douglas demand quantities = (alpha[np.newaxis, :] * budgets[:, np.newaxis]) / prices elif i < n_rational + n_noisy: # Noisy rational: perturbed demand sigma = rng.uniform(0.1, 0.5) optimal = (alpha[np.newaxis, :] * budgets[:, np.newaxis]) / prices noise = np.exp(rng.normal(0, sigma, size=(n_obs, n_goods))) quantities = optimal * noise else: # Irrational: random quantities within budget max_q = budgets[:, np.newaxis] / prices quantities = rng.uniform(0, 1, size=(n_obs, n_goods)) * max_q # Ensure positive quantities quantities = np.maximum(quantities, 1e-6) users.append((prices, quantities)) if return_panel: from prefgraph.core.session import BehaviorLog from prefgraph.core.panel import BehaviorPanel logs = [ BehaviorLog( cost_vectors=p, action_vectors=q, user_id=f"demo_{i:03d}", ) for i, (p, q) in enumerate(users) ] return BehaviorPanel.from_logs(logs) return users