Skip to content

Commit 824844a

Browse files
authored
Merge pull request #8 from wniec/switch_to_ioh
Switch to ioh
2 parents 5daf16b + 5660d6c commit 824844a

21 files changed

Lines changed: 647 additions & 2318 deletions

README.md

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,65 @@ Submit all agents for a given seed and portfolio:
302302
bash runner.sh
303303
```
304304

305-
Individual SLURM scripts:
305+
Each script accepts positional arguments: `SEED [PORTFOLIO...]` (RL-DAS takes only `SEED`).
306306

307-
| Script | Agent |
308-
|---|---|
309-
| `ppo_study.slurm` | PPO |
310-
| `rl_das_study.slurm` | RL-DAS |
311-
| `exp_das_study.slurm` | Exp-DAS |
312-
| `baselines.slurm` | Baselines |
307+
```bash
308+
sbatch baselines.slurm 42 CPSO NM TDE
309+
sbatch ppo_study.slurm 42 CPSO NM TDE
310+
sbatch rl_das_study.slurm 42
311+
sbatch exp_das_study.slurm 42 CPSO NM TDE
312+
```
313+
314+
### `baselines.slurm`
315+
316+
Single job (no array). Runs all baseline agent types (`random`, `fixed:*`, `single:*`, oracle) across all dimensions.
317+
318+
### `ppo_study.slurm` — array 0–9
319+
320+
| Task | CV mode | Dimensions |
321+
|------|---------|------------|
322+
| 0 | LOIO | 2 |
323+
| 1 | LOIO | 3 |
324+
| 2 | LOIO | 5 |
325+
| 3 | LOIO | 10 |
326+
| 4 | LOPO | 2 |
327+
| 5 | LOPO | 3 |
328+
| 6 | LOPO | 5 |
329+
| 7 | LOPO | 10 |
330+
| 8 | LOIO | 2, 3, 5, 10 (multi-dim) |
331+
| 9 | LOPO | 2, 3, 5, 10 (multi-dim) |
332+
333+
### `rl_das_study.slurm` — array 0–7
334+
335+
Fixed DE portfolio (`NL_SHADE_RSP / MADDE / JDE21`). One model per dimension.
336+
337+
| Task | CV mode | Dimension |
338+
|------|---------|-----------|
339+
| 0 | LOIO | 2 |
340+
| 1 | LOIO | 3 |
341+
| 2 | LOIO | 5 |
342+
| 3 | LOIO | 10 |
343+
| 4 | LOPO | 2 |
344+
| 5 | LOPO | 3 |
345+
| 6 | LOPO | 5 |
346+
| 7 | LOPO | 10 |
347+
348+
### `exp_das_study.slurm` — array 0–11
349+
350+
| Task | CV mode | Dimensions |
351+
|------|---------|------------|
352+
| 0 | LOIO | 2, 5, 10 (multi-dim) |
353+
| 1 | LOPO | 2, 5, 10 (multi-dim) |
354+
| 2 | LOIO | 2, 3, 5, 10 (multi-dim) |
355+
| 3 | LOPO | 2, 3, 5, 10 (multi-dim) |
356+
| 4 | LOIO | 2 |
357+
| 5 | LOPO | 2 |
358+
| 6 | LOIO | 3 |
359+
| 7 | LOPO | 3 |
360+
| 8 | LOIO | 5 |
361+
| 9 | LOPO | 5 |
362+
| 10 | LOIO | 10 |
363+
| 11 | LOPO | 10 |
313364

314365
---
315366

agents/exponential_das/trainer.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717

1818
import numpy as np
1919
import torch
20+
from tqdm import tqdm
2021

2122
from agents.exponential_das.agent import ExpDASAgent
2223
from das.env.das_env import DASEnv
23-
from das.training.common import compute_run_stats
24+
from das.training.common import compute_run_stats, get_ioh_optimum
2425

2526

2627
def train(
@@ -64,7 +65,8 @@ def train(
6465
episode_rewards: list[float] = []
6566
best_test_reward = -np.inf
6667

67-
for ep in range(1, total_episodes + 1):
68+
pbar = tqdm(range(1, total_episodes + 1), desc=f"train {name}", unit="ep")
69+
for ep in pbar:
6870
obs, info = train_env.reset()
6971
done = False
7072
step_idx = 0
@@ -117,17 +119,22 @@ def train(
117119
)
118120
entry["mean_test_reward"] = mean_test_r
119121
mean_train_r = float(np.mean(episode_rewards[-eval_interval:]))
120-
print(
121-
f"Ep {ep:5d}/{total_episodes}"
122-
f" train={mean_train_r:.4f}"
123-
f" test={mean_test_r:.4f}"
124-
f" entropy={agent.entropy_coef:.4f}"
125-
f" lr={agent.current_lr:.2e}"
126-
f" kl={agent.last_kl:.4f}"
122+
pbar.set_postfix(
123+
train=f"{mean_train_r:.4f}",
124+
test=f"{mean_test_r:.4f}",
125+
ent=f"{agent.entropy_coef:.3f}",
126+
kl=f"{agent.last_kl:.4f}",
127127
)
128128
if mean_test_r > best_test_reward:
129129
best_test_reward = mean_test_r
130130
agent.save(os.path.join(save_dir, f"{name}_best.pt"))
131+
else:
132+
mean_train_r = float(
133+
np.mean(episode_rewards[-min(eval_interval, len(episode_rewards)) :])
134+
)
135+
pbar.set_postfix(
136+
train=f"{mean_train_r:.4f}", ent=f"{agent.entropy_coef:.3f}"
137+
)
131138

132139
if ep % save_interval == 0:
133140
ckpt = os.path.join(save_dir, f"{name}_ep{ep}.pt")
@@ -148,13 +155,10 @@ def evaluate(
148155
env: DASEnv,
149156
agent: ExpDASAgent,
150157
n_episodes: int = 20,
151-
global_optima: dict[str, float] | None = None,
152158
) -> list[dict]:
153159
"""Run the agent deterministically and return per-episode results."""
154-
if global_optima is None:
155-
global_optima = {}
156160
results = []
157-
for _ in range(n_episodes):
161+
for _ in tqdm(range(n_episodes), desc="evaluate", unit="ep", leave=False):
158162
obs, info = env.reset()
159163
problem_id = info.get("problem_id", "")
160164
done = False
@@ -170,7 +174,7 @@ def evaluate(
170174
fitness_history.extend(step_info.get("fitness_history_step", []))
171175

172176
max_fe = step_info.get("n_fe", 0)
173-
global_minimum = global_optima.get(problem_id, 0.0)
177+
global_minimum = get_ioh_optimum(problem_id)
174178
stats = compute_run_stats(fitness_history, max_fe, global_minimum)
175179
results.append(
176180
{

agents/rl_das/env.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def _pop_features(
166166

167167

168168
class RLDASEnv(gym.Env):
169-
"""RL-DAS environment wrapping BBOB problems via a cocoex Suite.
169+
"""RL-DAS environment wrapping optimization problems via an IOHSuite.
170170
171171
Uses a Population object as shared warm-started state across all DE
172172
sub-optimizers (matching the original RL-DAS design).
@@ -176,7 +176,7 @@ class RLDASEnv(gym.Env):
176176
problem_ids:
177177
BBOB problem IDs to cycle through (one per episode).
178178
suite:
179-
cocoex Suite object.
179+
IOHSuite object.
180180
optimizers:
181181
List of instantiated DE optimizer objects (NL_SHADE_RSP, JDE21, MadDE).
182182
dim:

baselines.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,20 @@
3636
import os
3737
import warnings
3838

39-
import cocoex
4039
import numpy as np
4140
from tqdm import tqdm
4241

4342
from das.env.das_env import DASEnv
43+
from das.env.ioh_suite import IOHSuite
4444
from das.optimizers.portfolio import get_portfolio
4545
from das.utils import set_seed
4646
from das.env.bbob_splits import ALL_DIMS, get_train_test_split
47-
from das.training.common import compute_run_stats, load_global_optima
47+
from das.training.common import (
48+
compute_run_stats,
49+
get_ioh_optimum,
50+
ERT_TARGETS,
51+
_ert_key,
52+
)
4853

4954
warnings.filterwarnings("ignore")
5055

@@ -95,7 +100,6 @@ def collect_env_results(
95100
suite,
96101
optimizers: list,
97102
cfg: dict,
98-
global_optima: dict[str, float],
99103
) -> list[dict]:
100104
"""Run policy_fn on every problem in test_ids via DASEnv."""
101105
env = DASEnv(
@@ -112,7 +116,7 @@ def collect_env_results(
112116
for problem_id in tqdm(test_ids, desc=f" {agent_tag}", smoothing=0.0):
113117
step_info, fitness_history = run_episode(env, policy_fn)
114118
max_fe = step_info.get("n_fe", 0)
115-
global_minimum = global_optima.get(problem_id, 0.0)
119+
global_minimum = get_ioh_optimum(problem_id)
116120
stats = compute_run_stats(fitness_history, max_fe, global_minimum)
117121
results.append({problem_id: {**stats, "agent": agent_tag}})
118122
env.close()
@@ -164,13 +168,12 @@ def collect_single_results(
164168
suite,
165169
fe_multiplier: int,
166170
n_individuals: int,
167-
global_optima: dict[str, float],
168171
) -> list[dict]:
169172
"""Run the optimizer independently on every problem in test_ids."""
170173
results = []
171174
for problem_id in tqdm(test_ids, desc=f" {agent_tag}", smoothing=0.0):
172175
problem = suite.get_problem(problem_id)
173-
global_minimum = global_optima.get(problem_id, 0.0)
176+
global_minimum = get_ioh_optimum(problem_id)
174177
stats = run_single_algorithm(
175178
optimizer_class, problem, fe_multiplier, n_individuals, global_minimum
176179
)
@@ -212,6 +215,8 @@ def compute_oracle(all_results: dict[str, list[dict]]) -> tuple[list[dict], list
212215
],
213216
"aocc": best_m["aocc"],
214217
"final_fitness": best_m["final_fitness"],
218+
"hitting_times": best_m.get("hitting_times", {}),
219+
"max_fe": best_m.get("max_fe", 0),
215220
"agent": "oracle-best",
216221
"best_agent": best_m["agent"],
217222
}
@@ -225,6 +230,8 @@ def compute_oracle(all_results: dict[str, list[dict]]) -> tuple[list[dict], list
225230
],
226231
"aocc": worst_m["aocc"],
227232
"final_fitness": worst_m["final_fitness"],
233+
"hitting_times": worst_m.get("hitting_times", {}),
234+
"max_fe": worst_m.get("max_fe", 0),
228235
"agent": "oracle-worst",
229236
"worst_agent": worst_m["agent"],
230237
}
@@ -241,12 +248,29 @@ def compute_oracle(all_results: dict[str, list[dict]]) -> tuple[list[dict], list
241248
# ------------------------------------------------------------------ #
242249

243250

251+
def _ert_for_target(records: list[dict], target_key: str) -> float | None:
252+
"""ERT = total_FEs / n_successful_runs (unsuccessful runs contribute max_fe)."""
253+
total_fe = 0
254+
n_succ = 0
255+
for r in records:
256+
m = next(iter(r.values()))
257+
ht = m.get("hitting_times", {}).get(target_key)
258+
mfe = m.get("max_fe", 0)
259+
if ht is not None:
260+
total_fe += ht
261+
n_succ += 1
262+
else:
263+
total_fe += mfe
264+
return float(total_fe / n_succ) if n_succ > 0 else None
265+
266+
244267
def summarise(tag: str, records: list[dict]) -> dict:
245268
fitnesses = [next(iter(r.values()))["final_fitness"] for r in records]
246269
aocc_vals = [next(iter(r.values()))["aocc"] for r in records]
247270
auoc_vals = [
248271
next(iter(r.values()))["area_under_optimization_curve"] for r in records
249272
]
273+
ert = {_ert_key(t): _ert_for_target(records, _ert_key(t)) for t in ERT_TARGETS}
250274
return {
251275
"agent": tag,
252276
"n_problems": len(fitnesses),
@@ -256,6 +280,7 @@ def summarise(tag: str, records: list[dict]) -> dict:
256280
"worst_final_fitness": float(np.max(fitnesses)),
257281
"mean_aocc": float(np.mean(aocc_vals)),
258282
"mean_auoc": float(np.mean(auoc_vals)),
283+
"ert": ert,
259284
}
260285

261286

@@ -266,16 +291,23 @@ def save_results(records: list[dict], path: str) -> None:
266291

267292

268293
def print_summary(summaries: list[dict]) -> None:
294+
_ERT_PRINT_TARGET = "1e-04"
269295
width = max(len(s["agent"]) for s in summaries) + 2
270-
header = f" {'Agent':<{width}} {'Mean fitness':>14} {'Median fitness':>14} {'Mean AUOC':>14}"
296+
header = (
297+
f" {'Agent':<{width}} {'Mean fitness':>14} {'Median fitness':>14}"
298+
f" {'Mean AUOC':>14} {'ERT(1e-04)':>12}"
299+
)
271300
print(header)
272301
print(" " + "-" * (len(header) - 2))
273302
for s in summaries:
303+
ert_val = s.get("ert", {}).get(_ERT_PRINT_TARGET)
304+
ert_str = f"{ert_val:>12.1f}" if ert_val is not None else f"{'inf':>12}"
274305
print(
275306
f" {s['agent']:<{width}} "
276307
f"{s['mean_final_fitness']:>14.4e} "
277308
f"{s['median_final_fitness']:>14.4e} "
278-
f"{s['mean_auoc']:>14.4e}"
309+
f"{s['mean_auoc']:>14.4e} "
310+
f"{ert_str}"
279311
)
280312

281313

@@ -317,7 +349,7 @@ def parse_args():
317349
p.add_argument("-s", "--n-checkpoints", type=int, default=10)
318350
p.add_argument("-x", "--cdb", type=float, default=1.0)
319351
p.add_argument("-O", "--reward-option", type=int, default=1, choices=[1, 2, 3, 4])
320-
p.add_argument("-n", "--n-individuals", type=int, default=100)
352+
p.add_argument("-n", "--n-individuals", type=int, default=None)
321353
p.add_argument("--seed", type=int, default=42)
322354
return p.parse_args()
323355

@@ -334,11 +366,8 @@ def main():
334366

335367
optimizers = get_portfolio(args.portfolio)
336368
opt_names = args.portfolio
337-
cocoex.utilities.MiniPrint()
338-
suite = cocoex.Suite("bbob", "", "")
369+
suite = IOHSuite()
339370
_, test_ids = get_train_test_split(args.mode, args.dims)
340-
global_optima = load_global_optima()
341-
342371
cfg = {
343372
"fe_multiplier": args.fe_multiplier,
344373
"n_checkpoints": args.n_checkpoints,
@@ -384,7 +413,7 @@ def main():
384413

385414
if tag == "random":
386415
records = collect_env_results(
387-
tag, random_policy, test_ids, suite, optimizers, cfg, global_optima
416+
tag, random_policy, test_ids, suite, optimizers, cfg
388417
)
389418

390419
elif tag.startswith("fixed:"):
@@ -402,7 +431,6 @@ def main():
402431
suite,
403432
optimizers,
404433
cfg,
405-
global_optima,
406434
)
407435

408436
elif tag.startswith("single:"):
@@ -415,7 +443,6 @@ def main():
415443
suite,
416444
args.fe_multiplier,
417445
args.n_individuals,
418-
global_optima,
419446
)
420447

421448
else:

0 commit comments

Comments
 (0)