Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 549c189

Browse files
authored
Merge pull request #45 from mlojek/29
29
2 parents 7290a79 + 885152b commit 549c189

68 files changed

Lines changed: 4286 additions & 575 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,19 @@ jobs:
1212

1313
strategy:
1414
matrix:
15-
python-version: [3.11, 3.12, 3.13]
15+
python-version: [3.11, 3.12, 3.13, 3.14]
1616

1717
steps:
1818
- name: code checkout
19-
uses: actions/checkout@v2
19+
uses: actions/checkout@v6
2020

21-
- name: python setup
22-
uses: actions/setup-python@v2
21+
- name: Install uv
22+
uses: astral-sh/setup-uv@v7
2323
with:
2424
python-version: ${{ matrix.python-version }}
2525

2626
- name: dependencies installation
27-
run: |
28-
python -m pip install --upgrade pip
29-
make install
30-
make clean
27+
run: make install
3128

3229
- name: static code analysis
3330
run: make check

.github/workflows/dockerhub.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
id-token: write
2121
steps:
2222
- name: Check out the repo
23-
uses: actions/checkout@v4
23+
uses: actions/checkout@v6
2424

2525
- name: Log in to Docker Hub
2626
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a

.github/workflows/pypi.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ jobs:
1212
runs-on: ubuntu-latest
1313

1414
steps:
15-
- uses: actions/checkout@v4
15+
- uses: actions/checkout@v6
1616

17-
- uses: actions/setup-python@v5
17+
- name: Install uv
18+
uses: astral-sh/setup-uv@v7
1819
with:
1920
python-version: "3.13"
2021

CLAUDE.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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.

Dockerfile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
FROM python:3.13
1+
FROM python:3.13-slim
2+
3+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
24

35
WORKDIR /optilab
46

57
COPY . .
68

7-
RUN make install
9+
RUN uv sync --frozen
810

9-
ENTRYPOINT /bin/bash
11+
ENTRYPOINT ["/bin/bash"]

Makefile

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
all_code = src tests
22

33
install:
4-
pip install -e .
4+
uv sync
55

66
install_dev: install
7-
pre-commit install
7+
uvx pre-commit install
88

99
build_wheel:
10-
pip install build wheel twine
11-
python -m build --wheel . --outdir dist/
10+
uv build
1211

1312
docker: clean
1413
docker build . -t mlojek/optilab
@@ -17,18 +16,16 @@ clean:
1716
git clean -fdx
1817

1918
format:
20-
isort ${all_code} --profile black
21-
black ${all_code}
22-
pyprojectsort pyproject.toml
19+
uvx ruff format ${all_code}
20+
uvx pyprojectsort pyproject.toml
2321

2422
check: format
25-
black ${all_code} --check
26-
isort ${all_code} --check --profile black
27-
pylint ${all_code}
28-
pyprojectsort pyproject.toml --check
23+
uvx ruff check ${all_code}
24+
uvx ty check ${all_code}
25+
uvx pyprojectsort pyproject.toml --check
2926

3027
test:
31-
pytest
28+
uv run pytest
3229

3330
doc:
34-
sphinx-apidoc -o docs src/optilab -f
31+
uvx sphinx-apidoc -o docs src/optilab -f

pyproject.toml

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
[build-system]
2+
build-backend = "setuptools.build_meta"
3+
requires = [
4+
"setuptools",
5+
]
6+
7+
[dependency-groups]
8+
dev = [
9+
"jupyter>=1.1.1",
10+
"pre-commit>=4.6.0",
11+
"pyprojectsort>=0.4.0",
12+
"pytest>=9.0.3",
13+
"ruff>=0.15.12",
14+
"setuptools>=60,<70",
15+
"sphinx-rtd-theme>=3.1.0",
16+
"sphinx>=9.0.4",
17+
"ty>=0.0.32",
18+
]
19+
120
[project]
221
authors = [
322
{ email = "marcin.lojek@pw.edu.pl", name = "mlojek" },
@@ -8,49 +27,29 @@ classifiers = [
827
"Programming Language :: Python :: 3",
928
]
1029
dependencies = [
11-
"black",
12-
"cma==4.0.0",
30+
"cma>=4.0.0",
1331
"faiss-cpu",
14-
"isort",
15-
"jupyter",
16-
"matplotlib==3.9.2",
17-
"numpy==2.1.3",
18-
"opfunu==1.0.0",
19-
"pandas==2.2.3",
20-
"pre-commit",
21-
"pylint",
22-
"pyprojectsort",
23-
"pytest",
24-
"scikit-learn==1.5.2",
25-
"setuptools",
26-
"shapely==2.0.6",
27-
"sphinx",
28-
"sphinx-rtd-theme",
32+
"matplotlib>=3.9.2",
33+
"numpy>=2.1.3",
34+
"opfunu>=1.0.0",
35+
"pandas>=2.2.3",
36+
"pydantic>=2.0",
37+
"scikit-learn>=1.5.2",
38+
"setuptools<70",
39+
"shapely>=2.0.6",
2940
"tabulate",
3041
"tqdm",
31-
"typing",
3242
"xgboost",
3343
"zstandard",
3444
]
3545
description = "Simple framework for optimization functions and metamodels"
3646
name = "optilab"
3747
readme = "README.md"
3848
requires-python = ">=3.11.9"
39-
version = "28"
49+
version = "29"
4050

4151
[project.scripts]
4252
optilab = "optilab.__main__:main"
4353

4454
[project.urls]
4555
Homepage = "https://github.com/mlojek/optilab"
46-
47-
[tool.pylint]
48-
disable = [
49-
"too-few-public-methods",
50-
"too-many-arguments",
51-
"too-many-branches",
52-
"too-many-instance-attributes",
53-
"too-many-locals",
54-
"too-many-positional-arguments",
55-
"too-many-statements",
56-
]

0 commit comments

Comments
 (0)