|
| 1 | +# Getting Started |
| 2 | + |
| 3 | +## Installation |
| 4 | + |
| 5 | +```bash |
| 6 | +uv add gds-psuu |
| 7 | +# or: pip install gds-psuu |
| 8 | +``` |
| 9 | + |
| 10 | +For Bayesian optimization (optional): |
| 11 | + |
| 12 | +```bash |
| 13 | +uv add "gds-psuu[bayesian]" |
| 14 | +# or: pip install gds-psuu[bayesian] |
| 15 | +``` |
| 16 | + |
| 17 | +For development (monorepo): |
| 18 | + |
| 19 | +```bash |
| 20 | +git clone https://github.com/BlockScience/gds-core.git |
| 21 | +cd gds-core |
| 22 | +uv sync --all-packages |
| 23 | +``` |
| 24 | + |
| 25 | +## Your First Parameter Sweep |
| 26 | + |
| 27 | +Define a `gds-sim` model, then sweep a parameter to find the best value: |
| 28 | + |
| 29 | +```python |
| 30 | +from gds_sim import Model, StateUpdateBlock |
| 31 | +from gds_psuu import ( |
| 32 | + KPI, |
| 33 | + Continuous, |
| 34 | + GridSearchOptimizer, |
| 35 | + ParameterSpace, |
| 36 | + Sweep, |
| 37 | + final_value, |
| 38 | + mean_agg, |
| 39 | +) |
| 40 | + |
| 41 | + |
| 42 | +# 1. Define a growth model |
| 43 | +def growth_policy(state, params, **kw): |
| 44 | + return {"delta": state["population"] * params["growth_rate"]} |
| 45 | + |
| 46 | + |
| 47 | +def update_pop(state, params, *, signal=None, **kw): |
| 48 | + return ("population", state["population"] + signal["delta"]) |
| 49 | + |
| 50 | + |
| 51 | +model = Model( |
| 52 | + initial_state={"population": 100.0}, |
| 53 | + state_update_blocks=[ |
| 54 | + StateUpdateBlock( |
| 55 | + policies={"growth": growth_policy}, |
| 56 | + variables={"population": update_pop}, |
| 57 | + ) |
| 58 | + ], |
| 59 | +) |
| 60 | + |
| 61 | +# 2. Define what to search |
| 62 | +space = ParameterSpace( |
| 63 | + params={"growth_rate": Continuous(min_val=0.01, max_val=0.2)} |
| 64 | +) |
| 65 | + |
| 66 | +# 3. Define what to measure |
| 67 | +kpis = [ |
| 68 | + KPI( |
| 69 | + name="avg_final_pop", |
| 70 | + metric=final_value("population"), # per-run: final value |
| 71 | + aggregation=mean_agg, # cross-run: mean |
| 72 | + ), |
| 73 | +] |
| 74 | + |
| 75 | +# 4. Run the sweep |
| 76 | +sweep = Sweep( |
| 77 | + model=model, |
| 78 | + space=space, |
| 79 | + kpis=kpis, |
| 80 | + optimizer=GridSearchOptimizer(n_steps=5), |
| 81 | + timesteps=10, |
| 82 | + runs=3, # 3 Monte Carlo runs per parameter point |
| 83 | +) |
| 84 | +results = sweep.run() |
| 85 | + |
| 86 | +# 5. Inspect results |
| 87 | +best = results.best("avg_final_pop") |
| 88 | +print(f"Best growth_rate: {best.params['growth_rate']:.3f}") |
| 89 | +print(f"Best avg final pop: {best.scores['avg_final_pop']:.1f}") |
| 90 | +``` |
| 91 | + |
| 92 | +## Composable KPIs |
| 93 | + |
| 94 | +The key design is the **Metric + Aggregation = KPI** pattern: |
| 95 | + |
| 96 | +```python |
| 97 | +from gds_psuu import ( |
| 98 | + KPI, |
| 99 | + final_value, |
| 100 | + trajectory_mean, |
| 101 | + max_value, |
| 102 | + mean_agg, |
| 103 | + std_agg, |
| 104 | + percentile_agg, |
| 105 | + probability_above, |
| 106 | +) |
| 107 | + |
| 108 | +# Mean of final population across runs |
| 109 | +avg_final = KPI(name="avg_pop", metric=final_value("population"), aggregation=mean_agg) |
| 110 | + |
| 111 | +# Standard deviation of final population (measures uncertainty) |
| 112 | +std_final = KPI(name="std_pop", metric=final_value("population"), aggregation=std_agg) |
| 113 | + |
| 114 | +# 90th percentile of trajectory means |
| 115 | +p90_mean = KPI(name="p90_mean", metric=trajectory_mean("population"), aggregation=percentile_agg(90)) |
| 116 | + |
| 117 | +# Probability that max population exceeds 500 |
| 118 | +risk = KPI(name="boom_risk", metric=max_value("population"), aggregation=probability_above(500.0)) |
| 119 | +``` |
| 120 | + |
| 121 | +**Metric** extracts a scalar from each run. **Aggregation** reduces the per-run values to a single score. |
| 122 | + |
| 123 | +If no aggregation is specified, `mean_agg` is used by default: |
| 124 | + |
| 125 | +```python |
| 126 | +# These are equivalent: |
| 127 | +KPI(name="avg_pop", metric=final_value("population")) |
| 128 | +KPI(name="avg_pop", metric=final_value("population"), aggregation=mean_agg) |
| 129 | +``` |
| 130 | + |
| 131 | +## Per-Run Distributions |
| 132 | + |
| 133 | +Metric-based KPIs track the full distribution across Monte Carlo runs: |
| 134 | + |
| 135 | +```python |
| 136 | +results = sweep.run() |
| 137 | + |
| 138 | +for ev in results.evaluations: |
| 139 | + dist = ev.distributions["avg_final_pop"] |
| 140 | + print(f" params={ev.params}, per_run={dist}") |
| 141 | + # e.g. per_run=[265.3, 265.3, 265.3] for deterministic model |
| 142 | +``` |
| 143 | + |
| 144 | +## Multiple Optimizers |
| 145 | + |
| 146 | +```python |
| 147 | +from gds_psuu import GridSearchOptimizer, RandomSearchOptimizer |
| 148 | + |
| 149 | +# Exhaustive grid (good for 1-2 dimensions) |
| 150 | +grid = GridSearchOptimizer(n_steps=10) # 10 points per continuous dim |
| 151 | + |
| 152 | +# Random sampling (good for higher dimensions) |
| 153 | +rand = RandomSearchOptimizer(n_samples=50, seed=42) |
| 154 | +``` |
| 155 | + |
| 156 | +For Bayesian optimization (requires `gds-psuu[bayesian]`): |
| 157 | + |
| 158 | +```python |
| 159 | +from gds_psuu.optimizers.bayesian import BayesianOptimizer |
| 160 | + |
| 161 | +bayes = BayesianOptimizer(n_calls=30, target_kpi="avg_final_pop", seed=42) |
| 162 | +``` |
| 163 | + |
| 164 | +## Legacy KPI Support |
| 165 | + |
| 166 | +The older `fn`-based KPI interface still works: |
| 167 | + |
| 168 | +```python |
| 169 | +from gds_psuu import KPI, final_state_mean |
| 170 | + |
| 171 | +# Legacy style (backwards compatible) |
| 172 | +kpi = KPI(name="pop", fn=lambda r: final_state_mean(r, "population")) |
| 173 | +``` |
| 174 | + |
| 175 | +Legacy KPIs don't track per-run distributions -- use metric-based KPIs for full Monte Carlo awareness. |
| 176 | + |
| 177 | +## Next Steps |
| 178 | + |
| 179 | +- [Concepts](guide/concepts.md) -- Metric, Aggregation, KPI, and the full conceptual hierarchy |
| 180 | +- [Parameter Spaces](guide/spaces.md) -- dimensions, validation, and grid generation |
| 181 | +- [Optimizers](guide/optimizers.md) -- grid, random, and Bayesian search strategies |
| 182 | +- [API Reference](api/init.md) -- complete auto-generated API docs |
0 commit comments