From 3ae9b9731233477bcc39ea25196bd06e165f8f3a Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Mon, 23 Mar 2026 23:47:16 +0530 Subject: [PATCH 01/14] started the `lipo.py` --- src/hyperactive/opt/lipo.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/hyperactive/opt/lipo.py diff --git a/src/hyperactive/opt/lipo.py b/src/hyperactive/opt/lipo.py new file mode 100644 index 00000000..e69de29b From 7c90f99116c91e728528117d0efd20b8139d03f7 Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Mon, 23 Mar 2026 23:48:02 +0530 Subject: [PATCH 02/14] imports --- src/hyperactive/opt/lipo.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hyperactive/opt/lipo.py b/src/hyperactive/opt/lipo.py index e69de29b..4c130876 100644 --- a/src/hyperactive/opt/lipo.py +++ b/src/hyperactive/opt/lipo.py @@ -0,0 +1,2 @@ +import numpy as np +from lipo import GlobalOptimizer From 1d1087f35918eda603ceade8ffca07121dd8a069 Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Mon, 23 Mar 2026 23:49:06 +0530 Subject: [PATCH 03/14] `__init__()` --- src/hyperactive/opt/lipo.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/hyperactive/opt/lipo.py b/src/hyperactive/opt/lipo.py index 4c130876..1a900e89 100644 --- a/src/hyperactive/opt/lipo.py +++ b/src/hyperactive/opt/lipo.py @@ -1,2 +1,10 @@ import numpy as np from lipo import GlobalOptimizer + +class LIPOOptimizer: + """Parameter-free global optimizer via the lipo package.""" + def __init__(self, search_space, n_iter, experiment, maximize=True): + self.search_space = search_space + self.n_iter = n_iter + self.experiment = experiment + self.maximize = maximize \ No newline at end of file From f09a824b8a2179895b76330938d1578ac0874c49 Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Mon, 23 Mar 2026 23:50:02 +0530 Subject: [PATCH 04/14] `_parse_search_spaces()` --- src/hyperactive/opt/lipo.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/hyperactive/opt/lipo.py b/src/hyperactive/opt/lipo.py index 1a900e89..d14e91f9 100644 --- a/src/hyperactive/opt/lipo.py +++ b/src/hyperactive/opt/lipo.py @@ -3,8 +3,24 @@ class LIPOOptimizer: """Parameter-free global optimizer via the lipo package.""" + def __init__(self, search_space, n_iter, experiment, maximize=True): self.search_space = search_space self.n_iter = n_iter self.experiment = experiment - self.maximize = maximize \ No newline at end of file + self.maximize = maximize + + def _parse_search_space(self): + lower, upper, cats = {}, {}, {} + for key, values in self.search_space.items(): + # Categorical: list of strings + if isinstance(values, list) and isinstance(values[0], str): + cats[key] = values + else: + arr = np.array(values) + lower[key] = float(arr.min()) + upper[key] = float(arr.max()) + # Store grid so we can snap results back later + self._grids = getattr(self, "_grids", {}) + self._grids[key] = arr + return lower, upper, cats \ No newline at end of file From b679f04362a4efdc9aa3769cca2b3a42aabace64 Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Mon, 23 Mar 2026 23:50:38 +0530 Subject: [PATCH 05/14] `_snap_to_grid()` --- src/hyperactive/opt/lipo.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/hyperactive/opt/lipo.py b/src/hyperactive/opt/lipo.py index d14e91f9..82f6c90b 100644 --- a/src/hyperactive/opt/lipo.py +++ b/src/hyperactive/opt/lipo.py @@ -23,4 +23,15 @@ def _parse_search_space(self): # Store grid so we can snap results back later self._grids = getattr(self, "_grids", {}) self._grids[key] = arr - return lower, upper, cats \ No newline at end of file + return lower, upper, cats + + def _snap_to_grid(self, params): + """Snap lipo's continuous output to nearest valid grid point.""" + snapped = {} + for key, val in params.items(): + if key in getattr(self, "_grids", {}): + grid = self._grids[key] + snapped[key] = grid[np.argmin(np.abs(grid - val))] + else: + snapped[key] = val # categorical, pass through + return snapped From 8e5aef64388e57f8bce512ca7b4e58411ae7485f Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Mon, 23 Mar 2026 23:51:07 +0530 Subject: [PATCH 06/14] `solve()` --- src/hyperactive/opt/lipo.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/hyperactive/opt/lipo.py b/src/hyperactive/opt/lipo.py index 82f6c90b..d8297519 100644 --- a/src/hyperactive/opt/lipo.py +++ b/src/hyperactive/opt/lipo.py @@ -35,3 +35,19 @@ def _snap_to_grid(self, params): else: snapped[key] = val # categorical, pass through return snapped + + def solve(self): + lower, upper, cats = self._parse_search_space() + + def wrapped(**kwargs): + return self.experiment(self._snap_to_grid(kwargs)) + + opt = GlobalOptimizer( + wrapped, + lower_bounds=lower, + upper_bounds=upper, + categories=cats, + maximize=self.maximize, + ) + opt.run(self.n_iter) + return self._snap_to_grid(opt.maximum["x"]) \ No newline at end of file From 171e23466de95e1422691d40e9c5a7e328e656a4 Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Mon, 23 Mar 2026 23:53:16 +0530 Subject: [PATCH 07/14] Made LIPOOptimizer importable --- src/hyperactive/opt/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hyperactive/opt/__init__.py b/src/hyperactive/opt/__init__.py index da303a23..ed5ad2b2 100644 --- a/src/hyperactive/opt/__init__.py +++ b/src/hyperactive/opt/__init__.py @@ -4,6 +4,7 @@ from hyperactive.opt.gridsearch import GridSearchSk from hyperactive.opt.random_search import RandomSearchSk +from .lipo import LIPOOoptimizer from .gfo import ( BayesianOptimizer, From 34e66ded0c1a787761e29a4c9d75c1fd18ddf140 Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Mon, 23 Mar 2026 23:54:41 +0530 Subject: [PATCH 08/14] added `lipo` to `pyproject.toml` --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index f77e7362..a145f4a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,6 +99,7 @@ all_extras = [ "optuna<5", "cmaes", # Required for CmaEsOptimizer (optuna's CMA-ES sampler) "lightning", + "lipo", ] From e652fa76c7f6fb22f29af5a3ecbfb1049158f9db Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Mon, 23 Mar 2026 23:58:11 +0530 Subject: [PATCH 09/14] added test for `lipo` in `src/hyperactive/tests` --- src/hyperactive/tests/test_lipo.py | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/hyperactive/tests/test_lipo.py diff --git a/src/hyperactive/tests/test_lipo.py b/src/hyperactive/tests/test_lipo.py new file mode 100644 index 00000000..1f7adfa8 --- /dev/null +++ b/src/hyperactive/tests/test_lipo.py @@ -0,0 +1,41 @@ +import numpy as np +import pytest +from hyperactive.opt.lipo import LIPOOptimizer + + +def sphere(params): + return -(params["x"] ** 2 + params["y"] ** 2) + + +def test_lipo_basic(): + opt = LIPOOptimizer( + search_space={ + "x": np.arange(-5, 5, 0.1), + "y": np.arange(-5, 5, 0.1), + }, + n_iter=50, + experiment=sphere, + ) + best = opt.solve() + assert "x" in best and "y" in best + assert abs(best["x"]) < 1.5 # should be near 0 + + +def test_lipo_categorical(): + def fn(p): return 1.0 if p["kernel"] == "rbf" else 0.0 + opt = LIPOOptimizer( + search_space={"kernel": ["linear", "rbf", "poly"]}, + n_iter=20, experiment=fn, + ) + best = opt.solve() + assert best["kernel"] == "rbf" + + +def test_lipo_snap_to_grid(): + def fn(p): return -abs(p["x"] - 3) + opt = LIPOOptimizer( + search_space={"x": np.array([1, 2, 3, 4, 5])}, + n_iter=30, experiment=fn, + ) + best = opt.solve() + assert best["x"] in [1, 2, 3, 4, 5] # must be on the grid \ No newline at end of file From 54e225dc014e55e784390b884f18057be1730078 Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Tue, 24 Mar 2026 00:00:40 +0530 Subject: [PATCH 10/14] added usage exmaple in `examples/lipo/` --- examples/lipo/lipo_examples.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 examples/lipo/lipo_examples.py diff --git a/examples/lipo/lipo_examples.py b/examples/lipo/lipo_examples.py new file mode 100644 index 00000000..98e0d296 --- /dev/null +++ b/examples/lipo/lipo_examples.py @@ -0,0 +1,18 @@ +import numpy as np +from hyperactive.opt.lipo import LIPOOptimizer + + +def objective(params): + x, y = params["x"], params["y"] + return -(x ** 2 + y ** 2) # max at (0, 0) + + +opt = LIPOOptimizer( + search_space={ + "x": np.arange(-5, 5, 0.1), + "y": np.arange(-5, 5, 0.1), + }, + n_iter=100, + experiment=objective, +) +print(opt.solve()) # {'x': ~0.0, 'y': ~0.0} \ No newline at end of file From 2a464b92120ab2df426278472eda809b4e13912d Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Tue, 24 Mar 2026 00:05:05 +0530 Subject: [PATCH 11/14] made changes in `README.md` --- README.md | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0562174b..5e734982 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ pip install hyperactive ```bash pip install hyperactive[sklearn-integration] # scikit-learn integration pip install hyperactive[sktime-integration] # sktime/skpro integration +pip install hyperactive[lipo-integration] # lipo global optimizer pip install hyperactive[all_extras] # Everything including Optuna ``` @@ -111,7 +112,7 @@ pip install hyperactive[all_extras] # Everything including Optuna Multiple Backends
- GFO algorithms, Optuna samplers, and sklearn search methods through one unified API. + GFO algorithms, Optuna samplers, sklearn search methods, and lipo's parameter-free global optimizer through one unified API. Stable & Tested
@@ -177,13 +178,13 @@ flowchart TB GFO["GFO
21 algorithms"] OPTUNA["Optuna
8 algorithms"] SKL["sklearn
2 algorithms"] - MORE["...
more to come"] + LIPO["LIPO
1 algorithm"] end OPT --> GFO OPT --> OPTUNA OPT --> SKL - OPT --> MORE + OPT --> LIPO end subgraph OUT["Output"] @@ -366,6 +367,34 @@ best_params = optimizer.solve() +
+LIPO Global Optimizer + +```python +import numpy as np +from hyperactive.opt.lipo import LIPOOptimizer + +def objective(params): + x, y = params["x"], params["y"] + return -(x**2 + y**2) + +search_space = { + "x": np.arange(-5, 5, 0.1), + "y": np.arange(-5, 5, 0.1), +} + +optimizer = LIPOOptimizer( + search_space=search_space, + n_iter=100, + experiment=objective, +) +best_params = optimizer.solve() +``` + +
+ + +
Time Series Forecasting with sktime @@ -508,4 +537,4 @@ If you use this software in your research, please cite: ## License -[MIT License](./LICENSE) - Free for commercial and academic use. +[MIT License](./LICENSE) - Free for commercial and academic use. \ No newline at end of file From 3b0aa727487b4dfddd93fa3d4c55e4572f85d7ca Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Tue, 24 Mar 2026 00:34:28 +0530 Subject: [PATCH 12/14] fixed the errors --- src/hyperactive/opt/__init__.py | 2 +- src/hyperactive/opt/lipo.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/hyperactive/opt/__init__.py b/src/hyperactive/opt/__init__.py index ed5ad2b2..4daa4ebc 100644 --- a/src/hyperactive/opt/__init__.py +++ b/src/hyperactive/opt/__init__.py @@ -4,7 +4,7 @@ from hyperactive.opt.gridsearch import GridSearchSk from hyperactive.opt.random_search import RandomSearchSk -from .lipo import LIPOOoptimizer +from .lipo import LIPOOptimizer from .gfo import ( BayesianOptimizer, diff --git a/src/hyperactive/opt/lipo.py b/src/hyperactive/opt/lipo.py index d8297519..51cb7f10 100644 --- a/src/hyperactive/opt/lipo.py +++ b/src/hyperactive/opt/lipo.py @@ -35,7 +35,7 @@ def _snap_to_grid(self, params): else: snapped[key] = val # categorical, pass through return snapped - + def solve(self): lower, upper, cats = self._parse_search_space() @@ -50,4 +50,5 @@ def wrapped(**kwargs): maximize=self.maximize, ) opt.run(self.n_iter) - return self._snap_to_grid(opt.maximum["x"]) \ No newline at end of file + + return self._snap_to_grid(opt.optimum[0]) \ No newline at end of file From 2ffebd0841125ad98ea9d659402b5adb66857a48 Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Tue, 28 Apr 2026 04:52:28 +0000 Subject: [PATCH 13/14] fixed the precommits --- README.md | 2 +- examples/lipo/lipo_examples.py | 8 ++++++-- src/hyperactive/opt/__init__.py | 3 ++- src/hyperactive/opt/lipo.py | 10 +++++++--- src/hyperactive/tests/test_lipo.py | 28 +++++++++++++++++++++------- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 5e734982..a3ffadb3 100644 --- a/README.md +++ b/README.md @@ -537,4 +537,4 @@ If you use this software in your research, please cite: ## License -[MIT License](./LICENSE) - Free for commercial and academic use. \ No newline at end of file +[MIT License](./LICENSE) - Free for commercial and academic use. diff --git a/examples/lipo/lipo_examples.py b/examples/lipo/lipo_examples.py index 98e0d296..22ab533a 100644 --- a/examples/lipo/lipo_examples.py +++ b/examples/lipo/lipo_examples.py @@ -1,10 +1,14 @@ +"""Example usage of LIPOOptimizer.""" + import numpy as np + from hyperactive.opt.lipo import LIPOOptimizer def objective(params): + """Sphere function — maximum at origin.""" x, y = params["x"], params["y"] - return -(x ** 2 + y ** 2) # max at (0, 0) + return -(x**2 + y**2) # max at (0, 0) opt = LIPOOptimizer( @@ -15,4 +19,4 @@ def objective(params): n_iter=100, experiment=objective, ) -print(opt.solve()) # {'x': ~0.0, 'y': ~0.0} \ No newline at end of file +print(opt.solve()) # {'x': ~0.0, 'y': ~0.0} diff --git a/src/hyperactive/opt/__init__.py b/src/hyperactive/opt/__init__.py index 4daa4ebc..6d8d88a9 100644 --- a/src/hyperactive/opt/__init__.py +++ b/src/hyperactive/opt/__init__.py @@ -4,7 +4,6 @@ from hyperactive.opt.gridsearch import GridSearchSk from hyperactive.opt.random_search import RandomSearchSk -from .lipo import LIPOOptimizer from .gfo import ( BayesianOptimizer, @@ -29,6 +28,7 @@ StochasticHillClimbing, TreeStructuredParzenEstimators, ) +from .lipo import LIPOOptimizer from .optuna import ( CmaEsOptimizer, GPOptimizer, @@ -43,6 +43,7 @@ __all__ = [ "GridSearchSk", "RandomSearchSk", + "LIPOOptimizer", "HillClimbing", "RepulsingHillClimbing", "StochasticHillClimbing", diff --git a/src/hyperactive/opt/lipo.py b/src/hyperactive/opt/lipo.py index 51cb7f10..4eea3326 100644 --- a/src/hyperactive/opt/lipo.py +++ b/src/hyperactive/opt/lipo.py @@ -1,9 +1,12 @@ +"""LIPO optimizer integration for Hyperactive.""" + import numpy as np from lipo import GlobalOptimizer + class LIPOOptimizer: """Parameter-free global optimizer via the lipo package.""" - + def __init__(self, search_space, n_iter, experiment, maximize=True): self.search_space = search_space self.n_iter = n_iter @@ -33,10 +36,11 @@ def _snap_to_grid(self, params): grid = self._grids[key] snapped[key] = grid[np.argmin(np.abs(grid - val))] else: - snapped[key] = val # categorical, pass through + snapped[key] = val # categorical, pass through return snapped def solve(self): + """Run optimizer and return best parameters as a dict.""" lower, upper, cats = self._parse_search_space() def wrapped(**kwargs): @@ -51,4 +55,4 @@ def wrapped(**kwargs): ) opt.run(self.n_iter) - return self._snap_to_grid(opt.optimum[0]) \ No newline at end of file + return self._snap_to_grid(opt.optimum[0]) diff --git a/src/hyperactive/tests/test_lipo.py b/src/hyperactive/tests/test_lipo.py index 1f7adfa8..a3c9f080 100644 --- a/src/hyperactive/tests/test_lipo.py +++ b/src/hyperactive/tests/test_lipo.py @@ -1,13 +1,17 @@ +"""Tests for LIPOOptimizer.""" + import numpy as np -import pytest + from hyperactive.opt.lipo import LIPOOptimizer def sphere(params): + """Sphere function — minimum at origin.""" return -(params["x"] ** 2 + params["y"] ** 2) def test_lipo_basic(): + """LIPOOptimizer finds near-zero optimum on a continuous grid.""" opt = LIPOOptimizer( search_space={ "x": np.arange(-5, 5, 0.1), @@ -18,24 +22,34 @@ def test_lipo_basic(): ) best = opt.solve() assert "x" in best and "y" in best - assert abs(best["x"]) < 1.5 # should be near 0 + assert abs(best["x"]) < 1.5 # should be near 0 def test_lipo_categorical(): - def fn(p): return 1.0 if p["kernel"] == "rbf" else 0.0 + """LIPOOptimizer selects the best categorical value.""" + + def fn(p): + return 1.0 if p["kernel"] == "rbf" else 0.0 + opt = LIPOOptimizer( search_space={"kernel": ["linear", "rbf", "poly"]}, - n_iter=20, experiment=fn, + n_iter=20, + experiment=fn, ) best = opt.solve() assert best["kernel"] == "rbf" def test_lipo_snap_to_grid(): - def fn(p): return -abs(p["x"] - 3) + """_snap_to_grid maps continuous output to the nearest grid point.""" + + def fn(p): + return -abs(p["x"] - 3) + opt = LIPOOptimizer( search_space={"x": np.array([1, 2, 3, 4, 5])}, - n_iter=30, experiment=fn, + n_iter=30, + experiment=fn, ) best = opt.solve() - assert best["x"] in [1, 2, 3, 4, 5] # must be on the grid \ No newline at end of file + assert best["x"] in [1, 2, 3, 4, 5] # must be on the grid From ce40556e0bfccae6bf2f8f7c9fc7e4cf5482f1cb Mon Sep 17 00:00:00 2001 From: Direk Kakkar Date: Tue, 28 Apr 2026 05:33:41 +0000 Subject: [PATCH 14/14] failure for tests --- src/hyperactive/opt/lipo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hyperactive/opt/lipo.py b/src/hyperactive/opt/lipo.py index 4eea3326..94fc29fc 100644 --- a/src/hyperactive/opt/lipo.py +++ b/src/hyperactive/opt/lipo.py @@ -1,7 +1,6 @@ """LIPO optimizer integration for Hyperactive.""" import numpy as np -from lipo import GlobalOptimizer class LIPOOptimizer: @@ -41,6 +40,8 @@ def _snap_to_grid(self, params): def solve(self): """Run optimizer and return best parameters as a dict.""" + from lipo import GlobalOptimizer + lower, upper, cats = self._parse_search_space() def wrapped(**kwargs):