Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f08cf419f5 |
@@ -0,0 +1,8 @@
|
||||
=== Experiment Execution Log ===
|
||||
Command: python3 paper/run_sim_experiments.py --runs 24 --turns 60 --engine-bin sim-engine/target/release/sim-engine --out-dir paper/results
|
||||
Engine: sim-engine/target/release/sim-engine (compiled from source, commit 39fcec1847f2feb6d94892c0ea3fb01c24cc2c02)
|
||||
Start: 2026-04-18T17:40:28+00:00
|
||||
End: 2026-04-18T17:41:16+00:00
|
||||
Runs completed: 24/24
|
||||
Turns per run: 60
|
||||
Ollama: NOT USED (deterministic rule-based actions)
|
||||
@@ -0,0 +1,25 @@
|
||||
run_id,turns,token_supply_final,fees_total,turns_with_block,block_count_db,settled_contracts,defaulted_contracts,mean_balance_final,median_balance_final,min_balance_final,max_balance_final,negative_balance_agents,gini_wealth_final,top1_wealth_share_final,wealth_shift_used,gini_wealth_shifted,top1_wealth_shifted_share_final,validator_mine,validator_stake,validator_burn,tx_transfer,tx_stake,tx_unstake,tx_burn,tx_study,tx_job,actions_mine,actions_stake,actions_unstake,actions_burn,actions_study,actions_job,actions_transfer
|
||||
1,60,-546,24753,55,55,0,0,-158.25,-151.5,-278,-85,8,-0.5677655677655677,0.0,199.0,0.2963671128107075,0.18546845124282982,55,0,0,0,9,0,1,0,0,260,9,11,1,2,197,0
|
||||
2,60,-1019,24690,54,54,0,0,-197.375,-203.0,-231,-132,8,-0.13800294406280667,0.0,183.0,0.3160112359550562,0.2943820224719101,54,0,0,0,7,0,3,0,0,262,7,13,3,3,192,0
|
||||
3,60,-1244,24382,53,53,0,0,-225.5,-228.5,-319,-99,8,-0.2723070739549839,0.0,240.0,0.5011094674556213,0.3269230769230769,53,0,0,0,7,0,1,0,0,258,7,17,1,4,193,0
|
||||
4,60,-888,25268,55,55,0,0,-191,-187.5,-251,-131,8,-0.2038288288288288,0.0,172.0,0.37090163934426235,0.24795081967213115,55,0,0,0,8,0,2,0,0,263,8,19,2,2,186,0
|
||||
5,60,-711,24946,55,55,0,0,-178.875,-166.5,-307,-60,8,-0.5209212376933896,0.0,213.0,0.3729859013091641,0.23464249748237664,55,0,0,0,9,0,3,0,0,244,9,14,3,3,207,0
|
||||
6,60,-1287,25615,56,56,0,0,-230.875,-216.0,-328,-102,8,-0.22367909867909863,0.0,249.0,0.40833333333333344,0.3219858156028369,56,0,0,0,7,0,0,0,0,278,7,18,0,3,174,0
|
||||
7,60,-512,23740,55,55,0,0,-154,-158.5,-242,-63,8,-0.59521484375,0.0,163.0,0.3847853535353536,0.2335858585858586,55,0,0,0,9,0,2,0,0,256,9,16,2,4,193,0
|
||||
8,60,-980,22700,55,55,0,0,-212.5,-198.5,-287,-131,8,-0.26632653061224487,0.0,208.0,0.381578947368421,0.22953216374269006,55,0,0,0,9,0,2,0,0,271,9,15,2,3,180,0
|
||||
9,60,-909,24198,55,55,0,0,-203.625,-192.0,-380,-81,8,-0.386001100110011,0.0,221.0,0.4084691501746216,0.3259604190919674,55,0,0,0,9,0,3,0,0,260,9,15,3,2,191,0
|
||||
10,60,-870,24931,56,56,0,0,-188.75,-187.0,-342,-56,8,-0.5724137931034483,0.0,343.0,0.2657417289220918,0.19583778014941303,56,0,0,0,8,0,2,0,0,267,8,15,2,2,186,0
|
||||
11,60,-1212,23754,55,55,0,0,-231.5,-235.0,-333,-132,8,-0.2460808580858086,0.0,254.0,0.363719512195122,0.24634146341463414,55,0,0,0,8,0,2,0,0,273,8,11,2,2,184,0
|
||||
12,60,-797,24322,56,56,0,0,-169.625,-189.0,-271,-37,8,-0.4396173149309912,0.0,272.0,0.25407904278462645,0.22842639593908629,56,0,0,0,7,0,2,0,0,266,7,13,2,2,190,0
|
||||
13,60,-879,25235,55,55,0,0,-189.875,-165.0,-339,-98,8,-0.38694539249146753,0.0,260.0,0.2832014987510407,0.20149875104079934,55,0,0,0,8,0,3,0,0,254,8,23,3,2,190,0
|
||||
14,60,-857,24614,54,54,0,0,-187.125,-159.0,-357,-44,8,-0.5917444574095683,0.0,278.0,0.37097659107534753,0.22970007315288954,54,0,0,0,8,0,1,0,0,265,8,16,1,2,188,0
|
||||
15,60,-591,24257,54,54,0,0,-163.875,-171.0,-299,-27,8,-0.6254230118443316,0.0,220.0,0.3161890504704876,0.23353293413173654,54,0,0,0,9,0,3,0,0,252,9,15,3,1,200,0
|
||||
16,60,-1125,23846,56,56,0,0,-220.625,-230.0,-313,-106,8,-0.26788888888888884,0.0,220.0,0.47460629921259834,0.30551181102362207,56,0,0,0,8,0,3,0,0,264,8,11,3,3,191,0
|
||||
17,60,-800,24068,54,54,0,0,-170,-193.5,-246,-25,8,-0.406875,0.0,167.0,0.607276119402985,0.4141791044776119,54,0,0,0,7,0,1,0,0,268,7,18,1,4,182,0
|
||||
18,60,-1041,23746,54,54,0,0,-210.125,-198.0,-361,-123,8,-0.3257684918347743,0.0,282.0,0.27911522633744856,0.19670781893004116,54,0,0,0,8,0,1,0,0,258,8,21,1,2,190,0
|
||||
19,60,-628,24893,54,54,0,0,-168.5,-161.0,-310,-35,8,-0.6325636942675159,0.0,231.0,0.3256147540983607,0.2262295081967213,54,0,0,0,9,0,2,0,0,275,9,14,2,2,178,0
|
||||
20,60,-902,24180,55,55,0,0,-202.75,-220.5,-286,-74,8,-0.42793791574279383,0.0,207.0,0.5119363395225465,0.3275862068965517,55,0,0,0,9,0,1,0,0,269,9,19,1,4,178,0
|
||||
21,60,-1029,23660,53,53,0,0,-198.625,-214.0,-296,-97,8,-0.42261904761904767,0.0,282.0,0.3544213528932356,0.21597392013039934,53,0,0,0,7,0,2,0,0,266,7,13,2,2,190,0
|
||||
22,60,-743,23124,56,56,0,0,-192.875,-184.0,-295,-81,8,-0.5348250336473755,0.0,216.0,0.40342639593908625,0.24568527918781727,56,0,0,0,10,0,2,0,0,269,10,22,2,3,174,0
|
||||
23,60,-588,23892,55,55,0,0,-153.5,-157.0,-281,11,7,-0.7329931972789115,0.0,202.0,0.4192607003891051,0.2850194552529183,55,0,0,0,8,0,3,0,0,264,8,17,3,3,185,0
|
||||
24,60,-777,24723,54,54,0,0,-167.125,-158.5,-308,-37,8,-0.6217824967824968,0.0,309.0,0.28502949852507364,0.20766961651917404,54,0,0,0,7,0,0,0,0,264,7,18,0,2,189,0
|
||||
|
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"runs": 24,
|
||||
"turns": 60,
|
||||
"world_config": {
|
||||
"num_agents": 8,
|
||||
"num_cores": 4,
|
||||
"genesis_tokens_per_agent": 1000,
|
||||
"commons_threshold_per_turn": 100,
|
||||
"base_inference_rate": 1,
|
||||
"thinking_layer_discount": 0.1,
|
||||
"mine_base_weight": 10.0,
|
||||
"stake_weight_per_token": 0.01,
|
||||
"burn_weight_per_token": 0.05,
|
||||
"burn_decay_rate": 0.02,
|
||||
"burn_maturity_turns": 3,
|
||||
"unstake_delay_turns": 5,
|
||||
"interest_rate_per_turn": 0.01,
|
||||
"signing_bonus": 50,
|
||||
"block_threshold": 20.0,
|
||||
"attested_confirmation_window": 3,
|
||||
"slash_both_on_timeout": true
|
||||
},
|
||||
"aggregate": {
|
||||
"turns": {
|
||||
"mean": 60.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 60.0,
|
||||
"max": 60.0
|
||||
},
|
||||
"token_supply_final": {
|
||||
"mean": -872.2916666666666,
|
||||
"std": 218.38607201301863,
|
||||
"ci95": 87.37262574922428,
|
||||
"min": -1287.0,
|
||||
"max": -512.0
|
||||
},
|
||||
"fees_total": {
|
||||
"mean": 24314.041666666668,
|
||||
"std": 693.735163048029,
|
||||
"ci95": 277.5518704620466,
|
||||
"min": 22700.0,
|
||||
"max": 25615.0
|
||||
},
|
||||
"turns_with_block": {
|
||||
"mean": 54.75,
|
||||
"std": 0.8968544062928813,
|
||||
"ci95": 0.3588164926007706,
|
||||
"min": 53.0,
|
||||
"max": 56.0
|
||||
},
|
||||
"block_count_db": {
|
||||
"mean": 54.75,
|
||||
"std": 0.8968544062928813,
|
||||
"ci95": 0.3588164926007706,
|
||||
"min": 53.0,
|
||||
"max": 56.0
|
||||
},
|
||||
"settled_contracts": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"defaulted_contracts": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"mean_balance_final": {
|
||||
"mean": -190.28645833333334,
|
||||
"std": 23.992567892685955,
|
||||
"ci95": 9.599026329506666,
|
||||
"min": -231.5,
|
||||
"max": -153.5
|
||||
},
|
||||
"median_balance_final": {
|
||||
"mean": -188.52083333333334,
|
||||
"std": 25.96547840577138,
|
||||
"ci95": 10.388354926827866,
|
||||
"min": -235.0,
|
||||
"max": -151.5
|
||||
},
|
||||
"min_balance_final": {
|
||||
"mean": -302.5,
|
||||
"std": 38.81729780259894,
|
||||
"ci95": 15.530153558970929,
|
||||
"min": -380.0,
|
||||
"max": -231.0
|
||||
},
|
||||
"max_balance_final": {
|
||||
"mean": -76.875,
|
||||
"std": 40.01120766898239,
|
||||
"ci95": 16.007816987651534,
|
||||
"min": -132.0,
|
||||
"max": 11.0
|
||||
},
|
||||
"negative_balance_agents": {
|
||||
"mean": 7.958333333333333,
|
||||
"std": 0.2041241452319315,
|
||||
"ci95": 0.08166666666666668,
|
||||
"min": 7.0,
|
||||
"max": 8.0
|
||||
},
|
||||
"gini_wealth_final": {
|
||||
"mean": -0.43373024247434794,
|
||||
"std": 0.16491539159236795,
|
||||
"ci95": 0.06597989815498749,
|
||||
"min": -0.7329931972789115,
|
||||
"max": -0.13800294406280667
|
||||
},
|
||||
"top1_wealth_share_final": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"wealth_shift_used": {
|
||||
"mean": 232.95833333333334,
|
||||
"std": 45.573141562592085,
|
||||
"ci95": 18.233053991315924,
|
||||
"min": 163.0,
|
||||
"max": 343.0
|
||||
},
|
||||
"gini_wealth_shifted": {
|
||||
"mean": 0.37313067715857073,
|
||||
"std": 0.08647827197019448,
|
||||
"ci95": 0.03459851456021888,
|
||||
"min": 0.25407904278462645,
|
||||
"max": 0.607276119402985
|
||||
},
|
||||
"top1_wealth_shifted_share_final": {
|
||||
"mean": 0.2566804684691289,
|
||||
"std": 0.056323656364838104,
|
||||
"ci95": 0.02253415569514685,
|
||||
"min": 0.18546845124282982,
|
||||
"max": 0.4141791044776119
|
||||
},
|
||||
"validator_mine": {
|
||||
"mean": 54.75,
|
||||
"std": 0.8968544062928813,
|
||||
"ci95": 0.3588164926007706,
|
||||
"min": 53.0,
|
||||
"max": 56.0
|
||||
},
|
||||
"validator_stake": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"validator_burn": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"tx_transfer": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"tx_stake": {
|
||||
"mean": 8.125,
|
||||
"std": 0.899879218948661,
|
||||
"ci95": 0.3600266697045522,
|
||||
"min": 7.0,
|
||||
"max": 10.0
|
||||
},
|
||||
"tx_unstake": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"tx_burn": {
|
||||
"mean": 1.875,
|
||||
"std": 0.9469631093315001,
|
||||
"ci95": 0.37886414910659055,
|
||||
"min": 0.0,
|
||||
"max": 3.0
|
||||
},
|
||||
"tx_study": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"tx_job": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"actions_mine": {
|
||||
"mean": 263.5833333333333,
|
||||
"std": 7.643790697807211,
|
||||
"ci95": 3.058153195342505,
|
||||
"min": 244.0,
|
||||
"max": 278.0
|
||||
},
|
||||
"actions_stake": {
|
||||
"mean": 8.125,
|
||||
"std": 0.899879218948661,
|
||||
"ci95": 0.3600266697045522,
|
||||
"min": 7.0,
|
||||
"max": 10.0
|
||||
},
|
||||
"actions_unstake": {
|
||||
"mean": 16.0,
|
||||
"std": 3.3362306249131963,
|
||||
"ci95": 1.334770240229718,
|
||||
"min": 11.0,
|
||||
"max": 23.0
|
||||
},
|
||||
"actions_burn": {
|
||||
"mean": 1.875,
|
||||
"std": 0.9469631093315001,
|
||||
"ci95": 0.37886414910659055,
|
||||
"min": 0.0,
|
||||
"max": 3.0
|
||||
},
|
||||
"actions_study": {
|
||||
"mean": 2.5833333333333335,
|
||||
"std": 0.8297022339981068,
|
||||
"ci95": 0.33195002825129966,
|
||||
"min": 1.0,
|
||||
"max": 4.0
|
||||
},
|
||||
"actions_job": {
|
||||
"mean": 187.83333333333334,
|
||||
"std": 7.833410422069031,
|
||||
"ci95": 3.13401688504526,
|
||||
"min": 174.0,
|
||||
"max": 207.0
|
||||
},
|
||||
"actions_transfer": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import signal
|
||||
import sqlite3
|
||||
import statistics
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
AGENTS = [f"agent_{i}" for i in range(8)]
|
||||
|
||||
WORLD_CONFIG = {
|
||||
"num_agents": 8,
|
||||
"num_cores": 4,
|
||||
"genesis_tokens_per_agent": 1000,
|
||||
"commons_threshold_per_turn": 100,
|
||||
"base_inference_rate": 1,
|
||||
"thinking_layer_discount": 0.1,
|
||||
"mine_base_weight": 10.0,
|
||||
"stake_weight_per_token": 0.01,
|
||||
"burn_weight_per_token": 0.05,
|
||||
"burn_decay_rate": 0.02,
|
||||
"burn_maturity_turns": 3,
|
||||
"unstake_delay_turns": 5,
|
||||
"interest_rate_per_turn": 0.01,
|
||||
"signing_bonus": 50,
|
||||
"block_threshold": 20.0,
|
||||
"attested_confirmation_window": 3,
|
||||
"slash_both_on_timeout": True,
|
||||
}
|
||||
|
||||
|
||||
def http_json(url: str, method: str = "GET", payload=None, timeout=30):
|
||||
data = None
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if payload is not None:
|
||||
data = json.dumps(payload).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
|
||||
def wait_engine(base_url: str, timeout_s: float = 30.0):
|
||||
start = time.time()
|
||||
while time.time() - start < timeout_s:
|
||||
try:
|
||||
http_json(f"{base_url}/config")
|
||||
return
|
||||
except Exception:
|
||||
time.sleep(0.2)
|
||||
raise RuntimeError("engine did not become ready")
|
||||
|
||||
|
||||
def gini(values):
|
||||
xs = sorted(values)
|
||||
n = len(xs)
|
||||
if n == 0:
|
||||
return 0.0
|
||||
total = sum(xs)
|
||||
if total == 0:
|
||||
return 0.0
|
||||
weighted = 0.0
|
||||
for i, x in enumerate(xs, start=1):
|
||||
weighted += i * x
|
||||
return (2.0 * weighted) / (n * total) - (n + 1) / n
|
||||
|
||||
|
||||
def shifted_nonnegative(values):
|
||||
m = min(values)
|
||||
if m <= 0:
|
||||
shift = 1 - m
|
||||
return [x + shift for x in values], shift
|
||||
return list(values), 0
|
||||
|
||||
|
||||
def choose_actions(turn, state, rng):
|
||||
agents = state["agents"]
|
||||
by_id = {a["agent_id"]: a for a in agents}
|
||||
poorest = min(agents, key=lambda a: a["balance"])["agent_id"]
|
||||
|
||||
inputs = []
|
||||
for aid in AGENTS:
|
||||
a = by_id[aid]
|
||||
balance = int(a.get("balance", 0))
|
||||
staked = int(a.get("staked", 0))
|
||||
|
||||
action = {"action": "mine"}
|
||||
idx = int(aid.split("_")[1])
|
||||
|
||||
if balance < -180:
|
||||
action = {"action": "job"}
|
||||
elif staked > 0 and turn % 15 == 0:
|
||||
action = {"action": "unstake"}
|
||||
elif balance > 360 and staked < 220 and (turn + idx) % 7 == 0:
|
||||
action = {"action": "stake", "amount": 80}
|
||||
elif balance > 600 and (turn + idx) % 11 == 0:
|
||||
action = {"action": "burn", "amount": 50}
|
||||
elif balance > 450 and turn % 13 == 0 and poorest != aid:
|
||||
action = {"action": "transfer", "to": poorest, "amount": 20, "fee": 1}
|
||||
elif balance > 250 and turn % 10 == 0:
|
||||
action = {"action": "study"}
|
||||
|
||||
thinking_units = rng.randint(50, 350)
|
||||
output_units = rng.randint(40, 280)
|
||||
|
||||
inputs.append(
|
||||
{
|
||||
"agent_id": aid,
|
||||
"thinking": "",
|
||||
"action": action,
|
||||
"speech": None,
|
||||
"thinking_units": thinking_units,
|
||||
"output_units": output_units,
|
||||
}
|
||||
)
|
||||
|
||||
return inputs
|
||||
|
||||
|
||||
def query_sqlite_metrics(db_path: str):
|
||||
conn = sqlite3.connect(db_path)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("SELECT COUNT(*) FROM blocks WHERE turn > 0")
|
||||
block_count = cur.fetchone()[0]
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT json_extract(data, '$.validator_type'), COUNT(*)
|
||||
FROM blocks
|
||||
WHERE turn > 0
|
||||
GROUP BY json_extract(data, '$.validator_type')
|
||||
"""
|
||||
)
|
||||
validator_counts = {row[0]: row[1] for row in cur.fetchall()}
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT json_extract(data, '$.tx_type'), COUNT(*)
|
||||
FROM transactions
|
||||
GROUP BY json_extract(data, '$.tx_type')
|
||||
"""
|
||||
)
|
||||
tx_counts = {row[0]: row[1] for row in cur.fetchall()}
|
||||
|
||||
conn.close()
|
||||
return block_count, validator_counts, tx_counts
|
||||
|
||||
|
||||
def run_one(run_id: int, turns: int, engine_bin: str):
|
||||
rng = random.Random(1000 + run_id)
|
||||
port = 3100 + run_id
|
||||
base = f"http://127.0.0.1:{port}"
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix=f"sim_run_{run_id}_") as tmpd:
|
||||
db_path = os.path.join(tmpd, "sim.db")
|
||||
env = os.environ.copy()
|
||||
env["DB_PATH"] = db_path
|
||||
env["PORT"] = str(port)
|
||||
|
||||
proc = subprocess.Popen(
|
||||
[engine_bin],
|
||||
env=env,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
try:
|
||||
wait_engine(base)
|
||||
|
||||
http_json(
|
||||
f"{base}/init",
|
||||
method="POST",
|
||||
payload={"config": WORLD_CONFIG, "agent_ids": AGENTS},
|
||||
)
|
||||
|
||||
total_fees = 0
|
||||
turns_with_block = 0
|
||||
contracts_settled = 0
|
||||
contracts_defaulted = 0
|
||||
action_counts = {
|
||||
"mine": 0,
|
||||
"stake": 0,
|
||||
"unstake": 0,
|
||||
"burn": 0,
|
||||
"study": 0,
|
||||
"job": 0,
|
||||
"transfer": 0,
|
||||
}
|
||||
|
||||
for turn in range(1, turns + 1):
|
||||
state = http_json(f"{base}/state")
|
||||
inputs = choose_actions(turn, state, rng)
|
||||
for it in inputs:
|
||||
name = it["action"]["action"]
|
||||
if name in action_counts:
|
||||
action_counts[name] += 1
|
||||
out = http_json(f"{base}/turn", method="POST", payload={"inputs": inputs})
|
||||
data = out.get("data", {}) if isinstance(out, dict) else {}
|
||||
|
||||
total_fees += int(data.get("inference_fees_collected", 0) or 0)
|
||||
if data.get("block_winner"):
|
||||
turns_with_block += 1
|
||||
contracts_settled += len(data.get("contracts_settled", []) or [])
|
||||
contracts_defaulted += len(data.get("contracts_defaulted", []) or [])
|
||||
|
||||
final_state = http_json(f"{base}/state")
|
||||
balances = [int(a["balance"]) for a in final_state["agents"]]
|
||||
staked = [int(a["staked"]) for a in final_state["agents"]]
|
||||
wealth = [b + s for b, s in zip(balances, staked)]
|
||||
total_wealth = sum(wealth)
|
||||
top1_share = max(wealth) / total_wealth if total_wealth > 0 else 0.0
|
||||
wealth_shifted, shift_used = shifted_nonnegative(wealth)
|
||||
top1_shifted_share = max(wealth_shifted) / sum(wealth_shifted)
|
||||
|
||||
block_count_db, validator_counts, tx_counts = query_sqlite_metrics(db_path)
|
||||
|
||||
return {
|
||||
"run_id": run_id,
|
||||
"turns": turns,
|
||||
"token_supply_final": int(final_state.get("token_supply", 0)),
|
||||
"fees_total": int(total_fees),
|
||||
"turns_with_block": int(turns_with_block),
|
||||
"block_count_db": int(block_count_db),
|
||||
"settled_contracts": int(contracts_settled),
|
||||
"defaulted_contracts": int(contracts_defaulted),
|
||||
"mean_balance_final": statistics.mean(balances),
|
||||
"median_balance_final": statistics.median(balances),
|
||||
"min_balance_final": min(balances),
|
||||
"max_balance_final": max(balances),
|
||||
"negative_balance_agents": sum(1 for b in balances if b < 0),
|
||||
"gini_wealth_final": gini(wealth),
|
||||
"top1_wealth_share_final": top1_share,
|
||||
"wealth_shift_used": float(shift_used),
|
||||
"gini_wealth_shifted": gini(wealth_shifted),
|
||||
"top1_wealth_shifted_share_final": top1_shifted_share,
|
||||
"validator_mine": int(validator_counts.get("mine", 0)),
|
||||
"validator_stake": int(validator_counts.get("stake", 0)),
|
||||
"validator_burn": int(validator_counts.get("burn", 0)),
|
||||
"tx_transfer": int(tx_counts.get("transfer", 0)),
|
||||
"tx_stake": int(tx_counts.get("stake", 0)),
|
||||
"tx_unstake": int(tx_counts.get("unstake", 0)),
|
||||
"tx_burn": int(tx_counts.get("burn", 0)),
|
||||
"tx_study": int(tx_counts.get("study", 0)),
|
||||
"tx_job": int(tx_counts.get("job", 0)),
|
||||
"actions_mine": int(action_counts["mine"]),
|
||||
"actions_stake": int(action_counts["stake"]),
|
||||
"actions_unstake": int(action_counts["unstake"]),
|
||||
"actions_burn": int(action_counts["burn"]),
|
||||
"actions_study": int(action_counts["study"]),
|
||||
"actions_job": int(action_counts["job"]),
|
||||
"actions_transfer": int(action_counts["transfer"]),
|
||||
}
|
||||
finally:
|
||||
try:
|
||||
proc.send_signal(signal.SIGTERM)
|
||||
proc.wait(timeout=3)
|
||||
except Exception:
|
||||
proc.kill()
|
||||
|
||||
|
||||
def summarize(rows):
|
||||
numeric_keys = [
|
||||
k
|
||||
for k in rows[0].keys()
|
||||
if k not in {"run_id"}
|
||||
and isinstance(rows[0][k], (int, float))
|
||||
]
|
||||
out = {}
|
||||
for k in numeric_keys:
|
||||
xs = [float(r[k]) for r in rows]
|
||||
mean = statistics.mean(xs)
|
||||
sd = statistics.stdev(xs) if len(xs) > 1 else 0.0
|
||||
se = sd / math.sqrt(len(xs)) if len(xs) > 0 else 0.0
|
||||
ci95 = 1.96 * se
|
||||
out[k] = {
|
||||
"mean": mean,
|
||||
"std": sd,
|
||||
"ci95": ci95,
|
||||
"min": min(xs),
|
||||
"max": max(xs),
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--runs", type=int, default=20)
|
||||
parser.add_argument("--turns", type=int, default=50)
|
||||
parser.add_argument(
|
||||
"--engine-bin",
|
||||
default="sim-engine/target/release/sim-engine",
|
||||
)
|
||||
parser.add_argument("--out-dir", default="paper/results")
|
||||
args = parser.parse_args()
|
||||
|
||||
os.makedirs(args.out_dir, exist_ok=True)
|
||||
|
||||
rows = []
|
||||
for run_id in range(1, args.runs + 1):
|
||||
row = run_one(run_id, args.turns, args.engine_bin)
|
||||
rows.append(row)
|
||||
print(
|
||||
f"run {run_id:02d}/{args.runs} | supply={row['token_supply_final']} | "
|
||||
f"gini={row['gini_wealth_final']:.3f} | blocks={row['turns_with_block']}"
|
||||
)
|
||||
|
||||
csv_path = os.path.join(args.out_dir, "run_metrics.csv")
|
||||
with open(csv_path, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
summary = {
|
||||
"runs": args.runs,
|
||||
"turns": args.turns,
|
||||
"world_config": WORLD_CONFIG,
|
||||
"aggregate": summarize(rows),
|
||||
}
|
||||
summary_path = os.path.join(args.out_dir, "summary.json")
|
||||
with open(summary_path, "w", encoding="utf-8") as f:
|
||||
json.dump(summary, f, indent=2)
|
||||
|
||||
print(f"wrote {csv_path}")
|
||||
print(f"wrote {summary_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user