"""Parameter container for the AGI general-equilibrium model (see docs/model_dictionary.md). All defaults here are *placeholders of the right order of magnitude*; `calibration.py` overwrites the calibrated ones from data/processed/calibration_targets.csv and records the provenance of every value in results/data/parameters.json (field `source`: "data", "calibrated", "scenario" or "assumed"). """ from __future__ import annotations import dataclasses import json from dataclasses import dataclass, field from typing import Dict, List, Optional import numpy as np from .ces import rho_of_sigma @dataclass class TaskSpec: """Task classes. Index 0..n-1; bottleneck tasks have nu>0, accessory tasks have gamma>0.""" names: List[str] = field(default_factory=lambda: ["routine", "cognitive-L", "cognitive-H", "expert", "acc-L", "acc-H"]) skill: List[int] = field(default_factory=lambda: [0, 0, 1, 1, 0, 1]) # 0 = L, 1 = H nu: List[float] = field(default_factory=lambda: [0.30, 0.25, 0.25, 0.20, 0.0, 0.0]) gamma: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0, 0.0, 0.05, 0.05]) alpha_inf: List[float] = field(default_factory=lambda: [1.0, 2.0, 4.0, 8.0, 3.0, 6.0]) # terminal compute per worker @property def n(self): return len(self.names) @property def is_acc(self): return np.array([g > 0 for g in self.gamma]) @dataclass class Params: # --- horizon --- T: int = 275 # periods (2026 .. 2300) year0: int = 2026 # --- tasks --- tasks: TaskSpec = field(default_factory=TaskSpec) sigma_B: float = 0.5 # elasticity across bottleneck tasks H: List[float] = field(default_factory=lambda: [0.6, 0.4]) # skill supplies (L, H) # --- nests --- sigma_N: float = 0.7 # tasks vs structures nu_N: float = 0.30 sigma_T: float = 0.6 # top nest: land, energy, V nu_T: float = 0.05 nu_E: float = 0.03 # --- technology paths --- Z0: float = 1.0 g_Z: float = 0.01 # non-AI TFP growth ZE: float = 1.0 # energy TFP eta: float = 0.35 # resource share in energy RE: float = 1.0 # energy resource endowment T_land: float = 1.0 TH_land: float = 1.0 nu_H: float = 0.35 # land share in housing theta_H: float = 0.15 # housing expenditure share # --- capital --- delta: Dict[str, float] = field(default_factory=lambda: {"N": 0.06, "Q": 0.25, "E": 0.05, "H": 0.025}) phi: Dict[str, float] = field(default_factory=lambda: {"N": 3.0, "Q": 1.0, "E": 3.0, "H": 3.0}) pQ0: float = 1.0 # initial price of one compute unit g_pQ: float = 0.25 # rate of decline of compute price pQ_floor_ratio: float = 1e-3 # floor relative to pQ0 (Moore's law ends when reached) eps0: float = 1.0 # energy per compute unit g_eps: float = 0.10 # energy efficiency gains eps_floor_ratio: float = 1e-2 # --- preferences (two-class capitalists) --- beta: float = 0.96 gamma_u: float = 2.0 # --- alpha paths: log-linear decline from alpha0 to alpha_inf at rate g_alpha --- alpha0_mult: List[float] = field(default_factory=lambda: [3.0, 50.0, 400.0, 3000.0, 100.0, 1000.0]) g_alpha: float = 0.15 alpha_shock_year: Optional[int] = None # if set: alpha jumps to alpha_inf in that year (rapid AGI) alpha_freeze: bool = False # if True: alpha stays at alpha0 forever (no-AI reference) reinstate_task: Optional[int] = None # index of a task whose alpha stays at its 2026 value (never automated) alpha_inf_scale: float = 1.0 # multiplies all terminal alpha_i^inf (2026 values unchanged) # --- policy --- tau_T: float = 0.0 # land value tax (share of land rent) tau_K: float = 0.0 # tax on all capital income tau_Q: float = 0.0 # tax on compute rental income theta_F: float = 0.0 # citizen fund share of equity markup_Q: float = 1.0 # AI-provider markup on compute services (1 = competitive) foreign_share_Q: float = 0.0 # share of compute equity owned abroad # --- initial stocks (set by calibration) --- KN0: float = 2.5 KQ0: float = 0.02 KE0: float = 0.3 KH0: float = 1.2 # --- provenance --- source: Dict[str, str] = field(default_factory=dict) # ---------- derived ---------- @property def rho_B(self): return rho_of_sigma(self.sigma_B) @property def rho_N(self): return rho_of_sigma(self.sigma_N) @property def rho_T(self): return rho_of_sigma(self.sigma_T) def years(self): return np.arange(self.year0, self.year0 + self.T) def Z_path(self): """Hicks-neutral TFP: constant in the baseline (trend growth g_Z is Harrod-neutral, see transition.py); the 'AI for science' robustness scenario raises Z along a path set in scenarios.py.""" return self.Z0 * np.ones(self.T) def pQ_path(self): t = np.arange(self.T) return self.pQ0 * np.maximum(np.exp(-self.g_pQ * t), self.pQ_floor_ratio) def eps_path(self): t = np.arange(self.T) return self.eps0 * np.maximum(np.exp(-self.g_eps * t), self.eps_floor_ratio) def alpha_path(self): """Array (T, n_tasks) of alpha_{i,t}; np.inf = not automatable.""" n = self.tasks.n a_inf = np.array(self.tasks.alpha_inf, dtype=float) a0 = a_inf * np.array(self.alpha0_mult, dtype=float) a_inf = np.minimum(a_inf * self.alpha_inf_scale, a0) t = np.arange(self.T)[:, None] if self.alpha_freeze: return np.tile(a0, (self.T, 1)) if self.alpha_shock_year is not None: ts = self.alpha_shock_year - self.year0 path = np.where(t < ts, a0[None, :], a_inf[None, :]) if self.reinstate_task is not None: path[:, self.reinstate_task] = a0[self.reinstate_task] return path path = np.maximum(a0[None, :] * np.exp(-self.g_alpha * t), a_inf[None, :]) if self.reinstate_task is not None: path[:, self.reinstate_task] = a0[self.reinstate_task] return path # ---------- I/O ---------- def to_dict(self): d = dataclasses.asdict(self) return d def to_json(self, path): with open(path, "w") as f: json.dump(self.to_dict(), f, indent=2) @classmethod def from_dict(cls, d): d = dict(d) if "tasks" in d and isinstance(d["tasks"], dict): d["tasks"] = TaskSpec(**d["tasks"]) return cls(**d) @classmethod def from_json(cls, path): with open(path) as f: return cls.from_dict(json.load(f)) def replace(self, **kw): return dataclasses.replace(self, **kw)