1"""Parameter container for the AGI general-equilibrium model (see docs/model_dictionary.md).23All defaults here are *placeholders of the right order of magnitude*; `calibration.py` overwrites the4calibrated ones from data/processed/calibration_targets.csv and records the provenance of every value in5results/data/parameters.json (field `source`: "data", "calibrated", "scenario" or "assumed").6"""7from __future__ import annotations89import dataclasses10import json11from dataclasses import dataclass, field12from typing import Dict, List, Optional1314import numpy as np1516from .ces import rho_of_sigma171819@dataclass20class TaskSpec:21 """Task classes. Index 0..n-1; bottleneck tasks have nu>0, accessory tasks have gamma>0."""22 names: List[str] = field(default_factory=lambda: ["routine", "cognitive-L", "cognitive-H", "expert", "acc-L", "acc-H"])23 skill: List[int] = field(default_factory=lambda: [0, 0, 1, 1, 0, 1]) # 0 = L, 1 = H24 nu: List[float] = field(default_factory=lambda: [0.30, 0.25, 0.25, 0.20, 0.0, 0.0])25 gamma: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0, 0.0, 0.05, 0.05])26 alpha_inf: List[float] = field(default_factory=lambda: [1.0, 2.0, 4.0, 8.0, 3.0, 6.0]) # terminal compute per worker2728 @property29 def n(self):30 return len(self.names)3132 @property33 def is_acc(self):34 return np.array([g > 0 for g in self.gamma])353637@dataclass38class Params:39 # --- horizon ---40 T: int = 275 # periods (2026 .. 2300)41 year0: int = 202642 # --- tasks ---43 tasks: TaskSpec = field(default_factory=TaskSpec)44 sigma_B: float = 0.5 # elasticity across bottleneck tasks45 H: List[float] = field(default_factory=lambda: [0.6, 0.4]) # skill supplies (L, H)46 # --- nests ---47 sigma_N: float = 0.7 # tasks vs structures48 nu_N: float = 0.3049 sigma_T: float = 0.6 # top nest: land, energy, V50 nu_T: float = 0.0551 nu_E: float = 0.0352 # --- technology paths ---53 Z0: float = 1.054 g_Z: float = 0.01 # non-AI TFP growth55 ZE: float = 1.0 # energy TFP56 eta: float = 0.35 # resource share in energy57 RE: float = 1.0 # energy resource endowment58 T_land: float = 1.059 TH_land: float = 1.060 nu_H: float = 0.35 # land share in housing61 theta_H: float = 0.15 # housing expenditure share62 # --- capital ---63 delta: Dict[str, float] = field(default_factory=lambda: {"N": 0.06, "Q": 0.25, "E": 0.05, "H": 0.025})64 phi: Dict[str, float] = field(default_factory=lambda: {"N": 3.0, "Q": 1.0, "E": 3.0, "H": 3.0})65 pQ0: float = 1.0 # initial price of one compute unit66 g_pQ: float = 0.25 # rate of decline of compute price67 pQ_floor_ratio: float = 1e-3 # floor relative to pQ0 (Moore's law ends when reached)68 eps0: float = 1.0 # energy per compute unit69 g_eps: float = 0.10 # energy efficiency gains70 eps_floor_ratio: float = 1e-271 # --- preferences (two-class capitalists) ---72 beta: float = 0.9673 gamma_u: float = 2.074 # --- alpha paths: log-linear decline from alpha0 to alpha_inf at rate g_alpha ---75 alpha0_mult: List[float] = field(default_factory=lambda: [3.0, 50.0, 400.0, 3000.0, 100.0, 1000.0])76 g_alpha: float = 0.1577 alpha_shock_year: Optional[int] = None # if set: alpha jumps to alpha_inf in that year (rapid AGI)78 alpha_freeze: bool = False # if True: alpha stays at alpha0 forever (no-AI reference)79 reinstate_task: Optional[int] = None # index of a task whose alpha stays at its 2026 value (never automated)80 alpha_inf_scale: float = 1.0 # multiplies all terminal alpha_i^inf (2026 values unchanged)81 # --- policy ---82 tau_T: float = 0.0 # land value tax (share of land rent)83 tau_K: float = 0.0 # tax on all capital income84 tau_Q: float = 0.0 # tax on compute rental income85 theta_F: float = 0.0 # citizen fund share of equity86 markup_Q: float = 1.0 # AI-provider markup on compute services (1 = competitive)87 foreign_share_Q: float = 0.0 # share of compute equity owned abroad88 # --- initial stocks (set by calibration) ---89 KN0: float = 2.590 KQ0: float = 0.0291 KE0: float = 0.392 KH0: float = 1.293 # --- provenance ---94 source: Dict[str, str] = field(default_factory=dict)9596 # ---------- derived ----------97 @property98 def rho_B(self):99 return rho_of_sigma(self.sigma_B)100101 @property102 def rho_N(self):103 return rho_of_sigma(self.sigma_N)104105 @property106 def rho_T(self):107 return rho_of_sigma(self.sigma_T)108109 def years(self):110 return np.arange(self.year0, self.year0 + self.T)111112 def Z_path(self):113 """Hicks-neutral TFP: constant in the baseline (trend growth g_Z is Harrod-neutral, see transition.py);114 the 'AI for science' robustness scenario raises Z along a path set in scenarios.py."""115 return self.Z0 * np.ones(self.T)116117 def pQ_path(self):118 t = np.arange(self.T)119 return self.pQ0 * np.maximum(np.exp(-self.g_pQ * t), self.pQ_floor_ratio)120121 def eps_path(self):122 t = np.arange(self.T)123 return self.eps0 * np.maximum(np.exp(-self.g_eps * t), self.eps_floor_ratio)124125 def alpha_path(self):126 """Array (T, n_tasks) of alpha_{i,t}; np.inf = not automatable."""127 n = self.tasks.n128 a_inf = np.array(self.tasks.alpha_inf, dtype=float)129 a0 = a_inf * np.array(self.alpha0_mult, dtype=float)130 a_inf = np.minimum(a_inf * self.alpha_inf_scale, a0)131 t = np.arange(self.T)[:, None]132 if self.alpha_freeze:133 return np.tile(a0, (self.T, 1))134 if self.alpha_shock_year is not None:135 ts = self.alpha_shock_year - self.year0136 path = np.where(t < ts, a0[None, :], a_inf[None, :])137 if self.reinstate_task is not None:138 path[:, self.reinstate_task] = a0[self.reinstate_task]139 return path140 path = np.maximum(a0[None, :] * np.exp(-self.g_alpha * t), a_inf[None, :])141 if self.reinstate_task is not None:142 path[:, self.reinstate_task] = a0[self.reinstate_task]143 return path144145 # ---------- I/O ----------146 def to_dict(self):147 d = dataclasses.asdict(self)148 return d149150 def to_json(self, path):151 with open(path, "w") as f:152 json.dump(self.to_dict(), f, indent=2)153154 @classmethod155 def from_dict(cls, d):156 d = dict(d)157 if "tasks" in d and isinstance(d["tasks"], dict):158 d["tasks"] = TaskSpec(**d["tasks"])159 return cls(**d)160161 @classmethod162 def from_json(cls, path):163 with open(path) as f:164 return cls.from_dict(json.load(f))165166 def replace(self, **kw):167 return dataclasses.replace(self, **kw)168