mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-07-15 17:43:36 +00:00
Compare commits
10 Commits
optimizing
...
refactor-t
| Author | SHA1 | Date | |
|---|---|---|---|
| e77f037d62 | |||
| 9c464eaf3b | |||
| 58042ba4f2 | |||
| 18b41ff802 | |||
| 105b014976 | |||
| 220b6ce8c1 | |||
| 910dba0a7d | |||
| e62e842faa | |||
| 661a80b655 | |||
|
|
128911decc |
235
README.md
235
README.md
@@ -1,95 +1,160 @@
|
||||
<img width="200" align="left" src="https://github.com/user-attachments/assets/d148b00d-e9f9-4280-89cc-0cc866e17251" />
|
||||
<p align="center">
|
||||
<img width="180" src="https://github.com/user-attachments/assets/d148b00d-e9f9-4280-89cc-0cc866e17251" alt="PHANTOM logo" />
|
||||
</p>
|
||||
|
||||
### PHANTOM
|
||||
# PHANTOM
|
||||
|
||||
Agent-aware dynamic pricing research platform for studying how automated transaction orchestration changes pricing power, and for testing defenses that recover margin while protecting legitimate user experience.
|
||||
|
||||
[](https://huggingface.co/datasets/velocitatem/whoclickedit)
|
||||
[](https://github.com/velocitatem/PHANTOM/actions/workflows/latex.yml)
|
||||
[](https://pub-d5b94a3c29fd40c6b3881946e463fdb7.r2.dev/thesis-latest.pdf)
|
||||
[](https://huggingface.co/datasets/velocitatem/whoclickedit)
|
||||
[](https://sites.research.google/trc/faq/)
|
||||
[](https://phantom-hotel.vercel.app)
|
||||
[](https://phantom-airline.vercel.app)
|
||||
|
||||
**Live demos:** [Hotel](https://phantom-hotel.vercel.app) | [Airline](https://phantom-airline.vercel.app) | [Academic page](https://velocitatem.github.io/PHANTOM/)
|
||||
|
||||
## What this repository includes
|
||||
|
||||
PHANTOM is a mixed research + engineering monorepo with:
|
||||
|
||||
- a thesis (LaTeX) formalizing Cost of Information (COI) erosion under agentic reconnaissance,
|
||||
- a mode-switching web storefront (`hotel` and `airline`) for controlled human/agent interaction collection,
|
||||
- backend services for event ingestion and pricing,
|
||||
- an experimentation stack for benchmarks, contamination studies, and robust policy training.
|
||||
|
||||
## Why this matters
|
||||
|
||||
Dynamic pricing relies on demand signals collected during browsing. LLM-driven agents can split reconnaissance and execution into separate sessions, which weakens those signals and can collapse extractable price premium. PHANTOM exists to measure that mechanism directly and evaluate practical defenses in a controlled environment.
|
||||
|
||||
## Quick start (local platform)
|
||||
|
||||
### 1) Prerequisites
|
||||
|
||||
- Docker + Docker Compose
|
||||
- Node.js + npm
|
||||
- Python 3.8+
|
||||
- `latexmk` (only if you want to build the paper locally)
|
||||
|
||||
### 2) Install workspace tooling and create env files
|
||||
|
||||
```bash
|
||||
npm install
|
||||
cp .env.example .env
|
||||
cp .env.sweep.example .env.sweep
|
||||
```
|
||||
|
||||
### 3) Fill required values in `.env`
|
||||
|
||||
At minimum, set these before starting services:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_SUPABASE_URL=...
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
|
||||
AIRFLOW_FERNET_KEY=...
|
||||
AIRFLOW_SECRET_KEY=...
|
||||
```
|
||||
|
||||
### 4) Start the platform and web app
|
||||
|
||||
```bash
|
||||
make platform.up
|
||||
make web.dev
|
||||
```
|
||||
|
||||
### 5) Verify
|
||||
|
||||
- Web app: `http://localhost:3000`
|
||||
- Backend health: `http://localhost:5000/health`
|
||||
- Pricing provider health: `http://localhost:5001/health`
|
||||
- Airflow UI: `http://localhost:8085`
|
||||
- Kafka console (Redpanda): `http://localhost:8084` (using `.env.example` defaults)
|
||||
|
||||
## Common commands
|
||||
|
||||
| Goal | Command |
|
||||
| --- | --- |
|
||||
| Show all available workflows | `make help` |
|
||||
| Start/stop platform services | `make platform.up` / `make platform.down` |
|
||||
| Stream docker logs | `make platform.logs` |
|
||||
| Run backend tests | `make test.backend` |
|
||||
| Run end-to-end tests | `make test.e2e` |
|
||||
| Build thesis PDF | `make pdf.build` |
|
||||
| Watch thesis while editing | `make pdf.watch` |
|
||||
| Build general-public thesis variant | `make pdf.genpop` |
|
||||
| Run quick margin-erosion study | `make study.margin-erosion.quick` |
|
||||
| Run benchmark without W&B logging | `make benchmark LOCAL_BENCHMARK_ARGS='--tiers static,surge,linear --alpha-values 0.0,0.3 --episodes 3 --no-wandb'` |
|
||||
|
||||
## System map
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
PHANTOM((PHANTOM Project))
|
||||
North Star
|
||||
Study how automated actors change markets
|
||||
Build an experimentation platform for real-world-like commerce
|
||||
Two-loop learning system
|
||||
Online observation loop
|
||||
Offline "defense gym" loop
|
||||
Core Economic Questions
|
||||
Price Discovery
|
||||
How prices respond to demand signals
|
||||
How signal quality changes with bots/agents
|
||||
Demand & Elasticity
|
||||
Shifts in willingness-to-pay
|
||||
Short-run vs long-run elasticity
|
||||
Market Efficiency & Welfare
|
||||
Consumer surplus vs producer surplus
|
||||
Deadweight loss from frictions/manipulation
|
||||
Price Discrimination & Segmentation
|
||||
Behavioral feature-based segmentation
|
||||
Fairness vs profitability tradeoffs
|
||||
Information Asymmetry
|
||||
Agents amplify search and arbitrage
|
||||
Sellers infer more about buyers; buyers infer more about sellers
|
||||
Strategic Interaction
|
||||
Consumers vs firms vs agents
|
||||
Feedback loops: policy ↔ behavior ↔ price
|
||||
Market Power & Competition
|
||||
Algorithmic pricing as competitive tool
|
||||
Risks: tacit coordination / "algorithmic collusion"
|
||||
Externalities
|
||||
Congestion and attention costs
|
||||
Spillovers: one segment’s behavior affects others’ prices
|
||||
System-Level View
|
||||
Participants
|
||||
Humans
|
||||
Agents (automated buyers/actors)
|
||||
Firms (pricing decision-makers)
|
||||
Platform (measurement + control layer)
|
||||
Markets Simulated
|
||||
Repeated transactions
|
||||
Limited inventory / capacity constraints (conceptually)
|
||||
Time dynamics (learning over time)
|
||||
Interventions
|
||||
Pricing policies
|
||||
Experiment assignment / randomized exposure
|
||||
Agent behavioral policies (task-driven)
|
||||
Measurement & Causal Inference
|
||||
What is observed
|
||||
Actions (search, click, purchase intent)
|
||||
Context (product attributes, time, exposure)
|
||||
Outcomes (conversion, revenue, churn proxies)
|
||||
Identification strategy
|
||||
A/B tests and randomization
|
||||
Counterfactual baselines
|
||||
Robustness checks (offline replay)
|
||||
Key metrics
|
||||
Revenue / profit proxies
|
||||
Conversion & bounce
|
||||
Price volatility / stability
|
||||
Welfare proxies (e.g., dispersion, access)
|
||||
Risk, Governance, and Ethics
|
||||
Manipulation & Integrity
|
||||
Bot-driven demand distortion
|
||||
Measurement contamination
|
||||
Fairness & Transparency
|
||||
Differential pricing concerns
|
||||
Explainability and auditability
|
||||
Safety Constraints
|
||||
Guardrails on price moves
|
||||
Monitoring for runaway feedback loops
|
||||
Outputs
|
||||
Insights
|
||||
When do agents raise/lower prices via behavior shifts?
|
||||
Which market designs are robust to automation?
|
||||
Defenses
|
||||
Agent-aware pricing policies (robust control)
|
||||
Detection + mitigation strategies (feature-level separability)
|
||||
Platform Value
|
||||
Reusable testbed for market + AI-agent research
|
||||
flowchart LR
|
||||
U[Human / Agent Browser] --> W[Next.js Web App]
|
||||
W -->|Price requests| P[Pricing Provider]
|
||||
W -->|Interaction events| B[Backend Ingest API]
|
||||
B --> K[Kafka]
|
||||
K --> A[Airflow + Worker Jobs]
|
||||
A --> R[Redis Model Registry]
|
||||
P -->|Session/global prices| W
|
||||
E[Research Engine + Experiments] --> A
|
||||
E --> R
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Core runtime (`.env`)
|
||||
|
||||
| Variable | Purpose | Typical value |
|
||||
| --- | --- | --- |
|
||||
| `STORE_MODE` | Web mode switch (`hotel` or `airline`) | `hotel` |
|
||||
| `BACKEND_PORT` | Backend API port | `5000` |
|
||||
| `PROVIDER_PORT` | Pricing provider port | `5001` |
|
||||
| `KAFKA_HOST` | Kafka host for local runtime | `localhost` |
|
||||
| `KAFKA_PORT` | Kafka external port | `9092` |
|
||||
| `REDIS_PORT` | Redis exposed port | `6377` |
|
||||
| `REDPANDA_CONSOLE_PORT` | Kafka console UI port | `8084` |
|
||||
| `NEXT_PUBLIC_SUPABASE_URL` | Product catalog/data source URL | required |
|
||||
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Product catalog/data source key | required |
|
||||
| `AIRFLOW_FERNET_KEY` | Airflow crypto key | required |
|
||||
| `AIRFLOW_SECRET_KEY` | Airflow webserver secret | required |
|
||||
|
||||
### Training and sweep settings (`.env.sweep`)
|
||||
|
||||
| Variable | Purpose |
|
||||
| --- | --- |
|
||||
| `WANDB_API_KEY` | Required for training/benchmark runs that log to Weights & Biases |
|
||||
| `WANDB_ENTITY` | Optional W&B entity override |
|
||||
| `WANDB_PROJECT` | W&B project name (default: `capstone`) |
|
||||
| `GITHUB_TOKEN` | Required for `make train.bootstrap` |
|
||||
| `SWEEP_ID` | Required for sweep-agent workflows (`train.agent`, `benchmark.agent`) |
|
||||
|
||||
## Repository layout
|
||||
|
||||
| Path | Role |
|
||||
| --- | --- |
|
||||
| `paper/` | Thesis source, bibliography, and build artifacts |
|
||||
| `web/` | Next.js storefront and experiment interaction surface |
|
||||
| `backend/server/` | FastAPI ingestion API and product retrieval endpoints |
|
||||
| `backend/provider/` | FastAPI pricing service backed by model registry data |
|
||||
| `backend/worker/` | Celery worker for asynchronous jobs |
|
||||
| `engine/` | Training and benchmarking entrypoints |
|
||||
| `experiments/` | Data processing, ETL ideas, and analysis assets |
|
||||
| `docker/` | Dockerfiles for platform services |
|
||||
| `tests/e2e/` | Playwright end-to-end tests |
|
||||
| `docs/` | Academic project page source |
|
||||
|
||||
## Operational notes
|
||||
|
||||
- `make platform.up` starts the dockerized backend stack; the Next.js app is run separately with `make web.dev`.
|
||||
- `make test.e2e` expects backend (`5000`), web (`3000`), and Airflow (`8085`) to be up.
|
||||
- Research commands (`make train`, `make benchmark*`, `make train.agent`) auto-load `.env.sweep`.
|
||||
- Paper builds call `paper/concat_code.sh` before compilation to flatten code into the appendix.
|
||||
|
||||
## Research artifacts
|
||||
|
||||
- Thesis PDF: `thesis-latest.pdf` or [hosted PDF](https://pub-d5b94a3c29fd40c6b3881946e463fdb7.r2.dev/thesis-latest.pdf)
|
||||
- Public dataset: [velocitatem/whoclickedit](https://huggingface.co/datasets/velocitatem/whoclickedit)
|
||||
- Project page: [velocitatem.github.io/PHANTOM](https://velocitatem.github.io/PHANTOM/)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
This work is supported by Google TPU Research Cloud resources.
|
||||
|
||||
@@ -45,14 +45,12 @@
|
||||
<meta name="citation_pdf_url" content="https://pub-d5b94a3c29fd40c6b3881946e463fdb7.r2.dev/thesis-latest.pdf">
|
||||
|
||||
<!-- Additional SEO -->
|
||||
<meta name="theme-color" content="#2563eb">
|
||||
<meta name="msapplication-TileColor" content="#2563eb">
|
||||
<meta name="theme-color" content="#303030">
|
||||
<meta name="msapplication-TileColor" content="#303030">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
|
||||
<!-- Preconnect for performance -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preconnect" href="https://ajax.googleapis.com">
|
||||
<link rel="preconnect" href="https://documentcloud.adobe.com">
|
||||
<link rel="preconnect" href="https://cdn.jsdelivr.net">
|
||||
@@ -82,9 +80,6 @@
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jpswalsh/academicons@1/css/academicons.min.css">
|
||||
</noscript>
|
||||
|
||||
<!-- Fonts - Optimized loading -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Defer non-critical JavaScript -->
|
||||
<script defer src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script defer src="https://documentcloud.adobe.com/view-sdk/main.js"></script>
|
||||
@@ -239,14 +234,14 @@
|
||||
<div class="columns is-centered">
|
||||
<div class="column has-text-centered">
|
||||
<h1 class="title is-1 publication-title">Pricing Heuristics Against Non-human Transaction Orchestration Mechanisms</h1>
|
||||
<div class="is-size-5 publication-authors">
|
||||
<div class="is-size-5 publication-authors author-names">
|
||||
<span class="author-block">
|
||||
<a href="https://alves.world" target="_blank">Daniel Rösel</a></span>
|
||||
</div>
|
||||
|
||||
<div class="is-size-5 publication-authors">
|
||||
<div class="is-size-5 publication-authors author-meta">
|
||||
<span class="author-block">IE University<br>Bachelor's Thesis 2025</span>
|
||||
<span class="eql-cntrb"><small><br>Advisor: Alberto Martín Izquierdo</small></span>
|
||||
<span class="eql-cntrb">Advisor: Alberto Martín Izquierdo</span>
|
||||
</div>
|
||||
|
||||
<div class="column has-text-centered">
|
||||
|
||||
1015
docs/static/css/index.css
vendored
1015
docs/static/css/index.css
vendored
File diff suppressed because it is too large
Load Diff
4
docs/static/images/banner.svg
vendored
4
docs/static/images/banner.svg
vendored
@@ -41,7 +41,7 @@
|
||||
|
||||
<!-- Markers p and E[P] -->
|
||||
<line x1="150" y1="340" x2="150" y2="160" stroke="#E37862" stroke-width="2" stroke-dasharray="6,4"/>
|
||||
<text x="150" y="375" font-family="Georgia" font-style="italic" font-size="22" fill="#E37862" text-anchor="middle">p</text>
|
||||
<text x="150" y="375" font-family="Georgia" font-style="italic" font-size="22" fill="#E37862" text-anchor="middle"><tspan text-decoration="underline">p</tspan></text>
|
||||
|
||||
<line x1="260" y1="340" x2="260" y2="160" stroke="#85B589" stroke-width="2" stroke-dasharray="6,4"/>
|
||||
<text x="260" y="375" font-family="Georgia" font-style="italic" font-size="22" fill="#85B589" text-anchor="middle">E[P]</text>
|
||||
@@ -49,7 +49,7 @@
|
||||
<!-- COI Annotation -->
|
||||
<line x1="150" y1="150" x2="260" y2="150" stroke="#E37862" stroke-width="2" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
||||
<text x="310" y="138" font-size="16" fill="#E37862" text-anchor="middle">average information rent</text>
|
||||
<text x="310" y="118" font-family="Georgia" font-style="italic" font-size="22" fill="#E37862" font-weight="bold" text-anchor="middle">COI := E[P] - p</text>
|
||||
<text x="310" y="118" font-family="Georgia" font-style="italic" font-size="22" fill="#E37862" font-weight="bold" text-anchor="middle">COI := E[P] - <tspan text-decoration="underline">p</tspan></text>
|
||||
</g>
|
||||
|
||||
<!-- Bottom: Agent Saturation -->
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -1,12 +1,15 @@
|
||||
import numpy as np
|
||||
from typing import Dict
|
||||
|
||||
from lib.agent_probability import DEFAULT_AGENT_PRIOR, estimate_agent_probability
|
||||
|
||||
|
||||
def compute_agent_probability(
|
||||
trajectory: list,
|
||||
human_transitions: Dict,
|
||||
agent_transitions: Dict,
|
||||
temperature: float = 1.0,
|
||||
prior_agent: float = DEFAULT_AGENT_PRIOR,
|
||||
) -> float:
|
||||
"""estimate agent probability via KL divergence between trajectory transitions and reference models
|
||||
|
||||
@@ -18,10 +21,10 @@ def compute_agent_probability(
|
||||
agent_transitions: reference transition dict from agent MDP (event->event->prob)
|
||||
|
||||
returns:
|
||||
agent probability in [0, 1] via softmax over KL divergences
|
||||
agent probability in [0, 1] via sigma((delta_h - delta_a) / T)
|
||||
"""
|
||||
if len(trajectory) < 2:
|
||||
return 0.0 # insufficient data, assume human
|
||||
return float(prior_agent)
|
||||
|
||||
# build empirical transition distribution from trajectory
|
||||
trans_counts = {}
|
||||
@@ -54,11 +57,12 @@ def compute_agent_probability(
|
||||
kl_human = kl_div(empirical, human_transitions)
|
||||
kl_agent = kl_div(empirical, agent_transitions)
|
||||
|
||||
# convert to probability via softmax (lower KL = higher prob)
|
||||
t = float(max(temperature, 1e-6))
|
||||
exp_h = np.exp(-kl_human / t)
|
||||
exp_a = np.exp(-kl_agent / t)
|
||||
return float(exp_a / (exp_h + exp_a + 1e-10))
|
||||
return estimate_agent_probability(
|
||||
delta_h=kl_human,
|
||||
delta_a=kl_agent,
|
||||
temperature=temperature,
|
||||
prior_agent=prior_agent,
|
||||
)
|
||||
|
||||
|
||||
def extract_purchases(trajectories: list) -> Dict[int, int]:
|
||||
|
||||
@@ -7,10 +7,9 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Sequence
|
||||
|
||||
import joblib
|
||||
import numpy as np
|
||||
|
||||
from experiments.ml.arch import featurize_trajectory
|
||||
from lib.agent_probability import DEFAULT_AGENT_PRIOR, estimate_agent_probability
|
||||
|
||||
|
||||
DEFAULT_ARTIFACT_DIR = Path("data/separability")
|
||||
@@ -18,11 +17,7 @@ DEFAULT_ARTIFACT_DIR = Path("data/separability")
|
||||
|
||||
@dataclass
|
||||
class SeparabilityArtifacts:
|
||||
scaler: object
|
||||
classifier: object
|
||||
states: List[str]
|
||||
event_transitions: Dict[str, Dict[str, float]]
|
||||
feature_dim: int
|
||||
|
||||
|
||||
def _normalize_events(raw_events: Sequence[object]) -> List[object]:
|
||||
@@ -36,7 +31,9 @@ def _normalize_events(raw_events: Sequence[object]) -> List[object]:
|
||||
return events
|
||||
|
||||
|
||||
def _event_transition_distribution(events: Sequence[object]) -> Dict[str, Dict[str, float]]:
|
||||
def _event_transition_distribution(
|
||||
events: Sequence[object],
|
||||
) -> Dict[str, Dict[str, float]]:
|
||||
counts: Dict[str, Dict[str, int]] = {}
|
||||
for src_evt, dst_evt in zip(events, events[1:]):
|
||||
src_name = getattr(src_evt, "eventName", "unknown")
|
||||
@@ -47,11 +44,15 @@ def _event_transition_distribution(events: Sequence[object]) -> Dict[str, Dict[s
|
||||
distribution: Dict[str, Dict[str, float]] = {}
|
||||
for src, dsts in counts.items():
|
||||
total = float(sum(dsts.values()))
|
||||
distribution[src] = {dst: val / total for dst, val in dsts.items()} if total else {}
|
||||
distribution[src] = (
|
||||
{dst: val / total for dst, val in dsts.items()} if total else {}
|
||||
)
|
||||
return distribution
|
||||
|
||||
|
||||
def _kl_divergence(p: Dict[str, Dict[str, float]], q: Dict[str, Dict[str, float]]) -> float:
|
||||
def _kl_divergence(
|
||||
p: Dict[str, Dict[str, float]], q: Dict[str, Dict[str, float]]
|
||||
) -> float:
|
||||
eps = 1e-10
|
||||
total = 0.0
|
||||
for src, dsts in p.items():
|
||||
@@ -61,28 +62,28 @@ def _kl_divergence(p: Dict[str, Dict[str, float]], q: Dict[str, Dict[str, float]
|
||||
return float(total)
|
||||
|
||||
|
||||
def load_artifacts(artifact_dir: Path | str = DEFAULT_ARTIFACT_DIR) -> SeparabilityArtifacts:
|
||||
def load_artifacts(
|
||||
artifact_dir: Path | str = DEFAULT_ARTIFACT_DIR,
|
||||
) -> SeparabilityArtifacts:
|
||||
artifact_dir = Path(artifact_dir)
|
||||
scaler_path = artifact_dir / "scaler.joblib"
|
||||
model_path = artifact_dir / "classifier.joblib"
|
||||
metadata_path = artifact_dir / "metadata.json"
|
||||
|
||||
if not (scaler_path.exists() and model_path.exists() and metadata_path.exists()):
|
||||
if not metadata_path.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Separability artifacts not found in {artifact_dir}. Run sim.strong_learner.train first."
|
||||
f"Separability metadata not found in {artifact_dir}. Provide metadata.json with event transitions."
|
||||
)
|
||||
|
||||
scaler = joblib.load(scaler_path)
|
||||
classifier = joblib.load(model_path)
|
||||
with open(metadata_path, "r", encoding="utf-8") as fin:
|
||||
metadata = json.load(fin)
|
||||
|
||||
transitions = metadata.get("event_transitions")
|
||||
if not isinstance(transitions, dict):
|
||||
raise ValueError(
|
||||
"metadata.json must contain an 'event_transitions' object with 'human' and 'agent' kernels"
|
||||
)
|
||||
|
||||
return SeparabilityArtifacts(
|
||||
scaler=scaler,
|
||||
classifier=classifier,
|
||||
states=list(metadata["reference_states"]),
|
||||
event_transitions=metadata["event_transitions"],
|
||||
feature_dim=int(metadata["feature_dim"]),
|
||||
event_transitions=transitions,
|
||||
)
|
||||
|
||||
|
||||
@@ -92,37 +93,44 @@ def score_session(
|
||||
) -> dict:
|
||||
events = _normalize_events(raw_events)
|
||||
if not events:
|
||||
return {"prob_agent": 0.0, "delta_h": 0.0, "delta_a": 0.0}
|
||||
|
||||
reference_mdp = {"states": artifacts.states}
|
||||
features = featurize_trajectory(events, mdp=reference_mdp, input_dim=artifacts.feature_dim)
|
||||
scaled = artifacts.scaler.transform(features.reshape(1, -1))
|
||||
prob_agent = float(artifacts.classifier.predict_proba(scaled)[0, 1])
|
||||
return {
|
||||
"prob_agent": float(DEFAULT_AGENT_PRIOR),
|
||||
"delta_h": 0.0,
|
||||
"delta_a": 0.0,
|
||||
"gap": 0.0,
|
||||
}
|
||||
|
||||
session_dist = _event_transition_distribution(events)
|
||||
delta_h = _kl_divergence(session_dist, artifacts.event_transitions.get("human", {}))
|
||||
delta_a = _kl_divergence(session_dist, artifacts.event_transitions.get("agent", {}))
|
||||
gap = float(delta_h - delta_a)
|
||||
prob_agent = estimate_agent_probability(delta_h=delta_h, delta_a=delta_a)
|
||||
|
||||
return {
|
||||
"prob_agent": prob_agent,
|
||||
"delta_h": delta_h,
|
||||
"delta_a": delta_a,
|
||||
"gap": gap,
|
||||
}
|
||||
|
||||
|
||||
def estimate_alpha(prob_agent: float, delta_h: float, delta_a: float, temperature: float = 1.0) -> float:
|
||||
divergence_mass = delta_h + delta_a
|
||||
if divergence_mass <= 1e-8:
|
||||
return float(prob_agent)
|
||||
|
||||
ratio = delta_a / divergence_mass
|
||||
blended = 0.5 * prob_agent + 0.5 * ratio
|
||||
if temperature <= 0:
|
||||
return float(np.clip(blended, 0.0, 1.0))
|
||||
|
||||
scaled = 1.0 / (1.0 + np.exp(-temperature * (blended - 0.5)))
|
||||
return float(np.clip(scaled, 0.0, 1.0))
|
||||
def estimate_alpha(
|
||||
prob_agent: float,
|
||||
delta_h: float,
|
||||
delta_a: float,
|
||||
temperature: float = 1.0,
|
||||
prior_agent: float = DEFAULT_AGENT_PRIOR,
|
||||
) -> float:
|
||||
_ = prob_agent
|
||||
return estimate_agent_probability(
|
||||
delta_h=delta_h,
|
||||
delta_a=delta_a,
|
||||
temperature=temperature,
|
||||
prior_agent=prior_agent,
|
||||
)
|
||||
|
||||
|
||||
def score_sessions(raw_sessions: Iterable[Sequence[object]], artifacts: SeparabilityArtifacts) -> List[dict]:
|
||||
def score_sessions(
|
||||
raw_sessions: Iterable[Sequence[object]], artifacts: SeparabilityArtifacts
|
||||
) -> List[dict]:
|
||||
return [score_session(events, artifacts) for events in raw_sessions]
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
"chapters/05-discussion"
|
||||
"chapters/06-conclusion"
|
||||
"article"
|
||||
"art12"))
|
||||
"art12")
|
||||
(LaTeX-add-labels
|
||||
"app:compute_budget"
|
||||
"tab:compute_derivation"
|
||||
"app:whoclicked_card"))
|
||||
:latex)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ where:
|
||||
The platform does not directly observe the true underlying demand function $d(p)$. Instead, it observes a behavioral proxy $\hat{q}_t$, which is a composite signal derived from the mixture of actor types. We define the demand proxy for product $i$ at epoch $t$ as a weighted aggregation of events:
|
||||
\begin{equation}
|
||||
\label{eq:qhat}
|
||||
\hat{q}_{t,i} = \sum_{s \in \mathcal{S}_t} \sum_{k=1}^{L_s} \omega(a_{s,k}) \cdot \mathbb{1}[i_{s,k} = i]
|
||||
\hat{q}_{t,i} = \sum_{s \in \mathcal{S}_t} \sum_{k=1}^{L_s} \omega(a_{s,k}) \cdot \mathbf{1}[i_{s,k} = i]
|
||||
\end{equation}
|
||||
where $\omega: \mathcal{A} \to \mathbb{R}_+$ assigns weights to actions based on their signal strength regarding willingness to pay.
|
||||
|
||||
@@ -94,7 +94,8 @@ where $\mathbb{E}[P]$ is the expected price charged by the policy and $\underlin
|
||||
|
||||
We now formally demonstrate that standard dynamic pricing mechanisms are not incentive-compatible with high-frequency agentic traffic. As the number of independent competitive agents $N$ querying the system grows, the platform's ability to sustain a COI vanishes.
|
||||
|
||||
A fundamental assumption for our claim lies in the alignment of the AI agent through its prompt which has been demonstrated by \cite{fish_algorithmic_2025} to cause strong collusive behavior under linguistic nudges. This assumption can be generalized to the human user asking the agent to research products with a minimizing objective.
|
||||
\paragraph{Assumption Scope}
|
||||
The theorem and core experiments in this thesis assume a non-collusive independent-session setting: each agent queries prices independently and does not share sampled quotes across agents. Collusive coordination is outside the current proof scope and is treated as an extension scenario.
|
||||
|
||||
\begin{theorem}[COI Erosion in the Limit]
|
||||
Let $N$ be the number of independent, utility-maximizing agents querying the platform. Let $p_{(1)}$ be the first order statistic (minimum) of the prices offered to these agents. As $N \to \infty$, the Cost of Information converges to 0.
|
||||
@@ -317,7 +318,7 @@ To train a robust pricing learner, we need a simulator that can generate realist
|
||||
\subsubsection{Ground-Truth Distinguishability}
|
||||
Because sessions are collected under controlled experimental conditions where each actor is assigned a known type at the start of the trial, labels $\theta_s \in \{H, A\}$ are available as ground truth rather than as the output of a heuristic classifier. We therefore estimate separate transition kernels directly from each labeled partition $\mathcal{D}_H$ and $\mathcal{D}_A$, treating the resulting $\hat{\mathcal{T}}_H$ and $\hat{\mathcal{T}}_A$ as the ground-truth behavioral profiles for each class. We then ask a direct methodological question: are the kernels distinguishable enough to justify downstream pricing control that depends on that distinguishability?
|
||||
|
||||
To answer this, we compute per-session KL divergence scores against both class-level centroids. For each session $s$ in either partition, we fit a session-level event transition kernel $\hat{\mathcal{T}}_s$ from that session's trajectory alone, then compute its average KL divergence to the human centroid ($\Delta_{H,s}$) and to the agent centroid ($\Delta_{A,s}$). The per-session distinguishability score is the gap $\Delta_{H,s} - \Delta_{A,s}$: a negative value indicates proximity to human behavior, a positive value indicates proximity to agent behavior.
|
||||
To answer this, we compute per-session KL divergence scores against both class-level centroids. For each session $s$ in either partition, we fit a session-level event transition kernel $\hat{\mathcal{T}}_s$ from that session's trajectory alone, then compute its average KL divergence to the human centroid ($\Delta_{H,s}$) and to the agent centroid ($\Delta_{A,s}$). The per-session distinguishability score is the gap $\Delta_{H,s} - \Delta_{A,s}$: a negative value indicates proximity to human behavior, a positive value indicates proximity to agent behavior. The reason behind KL divergence for profile analysis is grounded in its nature and tailored characteristics for probability distributions.
|
||||
|
||||
The normality assumption cannot be made for KL divergence distributions, which are right-skewed and bounded below by zero, so we do not use a Student's $t$-test. Instead we apply a Mann-Whitney $U$ test \parencite{mann_test_1947} on the per-session gap scores between the two groups. The Mann-Whitney test is a rank-based nonparametric test that compares the stochastic ordering of two independent samples without distributional assumptions, making it appropriate for small samples drawn from skewed populations. We report $U$, the exact two-sided $p$-value, and group-level descriptive statistics for the gap scores.
|
||||
|
||||
@@ -331,7 +332,7 @@ where $\mathcal{S}_e$ denotes the set of destination events that follow $e$ in t
|
||||
|
||||
To obtain this statistic, we aggregate transitions by triggering event $e$ and treat normalized outgoing probabilities as categorical distributions $P_e$ (human) and $Q_e$ (agent). We intersect shared event labels, then accumulate log-ratio contributions over shared destinations. Large contributions, including near-zero $Q_e(k)$ cases, identify transitions where one actor class is difficult to mimic.
|
||||
|
||||
With these divergence features we train a contrastive model to estimate a weak agent probability $f(\tau)\in[0,1]$, which we later use as a weighting and control signal.
|
||||
With these divergence features we compute a weak agent probability $f(\tau')\in[0,1]$ directly from divergence gaps, which we later use as a weighting and control signal.
|
||||
|
||||
|
||||
\subsubsection{Transition Probability Estimation}
|
||||
@@ -375,10 +376,36 @@ Because contamination level $\alpha$ and demand shift are non-stationary online,
|
||||
\Delta_A &= D_{KL}(\hat{\mathcal{T}}^\prime \parallel \bar{\mathcal{T}}_A)
|
||||
\end{align}
|
||||
|
||||
This yields two centroid-like heuristics that act as a session-level agent score in the engine. On a per-customer or use-case basis a similar study should be done in order to obtain ground truth behavior models for humans and agents and their specific interaction with a given products website.
|
||||
From these two divergences we define the gap score:
|
||||
\begin{equation}
|
||||
g(\tau') := \Delta_H(\tau') - \Delta_A(\tau').
|
||||
\end{equation}
|
||||
Positive values indicate trajectories farther from the human centroid and closer to the agent centroid.
|
||||
|
||||
We map this gap to a weak agent probability using a temperature-controlled logistic map:
|
||||
\begin{equation}
|
||||
f(\tau') := P(Y=A\mid\tau') = \operatorname{softmax}(-\Delta_A,-\Delta_H)_A = \sigma\left(\frac{\Delta_H-\Delta_A}{T}\right), \quad T>0.
|
||||
\end{equation}
|
||||
The session-level control signal injected into pricing is then
|
||||
\begin{equation}
|
||||
\hat{\alpha}(\tau') := f(\tau').
|
||||
\end{equation}
|
||||
|
||||
This turns distinguishability into an operational control input in the engine. On a per-customer or use-case basis, a similar data collection and fitting process should be repeated to obtain domain-specific behavior kernels.
|
||||
|
||||
In implementation, we maintain an alternating game-history stack (our \textit{Limbo} stack) and execute it explicitly every epoch with exactly two transitions: first the platform publishes a price vector (leader move), then the market responds with trajectory-derived demand (follower move).
|
||||
|
||||
To avoid notation drift, we separate two COI objects used for different purposes:
|
||||
\begin{align}
|
||||
\text{COI}_{\text{level}}(\pi) &:= \mathbb{E}[P]-\underline{p} \quad \text{(global reporting KPI)} \\
|
||||
\text{COI}_{\text{leak}}(p,\tau') &:= f(\tau')\cdot \text{InfoValue}(p,\tau') \quad \text{(local control penalty)}
|
||||
\end{align}
|
||||
where $\text{COI}_{\text{level}}$ is evaluated at policy level and $\text{COI}_{\text{leak}}$ is evaluated per observed quote during training. We connect local leakage to expected global erosion with the operational assumption
|
||||
\begin{equation}
|
||||
\mathbb{E}[\Delta\text{COI}_{\text{level},t} \mid \tau_t'] \approx -\kappa\,\text{COI}_{\text{leak}}(p_t,\tau_t') + \xi_t,
|
||||
\end{equation}
|
||||
where $\kappa>0$ and $\xi_t$ is residual noise. This keeps theorem-level COI erosion (global, asymptotic) distinct from training-time leakage control (local surrogate).
|
||||
|
||||
% Mention discretized action space and the clipping and over shotting in continuous action spaces
|
||||
% Also talk about catastrophic economics, we add termination on bankrupcy or zero demand so market collaps
|
||||
|
||||
@@ -444,7 +471,8 @@ The robust policy $\pi^*$ is obtained by solving the maximin problem:
|
||||
\label{eq:robust_policy}
|
||||
\pi^* = \arg \max_{\pi} \min_{Q \in \mathcal{U}_\epsilon} \mathbb{E}_{d \sim Q} \left[ R(p, d) - \lambda \cdot \text{COI}_{\text{leak}}(p,\tau') \right]
|
||||
\end{equation}
|
||||
where $R(p, d)$ is the revenue function and $\lambda$ weighs the information-leakage penalty.
|
||||
where $R(p, d)$ is the revenue function and $\lambda$ weighs the information-leakage penalty. We note that $p$ is directly dependent on $\pi$ which is the one deicing that as its action.
|
||||
|
||||
|
||||
In practice, we parameterize this with a session-level leakage term:
|
||||
\begin{equation}
|
||||
@@ -452,6 +480,8 @@ In practice, we parameterize this with a session-level leakage term:
|
||||
\end{equation}
|
||||
where $f(\tau')$ is the weak agent probability and $\text{InfoValue}$ is implemented either as a constant query-tax surrogate or as a revelation surrogate $-\log\pi(p\mid\tau')$.
|
||||
|
||||
To make the intuition of our $\max \min$ easier in connection to the COI term which we are subtracting, we introduce the strongest possible penalization and try to maximize only for the worst case scenario in which the leakage is extremely high and that negation sends a signal to pick the candidate of the hardest problem.
|
||||
|
||||
For the baseline engine reported here, we intentionally use the constant query-tax surrogate to keep the mechanism minimal:
|
||||
\begin{equation}
|
||||
r_t = R(p_t,\tilde q_t) - \lambda\,f(\tau_t')\,c_{\text{info}}
|
||||
@@ -474,7 +504,7 @@ As part of reward engineering, we keep a UX factor ($UX\in[0,1]$) as an auxiliar
|
||||
\resizebox{0.5\columnwidth}{!}{%
|
||||
\input{chapters/balance_figure.tex}
|
||||
}
|
||||
\caption{Introducing the UX index allows us to better distinguish the kind of impact different methods have and allows us to compare them on this Pareto-like scale.}
|
||||
\caption{Introducing the UX index allows us to better distinguish the kind of impact different methods have and allows us to compare them on this Pareto-efficiency-like scale.}
|
||||
\end{figure}
|
||||
|
||||
We also consider taxation-like overlays for agent traffic under strategy-proof mechanism design (e.g., Vickrey-Clarke-Groves style rules). This remains an extension path and is not part of the main implementation in this thesis.
|
||||
|
||||
@@ -40,7 +40,12 @@ We report two preliminary stages before the full factorial interpretation. First
|
||||
|
||||
\subsubsection{The Impact of Contamination on Revenue}
|
||||
|
||||
A linear fit test on run-level data ($n=95$) shows a strong negative association between contamination and mean revenue. The fitted model mapping $\alpha \to \text{revenue}$ result in $t(93)=-8.2148$, $p=1.20\times 10^{-12}$, $R^2=0.4205$, and a 95\% confidence interval for the slope of $[-75{,}288.76,\,-45{,}975.13]$. In practical terms, a $+0.1$ increase in $\alpha$ corresponds to an average decrease of about $6{,}063$ revenue units within our environment.
|
||||
The contamination--revenue slope is estimated on a controlled cohort (single sweep, baseline policy, $n_{\text{products}}=100$, $n=95$). In this setting, contamination $\alpha$ is set exogenously by the experiment, so the slope identifies the within-sweep causal effect of contamination on revenue under fixed policy and environment settings. The fitted linear model is
|
||||
|
||||
\[
|
||||
\widehat{y}=348{,}823.41-90{,}140.53\,\alpha,
|
||||
\]
|
||||
with $t(93)=-61.45$, $p=4.27\times10^{-77}$, $R^2=0.976$, and a 95\% confidence interval for the slope of $[-93{,}053.38,\,-87{,}227.68]$. Interpreted on the contamination grid, a $+0.1$ increase in $\alpha$ corresponds to an average revenue decrease of about $9{,}014$ units. A heteroskedasticity-robust check (HC1) preserves the same direction and significance ($t=-41.25$, $p=1.42\times10^{-61}$), supporting a large and statistically stable impact in this controlled regime.
|
||||
|
||||
\subsubsection{Large Scale Factorial Training}
|
||||
|
||||
@@ -58,7 +63,20 @@ In our complete training runs we logged $\approx 180$ days of net compute time.
|
||||
\caption{Revenue curves by contamination for the final cohort. The baseline remains above the defended curve in most cells, but the gap narrows in the high-contamination region.}
|
||||
\label{fig:final_focus_revenue_by_alpha}
|
||||
\end{figure}
|
||||
% TODO: we need a similar plot which shows the COI preserved (what we gain across teh multiple conatmination leves, showing that the robust method has better COI optimization.)
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\input{chapters/figures/results/includes/final/final_focus_coi_by_alpha.tex}
|
||||
\caption{COI level curves by contamination for the final cohort. The shaded band marks the per-$\alpha$ gap between defended and baseline policies.}
|
||||
\label{fig:final_focus_coi_by_alpha}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
\input{chapters/figures/results/includes/final/final_focus_coi_preservation_grid.tex}
|
||||
\caption{COI preservation by product count at the contamination endpoints ($\alpha=0.0$ and $\alpha=1.0$). Bars report defended-minus-baseline mean COI level, with the zero line separating preservation from erosion.}
|
||||
\label{fig:final_focus_coi_preservation_grid}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[ht]
|
||||
\centering
|
||||
|
||||
@@ -3,7 +3,7 @@ alpha,revenue_delta,revenue_delta_pct,reward_delta,reward_delta_pct,volatility_d
|
||||
0.1,-14962.041501283413,-4.410637208586118,-14303.760282736213,-4.531344436782669,0.0011858665298920962,0.0,-0.004133727080174038
|
||||
0.2,-16153.416666167905,-4.826514761457546,-15398.621298776357,-4.9418165571901715,0.00200624274016295,0.0,-0.0033201883450373615
|
||||
0.3,-17294.9275360335,-5.382423616385397,-16544.91845114401,-5.533399709364953,-0.0011022484400295268,0.0,-0.0029151149203366505
|
||||
0.4,-19661.294346174283,-6.250307313590199,-18728.35578200908,-6.3953153560217535,3.582812967113658e-05,0.0,-0.0038123361988749577
|
||||
0.4,-19543.8750398212,-6.215299839915013,-18613.487687777204,-6.35858461426586,-2.7530592947980215e-05,0.0,-0.0038561140856475523
|
||||
0.5,-16411.03168918495,-5.3630681206030015,-15638.77510066732,-5.4888928630525315,0.00015428950526953644,0.0,-0.00439661338956944
|
||||
0.6,-14729.668247641937,-5.069964928178309,-13912.22417824401,-5.148827377884945,-0.002735776807082743,0.0,-0.004310129386364658
|
||||
0.7,-21160.81910514756,-7.351404104505076,-20171.762105623755,-7.525169314210056,-0.0008903632602569461,0.0,-0.0026198461183787186
|
||||
|
||||
|
@@ -7,7 +7,7 @@ alpha,mode,runs,revenue_mean,reward_mean,supra_mean,volatility_mean,coi_leakage_
|
||||
0.2,defended,35,318527.35122792586,296199.77820822067,0.0,0.07048630468445288,0.11265850300394666,137.2758153292305
|
||||
0.3,baseline,30,321322.30327214615,299000.9636054795,0.0,0.07085669473747759,0.11527347603412934,136.4452630715689
|
||||
0.3,defended,44,304027.37573611265,282456.0451543355,0.0,0.06975444629744806,0.11235836111379269,136.4704115371568
|
||||
0.4,baseline,33,314565.2423109539,292844.914432166,0.0,0.07031811881503117,0.11300307992768284,136.72547178046122
|
||||
0.4,baseline,33,314447.8230046008,292730.04633793415,0.0,0.07038147753765028,0.11304685781445543,136.70817144219887
|
||||
0.4,defended,38,294903.9479647796,274116.55865015695,0.0,0.0703539469447023,0.10919074372880788,136.75671002806396
|
||||
0.5,baseline,33,306000.80625751516,284916.7489847879,0.0,0.06938663916591635,0.11118137138243217,136.9528780620641
|
||||
0.5,defended,35,289589.7745683302,269277.9738841206,0.0,0.06954092867118589,0.10678475799286273,136.65018588845163
|
||||
|
||||
|
@@ -0,0 +1,45 @@
|
||||
alpha,n_products,baseline_runs,defended_runs,baseline_coi_level_mean,defended_coi_level_mean,coi_preserved,coi_preserved_pct
|
||||
0.0,5.0,9,10,137.060822623968,136.18680853180368,-0.874014092164316,-0.6376833842316922
|
||||
0.0,25.0,9,2,137.114858903596,136.13793579187393,-0.9769231117220727,-0.7124852255501622
|
||||
0.0,50.0,9,11,137.16224858153575,136.92415566181484,-0.23809291972091273,-0.17358487643878118
|
||||
0.0,100.0,9,12,135.86629045322655,137.3609873086303,1.4946968554037596,1.1001234010420895
|
||||
0.1,5.0,3,6,136.59581715538818,135.6308466787041,-0.9649704766840728,-0.7064421859904723
|
||||
0.1,25.0,11,8,135.9860669350444,136.43616365263273,0.45009671758833747,0.33098737814318313
|
||||
0.1,50.0,10,11,136.28362874897243,136.92880179422633,0.6451730452538982,0.4734046570203046
|
||||
0.1,100.0,8,8,137.35578496752095,137.53394777402949,0.17816280650853855,0.12970899372797937
|
||||
0.2,5.0,8,9,135.55116314329388,137.30311388107864,1.7519507377847674,1.2924645551973204
|
||||
0.2,25.0,10,9,137.01587649612287,137.22137163685403,0.20549514073115915,0.1499790724887083
|
||||
0.2,50.0,4,8,137.45096138958434,137.1307018163465,-0.32025957323784837,-0.2329991511155169
|
||||
0.2,100.0,9,9,137.50780776750915,137.43195025898902,-0.07585750852013007,-0.0551659645744523
|
||||
0.3,5.0,6,6,134.95569459599133,134.21855668602896,-0.7371379099623709,-0.5462073402453271
|
||||
0.3,25.0,9,16,136.38346021911525,136.32131251342705,-0.06214770568820427,-0.04556835967378819
|
||||
0.3,50.0,8,6,136.97414077213367,136.88041560990786,-0.09372516222580884,-0.06842544271310845
|
||||
0.3,100.0,7,16,137.19706520314455,137.31020460277784,0.11313939963329744,0.08246488324351146
|
||||
0.4,5.0,8,11,135.6494813257779,136.5487738152141,0.899292489436192,0.6629531352769695
|
||||
0.4,25.0,7,9,136.38451372914378,136.10614648175604,-0.27836724738773455,-0.20410473284420322
|
||||
0.4,50.0,7,10,137.12976275807247,136.98838321468799,-0.14137954338448822,-0.10309909427460566
|
||||
0.4,100.0,11,8,137.4158065068933,137.4849148270489,0.06910832015560686,0.050291390715769026
|
||||
0.5,5.0,7,19,135.91101413475477,136.145621134976,0.2346070002212457,0.1726180925915501
|
||||
0.5,25.0,8,7,137.0972914279529,137.35620682163616,0.2589153936832531,0.18885522170896996
|
||||
0.5,50.0,8,1,137.0714841014652,135.66696334266234,-1.404520758802846,-1.0246629837050352
|
||||
0.5,100.0,10,8,137.4717672869487,137.35366167964338,-0.11810560730532416,-0.08591262746975456
|
||||
0.6,5.0,8,13,133.13626070539635,136.09936023073067,2.9630995253343144,2.225614201296411
|
||||
0.6,25.0,5,10,136.0741624588533,136.26219778039936,0.18803532154606728,0.13818591137970535
|
||||
0.6,50.0,8,10,135.09036188289087,136.05846380616936,0.968101923278482,0.7166328595060871
|
||||
0.6,100.0,7,8,137.29304001584052,137.07512338179083,-0.2179166340496863,-0.15872372993164377
|
||||
0.7,5.0,7,7,136.0533783988379,135.14350016006424,-0.9098782387736719,-0.6687656341075052
|
||||
0.7,25.0,8,11,137.12781750399415,136.8176582131797,-0.3101592908144539,-0.2261826203172962
|
||||
0.7,50.0,14,11,137.06965735909125,136.7028634119364,-0.3667939471548607,-0.26759674914335285
|
||||
0.7,100.0,11,11,137.48279078937205,137.09121810549402,-0.39157268387802446,-0.28481578067317975
|
||||
0.8,5.0,4,7,135.3095773096514,136.59715728802078,1.2875799783693935,0.9515808148766959
|
||||
0.8,25.0,12,13,136.93488398652164,135.73319876476054,-1.201685221761096,-0.8775596011600497
|
||||
0.8,50.0,6,8,136.4704324290659,136.86568018140107,0.39524775233516607,0.289621528487943
|
||||
0.8,100.0,4,11,137.519864039095,137.4763376137669,-0.04352642532811046,-0.03165100957032396
|
||||
0.9,5.0,5,5,134.77024204025943,136.6651608019597,1.8949187617002679,1.4060364758669837
|
||||
0.9,25.0,9,13,136.7554042236364,136.06108143100832,-0.6943227926280713,-0.507711411164888
|
||||
0.9,50.0,10,12,136.08715955450202,137.07569864767092,0.988539093168896,0.7264014447836223
|
||||
0.9,100.0,11,9,137.57053132642514,137.30115968842037,-0.2693716380047704,-0.19580620602940735
|
||||
1.0,5.0,5,7,136.43177888041947,135.92674388998284,-0.5050349904366271,-0.37017401266847305
|
||||
1.0,25.0,11,9,136.7037183889911,136.22617845471228,-0.47753993427880914,-0.34932475861407586
|
||||
1.0,50.0,11,5,136.93074105866745,137.05826644845806,0.12752538979060546,0.09313130769953819
|
||||
1.0,100.0,8,9,136.4880191421812,137.41913068956546,0.9311115473842619,0.682192879079234
|
||||
|
@@ -1,11 +1,14 @@
|
||||
{
|
||||
"bundle": "engine/studies/results/wandb_sweep_bundles/bundle_20260317_093826",
|
||||
"bundle": "/home/velocitatem/Documents/Projects/PHANTOM/engine/studies/results/wandb_sweep_bundles/bundle_20260317_122818",
|
||||
"focus_cohort": "max_alpha_coverage",
|
||||
"focus_sweep_id": "i88nw811",
|
||||
"focus_run_count": 768,
|
||||
"git_commit": "105b01497600fd31ec07ae49271e680517321cba",
|
||||
"alpha_cells": 11,
|
||||
"alpha_min": 0.0,
|
||||
"alpha_max": 1.0,
|
||||
"mean_revenue_delta_pct": -4.787221975639986,
|
||||
"mean_reward_delta_pct": -4.91730667541704,
|
||||
"mean_revenue_delta_pct": -4.784039478033151,
|
||||
"mean_reward_delta_pct": -4.913967517075595,
|
||||
"zone_summary": [
|
||||
{
|
||||
"zone": "high_alpha_0_7_plus",
|
||||
@@ -18,10 +21,10 @@
|
||||
{
|
||||
"zone": "low_alpha_below_0_7",
|
||||
"alpha_cells": 7,
|
||||
"revenue_delta_pct_mean": -5.201949225367208,
|
||||
"reward_delta_pct_mean": -5.324947138914036,
|
||||
"coi_leakage_delta_mean": -0.0037041938968711296,
|
||||
"volatility_delta_mean": 0.00011102505536893832
|
||||
"revenue_delta_pct_mean": -5.196948157699325,
|
||||
"reward_delta_pct_mean": -5.319699890091765,
|
||||
"coi_leakage_delta_mean": -0.003710447880695786,
|
||||
"volatility_delta_mean": 0.00010197380928049306
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
zone,alpha_cells,revenue_delta_pct_mean,reward_delta_pct_mean,coi_leakage_delta_mean,volatility_delta_mean
|
||||
high_alpha_0_7_plus,4,-4.0614492886173466,-4.2039358642972955,-0.0018236753956396637,0.00026289072427068336
|
||||
low_alpha_below_0_7,7,-5.201949225367208,-5.324947138914036,-0.0037041938968711296,0.00011102505536893832
|
||||
low_alpha_below_0_7,7,-5.196948157699325,-5.319699890091765,-0.003710447880695786,0.00010197380928049306
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"normality": {
|
||||
"test": "jarque_bera",
|
||||
"available": true,
|
||||
"statistic": 362.38850707984324,
|
||||
"p_value": 2.0339278125496517e-79
|
||||
},
|
||||
"heteroskedasticity": {
|
||||
"test": "breusch_pagan",
|
||||
"available": true,
|
||||
"lm_stat": 6.0366025380616275,
|
||||
"df": 1,
|
||||
"p_value": 0.014012224810767138
|
||||
},
|
||||
"influence": {
|
||||
"max_leverage": 0.03769234230180875,
|
||||
"mean_leverage": 0.021052631578947392,
|
||||
"high_leverage_threshold": 0.042105263157894736,
|
||||
"high_leverage_count": 0,
|
||||
"max_cooks_distance": 0.29121755538277183,
|
||||
"high_cooks_threshold": 0.042105263157894736,
|
||||
"high_cooks_count": 6
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
sweep_id,sweep_full_id,run_id,run_name,state,run_url,created_at,runtime,downloaded_files,history_rows,selected_for_clone,download_error,alpha,n_products,eta_ux,lambda_coi,baseline_mode,no_robust,study_mode,eval_revenue_mean,eval_reward_mean,eval_stress_revenue_worst,eval_stress_reward_worst,eval_supra_share_mean,eval_supra_penalty_mean,eval_volatility_mean,eval_upward_volatility_mean,eval_coi_level_mean,eval_coi_leakage_mean,objective_score,mode
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,0yph6ddt,sweep/ppo/sb3/cpu/default/a0.7/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/0yph6ddt,2026-03-15T13:48:47Z,7579.766959963,0,0,0,,0.7,100.0,0.0,0.05,True,True,baseline,285875.15518050164,266287.2051805016,274356.50146499986,255620.24146499988,0.0,0.0,0.0711188680417482,0.0,137.42722406640746,0.1099719716550294,255620.24146499988,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,bjwmxlf4,sweep/ppo/sb3/cpu/default/a0.9/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/bjwmxlf4,2026-03-15T13:48:49Z,7514.003863569,0,0,0,,0.9,100.0,0.0,0.05,True,True,baseline,267194.6114143838,248902.78141438385,258791.60782635584,241079.0878263559,0.0,0.0,0.0706779448814682,0.0,137.4716591479769,0.1060063717489262,241079.0878263559,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,afod7srx,sweep/ppo/sb3/cpu/default/a0/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/afod7srx,2026-03-15T13:48:55Z,8428.923550896,0,0,0,,0.0,100.0,0.0,0.15,True,True,baseline,331626.71399641165,307929.2839964116,301903.22363424243,278909.22363424255,0.0,0.0,0.0699106903089938,0.0,134.44341240328637,0.1239456985672444,278909.22363424255,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,czbwbw4o,sweep/ppo/sb3/cpu/default/a0.3/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/czbwbw4o,2026-03-15T13:48:55Z,8019.834460958,0,0,0,,0.3,100.0,0.0,0.05,True,True,baseline,325062.60932028474,302657.9893202848,313580.73955351143,292103.1195535114,0.0,0.0,0.0700934793925504,0.0,137.30226556155992,0.1156304945350146,292103.1195535114,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,spncr5i5,sweep/ppo/sb3/cpu/default/a0.4/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/spncr5i5,2026-03-15T13:48:57Z,7984.536208498,0,0,0,,0.4,100.0,0.0,0.3,True,True,baseline,313890.156459866,292317.566459866,301905.6061551721,281189.2661551722,0.0,0.0,0.0700585666613017,0.0,137.27393385978286,0.1140225013120235,281189.2661551722,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,9utcbgal,sweep/ppo/sb3/cpu/default/a0.6/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/9utcbgal,2026-03-15T13:48:58Z,7794.573495005,0,0,0,,0.6,100.0,0.0,0.3,True,True,baseline,296881.4938150014,276559.4338150014,282693.0664052287,263321.0864052287,0.0,0.0,0.0689497793839256,0.0,137.65459475595475,0.1116745762120893,263321.0864052287,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,6uhc0zfi,sweep/ppo/sb3/cpu/default/a0.1/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/6uhc0zfi,2026-03-15T13:48:59Z,8739.343652451,5,5000,1,,0.1,100.0,0.0,0.3,True,True,baseline,345607.36851277394,321934.388512774,330271.9018417394,307619.2418417394,0.0,0.0,0.0688978199434404,0.0,137.65927138408344,0.1180576040723697,307619.2418417394,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,mid9h16o,sweep/ppo/sb3/cpu/default/a0.3/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/mid9h16o,2026-03-15T13:48:59Z,7934.709025792,0,0,0,,0.3,100.0,0.0,0.15,True,True,baseline,321120.1030044527,298922.9430044526,312002.2572538445,290604.6972538445,0.0,0.0,0.0725338635316591,0.0,136.9642983472208,0.1152504371251349,290604.6972538445,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,hm8geh95,sweep/ppo/sb3/cpu/default/a0.3/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/hm8geh95,2026-03-15T13:49:01Z,8324.170881475,0,0,0,,0.3,100.0,0.0,0.05,True,True,baseline,321120.1030044527,298922.9430044526,312002.2572538445,290604.6972538445,0.0,0.0,0.0725338635316591,0.0,136.9642983472208,0.1152504371251349,290604.6972538445,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,2k3bx48e,sweep/ppo/sb3/cpu/default/a0.7/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/2k3bx48e,2026-03-15T13:49:03Z,7579.046562713,0,0,0,,0.7,100.0,0.0,0.3,True,True,baseline,288003.5379862045,268208.7279862045,274205.49798255006,255466.81798255,0.0,0.0,0.0732015803628115,0.0,137.25851714050424,0.1065894678006264,255466.81798255,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,mlcllxuf,sweep/ppo/sb3/cpu/default/a0.3/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/mlcllxuf,2026-03-15T15:28:13Z,8048.447950291,0,0,0,,0.3,100.0,0.0,0.05,True,True,baseline,325062.60932028474,302657.9893202848,313580.73955351143,292103.1195535114,0.0,0.0,0.0700934793925504,0.0,137.30226556155992,0.1156304945350146,292103.1195535114,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,gsx5p3xl,sweep/ppo/sb3/cpu/default/a0.7/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/gsx5p3xl,2026-03-15T15:29:00Z,7666.062008427,0,0,0,,0.7,100.0,0.0,0.3,True,True,baseline,286859.8032779717,267231.9932779717,273198.5349293896,254530.3349293896,0.0,0.0,0.0694378534785247,0.0,137.6169536272908,0.1086813731317916,254530.3349293896,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,dh2sidg0,sweep/ppo/sb3/cpu/default/a0.8/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/dh2sidg0,2026-03-15T15:31:51Z,7450.114589126,0,0,0,,0.8,100.0,0.0,0.3,True,True,baseline,277537.1135308166,258574.23353081665,260525.6140973399,242761.4740973399,0.0,0.0,0.0691119185711536,0.0,137.63850710873982,0.1055234893030045,242761.4740973399,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,izb1xfjn,sweep/ppo/sb3/cpu/default/a0.4/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/izb1xfjn,2026-03-15T15:38:35Z,8138.431632101,0,0,0,,0.4,100.0,0.0,0.05,True,True,baseline,313890.156459866,292317.566459866,301905.6061551721,281189.2661551722,0.0,0.0,0.0700585666613017,0.0,137.27393385978286,0.1140225013120235,281189.2661551722,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,h5v0bjkk,sweep/ppo/sb3/cpu/default/a1/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/h5v0bjkk,2026-03-15T15:53:08Z,7430.137394885,0,0,0,,1.0,100.0,0.0,0.05,True,True,baseline,258250.4083985968,240558.37839859675,257579.27605596423,239906.35605596425,0.0,0.0,0.0710781742010645,0.0,137.43891114039735,0.1034797519569495,239906.35605596425,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,oo9x7mtj,sweep/ppo/sb3/cpu/default/a0/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/oo9x7mtj,2026-03-15T17:08:57Z,8434.676111878,0,0,0,,0.0,100.0,0.0,0.15,True,True,baseline,331626.71399641165,307929.2839964116,301903.22363424243,278909.22363424255,0.0,0.0,0.0699106903089938,0.0,134.44341240328637,0.1239456985672444,278909.22363424255,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,2tnqjvsr,sweep/ppo/sb3/cpu/default/a0.2/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/2tnqjvsr,2026-03-15T17:10:41Z,8326.316856098,0,0,0,,0.2,100.0,0.0,0.3,True,True,baseline,333463.32883383776,310606.38883383776,322375.37087837915,300349.6308783791,0.0,0.0,0.0694238399850746,0.0,137.6206723870474,0.1176551945750585,300349.6308783791,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,uwl4b1t4,sweep/ppo/sb3/cpu/default/a0.6/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/uwl4b1t4,2026-03-15T17:11:41Z,7730.138244902,0,0,0,,0.6,100.0,0.0,0.15,True,True,baseline,293934.0132863448,273673.5532863448,278235.2158621181,259045.3158621181,0.0,0.0,0.0702286844227449,0.0,137.02187396075487,0.1108792101893818,259045.3158621181,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,mq08631s,sweep/ppo/sb3/cpu/default/a0.7/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/mq08631s,2026-03-15T17:11:46Z,7830.903683379,0,0,0,,0.7,100.0,0.0,0.3,True,True,baseline,286859.8032779717,267231.9932779717,273198.5349293896,254530.3349293896,0.0,0.0,0.0694378534785247,0.0,137.6169536272908,0.1086813731317916,254530.3349293896,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,oenf81vs,sweep/ppo/sb3/cpu/default/a0.9/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/oenf81vs,2026-03-15T17:14:03Z,7571.420325966,0,0,0,,0.9,100.0,0.0,0.15,True,True,baseline,268129.28805568966,249777.98805568964,259354.03651639624,241657.8165163962,0.0,0.0,0.0692141212557269,0.0,137.56737533812094,0.1028102128114812,241657.8165163962,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,imvig8ea,sweep/ppo/sb3/cpu/default/a0.9/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/imvig8ea,2026-03-15T17:26:17Z,7548.356923917,0,0,0,,0.9,100.0,0.0,0.05,True,True,baseline,269095.26288012683,250709.3028801269,257985.06236888352,240343.2023688835,0.0,0.0,0.0687681637998595,0.0,137.63174822647662,0.1040919495927453,240343.2023688835,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,kc46mwot,sweep/ppo/sb3/cpu/default/a0.9/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/kc46mwot,2026-03-15T17:36:54Z,7402.437478922,0,0,0,,0.9,100.0,0.0,0.3,True,True,baseline,269095.26288012683,250709.3028801269,257985.06236888352,240343.2023688835,0.0,0.0,0.0687681637998595,0.0,137.63174822647662,0.1040919495927453,240343.2023688835,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,6c5g20m0,sweep/ppo/sb3/cpu/default/a0.4/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/6c5g20m0,2026-03-15T17:39:15Z,7987.751960449,0,0,0,,0.4,100.0,0.0,0.05,True,True,baseline,314792.9405088838,293199.96050888376,304000.02795477153,283160.5079547715,0.0,0.0,0.0706474903672308,0.0,137.54347765167836,0.1134114537317883,283160.5079547715,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,zmfirgme,sweep/ppo/sb3/cpu/default/a0.6/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/zmfirgme,2026-03-15T17:39:38Z,7729.43292327,0,0,0,,0.6,100.0,0.0,0.3,True,True,baseline,296881.4938150014,276559.4338150014,282693.0664052287,263321.0864052287,0.0,0.0,0.0689497793839256,0.0,137.65459475595475,0.1116745762120893,263321.0864052287,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,5w978f6n,sweep/ppo/sb3/cpu/default/a0.2/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/5w978f6n,2026-03-15T17:42:23Z,8196.563842857,0,0,0,,0.2,100.0,0.0,0.3,True,True,baseline,328662.28105387173,305848.95105387166,316489.4913151873,294621.8913151873,0.0,0.0,0.0726481757500429,0.0,136.60489081120323,0.115056283050696,294621.8913151873,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,v6yuq532,sweep/ppo/sb3/cpu/default/a0.3/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/v6yuq532,2026-03-15T18:27:32Z,8171.524047551,0,0,0,,0.3,100.0,0.0,0.3,True,True,baseline,325536.3728999571,303203.77289995714,311530.19009115506,290169.93009115505,0.0,0.0,0.0690101249418158,0.0,137.57976469566975,0.115140125484157,290169.93009115505,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,wzs4h708,sweep/ppo/sb3/cpu/default/a1/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/wzs4h708,2026-03-15T18:44:40Z,7213.500579862,0,0,0,,1.0,100.0,0.0,0.3,True,True,baseline,258250.4083985968,240558.37839859675,257579.27605596423,239906.35605596425,0.0,0.0,0.0710781742010645,0.0,137.43891114039735,0.1034797519569495,239906.35605596425,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,drjegsa8,sweep/ppo/sb3/cpu/default/a0.8/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/drjegsa8,2026-03-15T18:53:51Z,7642.750902648,0,0,0,,0.8,100.0,0.0,0.05,True,True,baseline,278042.9708277731,258987.21082777312,265119.53279206343,246979.39279206347,0.0,0.0,0.069699479796535,0.0,137.47635104131075,0.1063946886684759,246979.39279206347,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,np3fvzwt,sweep/ppo/sb3/cpu/default/a0.9/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/np3fvzwt,2026-03-15T18:57:50Z,7300.325366337,0,0,0,,0.9,100.0,0.0,0.3,True,True,baseline,269095.26288012683,250709.3028801269,257985.06236888352,240343.2023688835,0.0,0.0,0.0687681637998595,0.0,137.63174822647662,0.1040919495927453,240343.2023688835,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,kk0sqa97,sweep/ppo/sb3/cpu/default/a0.1/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/kk0sqa97,2026-03-15T19:06:17Z,8525.177181009,0,0,0,,0.1,100.0,0.0,0.3,True,True,baseline,341404.1205957663,317885.0305957663,329505.50925893825,306817.3492589383,0.0,0.0,0.0685274095002656,0.0,137.33021724658855,0.1206998447923596,306817.3492589383,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,i0rpx1kf,sweep/ppo/sb3/cpu/default/a0.2/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/i0rpx1kf,2026-03-15T19:20:36Z,8356.73493734,0,0,0,,0.2,100.0,0.0,0.05,True,True,baseline,333463.32883383776,310606.38883383776,322375.37087837915,300349.6308783791,0.0,0.0,0.0694238399850746,0.0,137.6206723870474,0.1176551945750585,300349.6308783791,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,lqmaq5g2,sweep/ppo/sb3/cpu/default/a1/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/lqmaq5g2,2026-03-15T20:02:28Z,7470.274064026,0,0,0,,1.0,100.0,0.0,0.05,True,True,baseline,246584.29279154172,229303.12279154177,244564.78814724492,227386.888147245,0.0,0.0,0.0692074374069363,0.0,135.2844805658817,0.1093837602765936,227386.888147245,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,2umearxm,sweep/ppo/sb3/cpu/default/a0.5/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/2umearxm,2026-03-15T20:09:56Z,7829.406313163,0,0,0,,0.5,100.0,0.0,0.3,True,True,baseline,303325.5596877454,282520.29968774534,291965.65710567136,271937.69710567134,0.0,0.0,0.0686525035124021,0.0,137.57073544790862,0.1132342695408356,271937.69710567134,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,k7pirqxy,sweep/ppo/sb3/cpu/default/a1/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/k7pirqxy,2026-03-15T20:33:53Z,7216.626889631,0,0,0,,1.0,100.0,0.0,0.15,True,True,baseline,254537.24517731377,236935.99517731369,254471.2696855663,236912.16968556636,0.0,0.0,0.0703905833083271,0.0,136.6143424312229,0.1038838810036006,236912.16968556636,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,algnjce4,sweep/ppo/sb3/cpu/default/a0.6/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/algnjce4,2026-03-15T20:54:24Z,7739.30650029,0,0,0,,0.6,100.0,0.0,0.05,True,True,baseline,296881.4938150014,276559.4338150014,282693.0664052287,263321.0864052287,0.0,0.0,0.0689497793839256,0.0,137.65459475595475,0.1116745762120893,263321.0864052287,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,vqe2dmcq,sweep/ppo/sb3/cpu/default/a0.4/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/vqe2dmcq,2026-03-15T21:08:22Z,7815.774646473,0,0,0,,0.4,100.0,0.0,0.05,True,True,baseline,316543.04043212667,294899.01043212664,299980.59649797506,279386.7564979751,0.0,0.0,0.067603468946279,0.0,137.7846896269947,0.1128739206843639,279386.7564979751,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,0xlvpawh,sweep/ppo/sb3/cpu/default/a0.3/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/0xlvpawh,2026-03-15T21:16:04Z,7997.68392245,0,0,0,,0.3,100.0,0.0,0.15,True,True,baseline,325062.60932028474,302657.9893202848,313580.73955351143,292103.1195535114,0.0,0.0,0.0700934793925504,0.0,137.30226556155992,0.1156304945350146,292103.1195535114,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,bofuxayn,sweep/ppo/sb3/cpu/default/a0.7/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/bofuxayn,2026-03-15T21:18:05Z,7486.102336723,0,0,0,,0.7,100.0,0.0,0.05,True,True,baseline,285875.15518050164,266287.2051805016,274356.50146499986,255620.24146499988,0.0,0.0,0.0711188680417482,0.0,137.42722406640746,0.1099719716550294,255620.24146499988,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,rujnezt7,sweep/ppo/sb3/cpu/default/a0.5/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/rujnezt7,2026-03-15T21:20:23Z,7936.01356938,0,0,0,,0.5,100.0,0.0,0.15,True,True,baseline,305342.590984541,284402.02098454104,287794.11179162114,267934.8717916211,0.0,0.0,0.0698329564541014,0.0,137.34875112178105,0.1110975441706762,267934.8717916211,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,f9e6wtv0,sweep/ppo/sb3/cpu/default/a0.7/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/f9e6wtv0,2026-03-15T22:07:04Z,8030.825365422,0,0,0,,0.7,100.0,0.0,0.05,True,True,baseline,288003.5379862045,268208.7279862045,274205.49798255006,255466.81798255,0.0,0.0,0.0732015803628115,0.0,137.25851714050424,0.1065894678006264,255466.81798255,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,r8hsz3ko,sweep/ppo/sb3/cpu/default/a0.7/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/r8hsz3ko,2026-03-15T22:13:06Z,7691.998775531,0,0,0,,0.7,100.0,0.0,0.3,True,True,baseline,286859.8032779717,267231.9932779717,273198.5349293896,254530.3349293896,0.0,0.0,0.0694378534785247,0.0,137.6169536272908,0.1086813731317916,254530.3349293896,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,yukg46hv,sweep/ppo/sb3/cpu/default/a1/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/yukg46hv,2026-03-15T23:03:27Z,7094.861108483,0,0,0,,1.0,100.0,0.0,0.15,True,True,baseline,254537.24517731377,236935.99517731369,254471.2696855663,236912.16968556636,0.0,0.0,0.0703905833083271,0.0,136.6143424312229,0.1038838810036006,236912.16968556636,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,e5tciezz,sweep/ppo/sb3/cpu/default/a0.7/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/e5tciezz,2026-03-16T00:16:08Z,7569.145925588,0,0,0,,0.7,100.0,0.0,0.05,True,True,baseline,285875.15518050164,266287.2051805016,274356.50146499986,255620.24146499988,0.0,0.0,0.0711188680417482,0.0,137.42722406640746,0.1099719716550294,255620.24146499988,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,1rop5sf9,sweep/ppo/sb3/cpu/default/a0.3/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/1rop5sf9,2026-03-16T00:21:00Z,8354.617713686,0,0,0,,0.3,100.0,0.0,0.05,True,True,baseline,321120.1030044527,298922.9430044526,312002.2572538445,290604.6972538445,0.0,0.0,0.0725338635316591,0.0,136.9642983472208,0.1152504371251349,290604.6972538445,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,7muxpseb,sweep/ppo/sb3/cpu/default/a0.2/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/7muxpseb,2026-03-16T00:21:21Z,8514.602541985,0,0,0,,0.2,100.0,0.0,0.05,True,True,baseline,333463.32883383776,310606.38883383776,322375.37087837915,300349.6308783791,0.0,0.0,0.0694238399850746,0.0,137.6206723870474,0.1176551945750585,300349.6308783791,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,304dyypp,sweep/ppo/sb3/cpu/default/a0.4/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/304dyypp,2026-03-16T00:37:04Z,7949.736292204,0,0,0,,0.4,100.0,0.0,0.3,True,True,baseline,313890.156459866,292317.566459866,301905.6061551721,281189.2661551722,0.0,0.0,0.0700585666613017,0.0,137.27393385978286,0.1140225013120235,281189.2661551722,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,zbw7nmeo,sweep/ppo/sb3/cpu/default/a0.1/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/zbw7nmeo,2026-03-16T00:53:02Z,8423.598177489,0,0,0,,0.1,100.0,0.0,0.05,True,True,baseline,340941.7898046945,317438.6698046944,328185.5337341634,305593.15373416344,0.0,0.0,0.0709483560344898,0.0,137.21682561970587,0.1186714838821206,305593.15373416344,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,oxu7rm37,sweep/ppo/sb3/cpu/default/a0.9/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/oxu7rm37,2026-03-16T00:53:31Z,7464.830361968,0,0,0,,0.9,100.0,0.0,0.3,True,True,baseline,268129.28805568966,249777.98805568964,259354.03651639624,241657.8165163962,0.0,0.0,0.0692141212557269,0.0,137.56737533812094,0.1028102128114812,241657.8165163962,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,m78p26vk,sweep/ppo/sb3/cpu/default/a0/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/m78p26vk,2026-03-16T00:56:58Z,8717.289024041,5,1004,1,,0.0,100.0,0.0,0.15,True,True,baseline,348861.1454509751,324713.0754509751,335967.6160126648,312660.3160126648,0.0,0.0,0.0674835742466741,0.0,136.8813175598437,0.118985751213389,312660.3160126648,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,65zzmszh,sweep/ppo/sb3/cpu/default/a1/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/65zzmszh,2026-03-16T01:14:03Z,7326.553384609,0,0,0,,1.0,100.0,0.0,0.3,True,True,baseline,246584.29279154172,229303.12279154177,244564.78814724492,227386.888147245,0.0,0.0,0.0692074374069363,0.0,135.2844805658817,0.1093837602765936,227386.888147245,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,47xraqt6,sweep/ppo/sb3/cpu/default/a0.9/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/47xraqt6,2026-03-16T01:22:01Z,7299.814264453,0,0,0,,0.9,100.0,0.0,0.3,True,True,baseline,269095.26288012683,250709.3028801269,257985.06236888352,240343.2023688835,0.0,0.0,0.0687681637998595,0.0,137.63174822647662,0.1040919495927453,240343.2023688835,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,mibyt0bf,sweep/ppo/sb3/cpu/default/a0.9/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/mibyt0bf,2026-03-16T01:34:44Z,7541.153639959,0,0,0,,0.9,100.0,0.0,0.3,True,True,baseline,267194.6114143838,248902.78141438385,258791.60782635584,241079.0878263559,0.0,0.0,0.0706779448814682,0.0,137.4716591479769,0.1060063717489262,241079.0878263559,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,8ww25eu1,sweep/ppo/sb3/cpu/default/a0.4/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/8ww25eu1,2026-03-16T01:45:51Z,8003.812511886,0,0,0,,0.4,100.0,0.0,0.3,True,True,baseline,313890.156459866,292317.566459866,301905.6061551721,281189.2661551722,0.0,0.0,0.0700585666613017,0.0,137.27393385978286,0.1140225013120235,281189.2661551722,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,cxdz0iyj,sweep/ppo/sb3/cpu/default/a0.6/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/cxdz0iyj,2026-03-16T01:50:19Z,7623.493600288,0,0,0,,0.6,100.0,0.0,0.3,True,True,baseline,293934.0132863448,273673.5532863448,278235.2158621181,259045.3158621181,0.0,0.0,0.0702286844227449,0.0,137.02187396075487,0.1108792101893818,259045.3158621181,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,1aeqr4sw,sweep/ppo/sb3/cpu/default/a1/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/1aeqr4sw,2026-03-16T01:58:10Z,7156.375097998,0,0,0,,1.0,100.0,0.0,0.3,True,True,baseline,254537.24517731377,236935.99517731369,254471.2696855663,236912.16968556636,0.0,0.0,0.0703905833083271,0.0,136.6143424312229,0.1038838810036006,236912.16968556636,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,7sgqchvk,sweep/ppo/sb3/cpu/default/a0.9/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/7sgqchvk,2026-03-16T02:09:14Z,7268.202978965,0,0,0,,0.9,100.0,0.0,0.15,True,True,baseline,267194.6114143838,248902.78141438385,258791.60782635584,241079.0878263559,0.0,0.0,0.0706779448814682,0.0,137.4716591479769,0.1060063717489262,241079.0878263559,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,3s777ena,sweep/ppo/sb3/cpu/default/a0.5/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/3s777ena,2026-03-16T02:14:54Z,7762.769931002,0,0,0,,0.5,100.0,0.0,0.05,True,True,baseline,303325.5596877454,282520.29968774534,291965.65710567136,271937.69710567134,0.0,0.0,0.0686525035124021,0.0,137.57073544790862,0.1132342695408356,271937.69710567134,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,oxsvuh5p,sweep/ppo/sb3/cpu/default/a0.1/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/oxsvuh5p,2026-03-16T02:27:01Z,8529.692612353,0,0,0,,0.1,100.0,0.0,0.15,True,True,baseline,340941.7898046945,317438.6698046944,328185.5337341634,305593.15373416344,0.0,0.0,0.0709483560344898,0.0,137.21682561970587,0.1186714838821206,305593.15373416344,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,4unnwl9l,sweep/ppo/sb3/cpu/default/a0.7/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/4unnwl9l,2026-03-16T02:34:01Z,7780.065361146,0,0,0,,0.7,100.0,0.0,0.15,True,True,baseline,286859.8032779717,267231.9932779717,273198.5349293896,254530.3349293896,0.0,0.0,0.0694378534785247,0.0,137.6169536272908,0.1086813731317916,254530.3349293896,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,qlfu6ts4,sweep/ppo/sb3/cpu/default/a0.1/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/qlfu6ts4,2026-03-16T02:46:52Z,8357.276406226,0,0,0,,0.1,100.0,0.0,0.3,True,True,baseline,340941.7898046945,317438.6698046944,328185.5337341634,305593.15373416344,0.0,0.0,0.0709483560344898,0.0,137.21682561970587,0.1186714838821206,305593.15373416344,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,ya2bb56z,sweep/ppo/sb3/cpu/default/a1/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/ya2bb56z,2026-03-16T03:04:37Z,7161.126998896,0,0,0,,1.0,100.0,0.0,0.15,True,True,baseline,254537.24517731377,236935.99517731369,254471.2696855663,236912.16968556636,0.0,0.0,0.0703905833083271,0.0,136.6143424312229,0.1038838810036006,236912.16968556636,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,9hrjmcaf,sweep/ppo/sb3/cpu/default/a0.1/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/9hrjmcaf,2026-03-16T03:13:29Z,8543.819880598,5,1004,1,,0.1,100.0,0.0,0.15,True,True,baseline,345607.36851277394,321934.388512774,330271.9018417394,307619.2418417394,0.0,0.0,0.0688978199434404,0.0,137.65927138408344,0.1180576040723697,307619.2418417394,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,bdz7jpg9,sweep/ppo/sb3/cpu/default/a0.4/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/bdz7jpg9,2026-03-16T03:19:29Z,8156.512730959,0,0,0,,0.4,100.0,0.0,0.15,True,True,baseline,313890.156459866,292317.566459866,301905.6061551721,281189.2661551722,0.0,0.0,0.0700585666613017,0.0,137.27393385978286,0.1140225013120235,281189.2661551722,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,4e8bw9fr,sweep/ppo/sb3/cpu/default/a0.4/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/4e8bw9fr,2026-03-16T03:23:44Z,7900.988162577,0,0,0,,0.4,100.0,0.0,0.3,True,True,baseline,313890.156459866,292317.566459866,301905.6061551721,281189.2661551722,0.0,0.0,0.0700585666613017,0.0,137.27393385978286,0.1140225013120235,281189.2661551722,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,rudposqg,sweep/ppo/sb3/cpu/default/a0.8/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/rudposqg,2026-03-16T04:16:36Z,7803.944972672,0,0,0,,0.8,100.0,0.0,0.15,True,True,baseline,277186.5585556976,258169.5585556976,260819.58418764165,242908.9641876417,0.0,0.0,0.0684627361221973,0.0,137.3260908975896,0.1077409453905398,242908.9641876417,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,r24xwwl9,sweep/ppo/sb3/cpu/default/a0.1/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/r24xwwl9,2026-03-16T04:43:43Z,8571.635566955,0,0,0,,0.1,100.0,0.0,0.15,True,True,baseline,340941.7898046945,317438.6698046944,328185.5337341634,305593.15373416344,0.0,0.0,0.0709483560344898,0.0,137.21682561970587,0.1186714838821206,305593.15373416344,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,34c0wzgt,sweep/ppo/sb3/cpu/default/a0.5/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/34c0wzgt,2026-03-16T04:43:54Z,7912.776898111,0,0,0,,0.5,100.0,0.0,0.05,True,True,baseline,306631.1127310434,285624.6727310434,292140.0218133485,272205.32181334845,0.0,0.0,0.0706121906603894,0.0,137.48236407441985,0.112886126809283,272205.32181334845,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,7bvonhab,sweep/ppo/sb3/cpu/default/a0.2/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/7bvonhab,2026-03-16T04:59:24Z,8276.510250338,0,0,0,,0.2,100.0,0.0,0.15,True,True,baseline,333463.32883383776,310606.38883383776,322375.37087837915,300349.6308783791,0.0,0.0,0.0694238399850746,0.0,137.6206723870474,0.1176551945750585,300349.6308783791,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,4f7j1z4p,sweep/ppo/sb3/cpu/default/a0/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/4f7j1z4p,2026-03-16T05:37:06Z,8672.519975981,5,1004,1,,0.0,100.0,0.0,0.3,True,True,baseline,352771.72255003714,328513.3625500371,337718.8770159761,314393.4970159762,0.0,0.0,0.0709252720738168,0.0,137.49769422651883,0.1192149910017191,314393.4970159762,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,c33cyjv9,sweep/ppo/sb3/cpu/default/a0.4/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/c33cyjv9,2026-03-16T05:38:08Z,8164.154912737,0,0,0,,0.4,100.0,0.0,0.15,True,True,baseline,314792.9405088838,293199.96050888376,304000.02795477153,283160.5079547715,0.0,0.0,0.0706474903672308,0.0,137.54347765167836,0.1134114537317883,283160.5079547715,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,i0pylqm1,sweep/ppo/sb3/cpu/default/a0.6/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/i0pylqm1,2026-03-16T05:54:46Z,7692.357589996,0,0,0,,0.6,100.0,0.0,0.15,True,True,baseline,293934.0132863448,273673.5532863448,278235.2158621181,259045.3158621181,0.0,0.0,0.0702286844227449,0.0,137.02187396075487,0.1108792101893818,259045.3158621181,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,p1lrhc1t,sweep/ppo/sb3/cpu/default/a0.5/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/p1lrhc1t,2026-03-16T06:06:24Z,7906.656203638,0,0,0,,0.5,100.0,0.0,0.15,True,True,baseline,304711.516143744,283789.716143744,290536.18598250934,270609.3259825093,0.0,0.0,0.0700712626186499,0.0,137.43043602946972,0.1112796769387625,270609.3259825093,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,lkhtnobk,sweep/ppo/sb3/cpu/default/a0.9/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/lkhtnobk,2026-03-16T06:25:11Z,7304.77470818,0,0,0,,0.9,100.0,0.0,0.3,True,True,baseline,269095.26288012683,250709.3028801269,257985.06236888352,240343.2023688835,0.0,0.0,0.0687681637998595,0.0,137.63174822647662,0.1040919495927453,240343.2023688835,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,dvf0av6p,sweep/ppo/sb3/cpu/default/a0/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/dvf0av6p,2026-03-16T06:34:22Z,8568.236301103,0,0,0,,0.0,100.0,0.0,0.3,True,True,baseline,331626.71399641165,307929.2839964116,301903.22363424243,278909.22363424255,0.0,0.0,0.0699106903089938,0.0,134.44341240328637,0.1239456985672444,278909.22363424255,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,k6dz4he1,sweep/ppo/sb3/cpu/default/a0/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/k6dz4he1,2026-03-16T06:38:33Z,8384.405275426,0,0,0,,0.0,100.0,0.0,0.05,True,True,baseline,331626.71399641165,307929.2839964116,301903.22363424243,278909.22363424255,0.0,0.0,0.0699106903089938,0.0,134.44341240328637,0.1239456985672444,278909.22363424255,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,3afj9zm5,sweep/ppo/sb3/cpu/default/a0.4/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/3afj9zm5,2026-03-16T06:51:33Z,7947.433015786,0,0,0,,0.4,100.0,0.0,0.3,True,True,baseline,313890.156459866,292317.566459866,301905.6061551721,281189.2661551722,0.0,0.0,0.0700585666613017,0.0,137.27393385978286,0.1140225013120235,281189.2661551722,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,lvlojvjv,sweep/ppo/sb3/cpu/default/a0.5/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/lvlojvjv,2026-03-16T07:17:09Z,8072.460782252,0,0,0,,0.5,100.0,0.0,0.05,True,True,baseline,305342.590984541,284402.02098454104,287794.11179162114,267934.8717916211,0.0,0.0,0.0698329564541014,0.0,137.34875112178105,0.1110975441706762,267934.8717916211,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,e6xtq7h5,sweep/ppo/sb3/cpu/default/a0.5/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/e6xtq7h5,2026-03-16T07:20:29Z,8062.476629606,0,0,0,,0.5,100.0,0.0,0.05,True,True,baseline,306631.1127310434,285624.6727310434,292140.0218133485,272205.32181334845,0.0,0.0,0.0706121906603894,0.0,137.48236407441985,0.112886126809283,272205.32181334845,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,6yrs8xci,sweep/ppo/sb3/cpu/default/a0.6/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/6yrs8xci,2026-03-16T07:50:01Z,7609.609823102,0,0,0,,0.6,100.0,0.0,0.15,True,True,baseline,293934.0132863448,273673.5532863448,278235.2158621181,259045.3158621181,0.0,0.0,0.0702286844227449,0.0,137.02187396075487,0.1108792101893818,259045.3158621181,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,16l3qjpm,sweep/ppo/sb3/cpu/default/a0/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/16l3qjpm,2026-03-16T07:50:41Z,8443.503878801,5,1004,1,,0.0,100.0,0.0,0.15,True,True,baseline,348861.1454509751,324713.0754509751,335967.6160126648,312660.3160126648,0.0,0.0,0.0674835742466741,0.0,136.8813175598437,0.118985751213389,312660.3160126648,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,rg98ht1b,sweep/ppo/sb3/cpu/default/a0/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/rg98ht1b,2026-03-16T07:55:36Z,8843.938343818,5,1004,1,,0.0,100.0,0.0,0.05,True,True,baseline,348861.1454509751,324713.0754509751,335967.6160126648,312660.3160126648,0.0,0.0,0.0674835742466741,0.0,136.8813175598437,0.118985751213389,312660.3160126648,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,mxd3i6wr,sweep/ppo/sb3/cpu/default/a0.2/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/mxd3i6wr,2026-03-16T07:58:03Z,8393.28184472,0,0,0,,0.2,100.0,0.0,0.15,True,True,baseline,333463.32883383776,310606.38883383776,322375.37087837915,300349.6308783791,0.0,0.0,0.0694238399850746,0.0,137.6206723870474,0.1176551945750585,300349.6308783791,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,0xvyhpg2,sweep/ppo/sb3/cpu/default/a0.9/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/0xvyhpg2,2026-03-16T08:01:43Z,7441.092473369,0,0,0,,0.9,100.0,0.0,0.05,True,True,baseline,268129.28805568966,249777.98805568964,259354.03651639624,241657.8165163962,0.0,0.0,0.0692141212557269,0.0,137.56737533812094,0.1028102128114812,241657.8165163962,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,eull6lat,sweep/ppo/sb3/cpu/default/a0.2/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/eull6lat,2026-03-16T08:03:08Z,8338.76018915,0,0,0,,0.2,100.0,0.0,0.05,True,True,baseline,333463.32883383776,310606.38883383776,322375.37087837915,300349.6308783791,0.0,0.0,0.0694238399850746,0.0,137.6206723870474,0.1176551945750585,300349.6308783791,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,5zekml75,sweep/ppo/sb3/cpu/default/a0.8/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/5zekml75,2026-03-16T08:06:29Z,7265.4990034,0,0,0,,0.8,100.0,0.0,0.15,True,True,baseline,277537.1135308166,258574.23353081665,260525.6140973399,242761.4740973399,0.0,0.0,0.0691119185711536,0.0,137.63850710873982,0.1055234893030045,242761.4740973399,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,fed0y4px,sweep/ppo/sb3/cpu/default/a0.7/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/fed0y4px,2026-03-16T08:13:55Z,7800.555020283,0,0,0,,0.7,100.0,0.0,0.05,True,True,baseline,286859.8032779717,267231.9932779717,273198.5349293896,254530.3349293896,0.0,0.0,0.0694378534785247,0.0,137.6169536272908,0.1086813731317916,254530.3349293896,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,byifn20j,sweep/ppo/sb3/cpu/default/a0.4/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/byifn20j,2026-03-16T08:20:55Z,8108.199462596,0,0,0,,0.4,100.0,0.0,0.3,True,True,baseline,316543.04043212667,294899.01043212664,299980.59649797506,279386.7564979751,0.0,0.0,0.067603468946279,0.0,137.7846896269947,0.1128739206843639,279386.7564979751,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,35rb8529,sweep/ppo/sb3/cpu/default/a0.5/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/35rb8529,2026-03-16T08:24:52Z,7749.649896228,0,0,0,,0.5,100.0,0.0,0.05,True,True,baseline,304711.516143744,283789.716143744,290536.18598250934,270609.3259825093,0.0,0.0,0.0700712626186499,0.0,137.43043602946972,0.1112796769387625,270609.3259825093,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,foinu2r1,sweep/ppo/sb3/cpu/default/a0.5/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/foinu2r1,2026-03-16T08:51:50Z,7924.351691656,0,0,0,,0.5,100.0,0.0,0.05,True,True,baseline,306631.1127310434,285624.6727310434,292140.0218133485,272205.32181334845,0.0,0.0,0.0706121906603894,0.0,137.48236407441985,0.112886126809283,272205.32181334845,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,nsg7m2ud,sweep/ppo/sb3/cpu/default/a0.5/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/nsg7m2ud,2026-03-16T09:06:10Z,7732.794663489,0,0,0,,0.5,100.0,0.0,0.3,True,True,baseline,303325.5596877454,282520.29968774534,291965.65710567136,271937.69710567134,0.0,0.0,0.0686525035124021,0.0,137.57073544790862,0.1132342695408356,271937.69710567134,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,gpririem,sweep/ppo/sb3/cpu/default/a0.2/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/gpririem,2026-03-16T09:20:57Z,8532.119121611,0,0,0,,0.2,100.0,0.0,0.3,True,True,baseline,333463.32883383776,310606.38883383776,322375.37087837915,300349.6308783791,0.0,0.0,0.0694238399850746,0.0,137.6206723870474,0.1176551945750585,300349.6308783791,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,9bmbalnk,sweep/ppo/sb3/cpu/default/a0.7/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/9bmbalnk,2026-03-16T10:05:49Z,7576.93090345,0,0,0,,0.7,100.0,0.0,0.15,True,True,baseline,285875.15518050164,266287.2051805016,274356.50146499986,255620.24146499988,0.0,0.0,0.0711188680417482,0.0,137.42722406640746,0.1099719716550294,255620.24146499988,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,9ma76sch,sweep/ppo/sb3/cpu/default/a0.1/baseline/s1337,finished,https://wandb.ai/lusiana/capstone_tpu/runs/9ma76sch,2026-03-16T10:23:59Z,8544.8427845,0,0,0,,0.1,100.0,0.0,0.3,True,True,baseline,341404.1205957663,317885.0305957663,329505.50925893825,306817.3492589383,0.0,0.0,0.0685274095002656,0.0,137.33021724658855,0.1206998447923596,306817.3492589383,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,cvrztiyb,sweep/ppo/sb3/cpu/default/a0.2/baseline/s42,finished,https://wandb.ai/lusiana/capstone_tpu/runs/cvrztiyb,2026-03-16T10:27:26Z,8353.396268583,0,0,0,,0.2,100.0,0.0,0.3,True,True,baseline,333463.32883383776,310606.38883383776,322375.37087837915,300349.6308783791,0.0,0.0,0.0694238399850746,0.0,137.6206723870474,0.1176551945750585,300349.6308783791,baseline
|
||||
i88nw811,lusiana/capstone_tpu/i88nw811,7z9spcc6,sweep/ppo/sb3/cpu/default/a0/baseline/s7777,finished,https://wandb.ai/lusiana/capstone_tpu/runs/7z9spcc6,2026-03-16T10:29:46Z,8444.449882423,5,1004,1,,0.0,100.0,0.0,0.3,True,True,baseline,348861.1454509751,324713.0754509751,335967.6160126648,312660.3160126648,0.0,0.0,0.0674835742466741,0.0,136.8813175598437,0.118985751213389,312660.3160126648,baseline
|
||||
|
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"n": 95,
|
||||
"k": 2,
|
||||
"dof": 93,
|
||||
"df_t": 93,
|
||||
"cov_type": "hc1",
|
||||
"clusters": null,
|
||||
"r2": 0.9759651432807543,
|
||||
"adj_r2": 0.9757067039611925,
|
||||
"sse": 1872600419.7223544,
|
||||
"coefficients": [
|
||||
{
|
||||
"name": "intercept",
|
||||
"coef": 348823.4131652292,
|
||||
"std_error": 1383.3660823209932,
|
||||
"t_stat": 252.15553397115096,
|
||||
"p_value": 0.0,
|
||||
"ci95_low": 346076.3222890517,
|
||||
"ci95_high": 351570.5040414067
|
||||
},
|
||||
{
|
||||
"name": "alpha",
|
||||
"coef": -90140.52744561416,
|
||||
"std_error": 2185.134882447838,
|
||||
"t_stat": -41.25169945785529,
|
||||
"p_value": 0.0,
|
||||
"ci95_low": -94479.77225976942,
|
||||
"ci95_high": -85801.2826314589
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
sweep_id,n,alpha_coef,alpha_std_error,alpha_t_stat,alpha_p_value,alpha_ci95_low,alpha_ci95_high,r2
|
||||
i88nw811,95,-90140.52744561416,2185.134882447838,-41.25169945785529,0.0,-94479.77225976942,-85801.2826314589,0.9759651432807543
|
||||
|
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"bundle_dir": "/home/velocitatem/Documents/Projects/PHANTOM/engine/studies/results/wandb_sweep_bundles/bundle_20260317_122818",
|
||||
"git_commit": "e62e842faad79b143f5555d187075e85c8926363",
|
||||
"cohort_name": "original_n95_baseline_n100",
|
||||
"filters": {
|
||||
"sweep_id": [
|
||||
"i88nw811"
|
||||
],
|
||||
"mode": "baseline",
|
||||
"n_products": 100.0,
|
||||
"eta_ux": 0.0,
|
||||
"lambda_coi": null,
|
||||
"alpha_min": 0.0,
|
||||
"alpha_max": 1.0
|
||||
},
|
||||
"n_rows": 95,
|
||||
"n_sweeps": 1,
|
||||
"alpha_unique": [
|
||||
0.0,
|
||||
0.1,
|
||||
0.2,
|
||||
0.3,
|
||||
0.4,
|
||||
0.5,
|
||||
0.6,
|
||||
0.7,
|
||||
0.8,
|
||||
0.9,
|
||||
1.0
|
||||
],
|
||||
"rows_by_sweep": {
|
||||
"i88nw811": 95
|
||||
},
|
||||
"rows_by_mode": {
|
||||
"baseline": 95
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"n": 95,
|
||||
"k": 2,
|
||||
"dof": 93,
|
||||
"df_t": 93,
|
||||
"cov_type": "iid",
|
||||
"clusters": null,
|
||||
"r2": 0.9759651432807543,
|
||||
"adj_r2": 0.9757067039611925,
|
||||
"sse": 1872600419.7223544,
|
||||
"coefficients": [
|
||||
{
|
||||
"name": "intercept",
|
||||
"coef": 348823.4131652292,
|
||||
"std_error": 860.7176431608721,
|
||||
"t_stat": 405.2704344298337,
|
||||
"p_value": 0.0,
|
||||
"ci95_low": 347114.1985078009,
|
||||
"ci95_high": 350532.6278226575
|
||||
},
|
||||
{
|
||||
"name": "alpha",
|
||||
"coef": -90140.52744561416,
|
||||
"std_error": 1466.838282353916,
|
||||
"t_stat": -61.452259959401054,
|
||||
"p_value": 0.0,
|
||||
"ci95_low": -93053.37756806448,
|
||||
"ci95_high": -87227.67732316385
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
\includegraphics[width=0.98\linewidth]{chapters/figures/results/generated/final/plots/final_focus_coi_by_alpha.pdf}
|
||||
@@ -0,0 +1 @@
|
||||
\includegraphics[width=0.98\linewidth]{chapters/figures/results/generated/final/plots/final_focus_coi_preservation_grid.pdf}
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
import matplotlib
|
||||
@@ -37,6 +38,20 @@ def _default_plot_dir(output_dir: Path) -> Path:
|
||||
return output_dir / "plots"
|
||||
|
||||
|
||||
def _git_commit() -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"],
|
||||
check=True,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
cwd=_project_root(),
|
||||
)
|
||||
except Exception:
|
||||
return "unknown"
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def _truthy(value: Any) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
@@ -195,6 +210,48 @@ def _zone_summary(alpha_deltas: pd.DataFrame) -> pd.DataFrame:
|
||||
)
|
||||
|
||||
|
||||
def _alpha_product_coi_preservation(runs: pd.DataFrame) -> pd.DataFrame:
|
||||
grouped = (
|
||||
runs.groupby(["alpha", "n_products", "mode"], as_index=False)
|
||||
.agg(
|
||||
runs=("run_id", "size"),
|
||||
coi_level_mean=("eval_coi_level_mean", "mean"),
|
||||
)
|
||||
.sort_values(["alpha", "n_products", "mode"])
|
||||
.reset_index(drop=True)
|
||||
)
|
||||
|
||||
rows: list[dict[str, float | int]] = []
|
||||
for (alpha, n_products), group in grouped.groupby(
|
||||
["alpha", "n_products"], sort=True
|
||||
):
|
||||
defended = group[group["mode"] == "defended"]
|
||||
baseline = group[group["mode"] == "baseline"]
|
||||
if defended.empty or baseline.empty:
|
||||
continue
|
||||
|
||||
d_coi = float(defended["coi_level_mean"].iloc[0])
|
||||
b_coi = float(baseline["coi_level_mean"].iloc[0])
|
||||
rows.append(
|
||||
{
|
||||
"alpha": float(alpha),
|
||||
"n_products": float(n_products),
|
||||
"baseline_runs": int(baseline["runs"].iloc[0]),
|
||||
"defended_runs": int(defended["runs"].iloc[0]),
|
||||
"baseline_coi_level_mean": b_coi,
|
||||
"defended_coi_level_mean": d_coi,
|
||||
"coi_preserved": d_coi - b_coi,
|
||||
"coi_preserved_pct": 0.0
|
||||
if b_coi == 0.0
|
||||
else 100.0 * (d_coi - b_coi) / b_coi,
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
pd.DataFrame(rows).sort_values(["alpha", "n_products"]).reset_index(drop=True)
|
||||
)
|
||||
|
||||
|
||||
def _save_plot(fig: plt.Figure, path: Path) -> Path:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
fig.savefig(path, bbox_inches="tight")
|
||||
@@ -202,6 +259,61 @@ def _save_plot(fig: plt.Figure, path: Path) -> Path:
|
||||
return path
|
||||
|
||||
|
||||
def _smoothed_curve(
|
||||
x: np.ndarray,
|
||||
y: np.ndarray,
|
||||
*,
|
||||
window: int = 5,
|
||||
points: int = 320,
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
x_values = np.asarray(x, dtype=float)
|
||||
y_values = np.asarray(y, dtype=float)
|
||||
mask = np.isfinite(x_values) & np.isfinite(y_values)
|
||||
x_values = x_values[mask]
|
||||
y_values = y_values[mask]
|
||||
if x_values.size == 0:
|
||||
return x_values, y_values
|
||||
|
||||
order = np.argsort(x_values)
|
||||
x_values = x_values[order]
|
||||
y_values = y_values[order]
|
||||
|
||||
unique_x = np.unique(x_values)
|
||||
if unique_x.size != x_values.size:
|
||||
dedup = (
|
||||
pd.DataFrame({"x": x_values, "y": y_values})
|
||||
.groupby("x", as_index=False)
|
||||
.agg(y=("y", "mean"))
|
||||
.sort_values("x")
|
||||
)
|
||||
x_values = dedup["x"].to_numpy(dtype=float)
|
||||
y_values = dedup["y"].to_numpy(dtype=float)
|
||||
|
||||
if x_values.size < 3:
|
||||
return x_values, y_values
|
||||
|
||||
win = int(max(3, window))
|
||||
if win % 2 == 0:
|
||||
win += 1
|
||||
if win > x_values.size:
|
||||
win = x_values.size if x_values.size % 2 == 1 else x_values.size - 1
|
||||
if win < 3:
|
||||
return x_values, y_values
|
||||
|
||||
half = win // 2
|
||||
offsets = np.arange(-half, half + 1, dtype=float)
|
||||
sigma = max(win / 3.0, 1.0)
|
||||
kernel = np.exp(-0.5 * (offsets / sigma) ** 2)
|
||||
kernel = kernel / np.sum(kernel)
|
||||
y_padded = np.pad(y_values, (half, half), mode="edge")
|
||||
y_smooth = np.convolve(y_padded, kernel, mode="valid")
|
||||
|
||||
n_points = max(int(points), x_values.size)
|
||||
x_dense = np.linspace(float(np.min(x_values)), float(np.max(x_values)), n_points)
|
||||
y_dense = np.interp(x_dense, x_values, y_smooth)
|
||||
return x_dense, y_dense
|
||||
|
||||
|
||||
def _plot_focus_revenue_by_alpha(alpha_mode: pd.DataFrame, out_path: Path) -> Path:
|
||||
fig, ax = plt.subplots(figsize=(7.8, 4.8), constrained_layout=True)
|
||||
for mode, color, label in (
|
||||
@@ -228,6 +340,148 @@ def _plot_focus_revenue_by_alpha(alpha_mode: pd.DataFrame, out_path: Path) -> Pa
|
||||
return _save_plot(fig, out_path)
|
||||
|
||||
|
||||
def _plot_focus_coi_by_alpha(alpha_mode: pd.DataFrame, out_path: Path) -> Path:
|
||||
fig, ax = plt.subplots(figsize=(7.8, 4.8), constrained_layout=True)
|
||||
for mode, color, label in (
|
||||
("baseline", "#4C72B0", "Baseline"),
|
||||
("defended", "#C44E52", "Defended"),
|
||||
):
|
||||
sub = alpha_mode[alpha_mode["mode"] == mode].sort_values("alpha")
|
||||
if sub.empty:
|
||||
continue
|
||||
x_raw = sub["alpha"].to_numpy(dtype=float)
|
||||
y_raw = sub["coi_level_mean"].to_numpy(dtype=float)
|
||||
x_smooth, y_smooth = _smoothed_curve(x_raw, y_raw)
|
||||
ax.plot(
|
||||
x_smooth,
|
||||
y_smooth,
|
||||
linewidth=1.9,
|
||||
color=color,
|
||||
label=label,
|
||||
)
|
||||
ax.scatter(
|
||||
x_raw,
|
||||
y_raw,
|
||||
s=18,
|
||||
color=color,
|
||||
edgecolor="#FFFFFF",
|
||||
linewidth=0.45,
|
||||
zorder=3,
|
||||
)
|
||||
|
||||
paired = alpha_mode.pivot_table(
|
||||
index="alpha",
|
||||
columns="mode",
|
||||
values="coi_level_mean",
|
||||
aggfunc="mean",
|
||||
).sort_index()
|
||||
if {"baseline", "defended"}.issubset(set(paired.columns)):
|
||||
paired = paired.dropna(subset=["baseline", "defended"], how="any")
|
||||
if not paired.empty:
|
||||
x = paired.index.to_numpy(dtype=float)
|
||||
y_baseline = paired["baseline"].to_numpy(dtype=float)
|
||||
y_defended = paired["defended"].to_numpy(dtype=float)
|
||||
x_fill, y_baseline_smooth = _smoothed_curve(x, y_baseline)
|
||||
_, y_defended_smooth = _smoothed_curve(x, y_defended)
|
||||
ax.fill_between(
|
||||
x_fill,
|
||||
y_baseline_smooth,
|
||||
y_defended_smooth,
|
||||
color="#55A868",
|
||||
alpha=0.12,
|
||||
label="Gap",
|
||||
)
|
||||
|
||||
ax.axvline(0.7, color="#666666", linewidth=1.0, linestyle="--")
|
||||
ax.set_xlabel(r"Contamination $\alpha$")
|
||||
ax.set_ylabel("Mean COI level")
|
||||
ax.set_title("Final Cohort COI Curves")
|
||||
ax.legend(loc="lower left")
|
||||
return _save_plot(fig, out_path)
|
||||
|
||||
|
||||
def _plot_focus_coi_preservation_grid(
|
||||
coi_preservation: pd.DataFrame, out_path: Path
|
||||
) -> Path:
|
||||
if coi_preservation.empty:
|
||||
raise ValueError("COI preservation grid requires at least one paired cell")
|
||||
|
||||
alpha_levels = sorted(coi_preservation["alpha"].dropna().unique().tolist())
|
||||
endpoint_targets = (0.0, 1.0)
|
||||
endpoint_levels = [
|
||||
alpha
|
||||
for target in endpoint_targets
|
||||
for alpha in alpha_levels
|
||||
if np.isclose(alpha, target, atol=1e-9)
|
||||
]
|
||||
if len(endpoint_levels) < 2 and alpha_levels:
|
||||
endpoint_levels = [alpha_levels[0], alpha_levels[-1]]
|
||||
endpoint_levels = sorted(set(endpoint_levels))
|
||||
|
||||
data = coi_preservation[coi_preservation["alpha"].isin(endpoint_levels)].copy()
|
||||
if data.empty:
|
||||
raise ValueError(
|
||||
"COI preservation grid has no rows for selected alpha endpoints"
|
||||
)
|
||||
|
||||
alpha_levels = sorted(data["alpha"].dropna().unique().tolist())
|
||||
product_levels = sorted(data["n_products"].dropna().unique().tolist())
|
||||
|
||||
bars = data.pivot_table(
|
||||
index="n_products",
|
||||
columns="alpha",
|
||||
values="coi_preserved",
|
||||
aggfunc="mean",
|
||||
).reindex(index=product_levels, columns=alpha_levels)
|
||||
|
||||
x = np.arange(len(product_levels), dtype=float)
|
||||
n_alpha = max(len(alpha_levels), 1)
|
||||
bar_width = min(0.78 / n_alpha, 0.35)
|
||||
offsets = (np.arange(n_alpha, dtype=float) - (n_alpha - 1) / 2.0) * bar_width
|
||||
palette = ["#4C72B0", "#C44E52", "#55A868", "#8172B3"]
|
||||
|
||||
fig, ax = plt.subplots(figsize=(7.8, 5.0), constrained_layout=True)
|
||||
for idx, alpha in enumerate(alpha_levels):
|
||||
values = bars[alpha].to_numpy(dtype=float)
|
||||
mask = np.isfinite(values)
|
||||
if not np.any(mask):
|
||||
continue
|
||||
xpos = x[mask] + offsets[idx]
|
||||
v = values[mask]
|
||||
ax.bar(
|
||||
xpos,
|
||||
v,
|
||||
width=bar_width * 0.96,
|
||||
color=palette[idx % len(palette)],
|
||||
label=rf"$\alpha={alpha:.1f}$",
|
||||
)
|
||||
for x_i, y_i in zip(xpos, v):
|
||||
ax.text(
|
||||
float(x_i),
|
||||
float(y_i) + (0.035 if y_i >= 0 else -0.035),
|
||||
f"{y_i:+.2f}",
|
||||
ha="center",
|
||||
va="bottom" if y_i >= 0 else "top",
|
||||
fontsize=7,
|
||||
)
|
||||
|
||||
valid = bars.to_numpy(dtype=float)
|
||||
valid = valid[np.isfinite(valid)]
|
||||
max_abs = float(np.max(np.abs(valid))) if valid.size else 1.0
|
||||
max_abs = max(max_abs * 1.22, 0.4)
|
||||
ax.set_ylim(-max_abs, max_abs)
|
||||
|
||||
ax.axhline(0.0, color="#444444", linewidth=1.0, linestyle="--")
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels([f"{int(v)}" for v in product_levels])
|
||||
ax.set_xlabel("Product count")
|
||||
ax.set_ylabel("COI preserved (defended minus baseline)")
|
||||
ax.set_title("COI Preservation by Product Count at $\\alpha=0.0$ vs $\\alpha=1.0$")
|
||||
ax.legend(loc="upper right")
|
||||
ax.grid(axis="y", alpha=0.22)
|
||||
return _save_plot(fig, out_path)
|
||||
|
||||
|
||||
def _plot_focus_revenue_delta(alpha_deltas: pd.DataFrame, out_path: Path) -> Path:
|
||||
fig, ax = plt.subplots(figsize=(7.8, 4.8), constrained_layout=True)
|
||||
x = alpha_deltas["alpha"].to_numpy(dtype=float)
|
||||
@@ -297,13 +551,21 @@ def _write_include(path: Path, figure_rel_path: str, width: str) -> Path:
|
||||
return path
|
||||
|
||||
|
||||
def run(bundle_dir: Path, output_dir: Path, plot_dir: Path) -> list[Path]:
|
||||
def run(
|
||||
bundle_dir: Path,
|
||||
output_dir: Path,
|
||||
plot_dir: Path,
|
||||
focus_sweep_id: str | None = None,
|
||||
) -> list[Path]:
|
||||
all_runs = _load_runs(bundle_dir)
|
||||
focus_id = _focus_sweep(all_runs)
|
||||
focus_id = str(focus_sweep_id) if focus_sweep_id else _focus_sweep(all_runs)
|
||||
if focus_id not in set(all_runs["sweep_id"].astype(str).unique()):
|
||||
raise ValueError(f"Requested focus sweep_id not found: {focus_id}")
|
||||
focus_runs = all_runs[all_runs["sweep_id"] == focus_id].copy()
|
||||
alpha_mode = _alpha_mode_summary(focus_runs)
|
||||
deltas = _alpha_deltas(alpha_mode)
|
||||
zones = _zone_summary(deltas)
|
||||
coi_preservation = _alpha_product_coi_preservation(focus_runs)
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
plot_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -321,9 +583,16 @@ def run(bundle_dir: Path, output_dir: Path, plot_dir: Path) -> list[Path]:
|
||||
zones.to_csv(zone_path, index=False)
|
||||
written.append(zone_path)
|
||||
|
||||
coi_grid_path = output_dir / "final_focus_coi_preservation_grid.csv"
|
||||
coi_preservation.to_csv(coi_grid_path, index=False)
|
||||
written.append(coi_grid_path)
|
||||
|
||||
headline = {
|
||||
"bundle": str(bundle_dir),
|
||||
"focus_cohort": "max_alpha_coverage",
|
||||
"focus_sweep_id": focus_id,
|
||||
"focus_run_count": int(len(focus_runs)),
|
||||
"git_commit": _git_commit(),
|
||||
"alpha_cells": int(deltas["alpha"].nunique()) if not deltas.empty else 0,
|
||||
"alpha_min": float(deltas["alpha"].min()) if not deltas.empty else None,
|
||||
"alpha_max": float(deltas["alpha"].max()) if not deltas.empty else None,
|
||||
@@ -345,6 +614,18 @@ def run(bundle_dir: Path, output_dir: Path, plot_dir: Path) -> list[Path]:
|
||||
plot_dir / "final_focus_revenue_by_alpha.pdf",
|
||||
)
|
||||
)
|
||||
written.append(
|
||||
_plot_focus_coi_by_alpha(
|
||||
alpha_mode,
|
||||
plot_dir / "final_focus_coi_by_alpha.pdf",
|
||||
)
|
||||
)
|
||||
written.append(
|
||||
_plot_focus_coi_preservation_grid(
|
||||
coi_preservation,
|
||||
plot_dir / "final_focus_coi_preservation_grid.pdf",
|
||||
)
|
||||
)
|
||||
written.append(
|
||||
_plot_focus_revenue_delta(
|
||||
deltas,
|
||||
@@ -366,6 +647,20 @@ def run(bundle_dir: Path, output_dir: Path, plot_dir: Path) -> list[Path]:
|
||||
"0.98\\linewidth",
|
||||
)
|
||||
)
|
||||
written.append(
|
||||
_write_include(
|
||||
include_dir / "final_focus_coi_by_alpha.tex",
|
||||
"chapters/figures/results/generated/final/plots/final_focus_coi_by_alpha.pdf",
|
||||
"0.98\\linewidth",
|
||||
)
|
||||
)
|
||||
written.append(
|
||||
_write_include(
|
||||
include_dir / "final_focus_coi_preservation_grid.tex",
|
||||
"chapters/figures/results/generated/final/plots/final_focus_coi_preservation_grid.pdf",
|
||||
"0.98\\linewidth",
|
||||
)
|
||||
)
|
||||
written.append(
|
||||
_write_include(
|
||||
include_dir / "final_focus_revenue_delta.tex",
|
||||
@@ -390,6 +685,7 @@ def main() -> None:
|
||||
parser.add_argument("--bundle-dir", type=Path, default=_default_bundle_dir())
|
||||
parser.add_argument("--output-dir", type=Path, default=_default_output_dir())
|
||||
parser.add_argument("--plot-dir", type=Path, default=None)
|
||||
parser.add_argument("--focus-sweep-id", type=str, default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
_configure_style()
|
||||
@@ -399,7 +695,10 @@ def main() -> None:
|
||||
else _default_plot_dir(args.output_dir)
|
||||
)
|
||||
outputs = run(
|
||||
bundle_dir=args.bundle_dir, output_dir=args.output_dir, plot_dir=plot_dir
|
||||
bundle_dir=args.bundle_dir,
|
||||
output_dir=args.output_dir,
|
||||
plot_dir=plot_dir,
|
||||
focus_sweep_id=args.focus_sweep_id,
|
||||
)
|
||||
for path in outputs:
|
||||
print(path)
|
||||
|
||||
454
paper/src/chapters/figures/results/revenue_alpha_analysis.py
Normal file
454
paper/src/chapters/figures/results/revenue_alpha_analysis.py
Normal file
@@ -0,0 +1,454 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy import stats
|
||||
|
||||
|
||||
def _project_root() -> Path:
|
||||
return Path(__file__).resolve().parents[5]
|
||||
|
||||
|
||||
def _default_bundle_dir() -> Path:
|
||||
base = _project_root() / "engine" / "studies" / "results" / "wandb_sweep_bundles"
|
||||
bundles = sorted(
|
||||
[path for path in base.glob("bundle_*") if path.is_dir()],
|
||||
key=lambda path: path.stat().st_mtime,
|
||||
reverse=True,
|
||||
)
|
||||
if not bundles:
|
||||
raise FileNotFoundError(f"No sweep bundle directories found in {base}")
|
||||
return bundles[0]
|
||||
|
||||
|
||||
def _bundle_dir_from_id(bundle_id: str) -> Path:
|
||||
token = str(bundle_id).strip()
|
||||
name = token if token.startswith("bundle_") else f"bundle_{token}"
|
||||
path = (
|
||||
_project_root()
|
||||
/ "engine"
|
||||
/ "studies"
|
||||
/ "results"
|
||||
/ "wandb_sweep_bundles"
|
||||
/ name
|
||||
)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Bundle not found: {path}")
|
||||
return path
|
||||
|
||||
|
||||
def _default_output_dir() -> Path:
|
||||
return Path(__file__).resolve().parent / "generated" / "final"
|
||||
|
||||
|
||||
def _truthy(value: object) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if value is None:
|
||||
return False
|
||||
return str(value).strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def _mode_of(row: pd.Series) -> str:
|
||||
mode_hint = str(row.get("study_mode", "")).strip().lower()
|
||||
if mode_hint in {"baseline", "no_robust"}:
|
||||
return "baseline"
|
||||
if mode_hint in {"defended", "robust"}:
|
||||
return "defended"
|
||||
if _truthy(row.get("baseline_mode")) or _truthy(row.get("no_robust")):
|
||||
return "baseline"
|
||||
return "defended"
|
||||
|
||||
|
||||
def _coerce_numeric(frame: pd.DataFrame, columns: Iterable[str]) -> None:
|
||||
for column in columns:
|
||||
if column in frame.columns:
|
||||
frame[column] = pd.to_numeric(frame[column], errors="coerce")
|
||||
|
||||
|
||||
def _load_runs(bundle_dir: Path) -> pd.DataFrame:
|
||||
path = bundle_dir / "runs_finished.csv"
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Missing required file: {path}")
|
||||
frame = pd.read_csv(path)
|
||||
frame["mode"] = frame.apply(_mode_of, axis=1)
|
||||
_coerce_numeric(
|
||||
frame,
|
||||
[
|
||||
"alpha",
|
||||
"n_products",
|
||||
"eta_ux",
|
||||
"lambda_coi",
|
||||
"eval_revenue_mean",
|
||||
],
|
||||
)
|
||||
frame = frame[frame["mode"].isin({"baseline", "defended"})].copy()
|
||||
return frame
|
||||
|
||||
|
||||
def _get_git_commit() -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"],
|
||||
check=True,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
cwd=_project_root(),
|
||||
)
|
||||
except Exception:
|
||||
return "unknown"
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def _apply_filters(frame: pd.DataFrame, args: argparse.Namespace) -> pd.DataFrame:
|
||||
data = frame.copy()
|
||||
if args.sweep_id:
|
||||
allowed = {str(value) for value in args.sweep_id}
|
||||
data = data[data["sweep_id"].astype(str).isin(allowed)]
|
||||
if args.mode != "all":
|
||||
data = data[data["mode"] == args.mode]
|
||||
if args.n_products is not None:
|
||||
data = data[data["n_products"] == float(args.n_products)]
|
||||
if args.eta_ux is not None:
|
||||
data = data[data["eta_ux"] == float(args.eta_ux)]
|
||||
if args.lambda_coi is not None:
|
||||
data = data[data["lambda_coi"] == float(args.lambda_coi)]
|
||||
data = data[data["alpha"].notna() & data["eval_revenue_mean"].notna()]
|
||||
data = data[data["alpha"] >= float(args.alpha_min)]
|
||||
data = data[data["alpha"] <= float(args.alpha_max)]
|
||||
return data.reset_index(drop=True)
|
||||
|
||||
|
||||
def _design_matrix(
|
||||
frame: pd.DataFrame,
|
||||
*,
|
||||
include_sweep_fixed_effects: bool,
|
||||
) -> tuple[np.ndarray, np.ndarray, list[str]]:
|
||||
y = frame["eval_revenue_mean"].to_numpy(dtype=float)
|
||||
x_alpha = frame["alpha"].to_numpy(dtype=float)
|
||||
columns = ["intercept", "alpha"]
|
||||
blocks = [np.ones_like(x_alpha), x_alpha]
|
||||
if include_sweep_fixed_effects:
|
||||
dummies = pd.get_dummies(
|
||||
frame["sweep_id"].astype(str), prefix="sweep", drop_first=True
|
||||
)
|
||||
if not dummies.empty:
|
||||
blocks.append(dummies.to_numpy(dtype=float).T)
|
||||
columns.extend(dummies.columns.tolist())
|
||||
X = np.vstack(blocks).T
|
||||
return X, y, columns
|
||||
|
||||
|
||||
def _covariance_hc1(X: np.ndarray, residuals: np.ndarray) -> np.ndarray:
|
||||
n, k = X.shape
|
||||
xtx_inv = np.linalg.pinv(X.T @ X)
|
||||
xr = X * residuals[:, None]
|
||||
meat = xr.T @ xr
|
||||
scale = float(n) / max(n - k, 1)
|
||||
return scale * (xtx_inv @ meat @ xtx_inv)
|
||||
|
||||
|
||||
def _covariance_cluster(
|
||||
X: np.ndarray, residuals: np.ndarray, groups: pd.Series
|
||||
) -> tuple[np.ndarray, int]:
|
||||
xtx_inv = np.linalg.pinv(X.T @ X)
|
||||
unique = pd.Series(groups).astype(str).dropna().unique().tolist()
|
||||
g = len(unique)
|
||||
n, k = X.shape
|
||||
if g <= 1:
|
||||
return _covariance_hc1(X, residuals), g
|
||||
meat = np.zeros((k, k), dtype=float)
|
||||
for value in unique:
|
||||
mask = pd.Series(groups).astype(str).to_numpy() == value
|
||||
Xg = X[mask]
|
||||
ug = residuals[mask]
|
||||
xu = Xg.T @ ug
|
||||
meat += np.outer(xu, xu)
|
||||
c = (g / (g - 1.0)) * ((n - 1.0) / max(n - k, 1.0))
|
||||
return c * (xtx_inv @ meat @ xtx_inv), g
|
||||
|
||||
|
||||
def _fit_ols(
|
||||
X: np.ndarray,
|
||||
y: np.ndarray,
|
||||
columns: list[str],
|
||||
*,
|
||||
cov_type: str,
|
||||
groups: pd.Series | None = None,
|
||||
) -> dict[str, object]:
|
||||
n, k = X.shape
|
||||
beta, _, _, _ = np.linalg.lstsq(X, y, rcond=None)
|
||||
fitted = X @ beta
|
||||
residuals = y - fitted
|
||||
dof = max(n - k, 1)
|
||||
sse = float(np.sum(residuals**2))
|
||||
y_centered = y - float(np.mean(y))
|
||||
sst = float(np.sum(y_centered**2))
|
||||
r2 = float(1.0 - sse / sst) if sst > 0 else 0.0
|
||||
adj_r2 = float(1.0 - (1.0 - r2) * ((n - 1.0) / max(n - k, 1.0)))
|
||||
|
||||
if cov_type == "iid":
|
||||
sigma2 = sse / dof
|
||||
cov = sigma2 * np.linalg.pinv(X.T @ X)
|
||||
df_t = dof
|
||||
clusters = None
|
||||
elif cov_type == "hc1":
|
||||
cov = _covariance_hc1(X, residuals)
|
||||
df_t = dof
|
||||
clusters = None
|
||||
elif cov_type == "cluster":
|
||||
if groups is None:
|
||||
raise ValueError("groups are required when cov_type='cluster'")
|
||||
cov, clusters = _covariance_cluster(X, residuals, groups)
|
||||
df_t = max(clusters - 1, 1)
|
||||
else:
|
||||
raise ValueError(f"Unsupported cov_type: {cov_type}")
|
||||
|
||||
se = np.sqrt(np.clip(np.diag(cov), 0.0, np.inf))
|
||||
t_stats = np.divide(beta, se, out=np.zeros_like(beta), where=se > 0)
|
||||
p_values = 2.0 * (1.0 - stats.t.cdf(np.abs(t_stats), df=df_t))
|
||||
t_crit = float(stats.t.ppf(0.975, df=df_t))
|
||||
ci_low = beta - t_crit * se
|
||||
ci_high = beta + t_crit * se
|
||||
|
||||
coef_rows = []
|
||||
for idx, name in enumerate(columns):
|
||||
coef_rows.append(
|
||||
{
|
||||
"name": name,
|
||||
"coef": float(beta[idx]),
|
||||
"std_error": float(se[idx]),
|
||||
"t_stat": float(t_stats[idx]),
|
||||
"p_value": float(p_values[idx]),
|
||||
"ci95_low": float(ci_low[idx]),
|
||||
"ci95_high": float(ci_high[idx]),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"n": int(n),
|
||||
"k": int(k),
|
||||
"dof": int(dof),
|
||||
"df_t": int(df_t),
|
||||
"cov_type": cov_type,
|
||||
"clusters": int(clusters) if clusters is not None else None,
|
||||
"r2": r2,
|
||||
"adj_r2": adj_r2,
|
||||
"sse": sse,
|
||||
"coefficients": coef_rows,
|
||||
"residuals": residuals,
|
||||
"fitted": fitted,
|
||||
"beta": beta,
|
||||
}
|
||||
|
||||
|
||||
def _diagnostics(
|
||||
X: np.ndarray, y: np.ndarray, fit: dict[str, object]
|
||||
) -> dict[str, object]:
|
||||
residuals = np.asarray(fit["residuals"], dtype=float)
|
||||
n, k = X.shape
|
||||
if residuals.size < 8:
|
||||
normality = {"test": "jarque_bera", "available": False}
|
||||
else:
|
||||
jb_stat, jb_p = stats.jarque_bera(residuals)
|
||||
normality = {
|
||||
"test": "jarque_bera",
|
||||
"available": True,
|
||||
"statistic": float(jb_stat),
|
||||
"p_value": float(jb_p),
|
||||
}
|
||||
|
||||
if k <= 1:
|
||||
hetero = {"test": "breusch_pagan", "available": False}
|
||||
else:
|
||||
u2 = residuals**2
|
||||
aux = _fit_ols(X, u2, [f"x{i}" for i in range(k)], cov_type="iid")
|
||||
lm = float(len(u2) * float(aux["r2"]))
|
||||
df_bp = k - 1
|
||||
p_bp = float(1.0 - stats.chi2.cdf(lm, df_bp))
|
||||
hetero = {
|
||||
"test": "breusch_pagan",
|
||||
"available": True,
|
||||
"lm_stat": lm,
|
||||
"df": int(df_bp),
|
||||
"p_value": p_bp,
|
||||
}
|
||||
|
||||
xtx_inv = np.linalg.pinv(X.T @ X)
|
||||
leverages = np.sum((X @ xtx_inv) * X, axis=1)
|
||||
mse = float(np.sum(residuals**2) / max(n - k, 1))
|
||||
if mse <= 0:
|
||||
cooks = np.zeros(n, dtype=float)
|
||||
else:
|
||||
denom = np.clip((1.0 - leverages) ** 2, 1e-10, np.inf)
|
||||
cooks = ((residuals**2) / (k * mse)) * (leverages / denom)
|
||||
|
||||
return {
|
||||
"normality": normality,
|
||||
"heteroskedasticity": hetero,
|
||||
"influence": {
|
||||
"max_leverage": float(np.max(leverages)) if leverages.size else 0.0,
|
||||
"mean_leverage": float(np.mean(leverages)) if leverages.size else 0.0,
|
||||
"high_leverage_threshold": float(2.0 * k / max(n, 1)),
|
||||
"high_leverage_count": int(np.sum(leverages > (2.0 * k / max(n, 1)))),
|
||||
"max_cooks_distance": float(np.max(cooks)) if cooks.size else 0.0,
|
||||
"high_cooks_threshold": float(4.0 / max(n, 1)),
|
||||
"high_cooks_count": int(np.sum(cooks > (4.0 / max(n, 1)))),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> list[Path]:
|
||||
output_dir = Path(args.output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
runs = _load_runs(Path(args.bundle_dir))
|
||||
filtered = _apply_filters(runs, args)
|
||||
if len(filtered) < 3:
|
||||
raise ValueError("Filtered cohort must contain at least 3 rows")
|
||||
if filtered["alpha"].nunique() < 2:
|
||||
raise ValueError("Filtered cohort must contain at least 2 unique alpha values")
|
||||
|
||||
filtered_csv = output_dir / "revenue_alpha_filtered.csv"
|
||||
filtered.to_csv(filtered_csv, index=False)
|
||||
|
||||
sample_accounting = {
|
||||
"bundle_dir": str(Path(args.bundle_dir)),
|
||||
"git_commit": _get_git_commit(),
|
||||
"cohort_name": str(args.cohort_name),
|
||||
"filters": {
|
||||
"sweep_id": args.sweep_id,
|
||||
"mode": args.mode,
|
||||
"n_products": args.n_products,
|
||||
"eta_ux": args.eta_ux,
|
||||
"lambda_coi": args.lambda_coi,
|
||||
"alpha_min": args.alpha_min,
|
||||
"alpha_max": args.alpha_max,
|
||||
},
|
||||
"n_rows": int(len(filtered)),
|
||||
"n_sweeps": int(filtered["sweep_id"].nunique()),
|
||||
"alpha_unique": sorted(
|
||||
float(v) for v in filtered["alpha"].dropna().unique().tolist()
|
||||
),
|
||||
"rows_by_sweep": filtered.groupby("sweep_id").size().astype(int).to_dict(),
|
||||
"rows_by_mode": filtered.groupby("mode").size().astype(int).to_dict(),
|
||||
}
|
||||
sample_path = output_dir / "revenue_alpha_sample_accounting.json"
|
||||
sample_path.write_text(json.dumps(sample_accounting, indent=2) + "\n")
|
||||
|
||||
X_simple, y, cols_simple = _design_matrix(
|
||||
filtered, include_sweep_fixed_effects=False
|
||||
)
|
||||
fit_simple = _fit_ols(X_simple, y, cols_simple, cov_type="iid")
|
||||
simple_path = output_dir / "revenue_alpha_simple_ols.json"
|
||||
simple_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
k: v
|
||||
for k, v in fit_simple.items()
|
||||
if k not in {"residuals", "fitted", "beta"}
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
X_fe, y_fe, cols_fe = _design_matrix(filtered, include_sweep_fixed_effects=True)
|
||||
cov_type = "cluster" if filtered["sweep_id"].nunique() > 1 else "hc1"
|
||||
fit_fe = _fit_ols(
|
||||
X_fe, y_fe, cols_fe, cov_type=cov_type, groups=filtered["sweep_id"]
|
||||
)
|
||||
fe_path = output_dir / "revenue_alpha_fixed_effects.json"
|
||||
fe_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
k: v
|
||||
for k, v in fit_fe.items()
|
||||
if k not in {"residuals", "fitted", "beta"}
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
per_sweep_rows: list[dict[str, float | str | int]] = []
|
||||
for sweep_id, group in filtered.groupby("sweep_id"):
|
||||
if len(group) < 3 or group["alpha"].nunique() < 2:
|
||||
continue
|
||||
X_sw, y_sw, cols_sw = _design_matrix(group, include_sweep_fixed_effects=False)
|
||||
fit_sw = _fit_ols(X_sw, y_sw, cols_sw, cov_type="hc1")
|
||||
alpha_row = next(
|
||||
row for row in fit_sw["coefficients"] if row["name"] == "alpha"
|
||||
)
|
||||
per_sweep_rows.append(
|
||||
{
|
||||
"sweep_id": str(sweep_id),
|
||||
"n": int(fit_sw["n"]),
|
||||
"alpha_coef": float(alpha_row["coef"]),
|
||||
"alpha_std_error": float(alpha_row["std_error"]),
|
||||
"alpha_t_stat": float(alpha_row["t_stat"]),
|
||||
"alpha_p_value": float(alpha_row["p_value"]),
|
||||
"alpha_ci95_low": float(alpha_row["ci95_low"]),
|
||||
"alpha_ci95_high": float(alpha_row["ci95_high"]),
|
||||
"r2": float(fit_sw["r2"]),
|
||||
}
|
||||
)
|
||||
per_sweep_frame = pd.DataFrame(per_sweep_rows)
|
||||
if not per_sweep_frame.empty:
|
||||
per_sweep_frame = per_sweep_frame.sort_values("sweep_id").reset_index(drop=True)
|
||||
per_sweep_path = output_dir / "revenue_alpha_per_sweep.csv"
|
||||
per_sweep_frame.to_csv(per_sweep_path, index=False)
|
||||
|
||||
fit_for_diagnostics = fit_fe if cov_type == "cluster" else fit_simple
|
||||
X_for_diagnostics = X_fe if cov_type == "cluster" else X_simple
|
||||
diagnostics = _diagnostics(X_for_diagnostics, y, fit_for_diagnostics)
|
||||
diagnostics_path = output_dir / "revenue_alpha_diagnostics.json"
|
||||
diagnostics_path.write_text(json.dumps(diagnostics, indent=2) + "\n")
|
||||
|
||||
return [
|
||||
filtered_csv,
|
||||
sample_path,
|
||||
simple_path,
|
||||
fe_path,
|
||||
per_sweep_path,
|
||||
diagnostics_path,
|
||||
]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Reproducible contamination-vs-revenue analysis from a sweep bundle"
|
||||
)
|
||||
parser.add_argument("--bundle-dir", type=Path, default=None)
|
||||
parser.add_argument("--bundle-id", type=str, default=None)
|
||||
parser.add_argument("--output-dir", type=Path, default=_default_output_dir())
|
||||
parser.add_argument("--cohort-name", type=str, default="custom")
|
||||
parser.add_argument("--sweep-id", action="append", default=[])
|
||||
parser.add_argument(
|
||||
"--mode", choices=["all", "baseline", "defended"], default="all"
|
||||
)
|
||||
parser.add_argument("--n-products", type=float, default=None)
|
||||
parser.add_argument("--eta-ux", type=float, default=None)
|
||||
parser.add_argument("--lambda-coi", type=float, default=None)
|
||||
parser.add_argument("--alpha-min", type=float, default=0.0)
|
||||
parser.add_argument("--alpha-max", type=float, default=1.0)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.bundle_id:
|
||||
args.bundle_dir = _bundle_dir_from_id(args.bundle_id)
|
||||
elif args.bundle_dir is None:
|
||||
args.bundle_dir = _default_bundle_dir()
|
||||
|
||||
outputs = run(args)
|
||||
for path in outputs:
|
||||
print(path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
63
paper/src/chapters/figures/results/revenue_alpha_classic.py
Normal file
63
paper/src/chapters/figures/results/revenue_alpha_classic.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy import stats
|
||||
|
||||
|
||||
root = Path(__file__).resolve().parents[5]
|
||||
runs = (
|
||||
root
|
||||
/ "engine/studies/results/wandb_sweep_bundles/bundle_20260317_122818/runs_finished.csv"
|
||||
)
|
||||
|
||||
df = pd.read_csv(runs)
|
||||
df = df[
|
||||
(df["sweep_id"].astype(str) == "i88nw811")
|
||||
& (df["study_mode"].astype(str) == "baseline")
|
||||
& (pd.to_numeric(df["n_products"], errors="coerce") == 100.0)
|
||||
& (pd.to_numeric(df["eta_ux"], errors="coerce") == 0.0)
|
||||
].copy()
|
||||
|
||||
alpha = pd.to_numeric(df["alpha"], errors="coerce")
|
||||
revenue = pd.to_numeric(df["eval_revenue_mean"], errors="coerce")
|
||||
mask = alpha.notna() & revenue.notna()
|
||||
alpha = alpha[mask].to_numpy(dtype=float)
|
||||
revenue = revenue[mask].to_numpy(dtype=float)
|
||||
|
||||
if len(alpha) < 3 or np.unique(alpha).size < 2:
|
||||
raise ValueError("Not enough data for regression")
|
||||
|
||||
fit = stats.linregress(alpha, revenue)
|
||||
n = len(alpha)
|
||||
dof = n - 2
|
||||
t_stat = fit.slope / fit.stderr
|
||||
p_val = 2.0 * stats.t.sf(abs(t_stat), df=dof)
|
||||
r2 = fit.rvalue**2
|
||||
t_crit = stats.t.ppf(0.975, dof)
|
||||
slope_ci = (fit.slope - t_crit * fit.stderr, fit.slope + t_crit * fit.stderr)
|
||||
|
||||
x = np.column_stack([np.ones(n), alpha])
|
||||
beta = np.linalg.lstsq(x, revenue, rcond=None)[0]
|
||||
resid = revenue - x @ beta
|
||||
xtx_inv = np.linalg.pinv(x.T @ x)
|
||||
meat = (x * resid[:, None]).T @ (x * resid[:, None])
|
||||
cov_hc1 = (n / (n - x.shape[1])) * (xtx_inv @ meat @ xtx_inv)
|
||||
se_hc1 = np.sqrt(np.diag(cov_hc1))
|
||||
t_hc1 = beta[1] / se_hc1[1]
|
||||
p_hc1 = 2.0 * stats.t.sf(abs(t_hc1), df=dof)
|
||||
slope_ci_hc1 = (beta[1] - t_crit * se_hc1[1], beta[1] + t_crit * se_hc1[1])
|
||||
|
||||
print("Contamination-Revenue Slope")
|
||||
print(
|
||||
"cohort: bundle_20260317_122818, sweep=i88nw811, mode=baseline, n_products=100, eta_ux=0.0"
|
||||
)
|
||||
print(f"n={n}")
|
||||
print(f"model: revenue = {fit.intercept:.2f} {fit.slope:+.2f} * alpha")
|
||||
print(
|
||||
f"OLS: t({dof})={t_stat:.2f}, p={p_val:.3e}, R^2={r2:.3f}, slope_95CI=[{slope_ci[0]:.2f}, {slope_ci[1]:.2f}]"
|
||||
)
|
||||
print(
|
||||
f"HC1: t={t_hc1:.2f}, p={p_hc1:.3e}, slope_95CI=[{slope_ci_hc1[0]:.2f}, {slope_ci_hc1[1]:.2f}]"
|
||||
)
|
||||
print(f"effect: +0.1 alpha -> {0.1 * fit.slope:.2f} revenue units")
|
||||
Binary file not shown.
Binary file not shown.
@@ -110,19 +110,6 @@ v4 & 64 & 275 & $64 \times 275 = 17{,}600$ \\
|
||||
|
||||
Converting to petaFLOPS: $160{,}320\;\text{TFLOPS} = 160.32\;\text{PFLOPS} \approx 160\;\text{PFLOPS}$. This is the theoretical peak under sustained BF16 arithmetic; realized throughput depends on memory bandwidth utilization and inter-chip communication overhead, but the figure serves as a useful upper bound for provisioning decisions.
|
||||
|
||||
\section{Slope-Test Verification: Revenue vs. Contamination}
|
||||
\label{app:alpha_revenue_slope}
|
||||
|
||||
This appendix provides a compact verification of the slope result reported in the main results section. Using the same run-level pairs $x_i=\texttt{study/alpha}_i$ and $y_i=\texttt{eval/revenue\_mean}_i$ ($n=95$), we re-checked the ordinary least squares slope test in Python with standard test routines (SciPy two-sided $t$ test for the slope).
|
||||
|
||||
\[
|
||||
\widehat{y}=326{,}878.57-60{,}631.95\,x,
|
||||
\]
|
||||
\[
|
||||
t(93)=-8.2148,\qquad p=1.2038\times 10^{-12},\qquad R^2=0.4205,\qquad 95\%\,\text{CI}_{\beta_1}=[-75{,}288.76,\,-45{,}975.13].
|
||||
\]
|
||||
|
||||
The Python verification reproduces the reported coefficients and inference values, confirming that the slope-test results are correct under standard methods.
|
||||
|
||||
\section{whoclickedit Dataset Card}
|
||||
\label{app:whoclicked_card}
|
||||
|
||||
@@ -4,15 +4,34 @@ set -euo pipefail
|
||||
|
||||
cmd="${1:-}"
|
||||
|
||||
sync_mdp_figures() {
|
||||
local script_dir project_root sim_dir chapters_dir
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
project_root="$(cd "$script_dir/.." && pwd)"
|
||||
sim_dir="$project_root/sim/rl/behavior_loader"
|
||||
chapters_dir="$project_root/paper/src/chapters"
|
||||
|
||||
printf '%s\n' 'Refreshing MDP figures for paper...'
|
||||
(
|
||||
cd "$sim_dir"
|
||||
python models.py
|
||||
)
|
||||
|
||||
cp "$sim_dir/human_mdp_viz.pdf" "$chapters_dir/mdp_human.pdf"
|
||||
cp "$sim_dir/agent_mdp_viz.pdf" "$chapters_dir/mdp_agent.pdf"
|
||||
}
|
||||
|
||||
case "$cmd" in
|
||||
build)
|
||||
mkdir -p paper/build
|
||||
sync_mdp_figures
|
||||
bash paper/concat_code.sh
|
||||
cd paper/src
|
||||
latexmk -pdf -jobname=main -f -interaction=nonstopmode -file-line-error -r ../.latexmkrc -outdir=../build main.tex
|
||||
;;
|
||||
watch)
|
||||
mkdir -p paper/build
|
||||
sync_mdp_figures
|
||||
cd paper/src
|
||||
latexmk -pvc -pdf -jobname=main -f -interaction=nonstopmode -file-line-error -r ../.latexmkrc -outdir=../build main.tex
|
||||
;;
|
||||
@@ -33,11 +52,13 @@ case "$cmd" in
|
||||
;;
|
||||
build-genpop)
|
||||
mkdir -p paper/build
|
||||
sync_mdp_figures
|
||||
cd paper/src
|
||||
latexmk -pdf -jobname=main-genpop -f -interaction=nonstopmode -file-line-error -r ../.latexmkrc -outdir=../build main-genpop.tex
|
||||
;;
|
||||
watch-genpop)
|
||||
mkdir -p paper/build
|
||||
sync_mdp_figures
|
||||
cd paper/src
|
||||
latexmk -pvc -pdf -jobname=main-genpop -f -interaction=nonstopmode -file-line-error -r ../.latexmkrc -outdir=../build main-genpop.tex
|
||||
;;
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
Computes divergence signals delta_H, delta_A from session trajectories using
|
||||
transition kernel estimation and KL divergence to prototype behavioral profiles.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Dict, List, Tuple, TYPE_CHECKING
|
||||
import numpy as np
|
||||
|
||||
from lib.agent_probability import DEFAULT_AGENT_PRIOR, estimate_agent_probability
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .simplified import Event, Session
|
||||
|
||||
@@ -32,7 +35,10 @@ TRANS_A = {
|
||||
def kl_div(p: Dict[str, float], q: Dict[str, float], eps: float = 1e-10) -> float:
|
||||
"""KL divergence D_KL(p || q) for discrete distributions."""
|
||||
keys = set(p.keys()) | set(q.keys())
|
||||
return sum(p.get(k, eps) * np.log((p.get(k, eps) + eps) / (q.get(k, eps) + eps)) for k in keys)
|
||||
return sum(
|
||||
p.get(k, eps) * np.log((p.get(k, eps) + eps) / (q.get(k, eps) + eps))
|
||||
for k in keys
|
||||
)
|
||||
|
||||
|
||||
def build_kernel(events: List["Event"]) -> Dict[str, Dict[str, float]]:
|
||||
@@ -44,7 +50,11 @@ def build_kernel(events: List["Event"]) -> Dict[str, Dict[str, float]]:
|
||||
trans.setdefault(prev, {})
|
||||
trans[prev][curr] = trans[prev].get(curr, 0) + 1
|
||||
prev = curr
|
||||
return {s: {d: c / sum(dsts.values()) for d, c in dsts.items()} for s, dsts in trans.items() if sum(dsts.values()) > 0}
|
||||
return {
|
||||
s: {d: c / sum(dsts.values()) for d, c in dsts.items()}
|
||||
for s, dsts in trans.items()
|
||||
if sum(dsts.values()) > 0
|
||||
}
|
||||
|
||||
|
||||
def compute_divergence(session: "Session") -> Tuple[float, float]:
|
||||
@@ -55,18 +65,35 @@ def compute_divergence(session: "Session") -> Tuple[float, float]:
|
||||
"""
|
||||
kernel = build_kernel(session.events)
|
||||
if not kernel:
|
||||
return 0.5, 0.5
|
||||
delta_h = sum(kl_div(kernel.get(s, {}), TRANS_H.get(s, {})) for s in kernel) / len(kernel)
|
||||
delta_a = sum(kl_div(kernel.get(s, {}), TRANS_A.get(s, {})) for s in kernel) / len(kernel)
|
||||
return 0.0, 0.0
|
||||
delta_h = sum(kl_div(kernel.get(s, {}), TRANS_H.get(s, {})) for s in kernel) / len(
|
||||
kernel
|
||||
)
|
||||
delta_a = sum(kl_div(kernel.get(s, {}), TRANS_A.get(s, {})) for s in kernel) / len(
|
||||
kernel
|
||||
)
|
||||
return delta_h, delta_a
|
||||
|
||||
|
||||
def estimate_alpha(session: "Session", beta: float = 2.0) -> float:
|
||||
"""Per-session contamination estimate alpha_hat = sigma(beta*(delta_H - delta_A)).
|
||||
def estimate_alpha(
|
||||
session: "Session",
|
||||
beta: float = 2.0,
|
||||
prior_agent: float = DEFAULT_AGENT_PRIOR,
|
||||
) -> float:
|
||||
"""Per-session contamination estimate alpha_hat = sigma((delta_H - delta_A) / T).
|
||||
|
||||
Returns probability session is agent-generated based on behavioral divergence.
|
||||
"""
|
||||
dh, da = compute_divergence(session)
|
||||
if (dh + da) <= 0:
|
||||
return 0.5
|
||||
return 1.0 / (1.0 + np.exp(-beta * (dh - da)))
|
||||
return float(prior_agent)
|
||||
if beta <= 0:
|
||||
return estimate_agent_probability(
|
||||
dh, da, temperature=1.0, prior_agent=prior_agent
|
||||
)
|
||||
return estimate_agent_probability(
|
||||
delta_h=dh,
|
||||
delta_a=da,
|
||||
temperature=1.0 / beta,
|
||||
prior_agent=prior_agent,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ try:
|
||||
except ImportError:
|
||||
from sim.rl.behavior_loader.loader import Loader, AgentLoader, JointLoader
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Tuple, Set
|
||||
from typing import Dict, List, Optional, Set, Tuple
|
||||
import numpy as np
|
||||
import graphviz
|
||||
import sys
|
||||
@@ -195,6 +195,110 @@ def aggregate_event_transitions(mdp: Dict) -> Dict[str, Dict[str, float]]:
|
||||
return dict(evt_trans)
|
||||
|
||||
|
||||
def _resolve_event_order(
|
||||
evt_trans: Dict[str, Dict[str, float]],
|
||||
event_order: Optional[List[str]] = None,
|
||||
) -> List[str]:
|
||||
observed = set(evt_trans.keys()) | {
|
||||
dst for transitions in evt_trans.values() for dst in transitions
|
||||
}
|
||||
if event_order:
|
||||
ordered = list(dict.fromkeys(event_order))
|
||||
missing = sorted(observed - set(ordered))
|
||||
return ordered + missing
|
||||
return sorted(observed)
|
||||
|
||||
|
||||
def _compass_from_angle(angle_rad: float) -> str:
|
||||
ports = ("e", "ne", "n", "nw", "w", "sw", "s", "se")
|
||||
normalized = (angle_rad + (2 * np.pi)) % (2 * np.pi)
|
||||
step = np.pi / 4
|
||||
idx = int(np.round(normalized / step)) % len(ports)
|
||||
return ports[idx]
|
||||
|
||||
|
||||
def _edge_ports(
|
||||
src: str,
|
||||
dst: str,
|
||||
positions: Dict[str, Tuple[float, float]],
|
||||
has_reverse: bool,
|
||||
) -> Tuple[str, str]:
|
||||
src_x, src_y = positions[src]
|
||||
dst_x, dst_y = positions[dst]
|
||||
angle = float(np.arctan2(dst_y - src_y, dst_x - src_x))
|
||||
|
||||
if has_reverse:
|
||||
bend = np.pi / 10
|
||||
angle += bend if src < dst else -bend
|
||||
|
||||
tail_port = _compass_from_angle(angle)
|
||||
head_port = _compass_from_angle(angle + np.pi)
|
||||
return tail_port, head_port
|
||||
|
||||
|
||||
def _edge_style(prob: float) -> Dict[str, str]:
|
||||
if prob >= 0.75:
|
||||
edge_color = "#111827"
|
||||
elif prob >= 0.50:
|
||||
edge_color = "#374151"
|
||||
elif prob >= 0.25:
|
||||
edge_color = "#6b7280"
|
||||
else:
|
||||
edge_color = "#9ca3af"
|
||||
return {
|
||||
"color": edge_color,
|
||||
"fontcolor": "#111827",
|
||||
"fontsize": "10",
|
||||
"penwidth": f"{0.9 + 3.6 * prob:.2f}",
|
||||
"arrowsize": f"{0.55 + 0.55 * prob:.2f}",
|
||||
}
|
||||
|
||||
|
||||
def _format_node_label(evt: str) -> str:
|
||||
max_line_len = 16
|
||||
tokens = evt.split("_")
|
||||
if len(tokens) == 1:
|
||||
return evt
|
||||
|
||||
lines: List[str] = []
|
||||
curr = ""
|
||||
for token in tokens:
|
||||
piece = token if not curr else f"_{token}"
|
||||
if curr and len(curr) + len(piece) > max_line_len:
|
||||
lines.append(curr)
|
||||
curr = token
|
||||
else:
|
||||
curr = f"{curr}{piece}" if curr else token
|
||||
if curr:
|
||||
lines.append(curr)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _compute_flow_positions(
|
||||
events: List[str],
|
||||
layout_radius: float,
|
||||
) -> Dict[str, Tuple[float, float]]:
|
||||
"""Balanced grid layout for paper-friendly diagrams."""
|
||||
if not events:
|
||||
return {}
|
||||
|
||||
num_events = len(events)
|
||||
cols = int(np.ceil(np.sqrt(num_events)))
|
||||
rows = int(np.ceil(num_events / cols))
|
||||
x_step = max(layout_radius * 1.10, 3.6)
|
||||
y_step = max(layout_radius * 0.95, 3.2)
|
||||
|
||||
positions: Dict[str, Tuple[float, float]] = {}
|
||||
for idx, evt in enumerate(events):
|
||||
row = idx // cols
|
||||
col = idx % cols
|
||||
x = (col - (cols - 1) / 2.0) * x_step
|
||||
y = ((rows - 1) / 2.0 - row) * y_step
|
||||
positions[evt] = (float(x), float(y))
|
||||
|
||||
return positions
|
||||
|
||||
|
||||
def visualize_mdp(
|
||||
model: BehaviorModel,
|
||||
threshold: float = 0.05,
|
||||
@@ -202,25 +306,80 @@ def visualize_mdp(
|
||||
fmt: str = "svg",
|
||||
view: bool = False,
|
||||
export_dot: bool = False,
|
||||
event_order: Optional[List[str]] = None,
|
||||
layout_radius: float = 10.0,
|
||||
node_diameter: float = 1.8,
|
||||
label_threshold: float = 0.08,
|
||||
):
|
||||
if not model.mdp:
|
||||
raise ValueError("build MDP first")
|
||||
|
||||
evt_trans = aggregate_event_transitions(model.mdp)
|
||||
g = graphviz.Digraph(format=fmt)
|
||||
g.attr(rankdir="LR", size="30")
|
||||
g.attr("node", shape="circle", width="1", height="1")
|
||||
ordered_events = _resolve_event_order(evt_trans, event_order=event_order)
|
||||
positions = _compute_flow_positions(ordered_events, layout_radius=layout_radius)
|
||||
|
||||
events = set(evt_trans.keys()) | {
|
||||
e for trans in evt_trans.values() for e in trans.keys()
|
||||
}
|
||||
for evt in events:
|
||||
g.node(evt)
|
||||
g = graphviz.Digraph(format=fmt, engine="neato")
|
||||
g.attr(
|
||||
overlap="false",
|
||||
splines="true",
|
||||
outputorder="edgesfirst",
|
||||
pad="0.5",
|
||||
sep="+9",
|
||||
esep="+4",
|
||||
bgcolor="white",
|
||||
dpi="180",
|
||||
)
|
||||
g.attr(
|
||||
"node",
|
||||
shape="circle",
|
||||
fixedsize="true",
|
||||
width=f"{node_diameter:.2f}",
|
||||
height=f"{node_diameter:.2f}",
|
||||
fontsize="11",
|
||||
fontname="Helvetica",
|
||||
style="filled",
|
||||
fillcolor="white",
|
||||
color="#374151",
|
||||
fontcolor="#111827",
|
||||
penwidth="1.8",
|
||||
peripheries="1",
|
||||
)
|
||||
g.attr(
|
||||
"edge",
|
||||
fontname="Helvetica",
|
||||
)
|
||||
|
||||
for src, dsts in evt_trans.items():
|
||||
for dst, prob in dsts.items():
|
||||
if prob > threshold:
|
||||
g.edge(src, dst, label=f"{prob:.2f}")
|
||||
for evt in ordered_events:
|
||||
x, y = positions[evt]
|
||||
g.node(evt, label=_format_node_label(evt), pos=f"{x:.2f},{y:.2f}!", pin="true")
|
||||
|
||||
edges = [
|
||||
(src, dst, prob)
|
||||
for src, dsts in evt_trans.items()
|
||||
for dst, prob in dsts.items()
|
||||
if prob > threshold
|
||||
]
|
||||
edge_set = {(src, dst) for src, dst, _ in edges}
|
||||
|
||||
for src, dst, prob in sorted(edges, key=lambda row: row[2]):
|
||||
edge_attrs: Dict[str, str] = _edge_style(prob)
|
||||
|
||||
if src == dst:
|
||||
# pick a loop port away from the main flow
|
||||
sx, sy = positions[src]
|
||||
loop_port = "n" if sy <= 0 else "s"
|
||||
edge_attrs.update({"tailport": loop_port, "headport": loop_port})
|
||||
else:
|
||||
has_reverse = (dst, src) in edge_set
|
||||
tail_port, head_port = _edge_ports(src, dst, positions, has_reverse)
|
||||
edge_attrs.update({"tailport": tail_port, "headport": head_port})
|
||||
if has_reverse:
|
||||
edge_attrs["constraint"] = "false"
|
||||
|
||||
if prob >= label_threshold or src == dst:
|
||||
edge_attrs["label"] = f" {prob:.2f} "
|
||||
|
||||
g.edge(src, dst, **edge_attrs)
|
||||
|
||||
g.render(output, view=view, cleanup=True)
|
||||
print(f"Saved MDP graph to {output}.{fmt}")
|
||||
@@ -342,11 +501,6 @@ if __name__ == "__main__":
|
||||
f"Built MDP: {human_mdp['num_states']} states, "
|
||||
f"{sum(len(t) for t in human_mdp['transitions'].values())} transitions"
|
||||
)
|
||||
if not human_mdp["states"]:
|
||||
exit("No states found")
|
||||
visualize_mdp(
|
||||
human_model, threshold=0.05, output="human_mdp_viz", fmt="pdf", export_dot=True
|
||||
)
|
||||
|
||||
agent_model = AgentBehaviorModel(agent_dir)
|
||||
agent_mdp = agent_model.build_MDP()
|
||||
@@ -355,14 +509,35 @@ if __name__ == "__main__":
|
||||
f"AGENT... Built MDP: {agent_mdp['num_states']} states, "
|
||||
f"{sum(len(t) for t in agent_mdp['transitions'].values())} transitions"
|
||||
)
|
||||
if not agent_mdp["states"]:
|
||||
exit("No states found")
|
||||
visualize_mdp(
|
||||
agent_model, threshold=0.05, output="agent_mdp_viz", fmt="pdf", export_dot=True
|
||||
)
|
||||
|
||||
human_evt = aggregate_event_transitions(human_mdp)
|
||||
agent_evt = aggregate_event_transitions(agent_mdp)
|
||||
canonical_events = sorted(
|
||||
(set(human_evt.keys()) | {e for tr in human_evt.values() for e in tr.keys()})
|
||||
| (set(agent_evt.keys()) | {e for tr in agent_evt.values() for e in tr.keys()})
|
||||
)
|
||||
|
||||
if not human_mdp["states"]:
|
||||
exit("No states found")
|
||||
visualize_mdp(
|
||||
human_model,
|
||||
threshold=0.05,
|
||||
output="human_mdp_viz",
|
||||
fmt="pdf",
|
||||
export_dot=True,
|
||||
event_order=canonical_events,
|
||||
)
|
||||
|
||||
if not agent_mdp["states"]:
|
||||
exit("No states found")
|
||||
visualize_mdp(
|
||||
agent_model,
|
||||
threshold=0.05,
|
||||
output="agent_mdp_viz",
|
||||
fmt="pdf",
|
||||
export_dot=True,
|
||||
event_order=canonical_events,
|
||||
)
|
||||
|
||||
common = set(human_evt.keys()) & set(agent_evt.keys())
|
||||
|
||||
@@ -394,6 +569,7 @@ if __name__ == "__main__":
|
||||
output="joint_mdp_viz",
|
||||
fmt="pdf",
|
||||
export_dot=True,
|
||||
event_order=canonical_events,
|
||||
)
|
||||
|
||||
inter_class_avg = float(np.mean([kl for _, kl in kl_divs]))
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
"""Vectorized KL divergence for separability scoring."""
|
||||
|
||||
import numpy as np
|
||||
from typing import Tuple
|
||||
|
||||
from lib.agent_probability import (
|
||||
DEFAULT_AGENT_PRIOR,
|
||||
estimate_agent_probability_batch,
|
||||
)
|
||||
|
||||
try:
|
||||
import jax.numpy as jnp
|
||||
from jax import jit
|
||||
|
||||
JAX_AVAILABLE = True
|
||||
except ImportError:
|
||||
jnp, JAX_AVAILABLE = np, False
|
||||
def jit(f): return f
|
||||
|
||||
def jit(f):
|
||||
return f
|
||||
|
||||
|
||||
@jit
|
||||
def batch_kl(P, Q_human, Q_agent, eps=1e-10):
|
||||
@@ -20,10 +30,15 @@ def batch_kl(P, Q_human, Q_agent, eps=1e-10):
|
||||
delta_a = jnp.sum(p * jnp.log(p / qa), axis=(1, 2))
|
||||
return delta_h, delta_a
|
||||
|
||||
def compute_divergences(session_trans: np.ndarray, ref_human: np.ndarray, ref_agent: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
||||
|
||||
def compute_divergences(
|
||||
session_trans: np.ndarray, ref_human: np.ndarray, ref_agent: np.ndarray
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""Compute KL divergence of each session from human/agent prototypes."""
|
||||
if JAX_AVAILABLE:
|
||||
dh, da = batch_kl(jnp.array(session_trans), jnp.array(ref_human), jnp.array(ref_agent))
|
||||
dh, da = batch_kl(
|
||||
jnp.array(session_trans), jnp.array(ref_human), jnp.array(ref_agent)
|
||||
)
|
||||
return np.asarray(dh), np.asarray(da)
|
||||
# numpy fallback
|
||||
eps = 1e-10
|
||||
@@ -34,10 +49,19 @@ def compute_divergences(session_trans: np.ndarray, ref_human: np.ndarray, ref_ag
|
||||
delta_a = np.sum(p * np.log(p / qa), axis=(1, 2))
|
||||
return delta_h, delta_a
|
||||
|
||||
def estimate_alpha_batch(prob_agent: np.ndarray, delta_h: np.ndarray, delta_a: np.ndarray, temp: float = 1.0) -> np.ndarray:
|
||||
"""Vectorized alpha estimation from classifier probs and divergences."""
|
||||
mass = delta_h + delta_a
|
||||
ratio = np.where(mass > 1e-8, delta_a / mass, 0.5)
|
||||
blended = 0.5 * prob_agent + 0.5 * ratio
|
||||
if temp <= 0: return np.clip(blended, 0.0, 1.0)
|
||||
return np.clip(1.0 / (1.0 + np.exp(-temp * (blended - 0.5))), 0.0, 1.0)
|
||||
|
||||
def estimate_alpha_batch(
|
||||
prob_agent: np.ndarray,
|
||||
delta_h: np.ndarray,
|
||||
delta_a: np.ndarray,
|
||||
temp: float = 1.0,
|
||||
prior_agent: float = DEFAULT_AGENT_PRIOR,
|
||||
) -> np.ndarray:
|
||||
"""Vectorized alpha estimation using divergence gap mapping."""
|
||||
_ = prob_agent
|
||||
return estimate_agent_probability_batch(
|
||||
delta_h=np.asarray(delta_h, dtype=float),
|
||||
delta_a=np.asarray(delta_a, dtype=float),
|
||||
temperature=temp,
|
||||
prior_agent=prior_agent,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user