-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconfig.py
More file actions
140 lines (121 loc) · 5.76 KB
/
Copy pathconfig.py
File metadata and controls
140 lines (121 loc) · 5.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""Basic model configuration."""
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class ModelConfig:
"""Small parameter object for the first version of the model."""
seed: int | None = None
steps: int = 100
sa_mode: bool = False
grid_size: int = 50
num_buyers: int = 200
num_sellers: int = 8
visual_range: int = 5
torus: bool = True
money_max: float = 4.0
money_regrowth_rate: float = 1.0
initial_money_probability: float = 0.05
money_regrowth_probability: float = 0.02
buyer_initial_money: float = 5.0
buyer_initial_oil: float = 10.0
buyer_money_weight: float = 0.5
psi_tick: float = 1.0
psi_move: float = 0.2
seller_initial_money: float = 20.0
seller_initial_oil: float = 50.0
seller_money_metabolism: float = 1.0
r_o: float = 2.0
o_max: float = 50.0
p_floor: float = 1.0
respawn_sellers: bool = True
# Dynamic seller pricing: p_s(t) = p_floor * (1 + a1*D_s + a2*C_s + a3*L_s + a4*Y_s)
a1: float = 1.0 # captive-demand weight [HEURISTIC]
a2: float = 0.5 # competitor-anchoring weight [HEURISTIC]
a3: float = 1.0 # liquidity-pressure weight [HEURISTIC]
a4: float = 0.0 # loyalty-markup weight; 0 = feature off [HEURISTIC]
loyalty_threshold_k: int = 3 # consecutive purchases required to count as loyal
competitor_anchoring_length_scale: float = (
10.0 # ell: kernel decay distance [HEURISTIC] - SHOULD depend on grid size and visual range
)
solvency_horizon_ticks: float = (
20.0 # T_h: ticks of metabolism a seller must be able to cover
)
# EMA price beliefs: unvisited sellers start at prior_price_mean; each
# visit updates: belief = (1-alpha)*belief + alpha*observed_price.
prior_price_mean: float = 2.0 # cold-start belief for unvisited sellers [HEURISTIC]
belief_update_weight: float = (
0.3 # alpha: EMA step size, must be in (0, 1] [HEURISTIC]
)
# Loss aversion (README §4.2): overpayment above the believed reference
# price is weighted lambda-fold in the buy decision (lambda >= 1).
loss_aversion: float = 2.25 # lambda [HEURISTIC]
def validate(self) -> None:
"""Raise ``ValueError`` when a parameter cannot support a run."""
if self.steps < 1:
raise ValueError("steps must be at least 1")
if self.grid_size < 1:
raise ValueError("grid_size must be at least 1")
if self.num_buyers < 0:
raise ValueError("num_buyers must be non-negative")
if self.num_sellers < 1:
raise ValueError("num_sellers must be at least 1")
if self.num_sellers > self.grid_size * self.grid_size:
raise ValueError("num_sellers cannot exceed the number of grid cells")
if self.visual_range < 1:
raise ValueError("visual_range must be at least 1")
if self.money_max <= 0:
raise ValueError("money_max must be positive")
if self.money_regrowth_rate < 0:
raise ValueError("money_regrowth_rate must be non-negative")
if not 0 <= self.initial_money_probability <= 1:
raise ValueError("initial_money_probability must be between 0 and 1")
if not 0 <= self.money_regrowth_probability <= 1:
raise ValueError("money_regrowth_probability must be between 0 and 1")
if self.buyer_initial_money < 0:
raise ValueError("buyer_initial_money must be non-negative")
if self.buyer_initial_oil <= 0:
raise ValueError("buyer_initial_oil must be positive")
if self.buyer_money_weight <= 0:
raise ValueError("buyer_money_weight must be positive")
if self.psi_tick <= 0:
raise ValueError("psi_tick must be positive")
if self.psi_move < 0:
raise ValueError("psi_move must be non-negative")
if self.seller_initial_money <= 0:
raise ValueError("seller_initial_money must be positive")
if self.seller_initial_oil < 0:
raise ValueError("seller_initial_oil must be non-negative")
if self.seller_money_metabolism < 0:
raise ValueError("seller_money_metabolism must be non-negative")
if self.r_o < 0:
raise ValueError("r_o must be non-negative")
if self.o_max <= 0:
raise ValueError("o_max must be positive")
if self.p_floor <= 0:
raise ValueError("p_floor must be positive")
if self.a1 < 0:
raise ValueError("a1 must be non-negative")
if self.a2 < 0:
raise ValueError("a2 must be non-negative")
if self.a2 >= 1.0:
# Competitor anchoring is linear feedback on last-tick prices;
# a2 >= 1 amplifies instead of damps each tick's deviation, so
# prices diverge exponentially. This is a stability bound, not a
# taste parameter.
raise ValueError("a2 must be less than 1.0 for price stability")
if self.a3 < 0:
raise ValueError("a3 must be non-negative")
if self.a4 < 0:
raise ValueError("a4 must be non-negative")
if self.loyalty_threshold_k < 1:
raise ValueError("loyalty_threshold_k must be at least 1")
if self.competitor_anchoring_length_scale <= 0:
raise ValueError("competitor_anchoring_length_scale must be positive")
if self.solvency_horizon_ticks <= 0:
raise ValueError("solvency_horizon_ticks must be positive")
if self.prior_price_mean <= 0:
raise ValueError("prior_price_mean must be positive")
if not 0 < self.belief_update_weight <= 1:
raise ValueError("belief_update_weight must be in (0, 1]")
if self.loss_aversion < 1:
raise ValueError("loss_aversion must be at least 1")