diff --git a/examples/example.ipynb b/examples/example.ipynb index 681a677..f5d0160 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": [ + "import optimini as om\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 = om.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, 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"] 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..c7237fe --- /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) + problem = InternalProblem(fun, converter) + x0 = converter.flatten(params) + raw_res = scipy_minimize( + fun=problem.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]))