|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Commands |
| 6 | + |
| 7 | +```bash |
| 8 | +make install # uv sync — install all dependencies |
| 9 | +make format # ruff format + pyprojectsort |
| 10 | +make check # format, then ruff lint + ty type check + pyprojectsort --check |
| 11 | +make test # uv run pytest |
| 12 | +make build_wheel # uv build |
| 13 | +``` |
| 14 | + |
| 15 | +Run a single test file or test by name: |
| 16 | +```bash |
| 17 | +uv run pytest tests/data_classes/test_point.py |
| 18 | +uv run pytest -k "test_rank" |
| 19 | +``` |
| 20 | + |
| 21 | +## Architecture |
| 22 | + |
| 23 | +Optilab is a black-box optimization framework. Results are saved as Python pickles and analysed via the CLI (`optilab <pickle_path> [options]`). |
| 24 | + |
| 25 | +### Data layer — `data_classes/` |
| 26 | + |
| 27 | +All data classes are **Pydantic `BaseModel`** subclasses (not standard dataclasses). Always use keyword arguments when constructing them. |
| 28 | + |
| 29 | +- **`Point(x, y, is_evaluated)`** — a single search-space point. `x: np.ndarray | None` is coerced to `float64` via a field validator. Equality is based on `x` only. |
| 30 | +- **`PointList(points)`** — a list of Points with list-like dunder methods (`__iter__`, `__len__`, `__getitem__`/slice, `rank()`, `best()`, etc.). Used both as an optimization log and as a surrogate training set. |
| 31 | +- **`Bounds(lower, upper)`** — search space bounds with `reflect`/`wrap`/`project` handlers and random sampling helpers. |
| 32 | +- **`OptimizationRun`** — the serialised result of one experiment: metadata + logs (`list[PointList]`). |
| 33 | + |
| 34 | +### Function layer — `functions/` |
| 35 | + |
| 36 | +- **`ObjectiveFunction`** — base class. `__call__` validates dimensionality and increments `num_calls`; subclasses must call `super().__call__()` and add `assert point.x is not None` before using `point.x`. |
| 37 | +- Concrete functions live in `unimodal/`, `multimodal/`, and `benchmarks/` (opfunu wrappers). |
| 38 | +- **`SurrogateObjectiveFunction`** (`functions/surrogate/`) — extends `ObjectiveFunction` with a `train(PointList)` method and `is_ready` flag. Implementations: KNN (FAISS), locally-weighted polynomial regression, MLP, XGBoost, plain polynomial regression. |
| 39 | + |
| 40 | +### Optimizer layer — `optimizers/` |
| 41 | + |
| 42 | +- **`Optimizer`** — base class with `optimize()` (single run, returns `PointList`) and `run_optimization()` (runs in parallel via `multiprocessing.Pool`, returns `OptimizationRun`). |
| 43 | +- All concrete optimizers wrap CMA-ES from the `cma` library. The hierarchy is: `Optimizer → CmaEs → IpopCmaEs`, with surrogate variants layering a metamodel on top. |
| 44 | + |
| 45 | +### Metamodel layer — `metamodels/` |
| 46 | + |
| 47 | +Metamodels sit between the optimizer and the objective function. They manage a training set and decide which candidate points to evaluate with the expensive objective function vs. the cheap surrogate. |
| 48 | + |
| 49 | +- **`ApproximateRankingMetamodel`** — the main metamodel. Adaptively evaluates only enough candidates to stabilise the top-`mu` ranking, growing or shrinking the evaluation budget each generation. |
| 50 | +- **`TopHalfMetamodel`** — evaluates only the top half of surrogate-ranked candidates. |
| 51 | +- **`IepolationSurrogate`** — uses convex hull interpolation to decide whether to trust the surrogate. |
| 52 | + |
| 53 | +### Surrogate ↔ Metamodel wiring |
| 54 | + |
| 55 | +Optimizers that use surrogates (e.g. `LmmCmaEs`) construct a `SurrogateObjectiveFunction` and an `ApproximateRankingMetamodel`, then call `metamodel.adapt(candidates)` each generation instead of evaluating candidates directly. The metamodel's `train_set` accumulates evaluated points and retrains the surrogate after each evaluation batch. |
| 56 | + |
| 57 | +### Plotting & utils — `plotting/`, `utils/` |
| 58 | + |
| 59 | +Plotting functions (`plot_convergence_curve`, `plot_ecdf_curves`, `plot_box_plot`) accept `savepath: str | Path | None`. Statistical utilities (`mann_whitney_u_test_grid`, `aggregate_pvalues`, `aggregate_stats`) operate on lists of `OptimizationRun` results loaded from pickles. |
| 60 | + |
| 61 | +### CLI — `cli.py` / `__main__.py` |
| 62 | + |
| 63 | +`__main__.py` is only argument parsing; all logic lives in `OptilabCLI` in `cli.py`. The class holds accumulator DataFrames across files and delegates per-file work to `_analyze_file`, `_plot`, `_report_stats`, `_test_y`, `_test_evals`, and `_finalize`. |
| 64 | + |
| 65 | +## Type checking notes |
| 66 | + |
| 67 | +- `ty` is the type checker (`uvx ty check src tests`). FAISS stub signatures are wrong (extra `n` parameter); suppress with bare `# type: ignore` on those lines only. |
| 68 | +- `PointList.__iter__` overrides `BaseModel.__iter__` with an incompatible return type; suppressed with `# type: ignore` on that line. |
| 69 | +- pandas `columns=list(...)` calls require `# type: ignore` due to stub incompatibility. |
0 commit comments