From e25e8a25041922517cecba187f20750d4674c1c8 Mon Sep 17 00:00:00 2001 From: Janos Gabler Date: Sun, 17 Aug 2025 14:55:24 +0200 Subject: [PATCH 1/5] Initial wrapper. --- src/optimini/converter.py | 18 ++++++++++++++++++ src/optimini/internal_problem.py | 10 ++++++++++ src/optimini/minimize.py | 23 +++++++++++++++++++++++ tests/test_optimini.py | 26 ++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 src/optimini/converter.py create mode 100644 src/optimini/internal_problem.py create mode 100644 src/optimini/minimize.py diff --git a/src/optimini/converter.py b/src/optimini/converter.py new file mode 100644 index 0000000..71f2a38 --- /dev/null +++ b/src/optimini/converter.py @@ -0,0 +1,18 @@ +import numpy as np + + +class Converter: + """Class to convert between parameter dictionaries and numpy arrays""" + + def __init__(self, params): + self.original = params + + def flatten(self, params): + if isinstance(params, dict): + params = np.array(list(params.values())) + return params + + def unflatten(self, x): + if isinstance(self.original, dict): + x = dict(zip(self.original.keys(), x.tolist(), strict=False)) + return x diff --git a/src/optimini/internal_problem.py b/src/optimini/internal_problem.py new file mode 100644 index 0000000..5626e03 --- /dev/null +++ b/src/optimini/internal_problem.py @@ -0,0 +1,10 @@ +class InternalProblem: + """Wraps a user provided function to add functionality""" + + def __init__(self, fun, converter): + self._user_fun = fun + self._converter = converter + + def fun(self, x): + params = self._converter.unflatten(x) + return self._user_fun(params) diff --git a/src/optimini/minimize.py b/src/optimini/minimize.py new file mode 100644 index 0000000..502199a --- /dev/null +++ b/src/optimini/minimize.py @@ -0,0 +1,23 @@ +from copy import deepcopy + +from scipy.optimize import minimize as scipy_minimize + +from optimini.converter import Converter +from optimini.internal_problem import InternalProblem + + +def minimize(fun, params, method, options=None): + """Minimize a function using a given method""" + options = {} if options is None else options + converter = Converter(params) + internal_fun = InternalProblem(fun, converter) + x0 = converter.flatten(params) + raw_res = scipy_minimize( + fun=internal_fun.fun, + x0=x0, + method=method, + options=options, + ) + res = deepcopy(raw_res) + res.x = converter.unflatten(res.x) + return res diff --git a/tests/test_optimini.py b/tests/test_optimini.py index e69de29..518938c 100644 --- a/tests/test_optimini.py +++ b/tests/test_optimini.py @@ -0,0 +1,26 @@ +import numpy as np + +from optimini.minimize import minimize + + +def dict_fun(params): + return params["a"] ** 2 + params["b"] ** 2 + + +def array_fun(params): + return params @ params + + +def test_simple_minimize_with_dict_params(): + params = {"a": 1, "b": 2} + res = minimize(dict_fun, params, method="L-BFGS-B") + assert isinstance(res.x, dict) + assert np.allclose(res.x["a"], 0) + assert np.allclose(res.x["b"], 0) + + +def test_simple_minimize_with_array_params(): + params = np.array([1, 2]) + res = minimize(array_fun, params, method="L-BFGS-B") + assert isinstance(res.x, np.ndarray) + assert np.allclose(res.x, np.array([0, 0])) From 4867da5492637ac6ceb1b2c8ce37c5e02f7466c4 Mon Sep 17 00:00:00 2001 From: Janos Gabler Date: Sun, 17 Aug 2025 15:00:43 +0200 Subject: [PATCH 2/5] Add example. --- examples/example.ipynb | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/examples/example.ipynb b/examples/example.ipynb index 681a677..a5fdb54 100644 --- a/examples/example.ipynb +++ b/examples/example.ipynb @@ -7,11 +7,44 @@ "source": [ "# Showcase how optimini is used" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "from optimini.minimize import minimize\n", + "\n", + "\n", + "def my_fun(params):\n", + " return params[\"a\"] ** 2 + params[\"b\"] ** 2\n", + "\n", + "\n", + "params = {\"a\": 1, \"b\": 2}\n", + "res = minimize(my_fun, params, method=\"L-BFGS-B\")\n", + "res.x" + ] } ], "metadata": { + "kernelspec": { + "display_name": "optimini", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.11" } }, "nbformat": 4, From b656480df38e01338d1997cefae11aeeb0658b9a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 Aug 2025 13:05:21 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d582e78..bb52865 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # optimini -A super minimalist bare bones rewrite to illustrate the core architectural ideas behind optimagic. +A super minimalist bare bones rewrite to illustrate the core architectural ideas behind optimagic. From 516beaeb0bbcfde30b1be936f4107db055b92633 Mon Sep 17 00:00:00 2001 From: Janos Gabler Date: Sun, 17 Aug 2025 17:13:48 +0200 Subject: [PATCH 4/5] Rename internal problem. --- src/optimini/minimize.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/optimini/minimize.py b/src/optimini/minimize.py index 502199a..c7237fe 100644 --- a/src/optimini/minimize.py +++ b/src/optimini/minimize.py @@ -10,10 +10,10 @@ def minimize(fun, params, method, options=None): """Minimize a function using a given method""" options = {} if options is None else options converter = Converter(params) - internal_fun = InternalProblem(fun, converter) + problem = InternalProblem(fun, converter) x0 = converter.flatten(params) raw_res = scipy_minimize( - fun=internal_fun.fun, + fun=problem.fun, x0=x0, method=method, options=options, From 3269d36d11ca84d4b25e3e9e682de3866f525237 Mon Sep 17 00:00:00 2001 From: Janos Gabler Date: Sun, 17 Aug 2025 17:24:18 +0200 Subject: [PATCH 5/5] Change import to om. --- examples/example.ipynb | 4 ++-- src/optimini/__init__.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/example.ipynb b/examples/example.ipynb index a5fdb54..f5d0160 100644 --- a/examples/example.ipynb +++ b/examples/example.ipynb @@ -15,7 +15,7 @@ "metadata": {}, "outputs": [], "source": [ - "from optimini.minimize import minimize\n", + "import optimini as om\n", "\n", "\n", "def my_fun(params):\n", @@ -23,7 +23,7 @@ "\n", "\n", "params = {\"a\": 1, \"b\": 2}\n", - "res = minimize(my_fun, params, method=\"L-BFGS-B\")\n", + "res = om.minimize(my_fun, params, method=\"L-BFGS-B\")\n", "res.x" ] } diff --git a/src/optimini/__init__.py b/src/optimini/__init__.py index e69de29..16b5779 100644 --- a/src/optimini/__init__.py +++ b/src/optimini/__init__.py @@ -0,0 +1,3 @@ +from optimini.minimize import minimize + +__all__ = ["minimize"]