diff --git a/docs/source/_static/images/optimagic_logo.svg b/docs/source/_static/images/optimagic_logo.svg
new file mode 100644
index 000000000..1e83bcbf4
--- /dev/null
+++ b/docs/source/_static/images/optimagic_logo.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/docs/source/_static/images/optimagic_logo_dark_mode.svg b/docs/source/_static/images/optimagic_logo_dark_mode.svg
new file mode 100644
index 000000000..93f55c402
--- /dev/null
+++ b/docs/source/_static/images/optimagic_logo_dark_mode.svg
@@ -0,0 +1,78 @@
+
+
diff --git a/docs/source/conf.py b/docs/source/conf.py
index f26665066..ca8e01219 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -198,8 +198,8 @@
html_theme_options = {
"sidebar_hide_name": True,
"navigation_with_keys": True,
- "light_logo": "images/estimagic_logo.svg",
- "dark_logo": "images/estimagic_logo_dark_mode.svg",
+ "light_logo": "images/optimagic_logo.svg",
+ "dark_logo": "images/optimagic_logo_dark_mode.svg",
"light_css_variables": {
"color-brand-primary": "#f04f43",
"color-brand-content": "#f04f43",
diff --git a/docs/source/how_to/how_to_bounds.ipynb b/docs/source/how_to/how_to_bounds.ipynb
index a1939dd7f..3b8c1ee22 100644
--- a/docs/source/how_to/how_to_bounds.ipynb
+++ b/docs/source/how_to/how_to_bounds.ipynb
@@ -17,14 +17,14 @@
"id": "b3c135aa",
"metadata": {},
"source": [
- "## Example criterion function\n",
+ "## Example objective function\n",
"\n",
"Let’s again look at the sphere function:"
]
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": 1,
"id": "ec477eb7",
"metadata": {},
"outputs": [],
@@ -35,7 +35,7 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": 2,
"id": "b0eb906d",
"metadata": {},
"outputs": [],
@@ -53,7 +53,7 @@
{
"data": {
"text/plain": [
- "array([ 0.00000000e+00, -1.33177530e-08, 7.18836679e-09])"
+ "array([ 0., -0., 0.])"
]
},
"execution_count": 4,
@@ -63,7 +63,7 @@
],
"source": [
"res = om.minimize(fun=fun, params=np.arange(3), algorithm=\"scipy_lbfgsb\")\n",
- "res.params"
+ "res.params.round(5)"
]
},
{
@@ -97,7 +97,10 @@
],
"source": [
"res = om.minimize(\n",
- " fun=fun, params=np.arange(3), lower_bounds=np.ones(3), algorithm=\"scipy_lbfgsb\"\n",
+ " fun=fun,\n",
+ " params=np.arange(3),\n",
+ " bounds=om.Bounds(lower=np.ones(3)),\n",
+ " algorithm=\"scipy_lbfgsb\",\n",
")\n",
"res.params"
]
@@ -112,7 +115,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 5,
"id": "26c5c0df",
"metadata": {},
"outputs": [
@@ -122,7 +125,7 @@
"array([-1.00000000e+00, -3.57647467e-08, 1.00000000e+00])"
]
},
- "execution_count": 6,
+ "execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
@@ -132,8 +135,10 @@
" fun=fun,\n",
" params=np.arange(3),\n",
" algorithm=\"scipy_lbfgsb\",\n",
- " lower_bounds=np.array([-2, -np.inf, 1]),\n",
- " upper_bounds=np.array([-1, np.inf, np.inf]),\n",
+ " bounds=om.Bounds(\n",
+ " lower=np.array([-2, -np.inf, 1]),\n",
+ " upper=np.array([-1, np.inf, np.inf]),\n",
+ " ),\n",
")\n",
"res.params"
]
@@ -185,7 +190,7 @@
" fun=fun,\n",
" params=params,\n",
" algorithm=\"scipy_lbfgsb\",\n",
- " lower_bounds={\"intercept\": -2},\n",
+ " bounds=om.Bounds(lower={\"intercept\": -2}),\n",
")\n",
"res.params"
]
@@ -295,7 +300,7 @@
},
{
"cell_type": "code",
- "execution_count": 24,
+ "execution_count": 10,
"id": "34d59f01",
"metadata": {},
"outputs": [],
@@ -309,7 +314,7 @@
},
{
"cell_type": "code",
- "execution_count": 25,
+ "execution_count": 11,
"id": "b284ad8a",
"metadata": {},
"outputs": [
@@ -374,7 +379,7 @@
"intercept 0 -2.0 -2"
]
},
- "execution_count": 25,
+ "execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
@@ -387,6 +392,24 @@
")\n",
"res.params"
]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d2a20601",
+ "metadata": {},
+ "source": [
+ "## Coming from scipy"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bd83f842",
+ "metadata": {},
+ "source": [
+ "If `params` is a flat numpy array, you can also provide bounds in any format that \n",
+ "is supported by [`scipy.optimize.minimize`](\n",
+ "https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html). "
+ ]
}
],
"metadata": {
diff --git a/docs/source/how_to/how_to_slice_plot.ipynb b/docs/source/how_to/how_to_slice_plot.ipynb
index 5407e59a8..e6d43b4e7 100644
--- a/docs/source/how_to/how_to_slice_plot.ipynb
+++ b/docs/source/how_to/how_to_slice_plot.ipynb
@@ -52,8 +52,10 @@
"outputs": [],
"source": [
"params = {\"alpha\": 0, \"beta\": 0, \"gamma\": 0, \"delta\": 0}\n",
- "lower_bounds = {name: -5 for name in params}\n",
- "upper_bounds = {name: i + 2 for i, name in enumerate(params)}"
+ "bounds = om.Bounds(\n",
+ " lower={name: -5 for name in params},\n",
+ " upper={name: i + 2 for i, name in enumerate(params)},\n",
+ ")"
]
},
{
@@ -70,7 +72,7 @@
"outputs": [
{
"data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAYAAACadoJwAAAgAElEQVR4XuzdB3gVxd7H8R8BAiShN+kdpRcB6b1IURQVG4ooqNgV9AqCylWxvFzBiiIgKiAoKAg2lF6kWOgtFOm9BhISSt5nFhMpITknu2dzynef5z5cye6Uzywz+e/uzGRKTExMFAcCCCCAAAIIIIAAAggg4IJAJgIQF5TJAgEEEEAAAQQQQAABBCwBAhBuBAQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAABBBBAAAECEO4BBBBAAAEEEEAAAQQQcE2AAMQ1ajJCAAEEEEAAAQQQQAABAhDuAQQQQAABBBBAAAEEEHBNgADENWoyQgABBBBAAAEEEEAAAQIQ7gEEEEAAAQQQQAABBBBwTYAAxDVqMkIAAQQQQAAB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QAABBBBwQIAAxAFEkkAAAQQQQAABBBBAAAHPBAhAPHPiLAQQQAABBBBAAAEEEHBAgADEAUSSQAABBBBAAAEEEEAAAc8ECEA8c+IsBBBAAAEEEEAAAQQQcECAAMQBRJJAAAEEEEAAAQQQQAABzwQIQDxz4iwEEEAAAQQQQAABBBBwQIAAxAFEkkAAAQQQQAABBBBAAAHPBAhAPHPiLAQQQAABBBBAAAEEEHBAgADEAUSSQAABBBBAAAEEEEAAAc8ECEA8c+IsBBBAAAEEEEAAAQQQcECAAMQBRJJAAAEEEEAAAQQQQAABzwQIQDxz4iwEEEAAAQQQQAABBBBwQIAAxAFEkkAAAQQQQAABBBBAAAHPBAhAPHPiLAQQQAABBBBAAAEEEHBAgADEAUSSQAABBBBAAAEEEEAAAc8ECEA8c+IsBBBAAAEEEEAAAQQQcECAAMQBRJJAAAEEEEAAAQQQQAABzwQIQDxz4iwEEEAAAQQQQAABBBBwQIAAxAFEkkAAAQQQQAABBBBAAAHPBAhAPHPiLAQQQAABBBBAAAEEEHBAgADEAUSSQAABBBBAAAEEEEAAAc8ECEA8c+IsBBBAAAEEEEAAAQQQcECAAMQBRJJAAAEEEEAAAQQQQAABzwQIQDxz4iwEEEAAAQQQQAABBBBwQIAAxAFEkkAAAQQQQAABBBBAAAHPBAhAPHPiLAQQQAABBBBAAAEEEHBAgADEAUSSQAABBBBAAAEEEEAAAc8ECEA8c+IsBBBAAAEEEEAAAQQQcECAAMQBRJJAAAEEEEAAAQQQQAABzwQIQDxz4iwEEEAAAQQQQAABBBBwQIAAxAFEkkAAAQQQQAABBBBAAAHPBAhAPHPiLAQQQAABBBBAAAEEEHBAgADEAUSSQAABBBBAAAEEEEAAAc8ECEA8c+IsBBBAAAEEEEAAAQQQcECAAMQBRJJAAAEEEEAAAQQQQAABzwQIQDxz4iwEEEAAAQQQQAABBBBwQIAAxAFEkkAAAQQQQAABBBBAAAHPBAhAPHPiLAQQQAABBBBAAAEEEHBAgADEAUSSQAABBBBAAAEEEEAAAc8ECEA8c+IsBBBAAAEEEEAAAQQQcECAAMQBRJJAAAEEEEAAAQQQQAABzwQIQDxz4iwEEEAAAQQQQAABBBBwQIAAxAFEkkAAAQQQQAABBBBAAAHPBAhAPHPiLAQQQAAB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qlULvXv3pgBxgzHKJpIACbyagNzvEBPaXbt2xZi8nj59Ws3p0SZYslkjd/XkuVel2AJEhMYHH3ygzLiik5QlZlzR5q5igtugQQOegHBQkoCVBChArATGx81NYOXKlZA/48aNg4eHB1KlShVzsVAWMll05MKi7HiJza/c45AjfS0FyLx581QdFixYoOycp06dit9//12d1NAEy9zjk60jARJ4PQFxuStmtPJHnH48ePBAecQSQRItQESkiMdA2UySu35yH082gN5++201n8YWIGLiWrVqVTXXyimKrAn9+/dH0qRJlXfDmzdvQk7N9+3bh1WrVvEEhIOTBKwgQAFiBSw+an4CspDIjpecakiSBUe8qsh9DblELh/78+fPVx6wxCxLTLLk6F1LASLvlvsechIjC+H777+v6iPuIClAzD9G2UISIIHXEzh16pQykxU3vGIKK2Ljl19+USIi+pK43BMRN7xyFy9lypQoV66c8ooVV4DIW2bMmKHMXcXUSjan5A6IzL/yHjHBEnNYKUvMumiCxZFJApYToACxnBWfNDkBcWfbvXt3tVgVKVJEtVbufXTr1g29evV6wWuKyVGweSRAAiRAAiRAAiTgNAIUIE5Dy4KNRmDu3Lk4duyYulwYO8kOmphZ8XTBaD3K+pIACZAACZAACeiRAAWIHnuFdXIJAbENluN0ceWYK1cuiD2xmFfJPY/ly5cjd+7cLqkXX0oCJEACJEACJEACZiJAAWKm3mRb7CYgpyBiKyyuGyWqrZhiyQmIeE1hIgESIAESIAESIAESsJ8ABYj9DFkCCZAACZAACZAACZAACZCAhQQoQCwExcdIgARIgARIgARIgARIgATsJ0ABYj9DlkACJEACJEACJEACJEACJGAhAQoQC0HxMRIgARIgARIgARIgARIgAfsJUIDYz5AlkAAJkAAJkAAJkAAJkAAJWEiAAsRCUHyMBEiABEiABEiABEiABEjAfgIUIPYzZAkkQAIkQAIkQAIkQAIkQAIWEqAAsRAUHyMBEiABEiABEiABEiABErCfAAWI/QxZAgmQAAmQAAmQAAmQAAmQgIUEKEAsBMXHSIAESIAESIAESIAESIAE7CdAAWI/Q5ZAAiRAAiRAAiRAAiRAAiRgIQEKEAtB8TESIAESIAESIAESIAESIAH7CVCA2M+QJZAACZAACZAACZAACZAACVhIgALEQlB8jARIgARIgARIgARIgARIwH4CFCD2M2QJJEACJEACJEACJEACJEACFhKgALEQFB8jARIgARIgARIgARIgARKwnwAFiP0MWQIJkAAJkAAJkAAJkAAJkICFBChALATFx0iABEiABEiABEiABEiABOwnQAFiP0OWQAIkQAIkQAIkQAIkQAIkYCEBChALQfExEiABEiABEiABEiABEiAB+wlQgNjPkCWQAAmQAAmQAAmQAAmQAAlYSIACxEJQfIwESIAESIAESIAESIAESMB+AhQg9jNkCSRAAiRAAiRAAiRAAiRAAhYSoACxEBQfIwESIAESIAESIAESIAESsJ8ABYj9DFkCCZAACZAACZAACZAACZCAhQQoQCwExcdIgARIgARIgARIgARIgATsJ0ABYj9DlkACJEACJEACJEACJEACJGAhgf8DKjGkyK7DXqoAAAAASUVORK5CYII="
+ "image/png": "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r7/iwIEDqFChAjp37oxixYqp++bMmQNZ6MqXL49Zs2bh+vXrqFmzJr799lvMmDEDS5YsQUhICD766CO0adMGXl5e6r4//vgD8+bNUzZKkfr69u0bUa8DkPERJEACJKAbAmfOnFFzuMzVModnyZJF7X6Y74DIvDx+/Hg1F/v4+OD9999H165d4e7uDvMdkLJly6rdj4sXL6JgwYJqByVPnjwYOnQo517d9DgNMTIBChAj9x5ttxmBq1evolq1amrRat++PdKkSYMFCxaoRcq0eK1evRq7du1C8eLF1WIk2/LLli1TIqBkyZJ49OgRSpQooWyS7Xu5bunSpTh06JD6u2bNmqFAgQLq+levXkHqkzJixAhMnjxZLYZyzcuXLzF27Fj1b76+vmjSpAnu37+PKVOmqIWzdu3a6t9GjRql6hEhJP/7119/4fz589i+fTuSJUtmMzasiARIgAT0TuDWrVuoWLEikiZNig4dOiBjxoxYvHgxAgICIubwLVu2qI9GMj/LPHr48GE198pHok6dOkUSIH5+fuqjkXwU6tWrl1oTZH2oV68e5169DwbaZwgCFCCG6CYaaW8C8tXszz//VC/vGTJkUI+Lafs+PDwcwcHBuHv3LurWrYsvv/xSLWomAfLdd9+p3QopIj6aNm2K3377De+++676O9MiuG3bNrVIigBZuHCh+nuTn7IsoNeuXVMCx3R4UoRI4cKF8f3330fCERoaqgSK7LrIDogIJ9Puir25sX4SIAES0AMBObcxadIkbN68We18RDeHyxycPn16TJs2LcLk3r17IzAwUH0QinoGJDYXLM69euh52mBUAhQgRu052m1TArLV/vDhQ/XCbypRBci9e/cwfPhwrF27VrlgmYosYPKFzCRAzMXG5cuXUaNGDbVlX6VKFXXL8ePHlduWSSiIAJHFb+PGjRF1fvXVVzh9+rT6gmcq4iYgrlhSlxRxGRN7xN3AvMgXO3EfYCEBEiABVyEgO9fivmraWY4qQBImTIhChQqpXQz58GMqJpdaESGWChDOva4yqthOexKgALEnXdZtGALNmzeHLFBz5859Q4AcO3YMnp6eaidDBMU333yDd955R53rqF69utrtiEmAmFy7zAWILF7vvffeWwXIwIEDlcAwFyDdu3fHixcvlACRHZhSpUpFnPvInTu3ElANGjRQLgMUIIYZejSUBEjABgRkhzhlypSRdjfMPyLJboW4xcpcX6tWrUhPTJAggfpAZIkA4dxrg85iFSQAgAKEw4AEAOVGJS/74hOcJEkSxWT69On44YcflP+wafHq378/unTpEsFMXvS1ECBykFLctObPnx9x7kS+5MnhdQoQDmkSIAFXIyA70bL7cerUKSRKlEg1P+outszX8mfMmDGR8IhbrYiQqAJEzgCKYFm5ciXy5s2r7uHc62oji+21FwEKEHuRZb2GImA6qyHb8+IeFRQUhPXr16s2mA6hy8FFWdhErIggERcqWZje5oJlrx0QcQeThVQiuLRq1Qo3b95UCbdOnDhBAWKokUdjSYAEbEFgzZo1aie6Tp06aNmypYpeJUJD5krTHC4fZwYPHqyiEMp8LgE/RGTIuRE5FxJVgMi/i9uWuOiKEHn8+DFy5szJudcWHcY6XJ4ABYjLDwECMBGQrLkSSerOnTsoUqSI8hWWCClyFkPcs+TQuBwwF3EiRRYwOTPSp08flTlXFifZ4jc/AyIHySWjrixulStXVvfJFzoJ1SsHz01Jr1atWhXpDIi4eYmYMHfBkmeIC5ZEw5IirlgSLct0HkVcEOR6yfpbpkwZdiwJkAAJuAwBiQQoO9YSNl2KzN8SnVA+JJncaOUaCW0+evToSOf4RJB8/vnnKpS6uNeaEhFKPRKcZOLEiUrISHAP+fDEuddlhhUbakcCFCB2hMuqjU1AxIZEQTHthEhrZKv+woULKiSjt7e35g0UQSK7LJJvxJQfRHOjaAAJkAAJaERAzmjIn6xZs6oPR9EVmcclf5L8b9q0aSNctmIyWXa85cOUXOvm5qYu49yrUQfzsU5DgALEabqSDbGGgCwmsm0vyQBlkZFdCtlNkKRTsvXOQgIkQAIkQAIkQAIkYBsCFCC24chaDE5AwtvK2Q7JpCtiJFu2bOosiMSNl8OJLCRAAiRAAiRAAiRAArYhQAFiG46shQRIgARIgARIgARIgARIwAICFCAWQOIlJEACJEACJEACJEACJEACtiFAAWIbjqyFBEiABEiABEiABEiABEjAAgIUIBZA4iUkQAIkQAIkQAIkQAIkQAK2IUABYhuOrIUESIAESIAESIAESIAESMACAhQgFkDiJSRAAiRAAiRAAiRAAiRAArYhQAFiG46shQRIgARIgARIgARIgARIwAICFCAWQOIlJEACJEACJEACJEACJEACtiFAAWIbjqyFBEiABEiABEiABEiABEjAAgIUIBZA4iUkQAIkQAIkQAIkQAIkQAK2IUABYhuOrIUESIAESIAESIAESIAESMACAhQgFkDiJSRAAiRAAiRAAiRAAiRAArYhQAFiG46shQRIgARIgARIgARIgARIwAICFCAWQOIlJEACJEACJEACJEACJEACtiFAAWIbjqyFBEiABEiABEiABEiABEjAAgIUIBZA4iUkQAIkQAIkQAIkQAIkQAK2IUABYhuOrIUESIAESIAESIAESIAESMACAhQgFkDiJSRAAiRAAiRAAiRAAiRAArYhQAFiG46shQRIgARIgARIgARIgARIwAICFCAWQOIlJEACJEACJEACJEACJEACtiFAAWIbjqyFBEiABEiABEiABEiABEjAAgIUIBZA4iUkQAIkQAIkQAIkQAIkQAK2IUABYhuOrIUESIAESIAESIAESIAESMACAhQgFkDiJSRAAiRAAiRAAiRAAiRAArYhQAFiG46shQRIgARIgARIgARIgARIwAICFCAWQOIlJEACJEACJEACJEACJEACtiFAAWIbjqyFBEiABEiABEiABEiABEjAAgIUIBZA4iUkQAIkQAIkQAIkQAIkQAK2IUABYhuOrIUESIAESIAESIAESIAESMACAhQgFkDiJSRAAiRAAiRAAiRAAiRAArYh8H8T6DgEjaxT5QAAAABJRU5ErkJggg=="
},
"metadata": {},
"output_type": "display_data"
@@ -80,8 +82,7 @@
"fig = om.slice_plot(\n",
" func=sphere,\n",
" params=params,\n",
- " lower_bounds=lower_bounds,\n",
- " upper_bounds=upper_bounds,\n",
+ " bounds=bounds,\n",
")\n",
"fig.show(renderer=\"png\")"
]
@@ -115,7 +116,7 @@
"outputs": [
{
"data": {
- "image/png": "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"
+ "image/png": "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IIAAAggggEAgCBCABEItU0YEEEAAAQQQQAABBCwiQABikYogG9YXSExMlAULFkhSUpK0adNGQkNDkzN98eJFuffee+XJJ5+UG264wanCrFmzRt5880357LPPpFixYk6dw0EIIIAAAhkLrF27Vo4fP24OyJUrl+TNm1cqVKggZcqUgQ0BBCwkQABiocogK9YW0IChT58+JpNffPGFtGvXLjnD58+fl/r168vIkSOle/fuThVk4cKFMmDAAFm0aJGUKlXKqXM4CAEEEEAgY4HHH39c5s6de80Bd999twwZMsRpujvvvNMELvolEQkBBNwvQADiflOu6KcC+vKaPXu2KV3Tpk1l1KhRBCB+WtcUCwEE7CmgAcixY8fkxx9/NAU4c+aMvPXWW+bv8+fPl6ioKKcKpl82aQAyYsQIp47nIAQQyJ4AAUj2vDg6QAUuXbpkgo4HHnjACHz88ceyatUqCQ8PN39P2wNy4cIF6devn9x4443muGXLlklERIToy/H222835zh6QPTlOG3aNNm4caO0b99e+vfvL3Xq1DHHLF26VN5++23Zu3evxMXFSfXq1U2vSY8ePQK0Jig2AgggkLFA2gBEj/z555/lueeekz/++CN5KNbZs2flvffek3nz5snhw4elWbNm8vLLL0vNmjXNsFj9WYECBUybq+mll16SyMhIGTRokOzYsUNiY2PN37UtfvrppyUkJIRqQQCBbAgQgGQDi0MDV0C79PXF9ssvvxiEbt26ma55RzCRNgDRl1vDhg3NsXqMfpM2efJk2b9/v0yaNEkaNWqUHIDoMRrY6DdzX3/9tRQuXFh++uknc672uPz5559y3XXXmbHM+g3e9OnTk68RuDVCyRFAAIFrBbSd1gDhnXfeEZ23d+TIEfP/+gWSYzjVlStXpHfv3nLq1Ckzd0+/HPr2229l165dsnjxYlm/fr28+OKLUrx4cenVq5e5Sdu2bSUhIUHeffddE6wULVpUtm/fbnrCn332WXn00UepDgQQyIYAAUg2sDg0cAX0pbZv3z6ZOXOmQbj55pulYMGCMnHiRPP3jAKQ4cOHS9++fc0x586dM4GEdu2//vrryQHIf//7X/OtmyZHoLNkyRIpWbJkMrhOfD99+rScOHFCunTpIi+88ILpCSEhgAACCPwjkNkckKeeesoEDvpFjgYMOiyrQYMG5uRt27aZ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"
},
"metadata": {},
"output_type": "display_data"
@@ -125,8 +126,7 @@
"fig = om.slice_plot(\n",
" func=sphere,\n",
" params=params,\n",
- " lower_bounds=lower_bounds,\n",
- " upper_bounds=upper_bounds,\n",
+ " bounds=bounds,\n",
" # selecting a subset of params\n",
" selector=lambda x: [x[\"alpha\"], x[\"beta\"]],\n",
" # evaluate func in parallel\n",
@@ -157,7 +157,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.8"
+ "version": "3.10.14"
}
},
"nbformat": 4,
diff --git a/docs/source/how_to/how_to_visualize_histories.ipynb b/docs/source/how_to/how_to_visualize_histories.ipynb
index 7d5091ff5..91fd96c45 100644
--- a/docs/source/how_to/how_to_visualize_histories.ipynb
+++ b/docs/source/how_to/how_to_visualize_histories.ipynb
@@ -17,7 +17,7 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 1,
"id": "8675ff3f",
"metadata": {},
"outputs": [],
@@ -36,7 +36,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 2,
"id": "5efb43c8",
"metadata": {},
"outputs": [],
@@ -60,7 +60,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 3,
"id": "32cf04a2",
"metadata": {},
"outputs": [
@@ -87,7 +87,7 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 4,
"id": "d641708a",
"metadata": {},
"outputs": [
@@ -114,7 +114,7 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 5,
"id": "72b6938c",
"metadata": {},
"outputs": [
@@ -147,7 +147,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 6,
"id": "45e853a5",
"metadata": {},
"outputs": [
@@ -174,7 +174,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 7,
"id": "c09ded87",
"metadata": {},
"outputs": [
@@ -207,7 +207,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 8,
"id": "70099614",
"metadata": {},
"outputs": [],
@@ -219,8 +219,7 @@
"res = om.minimize(\n",
" sphere,\n",
" params=np.arange(10),\n",
- " soft_lower_bounds=np.full(10, -3),\n",
- " soft_upper_bounds=np.full(10, 10),\n",
+ " bounds=om.Bounds(soft_lower=np.full(10, -3), soft_upper=np.full(10, 10)),\n",
" algorithm=\"scipy_neldermead\",\n",
" multistart=True,\n",
" multistart_options={\"n_samples\": 1000, \"convergence.max_discoveries\": 10},\n",
@@ -229,7 +228,7 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 9,
"id": "e21dcd65",
"metadata": {},
"outputs": [
diff --git a/docs/source/index.md b/docs/source/index.md
index 6b8e1b9fb..20b5a20d5 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -4,9 +4,9 @@
```{raw} html
-
+
-
+
```
diff --git a/docs/source/tutorials/numdiff_overview.ipynb b/docs/source/tutorials/numdiff_overview.ipynb
index b034796ce..709f8dd27 100644
--- a/docs/source/tutorials/numdiff_overview.ipynb
+++ b/docs/source/tutorials/numdiff_overview.ipynb
@@ -11,7 +11,7 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
@@ -411,54 +411,30 @@
"fd = om.first_derivative(\n",
" func=sphere,\n",
" params=params,\n",
- " lower_bounds=params, # forces first_derivative to use forward differences\n",
- " upper_bounds=params + 1,\n",
+ " # forces first_derivative to use forward differences\n",
+ " bounds=om.Bounds(lower=params, upper=params + 1),\n",
")\n",
"\n",
"fd[\"derivative\"]"
]
},
{
- "cell_type": "code",
- "execution_count": 13,
+ "cell_type": "markdown",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[2.006, 0. , 0. , 0. , 0. ],\n",
- " [0. , 2. , 0. , 0. , 0. ],\n",
- " [0. , 0. , 2. , 0. , 0. ],\n",
- " [0. , 0. , 0. , 2. , 0. ],\n",
- " [0. , 0. , 0. , 0. , 2. ]])"
- ]
- },
- "execution_count": 13,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
"source": [
- "sd = om.second_derivative(\n",
- " func=sphere,\n",
- " params=params,\n",
- " lower_bounds=params, # forces first_derivative to use forward differences\n",
- " upper_bounds=params + 1,\n",
- ")\n",
- "\n",
- "sd[\"derivative\"].round(3)"
+ "Of course, bounds also work in second_derivative."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Or use parallelized numerical derivatives"
+ "## You can parallelize"
]
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": 13,
"metadata": {},
"outputs": [
{
@@ -467,7 +443,7 @@
"array([0., 2., 4., 6., 8.])"
]
},
- "execution_count": 14,
+ "execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
@@ -484,7 +460,7 @@
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": 14,
"metadata": {},
"outputs": [
{
@@ -497,7 +473,7 @@
" [0. , 0. , 0. , 0. , 2. ]])"
]
},
- "execution_count": 15,
+ "execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
@@ -529,7 +505,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.8"
+ "version": "3.10.14"
},
"vscode": {
"interpreter": {
diff --git a/docs/source/tutorials/optimization_overview.ipynb b/docs/source/tutorials/optimization_overview.ipynb
index 51a4a4b66..07eb25bb3 100644
--- a/docs/source/tutorials/optimization_overview.ipynb
+++ b/docs/source/tutorials/optimization_overview.ipynb
@@ -231,7 +231,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 10,
"metadata": {},
"outputs": [
{
@@ -240,18 +240,19 @@
"array([0., 0., 0., 1., 2.])"
]
},
- "execution_count": 9,
+ "execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
+ "bounds = om.Bounds(lower=np.arange(5) - 2, upper=np.array([10, 10, 10, np.inf, np.inf]))\n",
+ "\n",
"res = om.minimize(\n",
" fun=sphere,\n",
" params=np.arange(5),\n",
" algorithm=\"scipy_lbfgsb\",\n",
- " lower_bounds=np.arange(5) - 2,\n",
- " upper_bounds=np.array([10, 10, 10, np.inf, np.inf]),\n",
+ " bounds=bounds,\n",
")\n",
"\n",
"res.params.round(5)"
@@ -266,7 +267,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 11,
"metadata": {},
"outputs": [
{
@@ -275,7 +276,7 @@
"array([0., 1., 0., 3., 0.])"
]
},
- "execution_count": 10,
+ "execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
@@ -302,7 +303,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 12,
"metadata": {},
"outputs": [
{
@@ -311,7 +312,7 @@
"array([ 0.33333, 0.33333, 0.33334, -0. , 0. ])"
]
},
- "execution_count": 11,
+ "execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
@@ -347,7 +348,7 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
@@ -357,7 +358,7 @@
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": 14,
"metadata": {},
"outputs": [
{
@@ -366,7 +367,7 @@
"array([ 0., -0., -0., 0., 0.])"
]
},
- "execution_count": 13,
+ "execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
@@ -397,7 +398,7 @@
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
@@ -407,7 +408,7 @@
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": 16,
"metadata": {},
"outputs": [
{
@@ -416,7 +417,7 @@
"array([ 0., -0., -0., -0., -0.])"
]
},
- "execution_count": 15,
+ "execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
@@ -440,7 +441,7 @@
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": 17,
"metadata": {},
"outputs": [
{
@@ -449,7 +450,7 @@
"array([ 0., -0., -0., -0., -0.])"
]
},
- "execution_count": 16,
+ "execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
@@ -469,12 +470,15 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "## Turn local optimizers global with multistart"
+ "## Turn local optimizers global with multistart\n",
+ "\n",
+ "Multistart optimization requires finite soft bounds on all parameters. Those bounds will\n",
+ "be used for sampling but not enforced during optimization."
]
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": 18,
"metadata": {},
"outputs": [
{
@@ -483,18 +487,19 @@
"array([ 0., 0., -0., -0., 0., -0., 0., -0., -0., -0.])"
]
},
- "execution_count": 17,
+ "execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
+ "bounds = om.Bounds(soft_lower=np.full(10, -5), soft_upper=np.full(10, 15))\n",
+ "\n",
"res = om.minimize(\n",
" fun=sphere,\n",
" params=np.arange(10),\n",
" algorithm=\"scipy_neldermead\",\n",
- " soft_lower_bounds=np.full(10, -5),\n",
- " soft_upper_bounds=np.full(10, 15),\n",
+ " bounds=bounds,\n",
" multistart=True,\n",
" multistart_options={\"convergence.max_discoveries\": 5},\n",
")\n",
@@ -510,7 +515,7 @@
},
{
"cell_type": "code",
- "execution_count": 18,
+ "execution_count": 19,
"metadata": {},
"outputs": [
{
@@ -539,7 +544,7 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
@@ -559,7 +564,7 @@
},
{
"cell_type": "code",
- "execution_count": 20,
+ "execution_count": 21,
"metadata": {},
"outputs": [
{
@@ -568,7 +573,7 @@
"array([-0., 0., 0., 0., -0.])"
]
},
- "execution_count": 20,
+ "execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
@@ -593,7 +598,7 @@
},
{
"cell_type": "code",
- "execution_count": 21,
+ "execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
@@ -615,7 +620,7 @@
},
{
"cell_type": "code",
- "execution_count": 22,
+ "execution_count": 23,
"metadata": {},
"outputs": [
{
@@ -624,7 +629,7 @@
"dict_keys(['params', 'criterion', 'runtime'])"
]
},
- "execution_count": 22,
+ "execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
@@ -654,7 +659,7 @@
},
{
"cell_type": "code",
- "execution_count": 23,
+ "execution_count": 24,
"metadata": {},
"outputs": [
{
@@ -663,7 +668,7 @@
"array([ 0., -0., -0., -0., -0.])"
]
},
- "execution_count": 23,
+ "execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
diff --git a/pyproject.toml b/pyproject.toml
index af4f20fef..b3bb726ba 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -136,6 +136,8 @@ extend-ignore = [
"PLR5501",
# For calls to warnings.warn(): No explicit `stacklevel` keyword argument found
"B028",
+ # Incompatible with formatting
+ "ISC001",
]
[tool.ruff.lint.per-file-ignores]
@@ -246,8 +248,9 @@ module = [
"optimagic.optimization.optimize",
"optimagic.optimization.process_multistart_sample",
"optimagic.optimization.process_results",
- "optimagic.optimization.tiktak",
+ "optimagic.optimization.multistart",
"optimagic.optimization.scipy_aliases",
+ "optimagic.optimization.create_optimization_problem",
"optimagic.optimizers._pounders",
"optimagic.optimizers._pounders.pounders_auxiliary",
@@ -281,8 +284,6 @@ module = [
"optimagic.parameters.conversion",
"optimagic.parameters.kernel_transformations",
"optimagic.parameters.nonlinear_constraints",
- "optimagic.parameters.parameter_bounds",
- "optimagic.parameters.parameter_groups",
"optimagic.parameters.process_constraints",
"optimagic.parameters.process_selectors",
"optimagic.parameters.scale_conversion",
diff --git a/src/estimagic/estimate_ml.py b/src/estimagic/estimate_ml.py
index 81fde607f..4e292d8f0 100644
--- a/src/estimagic/estimate_ml.py
+++ b/src/estimagic/estimate_ml.py
@@ -37,6 +37,8 @@
check_optimization_options,
)
from optimagic.utilities import get_rng, to_pickle
+from optimagic.parameters.bounds import Bounds, pre_process_bounds
+from optimagic.deprecations import replace_and_warn_about_deprecated_bounds
def estimate_ml(
@@ -44,8 +46,7 @@ def estimate_ml(
params,
optimize_options,
*,
- lower_bounds=None,
- upper_bounds=None,
+ bounds=None,
constraints=None,
logging=False,
log_options=None,
@@ -56,6 +57,9 @@ def estimate_ml(
hessian=None,
hessian_kwargs=None,
design_info=None,
+ # deprecated
+ lower_bounds=None,
+ upper_bounds=None,
):
"""Do a maximum likelihood (ml) estimation.
@@ -85,11 +89,13 @@ def estimate_ml(
you signal that ``params`` are already the optimal parameters and no
numerical optimization is needed. If you pass a str as optimize_options it
is used as the ``algorithm`` option.
- lower_bounds (pytree): A pytree with the same structure as params with lower
- bounds for the parameters. Can be ``-np.inf`` for parameters with no lower
- bound.
- upper_bounds (pytree): As lower_bounds. Can be ``np.inf`` for parameters with
- no upper bound.
+ bounds: Lower and upper bounds on the parameters. The most general and preferred
+ way to specify bounds is an `optimagic.Bounds` object that collects lower,
+ upper, soft_lower and soft_upper bounds. The soft bounds are used for
+ sampling based optimizers but are not enforced during optimization. Each
+ bound type mirrors the structure of params. Check our how-to guide on bounds
+ for examples. If params is a flat numpy array, you can also provide bounds
+ via any format that is supported by scipy.optimize.minimize.
constraints (list, dict): List with constraint dictionaries or single dict.
See :ref:`constraints`.
logging (pathlib.Path, str or False): Path to sqlite3 file (which typically has
@@ -132,9 +138,22 @@ def estimate_ml(
LikelihoodResult: A LikelihoodResult object.
"""
+ # ==================================================================================
+ # handle deprecations
+ # ==================================================================================
+
+ bounds = replace_and_warn_about_deprecated_bounds(
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ bounds=bounds,
+ )
+
# ==================================================================================
# Check and process inputs
# ==================================================================================
+
+ bounds = pre_process_bounds(bounds)
+
is_optimized = optimize_options is False
if not is_optimized:
@@ -169,8 +188,7 @@ def estimate_ml(
fun=loglike,
fun_kwargs=loglike_kwargs,
params=params,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
constraints=constraints,
logging=logging,
log_options=log_options,
@@ -219,8 +237,7 @@ def estimate_ml(
converter, internal_estimates = get_converter(
params=estimates,
constraints=constraints,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
func_eval=loglike_eval,
primary_key="contributions",
scaling=False,
@@ -247,8 +264,10 @@ def func(x):
jac_res = first_derivative(
func=func,
params=internal_estimates.values,
- lower_bounds=internal_estimates.lower_bounds,
- upper_bounds=internal_estimates.upper_bounds,
+ bounds=Bounds(
+ lower=internal_estimates.lower_bounds,
+ upper=internal_estimates.upper_bounds,
+ ),
**numdiff_options,
)
@@ -290,8 +309,10 @@ def func(x):
hess_res = second_derivative(
func=func,
params=internal_estimates.values,
- lower_bounds=internal_estimates.lower_bounds,
- upper_bounds=internal_estimates.upper_bounds,
+ bounds=Bounds(
+ lower=internal_estimates.lower_bounds,
+ upper=internal_estimates.upper_bounds,
+ ),
**numdiff_options,
)
int_hess = hess_res["derivative"]
diff --git a/src/estimagic/estimate_msm.py b/src/estimagic/estimate_msm.py
index 562dc6c47..2b85faa30 100644
--- a/src/estimagic/estimate_msm.py
+++ b/src/estimagic/estimate_msm.py
@@ -46,6 +46,8 @@
check_optimization_options,
)
from optimagic.utilities import get_rng, to_pickle
+from optimagic.deprecations import replace_and_warn_about_deprecated_bounds
+from optimagic.parameters.bounds import Bounds, pre_process_bounds
def estimate_msm(
@@ -55,8 +57,7 @@ def estimate_msm(
params,
optimize_options,
*,
- lower_bounds=None,
- upper_bounds=None,
+ bounds=None,
constraints=None,
logging=False,
log_options=None,
@@ -65,6 +66,9 @@ def estimate_msm(
numdiff_options=None,
jacobian=None,
jacobian_kwargs=None,
+ # deprecated
+ lower_bounds=None,
+ upper_bounds=None,
):
"""Do a method of simulated moments or indirect inference estimation.
@@ -101,11 +105,13 @@ def estimate_msm(
``optimize_options`` you signal that ``params`` are already
the optimal parameters and no numerical optimization is needed. If you pass
a str as optimize_options it is used as the ``algorithm`` option.
- lower_bounds (pytree): A pytree with the same structure as params with lower
- bounds for the parameters. Can be ``-np.inf`` for parameters with no lower
- bound.
- upper_bounds (pytree): As lower_bounds. Can be ``np.inf`` for parameters with
- no upper bound.
+ bounds: Lower and upper bounds on the parameters. The most general and preferred
+ way to specify bounds is an `optimagic.Bounds` object that collects lower,
+ upper, soft_lower and soft_upper bounds. The soft bounds are used for
+ sampling based optimizers but are not enforced during optimization. Each
+ bound type mirrors the structure of params. Check our how-to guide on bounds
+ for examples. If params is a flat numpy array, you can also provide bounds
+ via any format that is supported by scipy.optimize.minimize.
simulate_moments_kwargs (dict): Additional keyword arguments for
``simulate_moments``.
weights (str): One of "diagonal" (default), "identity" or "optimal".
@@ -148,9 +154,20 @@ def estimate_msm(
"""
# ==================================================================================
+ # handle deprecations
+ # ==================================================================================
+
+ bounds = replace_and_warn_about_deprecated_bounds(
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ bounds=bounds,
+ )
+ # ==================================================================================
# Check and process inputs
# ==================================================================================
+ bounds = pre_process_bounds(bounds)
+
if weights not in ["diagonal", "optimal", "identity"]:
raise NotImplementedError("Custom weighting matrices are not yet implemented.")
@@ -213,8 +230,7 @@ def estimate_msm(
)
opt_res = minimize(
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
constraints=constraints,
logging=logging,
log_options=log_options,
@@ -269,8 +285,7 @@ def helper(params):
converter, internal_estimates = get_converter(
params=estimates,
constraints=constraints,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
func_eval=func_eval,
primary_key="contributions",
scaling=False,
@@ -296,8 +311,10 @@ def func(x):
int_jac = first_derivative(
func=func,
params=internal_estimates.values,
- lower_bounds=internal_estimates.lower_bounds,
- upper_bounds=internal_estimates.upper_bounds,
+ bounds=Bounds(
+ lower=internal_estimates.lower_bounds,
+ upper=internal_estimates.upper_bounds,
+ ),
**numdiff_options,
)["derivative"]
diff --git a/src/optimagic/__init__.py b/src/optimagic/__init__.py
index 520d872ef..f9498cda9 100644
--- a/src/optimagic/__init__.py
+++ b/src/optimagic/__init__.py
@@ -14,6 +14,7 @@
from optimagic.visualization.history_plots import criterion_plot, params_plot
from optimagic.visualization.profile_plot import profile_plot
from optimagic.visualization.slice_plot import slice_plot
+from optimagic.parameters.bounds import Bounds
try:
from ._version import version as __version__
@@ -43,5 +44,6 @@
"check_constraints",
"OptimizeLogReader",
"OptimizeResult",
+ "Bounds",
"__version__",
]
diff --git a/src/optimagic/benchmarking/cartis_roberts.py b/src/optimagic/benchmarking/cartis_roberts.py
index 6a827ba05..c809bdf49 100644
--- a/src/optimagic/benchmarking/cartis_roberts.py
+++ b/src/optimagic/benchmarking/cartis_roberts.py
@@ -19,6 +19,7 @@
import numpy as np
from optimagic.config import IS_NUMBA_INSTALLED
+from optimagic.parameters.bounds import Bounds
if IS_NUMBA_INSTALLED:
from numba import njit
@@ -4953,7 +4954,7 @@ def get_start_points_methanl8():
"solution_x": None,
"start_criterion": 3.0935,
"solution_criterion": 0,
- "lower_bounds": np.concatenate([np.zeros(50), 1e-6 * np.ones(50)]),
+ "bounds": Bounds(lower=np.concatenate([np.zeros(50), 1e-6 * np.ones(50)])),
},
"chemrctb": {
"fun": chemrctb,
@@ -4961,7 +4962,7 @@ def get_start_points_methanl8():
"solution_x": solution_x_chemrctb,
"start_criterion": 1.446513,
"solution_criterion": 1.404424e-3,
- "lower_bounds": 1e-6 * np.ones(100),
+ "bounds": Bounds(lower=1e-6 * np.ones(100)),
},
"chnrsbne": {
"fun": chnrsbne,
@@ -4997,7 +4998,7 @@ def get_start_points_methanl8():
"solution_x": [*np.arange(1, 11).tolist(), 1] + ([0] * 10 + [1]) * 9,
"start_criterion": 285,
"solution_criterion": 0,
- "lower_bounds": np.zeros(110),
+ "bounds": Bounds(lower=np.zeros(110)),
},
"eigenb": {
"fun": partial(
@@ -5255,8 +5256,10 @@ def get_start_points_methanl8():
"solution_x": solution_x_qr3d,
"start_criterion": 1.2,
"solution_criterion": 0,
- "lower_bounds": [-np.inf] * 25
- + [0 if i == j else -np.inf for i in range(5) for j in range(5)],
+ "bounds": Bounds(
+ lower=[-np.inf] * 25
+ + [0 if i == j else -np.inf for i in range(5) for j in range(5)]
+ ),
},
"qr3dbd": {
"fun": partial(qr3dbd, m=5),
@@ -5264,8 +5267,10 @@ def get_start_points_methanl8():
"solution_x": solution_x_qr3dbd,
"start_criterion": 1.2,
"solution_criterion": 0,
- "lower_bounds": [-np.inf] * 25
- + [0 if i == j else -np.inf for i in range(5) for j in range(5)],
+ "bounds": Bounds(
+ lower=[-np.inf] * 25
+ + [0 if i == j else -np.inf for i in range(5) for j in range(5)]
+ ),
},
"spmsqrt": {
"fun": spmsqrt,
@@ -5287,8 +5292,7 @@ def get_start_points_methanl8():
"solution_x": solution_x_semicon2,
"start_criterion": 2.025037e4,
"solution_criterion": 0,
- "lower_bounds": -5 * np.ones(100),
- "upper_bounds": 0.2 * 700 * np.ones(100),
+ "bounds": Bounds(lower=-5 * np.ones(100), upper=0.2 * 700 * np.ones(100)),
},
"vardimne": {
"fun": vardimne,
diff --git a/src/optimagic/deprecations.py b/src/optimagic/deprecations.py
index 1d5defe8e..43abea5c0 100644
--- a/src/optimagic/deprecations.py
+++ b/src/optimagic/deprecations.py
@@ -1,4 +1,5 @@
import warnings
+from optimagic.parameters.bounds import Bounds
def throw_criterion_future_warning():
@@ -95,3 +96,35 @@ def replace_and_warn_about_deprecated_algo_options(algo_options):
out[replacements[k]] = algo_options[k]
return out
+
+
+def replace_and_warn_about_deprecated_bounds(
+ lower_bounds,
+ upper_bounds,
+ bounds,
+ soft_lower_bounds=None,
+ soft_upper_bounds=None,
+):
+ old_bounds = {
+ "lower": lower_bounds,
+ "upper": upper_bounds,
+ "soft_lower": soft_lower_bounds,
+ "soft_upper": soft_upper_bounds,
+ }
+
+ old_present = [k for k, v in old_bounds.items() if v is not None]
+
+ if old_present:
+ substring = ", ".join(f"{b}_bound" for b in old_present)
+ substring = substring.replace(", ", ", and ", -1)
+ msg = (
+ f"Specifying bounds via the arguments {substring} is "
+ "deprecated and will be removed in optimagic version 0.6.0 and later. "
+ "Please use the `bounds` argument instead."
+ )
+ warnings.warn(msg, FutureWarning)
+
+ if bounds is None and old_present:
+ bounds = Bounds(**old_bounds)
+
+ return bounds
diff --git a/src/optimagic/differentiation/derivatives.py b/src/optimagic/differentiation/derivatives.py
index 9c7ca22ab..a4dae849b 100644
--- a/src/optimagic/differentiation/derivatives.py
+++ b/src/optimagic/differentiation/derivatives.py
@@ -15,8 +15,10 @@
from optimagic.differentiation.generate_steps import generate_steps
from optimagic.differentiation.richardson_extrapolation import richardson_extrapolation
from optimagic.parameters.block_trees import hessian_to_block_tree, matrix_to_block_tree
-from optimagic.parameters.parameter_bounds import get_bounds
+from optimagic.parameters.bounds import get_internal_bounds
from optimagic.parameters.tree_registry import get_registry
+from optimagic.deprecations import replace_and_warn_about_deprecated_bounds
+from optimagic.parameters.bounds import Bounds, pre_process_bounds
class Evals(NamedTuple):
@@ -28,13 +30,12 @@ def first_derivative(
func,
params,
*,
+ bounds=None,
func_kwargs=None,
method="central",
n_steps=1,
base_steps=None,
scaling_factor=1,
- lower_bounds=None,
- upper_bounds=None,
step_ratio=2,
min_steps=None,
f0=None,
@@ -44,6 +45,9 @@ def first_derivative(
return_func_value=False,
return_info=False,
key=None,
+ # deprecated
+ lower_bounds=None,
+ upper_bounds=None,
):
"""Evaluate first derivative of func at params according to method and step options.
@@ -61,6 +65,13 @@ def first_derivative(
Args:
func (callable): Function of which the derivative is calculated.
params (pytree): A pytree. See :ref:`params`.
+ bounds: Lower and upper bounds on the parameters. The most general and preferred
+ way to specify bounds is an `optimagic.Bounds` object that collects lower,
+ upper, soft_lower and soft_upper bounds. The soft bounds are not used during
+ numerical differentiation. Each bound type mirrors the structure of params.
+ Check our how-to guide on bounds for examples. If params is a flat numpy
+ array, you can also provide bounds via any format that is supported by
+ scipy.optimize.minimize.
func_kwargs (dict): Additional keyword arguments for func, optional.
method (str): One of ["central", "forward", "backward"], default "central".
n_steps (int): Number of steps needed. For central methods, this is
@@ -77,8 +88,6 @@ def first_derivative(
scaling_factor is useful if you want to increase or decrease the base_step
relative to the rule-of-thumb or user provided base_step, for example to
benchmark the effect of the step size. Default 1.
- lower_bounds (pytree): To be written.
- upper_bounds (pytree): To be written.
step_ratio (float, numpy.array): Ratio between two consecutive Richardson
extrapolation steps in the same direction. default 2.0. Has to be larger
than one. The step ratio is only used if n_steps > 1.
@@ -130,10 +139,23 @@ def first_derivative(
1.
"""
+ # ==================================================================================
+ # handle deprecations
+ # ==================================================================================
+ bounds = replace_and_warn_about_deprecated_bounds(
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ bounds=bounds,
+ )
+
+ # ==================================================================================
+
+ bounds = pre_process_bounds(bounds)
+
_is_fast_params = isinstance(params, np.ndarray) and params.ndim == 1
registry = get_registry(extended=True)
- lower_bounds, upper_bounds = get_bounds(params, lower_bounds, upper_bounds)
+ internal_lb, internal_ub = get_internal_bounds(params, bounds=bounds)
# handle keyword arguments
func_kwargs = {} if func_kwargs is None else func_kwargs
@@ -157,8 +179,7 @@ def first_derivative(
target="first_derivative",
base_steps=base_steps,
scaling_factor=scaling_factor,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=Bounds(lower=internal_lb, upper=internal_ub),
step_ratio=step_ratio,
min_steps=min_steps,
)
@@ -283,13 +304,12 @@ def second_derivative(
func,
params,
*,
+ bounds=None,
func_kwargs=None,
method="central_cross",
n_steps=1,
base_steps=None,
scaling_factor=1,
- lower_bounds=None,
- upper_bounds=None,
step_ratio=2,
min_steps=None,
f0=None,
@@ -299,6 +319,9 @@ def second_derivative(
return_func_value=False,
return_info=False,
key=None,
+ # deprecated
+ lower_bounds=None,
+ upper_bounds=None,
):
"""Evaluate second derivative of func at params according to method and step
options.
@@ -321,6 +344,13 @@ def second_derivative(
:class:`pandas.DataFrame` with parameters at which the derivative is
calculated. If it is a DataFrame, it can contain the columns "lower_bound"
and "upper_bound" for bounds. See :ref:`params`.
+ bounds: Lower and upper bounds on the parameters. The most general and preferred
+ way to specify bounds is an `optimagic.Bounds` object that collects lower,
+ upper, soft_lower and soft_upper bounds. The soft bounds are not used during
+ numerical differentiation. Each bound type mirrors the structure of params.
+ Check our how-to guide on bounds for examples. If params is a flat numpy
+ array, you can also provide bounds via any format that is supported by
+ scipy.optimize.minimize.
func_kwargs (dict): Additional keyword arguments for func, optional.
method (str): One of {"forward", "backward", "central_average", "central_cross"}
These correspond to the finite difference approximations defined in
@@ -341,12 +371,6 @@ def second_derivative(
scaling_factor is useful if you want to increase or decrease the base_step
relative to the rule-of-thumb or user provided base_step, for example to
benchmark the effect of the step size. Default 1.
- lower_bounds (numpy.ndarray): 1d array with lower bounds for each parameter. If
- params is a DataFrame and has the columns "lower_bound", this will be taken
- as lower_bounds if now lower_bounds have been provided explicitly.
- upper_bounds (numpy.ndarray): 1d array with upper bounds for each parameter. If
- params is a DataFrame and has the columns "upper_bound", this will be taken
- as upper_bounds if no upper_bounds have been provided explicitly.
step_ratio (float, numpy.array): Ratio between two consecutive Richardson
extrapolation steps in the same direction. default 2.0. Has to be larger
than one. The step ratio is only used if n_steps > 1.
@@ -376,6 +400,7 @@ def second_derivative(
key (str): If func returns a dictionary, take the derivative of
func(params)[key].
+
Returns:
result (dict): Result dictionary with keys:
- "derivative" (numpy.ndarray, pandas.Series or pandas.DataFrame): The
@@ -407,7 +432,20 @@ def second_derivative(
returned if return_info is True.
"""
- lower_bounds, upper_bounds = get_bounds(params, lower_bounds, upper_bounds)
+
+ # ==================================================================================
+ # handle deprecations
+ # ==================================================================================
+ bounds = replace_and_warn_about_deprecated_bounds(
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ bounds=bounds,
+ )
+ # ==================================================================================
+
+ bounds = pre_process_bounds(bounds)
+
+ internal_lb, internal_ub = get_internal_bounds(params, bounds=bounds)
# handle keyword arguments
func_kwargs = {} if func_kwargs is None else func_kwargs
@@ -433,8 +471,7 @@ def second_derivative(
target="second_derivative",
base_steps=base_steps,
scaling_factor=scaling_factor,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=Bounds(lower=internal_lb, upper=internal_ub),
step_ratio=step_ratio,
min_steps=min_steps,
)
diff --git a/src/optimagic/differentiation/generate_steps.py b/src/optimagic/differentiation/generate_steps.py
index b1eb8ec83..da5c05632 100644
--- a/src/optimagic/differentiation/generate_steps.py
+++ b/src/optimagic/differentiation/generate_steps.py
@@ -16,8 +16,7 @@ def generate_steps(
target,
base_steps,
scaling_factor,
- lower_bounds,
- upper_bounds,
+ bounds,
step_ratio,
min_steps,
):
@@ -92,11 +91,11 @@ def generate_steps(
min_steps = base_steps if min_steps is None else min_steps
assert (
- upper_bounds - lower_bounds >= 2 * min_steps
+ bounds.upper - bounds.lower >= 2 * min_steps
).all(), "min_steps is too large to fit into bounds."
- upper_step_bounds = upper_bounds - x
- lower_step_bounds = lower_bounds - x
+ upper_step_bounds = bounds.upper - x
+ lower_step_bounds = bounds.lower - x
pos = step_ratio ** np.arange(n_steps) * base_steps.reshape(-1, 1)
neg = -pos.copy()
@@ -106,7 +105,7 @@ def generate_steps(
x, pos, neg, method, lower_step_bounds, upper_step_bounds
)
- if np.isfinite(lower_bounds).any() or np.isfinite(upper_bounds).any():
+ if np.isfinite(bounds.lower).any() or np.isfinite(bounds.upper).any():
pos, neg = _rescale_to_accomodate_bounds(
base_steps, pos, neg, lower_step_bounds, upper_step_bounds, min_steps
)
diff --git a/src/optimagic/optimization/create_optimization_problem.py b/src/optimagic/optimization/create_optimization_problem.py
new file mode 100644
index 000000000..6d8461184
--- /dev/null
+++ b/src/optimagic/optimization/create_optimization_problem.py
@@ -0,0 +1,416 @@
+from dataclasses import dataclass
+from typing import Callable, Literal, Any
+from optimagic.typing import PyTree
+from optimagic.parameters.bounds import Bounds, pre_process_bounds
+
+from pathlib import Path
+
+from optimagic.exceptions import (
+ MissingInputError,
+ AliasError,
+)
+from optimagic.optimization.check_arguments import check_optimize_kwargs
+from optimagic.optimization.get_algorithm import (
+ process_user_algorithm,
+)
+from optimagic.shared.process_user_function import (
+ process_func_of_params,
+ get_kwargs_from_args,
+)
+from optimagic.optimization.scipy_aliases import (
+ map_method_to_algorithm,
+ split_fun_and_jac,
+)
+from optimagic import deprecations
+from optimagic.deprecations import (
+ replace_and_warn_about_deprecated_algo_options,
+ replace_and_warn_about_deprecated_bounds,
+)
+from optimagic.decorators import AlgoInfo
+
+
+@dataclass(frozen=True)
+class OptimizationProblem:
+ """Collect everything that defines the optimization problem.
+
+ The attributes are very close to the arguments of `maximize` and `minimize` but they
+ are converted to stricter types. For example, the bounds argument that can be a
+ sequence of tuples, a scipy.optimize.Bounds object or an optimagic.Bounds when
+ calling `maximize` or `minimize` is converted to an optimagic.Bounds object.
+
+ All deprecated arguments are removed and all scipy aliases are replaced by their
+ optimagic counterparts.
+
+ All user provided functions are partialled if corresponding `kwargs` dictionaries
+ were provided.
+
+ # TODO: Document attributes after other todos are resolved.
+
+ """
+
+ fun: Callable[[PyTree], float | PyTree]
+ params: PyTree
+ # TODO: algorithm will become an Algorithm object; algo_options and algo_info will
+ # be removed and become part of Algorithm
+ algorithm: Callable
+ algo_options: dict[str, Any] | None
+ algo_info: AlgoInfo
+ bounds: Bounds
+ # TODO: constraints will become list[Constraint] | None
+ constraints: list[dict[str, Any]]
+ jac: Callable[[PyTree], PyTree] | None
+ fun_and_jac: Callable[[PyTree], tuple[float, PyTree]] | None
+ # TODO: numdiff_options will become NumDiffOptions
+ numdiff_options: dict[str, Any] | None
+ # TODO: logging will become None | Logger and log_options will be removed
+ logging: bool | Path | None
+ log_options: dict[str, Any] | None
+ # TODO: error_handling will become None | ErrorHandlingOptions and error_penalty
+ # will be removed
+ error_handling: Literal["raise", "continue"]
+ error_penalty: dict[str, Any] | None
+ # TODO: scaling will become None | ScalingOptions and scaling_options will be
+ # removed
+ scaling: bool
+ scaling_options: dict[str, Any] | None
+ # TODO: multistart will become None | MultistartOptions and multistart_options will
+ # be removed
+ multistart: bool
+ multistart_options: dict[str, Any] | None
+ collect_history: bool
+ skip_checks: bool
+ direction: Literal["minimize", "maximize"]
+
+
+def create_optimization_problem(
+ direction,
+ fun,
+ params,
+ algorithm,
+ *,
+ bounds,
+ fun_kwargs,
+ constraints,
+ algo_options,
+ jac,
+ jac_kwargs,
+ fun_and_jac,
+ fun_and_jac_kwargs,
+ numdiff_options,
+ logging,
+ log_options,
+ error_handling,
+ error_penalty,
+ scaling,
+ scaling_options,
+ multistart,
+ multistart_options,
+ collect_history,
+ skip_checks,
+ # scipy aliases
+ x0,
+ method,
+ args,
+ # scipy arguments that are not yet supported
+ hess,
+ hessp,
+ callback,
+ # scipy arguments that will never be supported
+ options,
+ tol,
+ # deprecated arguments
+ criterion,
+ criterion_kwargs,
+ derivative,
+ derivative_kwargs,
+ criterion_and_derivative,
+ criterion_and_derivative_kwargs,
+ lower_bounds,
+ upper_bounds,
+ soft_lower_bounds,
+ soft_upper_bounds,
+):
+ # ==================================================================================
+ # error handling needed as long as fun is an optional argument (i.e. until
+ # criterion is fully removed).
+ # ==================================================================================
+
+ if fun is None and criterion is None:
+ msg = (
+ "Missing objective function. Please provide an objective function as the "
+ "first positional argument or as the keyword argument `fun`."
+ )
+ raise MissingInputError(msg)
+
+ if params is None and x0 is None:
+ msg = (
+ "Missing start parameters. Please provide start parameters as the second "
+ "positional argument or as the keyword argument `params`."
+ )
+ raise MissingInputError(msg)
+
+ if algorithm is None and method is None:
+ msg = (
+ "Missing algorithm. Please provide an algorithm as the third positional "
+ "argument or as the keyword argument `algorithm`."
+ )
+ raise MissingInputError(msg)
+
+ # ==================================================================================
+ # deprecations
+ # ==================================================================================
+
+ if criterion is not None:
+ deprecations.throw_criterion_future_warning()
+ fun = criterion if fun is None else fun
+
+ if criterion_kwargs is not None:
+ deprecations.throw_criterion_kwargs_future_warning()
+ fun_kwargs = criterion_kwargs if fun_kwargs is None else fun_kwargs
+
+ if derivative is not None:
+ deprecations.throw_derivative_future_warning()
+ jac = derivative if jac is None else jac
+
+ if derivative_kwargs is not None:
+ deprecations.throw_derivative_kwargs_future_warning()
+ jac_kwargs = derivative_kwargs if jac_kwargs is None else jac_kwargs
+
+ if criterion_and_derivative is not None:
+ deprecations.throw_criterion_and_derivative_future_warning()
+ fun_and_jac = criterion_and_derivative if fun_and_jac is None else fun_and_jac
+
+ if criterion_and_derivative_kwargs is not None:
+ deprecations.throw_criterion_and_derivative_kwargs_future_warning()
+ fun_and_jac_kwargs = (
+ criterion_and_derivative_kwargs
+ if fun_and_jac_kwargs is None
+ else fun_and_jac_kwargs
+ )
+
+ algo_options = replace_and_warn_about_deprecated_algo_options(algo_options)
+
+ bounds = replace_and_warn_about_deprecated_bounds(
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ bounds=bounds,
+ soft_lower_bounds=soft_lower_bounds,
+ soft_upper_bounds=soft_upper_bounds,
+ )
+
+ # ==================================================================================
+ # handle scipy aliases
+ # ==================================================================================
+
+ if x0 is not None:
+ if params is not None:
+ msg = (
+ "x0 is an alias for params (for better compatibility with scipy). "
+ "Do not use both x0 and params."
+ )
+ raise AliasError(msg)
+ else:
+ params = x0
+
+ if method is not None:
+ if algorithm is not None:
+ msg = (
+ "method is an alias for algorithm to select the scipy optimizers under "
+ "their original name. Do not use both method and algorithm."
+ )
+ raise AliasError(msg)
+ else:
+ algorithm = map_method_to_algorithm(method)
+
+ if args is not None:
+ if (
+ fun_kwargs is not None
+ or jac_kwargs is not None
+ or fun_and_jac_kwargs is not None
+ ):
+ msg = (
+ "args is an alternative to fun_kwargs, jac_kwargs and "
+ "fun_and_jac_kwargs that optimagic supports for compatibility "
+ "with scipy. Do not use args in conjunction with any of the other "
+ "arguments."
+ )
+ raise AliasError(msg)
+ else:
+ kwargs = get_kwargs_from_args(args, fun, offset=1)
+ fun_kwargs, jac_kwargs, fun_and_jac_kwargs = kwargs, kwargs, kwargs
+
+ # jac is not an alias but we need to handle the case where `jac=True`, i.e. fun is
+ # actually fun_and_jac. This is not recommended in optimagic because then optimizers
+ # cannot evaluate fun in isolation but we can easily support it for compatibility.
+ if jac is True:
+ jac = None
+ if fun_and_jac is None:
+ fun_and_jac = fun
+ fun = split_fun_and_jac(fun_and_jac, target="fun")
+
+ bounds = pre_process_bounds(bounds)
+
+ # ==================================================================================
+ # Handle scipy arguments that are not yet implemented
+ # ==================================================================================
+
+ if hess is not None:
+ msg = (
+ "The hess argument is not yet supported in optimagic. Creat an issue on "
+ "https://github.com/OpenSourceEconomics/optimagic/ if you have urgent need "
+ "for this feature."
+ )
+ raise NotImplementedError(msg)
+
+ if hessp is not None:
+ msg = (
+ "The hessp argument is not yet supported in optimagic. Creat an issue on "
+ "https://github.com/OpenSourceEconomics/optimagic/ if you have urgent need "
+ "for this feature."
+ )
+ raise NotImplementedError(msg)
+
+ if callback is not None:
+ msg = (
+ "The callback argument is not yet supported in optimagic. Creat an issue "
+ "on https://github.com/OpenSourceEconomics/optimagic/ if you have urgent "
+ "need for this feature."
+ )
+ raise NotImplementedError(msg)
+
+ # ==================================================================================
+ # Handle scipy arguments that will never be supported
+ # ==================================================================================
+
+ if options is not None:
+ # TODO: Add link to a how-to guide or tutorial for this
+ msg = (
+ "The options argument is not supported in optimagic. Please use the "
+ "algo_options argument instead."
+ )
+ raise NotImplementedError(msg)
+
+ if tol is not None:
+ # TODO: Add link to a how-to guide or tutorial for this
+ msg = (
+ "The tol argument is not supported in optimagic. Please use "
+ "algo_options or configured algorithms instead to set convergence criteria "
+ "for your optimizer."
+ )
+ raise NotImplementedError(msg)
+
+ # ==================================================================================
+ # Set default values and check options
+ # ==================================================================================
+ fun_kwargs = {} if fun_kwargs is None else fun_kwargs
+ constraints = [] if constraints is None else constraints
+ algo_options = {} if algo_options is None else algo_options
+ jac_kwargs = {} if jac_kwargs is None else jac_kwargs
+ fun_and_jac_kwargs = {} if fun_and_jac_kwargs is None else fun_and_jac_kwargs
+ numdiff_options = {} if numdiff_options is None else numdiff_options
+ log_options = {} if log_options is None else log_options
+ error_penalty = {} if error_penalty is None else error_penalty
+ scaling_options = {} if scaling_options is None else scaling_options
+ multistart_options = {} if multistart_options is None else multistart_options
+ if logging:
+ logging = Path(logging)
+
+ # ==================================================================================
+ # Check types of arguments
+ # ==================================================================================
+ # TODO: This should probably be inlined
+
+ if not skip_checks:
+ check_optimize_kwargs(
+ direction=direction,
+ criterion=fun,
+ criterion_kwargs=fun_kwargs,
+ params=params,
+ algorithm=algorithm,
+ constraints=constraints,
+ algo_options=algo_options,
+ derivative=jac,
+ derivative_kwargs=jac_kwargs,
+ criterion_and_derivative=fun_and_jac,
+ criterion_and_derivative_kwargs=fun_and_jac_kwargs,
+ numdiff_options=numdiff_options,
+ logging=logging,
+ log_options=log_options,
+ error_handling=error_handling,
+ error_penalty=error_penalty,
+ scaling=scaling,
+ scaling_options=scaling_options,
+ multistart=multistart,
+ multistart_options=multistart_options,
+ )
+ # ==================================================================================
+ # Get the algorithm info
+ # ==================================================================================
+ raw_algo, algo_info = process_user_algorithm(algorithm)
+
+ if algo_info.primary_criterion_entry == "root_contributions":
+ if direction == "maximize":
+ msg = (
+ "Optimizers that exploit a least squares structure like {} can only be "
+ "used for minimization."
+ )
+ raise ValueError(msg.format(algo_info.name))
+
+ # ==================================================================================
+ # partial the kwargs into corresponding functions
+ # ==================================================================================
+ fun = process_func_of_params(
+ func=fun,
+ kwargs=fun_kwargs,
+ name="criterion",
+ skip_checks=skip_checks,
+ )
+ if isinstance(jac, dict):
+ jac = jac.get(algo_info.primary_criterion_entry)
+ if jac is not None:
+ jac = process_func_of_params(
+ func=jac,
+ kwargs=jac_kwargs,
+ name="derivative",
+ skip_checks=skip_checks,
+ )
+ if isinstance(fun_and_jac, dict):
+ fun_and_jac = fun_and_jac.get(algo_info.primary_criterion_entry)
+
+ if fun_and_jac is not None:
+ fun_and_jac = process_func_of_params(
+ func=fun_and_jac,
+ kwargs=fun_and_jac_kwargs,
+ name="criterion_and_derivative",
+ skip_checks=skip_checks,
+ )
+
+ # ==================================================================================
+ # create the problem object
+ # ==================================================================================
+
+ problem = OptimizationProblem(
+ fun=fun,
+ params=params,
+ algorithm=raw_algo,
+ algo_options=algo_options,
+ algo_info=algo_info,
+ bounds=bounds,
+ constraints=constraints,
+ jac=jac,
+ fun_and_jac=fun_and_jac,
+ numdiff_options=numdiff_options,
+ logging=logging,
+ log_options=log_options,
+ error_handling=error_handling,
+ error_penalty=error_penalty,
+ scaling=scaling,
+ scaling_options=scaling_options,
+ multistart=multistart,
+ multistart_options=multistart_options,
+ collect_history=collect_history,
+ skip_checks=skip_checks,
+ direction=direction,
+ )
+
+ return problem
diff --git a/src/optimagic/optimization/get_algorithm.py b/src/optimagic/optimization/get_algorithm.py
index b61e509d0..b94df217d 100644
--- a/src/optimagic/optimization/get_algorithm.py
+++ b/src/optimagic/optimization/get_algorithm.py
@@ -26,7 +26,6 @@ def process_user_algorithm(algorithm):
Returns:
callable: the raw internal algorithm
AlgoInfo: Attributes of the algorithm
- set: The free arguments of the algorithm.
"""
if isinstance(algorithm, str):
diff --git a/src/optimagic/optimization/tiktak.py b/src/optimagic/optimization/multistart.py
similarity index 100%
rename from src/optimagic/optimization/tiktak.py
rename to src/optimagic/optimization/multistart.py
diff --git a/src/optimagic/optimization/optimize.py b/src/optimagic/optimization/optimize.py
index 9140984f3..6c12b2977 100644
--- a/src/optimagic/optimization/optimize.py
+++ b/src/optimagic/optimization/optimize.py
@@ -1,3 +1,17 @@
+"""Public functions for optimization.
+
+This module defines the public functions `maximize` and `minimize` that will be called
+by users.
+
+Internally, `maximize` and `minimize` just call `create_optimization_problem` with
+all arguments and add the `direction`. In `create_optimization_problem`, the user input
+is consolidated and converted to stricter types. The resulting `OptimizationProblem`
+is then passed to `_optimize` which handles the optimization logic.
+
+`_optimize` processes the optimization problem and performs the actual optimization.
+
+"""
+
import functools
import warnings
from pathlib import Path
@@ -6,8 +20,6 @@
from optimagic.exceptions import (
InvalidFunctionError,
InvalidKwargsError,
- MissingInputError,
- AliasError,
)
from optimagic.logging.create_tables import (
make_optimization_iteration_table,
@@ -16,11 +28,9 @@
)
from optimagic.logging.load_database import load_database
from optimagic.logging.write_to_database import append_row
-from optimagic.optimization.check_arguments import check_optimize_kwargs
from optimagic.optimization.error_penalty import get_error_penalty_function
from optimagic.optimization.get_algorithm import (
get_final_algorithm,
- process_user_algorithm,
)
from optimagic.optimization.internal_criterion_template import (
internal_criterion_and_derivative_template,
@@ -28,22 +38,22 @@
from optimagic.optimization.optimization_logging import log_scheduled_steps_and_get_ids
from optimagic.optimization.process_multistart_sample import process_multistart_sample
from optimagic.optimization.process_results import process_internal_optimizer_result
-from optimagic.optimization.tiktak import WEIGHT_FUNCTIONS, run_multistart_optimization
+from optimagic.optimization.multistart import (
+ WEIGHT_FUNCTIONS,
+ run_multistart_optimization,
+)
from optimagic.parameters.conversion import (
aggregate_func_output_to_value,
get_converter,
)
from optimagic.parameters.nonlinear_constraints import process_nonlinear_constraints
-from optimagic.shared.process_user_function import (
- process_func_of_params,
- get_kwargs_from_args,
-)
-from optimagic.optimization.scipy_aliases import (
- map_method_to_algorithm,
- split_fun_and_jac,
+
+from optimagic.parameters.bounds import Bounds
+from optimagic.optimization.create_optimization_problem import (
+ create_optimization_problem,
+ OptimizationProblem,
)
-from optimagic import deprecations
-from optimagic.deprecations import replace_and_warn_about_deprecated_algo_options
+from optimagic.optimization.optimize_result import OptimizeResult
def maximize(
@@ -51,12 +61,9 @@ def maximize(
params=None,
algorithm=None,
*,
- lower_bounds=None,
- upper_bounds=None,
- soft_lower_bounds=None,
- soft_upper_bounds=None,
- fun_kwargs=None,
+ bounds=None,
constraints=None,
+ fun_kwargs=None,
algo_options=None,
jac=None,
jac_kwargs=None,
@@ -91,17 +98,31 @@ def maximize(
derivative_kwargs=None,
criterion_and_derivative=None,
criterion_and_derivative_kwargs=None,
+ lower_bounds=None,
+ upper_bounds=None,
+ soft_lower_bounds=None,
+ soft_upper_bounds=None,
):
- """Maximize criterion using algorithm subject to constraints."""
- return _optimize(
+ """Maximize fun using algorithm subject to constraints.
+
+ TODO: Write docstring after enhancement proposals are implemented.
+
+ Args:
+ bounds: Lower and upper bounds on the parameters. The most general and preferred
+ way to specify bounds is an `optimagic.Bounds` object that collects lower,
+ upper, soft_lower and soft_upper bounds. The soft bounds are used for
+ sampling based optimizers but are not enforced during optimization. Each
+ bound type mirrors the structure of params. Check our how-to guide on bounds
+ for examples. If params is a flat numpy array, you can also provide bounds
+ via any format that is supported by scipy.optimize.minimize.
+
+ """
+ problem = create_optimization_problem(
direction="maximize",
fun=fun,
params=params,
+ bounds=bounds,
algorithm=algorithm,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
- soft_lower_bounds=soft_lower_bounds,
- soft_upper_bounds=soft_upper_bounds,
fun_kwargs=fun_kwargs,
constraints=constraints,
algo_options=algo_options,
@@ -138,19 +159,21 @@ def maximize(
derivative_kwargs=derivative_kwargs,
criterion_and_derivative=criterion_and_derivative,
criterion_and_derivative_kwargs=criterion_and_derivative_kwargs,
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ soft_lower_bounds=soft_lower_bounds,
+ soft_upper_bounds=soft_upper_bounds,
)
+ return _optimize(problem)
+
def minimize(
fun=None,
params=None,
algorithm=None,
*,
- lower_bounds=None,
- upper_bounds=None,
- soft_lower_bounds=None,
- soft_upper_bounds=None,
- fun_kwargs=None,
+ bounds=None,
constraints=None,
algo_options=None,
jac=None,
@@ -186,18 +209,33 @@ def minimize(
derivative_kwargs=None,
criterion_and_derivative=None,
criterion_and_derivative_kwargs=None,
+ lower_bounds=None,
+ upper_bounds=None,
+ soft_lower_bounds=None,
+ soft_upper_bounds=None,
+ fun_kwargs=None,
):
- """Minimize criterion using algorithm subject to constraints."""
+ """Minimize criterion using algorithm subject to constraints.
+
+ TODO: Write docstring after enhancement proposals are implemented.
- return _optimize(
+ Args:
+ bounds: Lower and upper bounds on the parameters. The most general and preferred
+ way to specify bounds is an `optimagic.Bounds` object that collects lower,
+ upper, soft_lower and soft_upper bounds. The soft bounds are used for
+ sampling based optimizers but are not enforced during optimization. Each
+ bound type mirrors the structure of params. Check our how-to guide on bounds
+ for examples. If params is a flat numpy array, you can also provide bounds
+ via any format that is supported by scipy.optimize.minimize.
+
+ """
+
+ problem = create_optimization_problem(
direction="minimize",
fun=fun,
params=params,
algorithm=algorithm,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
- soft_lower_bounds=soft_lower_bounds,
- soft_upper_bounds=soft_upper_bounds,
+ bounds=bounds,
fun_kwargs=fun_kwargs,
constraints=constraints,
algo_options=algo_options,
@@ -234,349 +272,40 @@ def minimize(
derivative_kwargs=derivative_kwargs,
criterion_and_derivative=criterion_and_derivative,
criterion_and_derivative_kwargs=criterion_and_derivative_kwargs,
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ soft_lower_bounds=soft_lower_bounds,
+ soft_upper_bounds=soft_upper_bounds,
)
+ return _optimize(problem)
-def _optimize(
- direction,
- fun,
- params,
- algorithm,
- *,
- lower_bounds,
- upper_bounds,
- soft_lower_bounds,
- soft_upper_bounds,
- fun_kwargs,
- constraints,
- algo_options,
- jac,
- jac_kwargs,
- fun_and_jac,
- fun_and_jac_kwargs,
- numdiff_options,
- logging,
- log_options,
- error_handling,
- error_penalty,
- scaling,
- scaling_options,
- multistart,
- multistart_options,
- collect_history,
- skip_checks,
- # scipy aliases
- x0,
- method,
- args,
- # scipy arguments that are not yet supported
- hess,
- hessp,
- callback,
- # scipy arguments that will never be supported
- options,
- tol,
- # deprecated arguments
- criterion,
- criterion_kwargs,
- derivative,
- derivative_kwargs,
- criterion_and_derivative,
- criterion_and_derivative_kwargs,
-):
- """Minimize or maximize criterion using algorithm subject to constraints.
-
- Arguments are the same as in maximize and minimize, with an additional direction
- argument. Direction is a string that can take the values "maximize" and "minimize".
-
- Returns are the same as in maximize and minimize.
-
- """
- # ==================================================================================
- # error handling needed as long as fun is an optional argument (i.e. until
- # criterion is fully removed).
- # ==================================================================================
-
- if fun is None and criterion is None:
- msg = (
- "Missing objective function. Please provide an objective function as the "
- "first positional argument or as the keyword argument `fun`."
- )
- raise MissingInputError(msg)
-
- if params is None and x0 is None:
- msg = (
- "Missing start parameters. Please provide start parameters as the second "
- "positional argument or as the keyword argument `params`."
- )
- raise MissingInputError(msg)
-
- if algorithm is None and method is None:
- msg = (
- "Missing algorithm. Please provide an algorithm as the third positional "
- "argument or as the keyword argument `algorithm`."
- )
- raise MissingInputError(msg)
-
- # ==================================================================================
- # deprecations
- # ==================================================================================
-
- if criterion is not None:
- deprecations.throw_criterion_future_warning()
- fun = criterion if fun is None else fun
-
- if criterion_kwargs is not None:
- deprecations.throw_criterion_kwargs_future_warning()
- fun_kwargs = criterion_kwargs if fun_kwargs is None else fun_kwargs
-
- if derivative is not None:
- deprecations.throw_derivative_future_warning()
- jac = derivative if jac is None else jac
-
- if derivative_kwargs is not None:
- deprecations.throw_derivative_kwargs_future_warning()
- jac_kwargs = derivative_kwargs if jac_kwargs is None else jac_kwargs
-
- if criterion_and_derivative is not None:
- deprecations.throw_criterion_and_derivative_future_warning()
- fun_and_jac = criterion_and_derivative if fun_and_jac is None else fun_and_jac
-
- if criterion_and_derivative_kwargs is not None:
- deprecations.throw_criterion_and_derivative_kwargs_future_warning()
- fun_and_jac_kwargs = (
- criterion_and_derivative_kwargs
- if fun_and_jac_kwargs is None
- else fun_and_jac_kwargs
- )
-
- algo_options = replace_and_warn_about_deprecated_algo_options(algo_options)
-
- # ==================================================================================
- # handle scipy aliases
- # ==================================================================================
-
- if x0 is not None:
- if params is not None:
- msg = (
- "x0 is an alias for params (for better compatibility with scipy). "
- "Do not use both x0 and params."
- )
- raise AliasError(msg)
- else:
- params = x0
-
- if method is not None:
- if algorithm is not None:
- msg = (
- "method is an alias for algorithm to select the scipy optimizers under "
- "their original name. Do not use both method and algorithm."
- )
- raise AliasError(msg)
- else:
- algorithm = map_method_to_algorithm(method)
-
- if args is not None:
- if (
- fun_kwargs is not None
- or jac_kwargs is not None
- or fun_and_jac_kwargs is not None
- ):
- msg = (
- "args is an alternative to fun_kwargs, jac_kwargs and "
- "fun_and_jac_kwargs that optimagic supports for compatibility "
- "with scipy. Do not use args in conjunction with any of the other "
- "arguments."
- )
- raise AliasError(msg)
- else:
- kwargs = get_kwargs_from_args(args, fun, offset=1)
- fun_kwargs, jac_kwargs, fun_and_jac_kwargs = kwargs, kwargs, kwargs
-
- # jac is not an alias but we need to handle the case where `jac=True`, i.e. fun is
- # actually fun_and_jac. This is not recommended in optimagic because then optimizers
- # cannot evaluate fun in isolation but we can easily support it for compatibility.
- if jac is True:
- jac = None
- if fun_and_jac is None:
- fun_and_jac = fun
- fun = split_fun_and_jac(fun_and_jac, target="fun")
-
- # ==================================================================================
- # Handle scipy arguments that are not yet implemented
- # ==================================================================================
-
- if hess is not None:
- msg = (
- "The hess argument is not yet supported in optimagic. Creat an issue on "
- "https://github.com/OpenSourceEconomics/optimagic/ if you have urgent need "
- "for this feature."
- )
- raise NotImplementedError(msg)
-
- if hessp is not None:
- msg = (
- "The hessp argument is not yet supported in optimagic. Creat an issue on "
- "https://github.com/OpenSourceEconomics/optimagic/ if you have urgent need "
- "for this feature."
- )
- raise NotImplementedError(msg)
-
- if callback is not None:
- msg = (
- "The callback argument is not yet supported in optimagic. Creat an issue "
- "on https://github.com/OpenSourceEconomics/optimagic/ if you have urgent "
- "need for this feature."
- )
- raise NotImplementedError(msg)
-
- # ==================================================================================
- # Handle scipy arguments that will never be supported
- # ==================================================================================
-
- if options is not None:
- # TODO: Add link to a how-to guide or tutorial for this
- msg = (
- "The options argument is not supported in optimagic. Please use the "
- "algo_options argument instead."
- )
- raise NotImplementedError(msg)
-
- if tol is not None:
- # TODO: Add link to a how-to guide or tutorial for this
- msg = (
- "The tol argument is not supported in optimagic. Please use "
- "algo_options or configured algorithms instead to set convergence criteria "
- "for your optimizer."
- )
- raise NotImplementedError(msg)
-
- # ==================================================================================
- # Set default values and check options
- # ==================================================================================
- fun_kwargs = _setdefault(fun_kwargs, {})
- constraints = _setdefault(constraints, [])
- algo_options = _setdefault(algo_options, {})
- jac_kwargs = _setdefault(jac_kwargs, {})
- fun_and_jac_kwargs = _setdefault(fun_and_jac_kwargs, {})
- numdiff_options = _setdefault(numdiff_options, {})
- log_options = _setdefault(log_options, {})
- scaling_options = _setdefault(scaling_options, {})
- error_penalty = _setdefault(error_penalty, {})
- multistart_options = _setdefault(multistart_options, {})
- if logging:
- logging = Path(logging)
-
- if not skip_checks:
- check_optimize_kwargs(
- direction=direction,
- criterion=fun,
- criterion_kwargs=fun_kwargs,
- params=params,
- algorithm=algorithm,
- constraints=constraints,
- algo_options=algo_options,
- derivative=jac,
- derivative_kwargs=jac_kwargs,
- criterion_and_derivative=fun_and_jac,
- criterion_and_derivative_kwargs=fun_and_jac_kwargs,
- numdiff_options=numdiff_options,
- logging=logging,
- log_options=log_options,
- error_handling=error_handling,
- error_penalty=error_penalty,
- scaling=scaling,
- scaling_options=scaling_options,
- multistart=multistart,
- multistart_options=multistart_options,
- )
- # ==================================================================================
- # Get the algorithm info
- # ==================================================================================
- raw_algo, algo_info = process_user_algorithm(algorithm)
-
- algo_kwargs = set(algo_info.arguments)
-
- if algo_info.primary_criterion_entry == "root_contributions":
- if direction == "maximize":
- msg = (
- "Optimizers that exploit a least squares structure like {} can only be "
- "used for minimization."
- )
- raise ValueError(msg.format(algo_info.name))
-
+def _optimize(problem: OptimizationProblem) -> OptimizeResult:
+ """Solve an optimization problem."""
# ==================================================================================
# Split constraints into nonlinear and reparametrization parts
# ==================================================================================
+ constraints = problem.constraints
if isinstance(constraints, dict):
constraints = [constraints]
nonlinear_constraints = [c for c in constraints if c["type"] == "nonlinear"]
+ algo_kwargs = set(problem.algo_info.arguments)
if nonlinear_constraints and "nonlinear_constraints" not in algo_kwargs:
raise ValueError(
- f"Algorithm {algo_info.name} does not support nonlinear constraints."
+ f"Algorithm {problem.algo_info.name} does not support nonlinear "
+ "constraints."
)
# the following constraints will be handled via reparametrization
constraints = [c for c in constraints if c["type"] != "nonlinear"]
- # ==================================================================================
- # prepare logging
- # ==================================================================================
- if logging:
- problem_data = {
- "direction": direction,
- # "criterion"-criterion,
- "criterion_kwargs": fun_kwargs,
- "algorithm": algorithm,
- "constraints": constraints,
- "algo_options": algo_options,
- # "derivative"-derivative,
- "derivative_kwargs": jac_kwargs,
- # "criterion_and_derivative"-criterion_and_derivative,
- "criterion_and_derivative_kwargs": fun_and_jac_kwargs,
- "numdiff_options": numdiff_options,
- "log_options": log_options,
- "error_handling": error_handling,
- "error_penalty": error_penalty,
- "params": params,
- }
-
- # ==================================================================================
- # partial the kwargs into corresponding functions
- # ==================================================================================
- fun = process_func_of_params(
- func=fun,
- kwargs=fun_kwargs,
- name="criterion",
- skip_checks=skip_checks,
- )
- if isinstance(jac, dict):
- jac = jac.get(algo_info.primary_criterion_entry)
- if jac is not None:
- jac = process_func_of_params(
- func=jac,
- kwargs=jac_kwargs,
- name="derivative",
- skip_checks=skip_checks,
- )
- if isinstance(fun_and_jac, dict):
- fun_and_jac = fun_and_jac.get(algo_info.primary_criterion_entry)
-
- if fun_and_jac is not None:
- fun_and_jac = process_func_of_params(
- func=fun_and_jac,
- kwargs=fun_and_jac_kwargs,
- name="criterion_and_derivative",
- skip_checks=skip_checks,
- )
-
# ==================================================================================
# Do first evaluation of user provided functions
# ==================================================================================
try:
- first_crit_eval = fun(params)
+ first_crit_eval = problem.fun(problem.params)
except (KeyboardInterrupt, SystemExit):
raise
except Exception as e:
@@ -584,27 +313,27 @@ def _optimize(
raise InvalidFunctionError(msg) from e
# do first derivative evaluation (if given)
- if jac is not None:
+ if problem.jac is not None:
try:
- first_deriv_eval = jac(params)
+ first_deriv_eval = problem.jac(problem.params)
except (KeyboardInterrupt, SystemExit):
raise
except Exception as e:
msg = "Error while evaluating derivative at start params."
raise InvalidFunctionError(msg) from e
- if fun_and_jac is not None:
+ if problem.fun_and_jac is not None:
try:
- first_crit_and_deriv_eval = fun_and_jac(params)
+ first_crit_and_deriv_eval = problem.fun_and_jac(problem.params)
except (KeyboardInterrupt, SystemExit):
raise
except Exception as e:
msg = "Error while evaluating criterion_and_derivative at start params."
raise InvalidFunctionError(msg) from e
- if jac is not None:
+ if problem.jac is not None:
used_deriv = first_deriv_eval
- elif fun_and_jac is not None:
+ elif problem.fun_and_jac is not None:
used_deriv = first_crit_and_deriv_eval[1]
else:
used_deriv = None
@@ -613,26 +342,33 @@ def _optimize(
# Get the converter (for tree flattening, constraints and scaling)
# ==================================================================================
converter, internal_params = get_converter(
- params=params,
+ params=problem.params,
constraints=constraints,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=problem.bounds,
func_eval=first_crit_eval,
- primary_key=algo_info.primary_criterion_entry,
- scaling=scaling,
- scaling_options=scaling_options,
+ primary_key=problem.algo_info.primary_criterion_entry,
+ scaling=problem.scaling,
+ scaling_options=problem.scaling_options,
derivative_eval=used_deriv,
- soft_lower_bounds=soft_lower_bounds,
- soft_upper_bounds=soft_upper_bounds,
- add_soft_bounds=multistart,
+ add_soft_bounds=problem.multistart,
)
# ==================================================================================
# initialize the log database
# ==================================================================================
- if logging:
- problem_data["free_mask"] = internal_params.free_mask
- database = _create_and_initialize_database(logging, log_options, problem_data)
+ if problem.logging:
+ # TODO: We want to remove the optimization_problem table completely but we
+ # probably do need to store the start parameters in the database because it is
+ # used by the log reader.
+ problem_data = {
+ "direction": problem.direction,
+ "params": problem.params,
+ }
+ database = _create_and_initialize_database(
+ logging=problem.logging,
+ log_options=problem.log_options,
+ problem_data=problem_data,
+ )
else:
database = None
@@ -640,35 +376,35 @@ def _optimize(
# Do some things that require internal parameters or bounds
# ==================================================================================
- if converter.has_transforming_constraints and multistart:
+ if converter.has_transforming_constraints and problem.multistart:
raise NotImplementedError(
"multistart optimizations are not yet compatible with transforming "
"constraints."
)
numdiff_options = _fill_numdiff_options_with_defaults(
- numdiff_options=numdiff_options,
+ numdiff_options=problem.numdiff_options,
lower_bounds=internal_params.lower_bounds,
upper_bounds=internal_params.upper_bounds,
)
# get error penalty function
error_penalty_func = get_error_penalty_function(
- error_handling=error_handling,
+ error_handling=problem.error_handling,
start_x=internal_params.values,
start_criterion=converter.func_to_internal(first_crit_eval),
- error_penalty=error_penalty,
- primary_key=algo_info.primary_criterion_entry,
- direction=direction,
+ error_penalty=problem.error_penalty,
+ primary_key=problem.algo_info.primary_criterion_entry,
+ direction=problem.direction,
)
# process nonlinear constraints:
internal_constraints = process_nonlinear_constraints(
nonlinear_constraints=nonlinear_constraints,
- params=params,
+ params=problem.params,
converter=converter,
numdiff_options=numdiff_options,
- skip_checks=skip_checks,
+ skip_checks=problem.skip_checks,
)
x = internal_params.values
@@ -676,31 +412,31 @@ def _optimize(
# get the internal algorithm
# ==================================================================================
internal_algorithm = get_final_algorithm(
- raw_algorithm=raw_algo,
- algo_info=algo_info,
+ raw_algorithm=problem.algorithm,
+ algo_info=problem.algo_info,
valid_kwargs=algo_kwargs,
lower_bounds=internal_params.lower_bounds,
upper_bounds=internal_params.upper_bounds,
nonlinear_constraints=internal_constraints,
- algo_options=algo_options,
- logging=logging,
+ algo_options=problem.algo_options,
+ logging=problem.logging,
database=database,
- collect_history=collect_history,
+ collect_history=problem.collect_history,
)
# ==================================================================================
# partial arguments into the internal_criterion_and_derivative_template
# ==================================================================================
to_partial = {
- "direction": direction,
- "criterion": fun,
+ "direction": problem.direction,
+ "criterion": problem.fun,
"converter": converter,
- "derivative": jac,
- "criterion_and_derivative": fun_and_jac,
+ "derivative": problem.jac,
+ "criterion_and_derivative": problem.fun_and_jac,
"numdiff_options": numdiff_options,
- "logging": logging,
+ "logging": problem.logging,
"database": database,
- "algo_info": algo_info,
- "error_handling": error_handling,
+ "algo_info": problem.algo_info,
+ "error_handling": problem.error_handling,
"error_penalty_func": error_penalty_func,
}
@@ -720,35 +456,35 @@ def _optimize(
# ==================================================================================
# Do actual optimization
# ==================================================================================
- if not multistart:
+ if not problem.multistart:
steps = [{"type": "optimization", "name": "optimization"}]
step_ids = log_scheduled_steps_and_get_ids(
steps=steps,
- logging=logging,
+ logging=problem.logging,
database=database,
)
raw_res = internal_algorithm(**problem_functions, x=x, step_id=step_ids[0])
else:
multistart_options = _fill_multistart_options_with_defaults(
- options=multistart_options,
- params=params,
+ options=problem.multistart_options,
+ params=problem.params,
x=x,
params_to_internal=converter.params_to_internal,
)
raw_res = run_multistart_optimization(
local_algorithm=internal_algorithm,
- primary_key=algo_info.primary_criterion_entry,
+ primary_key=problem.algo_info.primary_criterion_entry,
problem_functions=problem_functions,
x=x,
lower_sampling_bounds=internal_params.soft_lower_bounds,
upper_sampling_bounds=internal_params.soft_upper_bounds,
options=multistart_options,
- logging=logging,
+ logging=problem.logging,
database=database,
- error_handling=error_handling,
+ error_handling=problem.error_handling,
)
# ==================================================================================
@@ -757,23 +493,23 @@ def _optimize(
_scalar_start_criterion = aggregate_func_output_to_value(
converter.func_to_internal(first_crit_eval),
- algo_info.primary_criterion_entry,
+ problem.algo_info.primary_criterion_entry,
)
fixed_result_kwargs = {
"start_fun": _scalar_start_criterion,
- "start_params": params,
- "algorithm": algo_info.name,
- "direction": direction,
+ "start_params": problem.params,
+ "algorithm": problem.algo_info.name,
+ "direction": problem.direction,
"n_free": internal_params.free_mask.sum(),
}
res = process_internal_optimizer_result(
raw_res,
converter=converter,
- primary_key=algo_info.primary_criterion_entry,
+ primary_key=problem.algo_info.primary_criterion_entry,
fixed_kwargs=fixed_result_kwargs,
- skip_checks=skip_checks,
+ skip_checks=problem.skip_checks,
)
return res
@@ -863,8 +599,7 @@ def _fill_numdiff_options_with_defaults(numdiff_options, lower_bounds, upper_bou
# only define the ones that deviate from the normal defaults
default_numdiff_options = {
"method": "forward",
- "lower_bounds": lower_bounds,
- "upper_bounds": upper_bounds,
+ "bounds": Bounds(lower=lower_bounds, upper=upper_bounds),
"error_handling": default_error_handling,
"return_info": False,
}
diff --git a/src/optimagic/parameters/bounds.py b/src/optimagic/parameters/bounds.py
new file mode 100644
index 000000000..eb045a63a
--- /dev/null
+++ b/src/optimagic/parameters/bounds.py
@@ -0,0 +1,245 @@
+from __future__ import annotations
+
+import numpy as np
+from pybaum import leaf_names, tree_map
+from pybaum import tree_just_flatten as tree_leaves
+
+from optimagic.exceptions import InvalidBoundsError
+from optimagic.parameters.tree_registry import get_registry
+from dataclasses import dataclass
+from optimagic.typing import PyTree, PyTreeRegistry
+from scipy.optimize import Bounds as ScipyBounds
+from typing import Sequence
+from numpy.typing import NDArray
+from typing import Any
+
+
+@dataclass(frozen=True)
+class Bounds:
+ lower: PyTree | None = None
+ upper: PyTree | None = None
+ soft_lower: PyTree | None = None
+ soft_upper: PyTree | None = None
+
+
+def pre_process_bounds(
+ bounds: None | Bounds | ScipyBounds | Sequence[tuple[float, float]],
+) -> Bounds | None:
+ """Convert all valid types of specifying bounds to optimagic.Bounds.
+
+ This just harmonizes multiple ways of specifying bounds into a single format.
+ It does not check that bounds are valid or compatible with params.
+
+ Args:
+ bounds: The user provided bounds.
+
+ Returns:
+ The bounds in the optimagic format.
+
+ Raises:
+ InvalidBoundsError: If bounds cannot be processed, e.g. because they do not have
+ the correct type.
+
+ """
+ if isinstance(bounds, ScipyBounds):
+ bounds = Bounds(lower=bounds.lb, upper=bounds.ub)
+ elif isinstance(bounds, Bounds) or bounds is None:
+ pass
+ else:
+ try:
+ bounds = _process_bounds_sequence(bounds)
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except Exception as e:
+ raise InvalidBoundsError(
+ f"Invalid bounds of type: {type(bounds)}. Bounds must be "
+ "optimagic.Bounds, scipy.optimize.Bounds or a Sequence of tuples with "
+ "lower and upper bounds."
+ ) from e
+ return bounds
+
+
+def _process_bounds_sequence(bounds: Sequence[tuple[float, float]]) -> Bounds:
+ lower = np.full(len(bounds), -np.inf)
+ upper = np.full(len(bounds), np.inf)
+
+ for i, (lb, ub) in enumerate(bounds):
+ if lb is not None:
+ lower[i] = lb
+ if ub is not None:
+ upper[i] = ub
+ return Bounds(lower=lower, upper=upper)
+
+
+def get_internal_bounds(
+ params: PyTree,
+ bounds: Bounds | None = None,
+ registry: PyTreeRegistry | None = None,
+ add_soft_bounds: bool = False,
+) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
+ """Create consolidated and flattened bounds for params.
+
+ If params is a DataFrame with value column, the user provided bounds are
+ extended with bounds from the params DataFrame.
+
+ If no bounds are available the entry is set to minus np.inf for the lower bound and
+ np.inf for the upper bound.
+
+ The bounds provided in `bounds` override bounds provided in params if both are
+ specified (in the case where params is a DataFrame with bounds as a column).
+
+ Args:
+ params: The parameter pytree.
+ bounds: The lower and upper bounds.
+ registry: pybaum registry.
+ add_soft_bounds: If True, the element-wise maximum (minimum) of the lower and
+ soft_lower (upper and soft_upper) bounds are taken. If False, the lower
+ (upper) bounds are returned.
+
+ Returns:
+ Consolidated and flattened lower_bounds.
+ Consolidated and flattened upper_bounds.
+
+ """
+ bounds = Bounds() if bounds is None else bounds
+
+ fast_path = _is_fast_path(
+ params=params,
+ bounds=bounds,
+ add_soft_bounds=add_soft_bounds,
+ )
+ if fast_path:
+ return _get_fast_path_bounds(
+ params=params,
+ bounds=bounds,
+ )
+
+ registry = get_registry(extended=True) if registry is None else registry
+ n_params = len(tree_leaves(params, registry=registry))
+
+ # Fill leaves with np.nan. If params contains a data frame with bounds as a column,
+ # that column is NOT overwritten (as long as an extended registry is used).
+ nan_tree = tree_map(lambda leaf: np.nan, params, registry=registry) # noqa: ARG005
+
+ lower_flat = _update_bounds_and_flatten(nan_tree, bounds.lower, kind="lower_bound")
+ upper_flat = _update_bounds_and_flatten(nan_tree, bounds.upper, kind="upper_bound")
+
+ if len(lower_flat) != n_params:
+ raise InvalidBoundsError("lower_bounds do not match dimension of params.")
+ if len(upper_flat) != n_params:
+ raise InvalidBoundsError("upper_bounds do not match dimension of params.")
+
+ lower_flat[np.isnan(lower_flat)] = -np.inf
+ upper_flat[np.isnan(upper_flat)] = np.inf
+
+ if add_soft_bounds:
+ lower_flat_soft = _update_bounds_and_flatten(
+ nan_tree, bounds.soft_lower, kind="soft_lower_bound"
+ )
+ lower_flat_soft[np.isnan(lower_flat_soft)] = -np.inf
+ lower_flat = np.maximum(lower_flat, lower_flat_soft)
+
+ upper_flat_soft = _update_bounds_and_flatten(
+ nan_tree, bounds.soft_upper, kind="soft_upper_bound"
+ )
+ upper_flat_soft[np.isnan(upper_flat_soft)] = np.inf
+ upper_flat = np.minimum(upper_flat, upper_flat_soft)
+
+ if (lower_flat > upper_flat).any():
+ msg = "Invalid bounds. Some lower bounds are larger than upper bounds."
+ raise InvalidBoundsError(msg)
+
+ return lower_flat, upper_flat
+
+
+def _update_bounds_and_flatten(
+ nan_tree: PyTree, bounds: PyTree, kind: str
+) -> NDArray[np.float64]:
+ """Flatten bounds array and update it with bounds from params.
+
+ Args:
+ nan_tree: Pytree with the same structure as params, filled with nans.
+ bounds: The candidate bounds to be updated and flattened.
+ kind: One of "lower_bound", "upper_bound", "soft_lower_bound",
+ "soft_upper_bound".
+
+ Returns:
+ np.ndarray: The updated and flattened bounds.
+
+ """
+ registry = get_registry(extended=True, data_col=kind)
+ flat_nan_tree = tree_leaves(nan_tree, registry=registry)
+
+ if bounds is not None:
+ registry = get_registry(extended=True)
+ flat_bounds = tree_leaves(bounds, registry=registry)
+
+ seperator = 10 * "$"
+ params_names = leaf_names(nan_tree, registry=registry, separator=seperator)
+ bounds_names = leaf_names(bounds, registry=registry, separator=seperator)
+
+ flat_nan_dict = dict(zip(params_names, flat_nan_tree, strict=False))
+
+ invalid = {"names": [], "bounds": []} # type: ignore
+ for bounds_name, bounds_leaf in zip(bounds_names, flat_bounds, strict=False):
+ # if a bounds leaf is None we treat it as saying the the corresponding
+ # subtree of params has no bounds.
+ if bounds_leaf is not None:
+ if bounds_name in flat_nan_dict:
+ flat_nan_dict[bounds_name] = bounds_leaf
+ else:
+ invalid["names"].append(bounds_name)
+ invalid["bounds"].append(bounds_leaf)
+
+ if invalid["bounds"]:
+ msg = (
+ f"{kind} could not be matched to params pytree. The bounds "
+ f"{invalid['bounds']} with names {invalid['names']} are not part of "
+ "params."
+ )
+ raise InvalidBoundsError(msg)
+
+ flat_nan_tree = list(flat_nan_dict.values())
+
+ updated = np.array(flat_nan_tree, dtype=np.float64)
+ return updated
+
+
+def _is_fast_path(params: PyTree, bounds: Bounds, add_soft_bounds: bool) -> bool:
+ out = True
+ if add_soft_bounds:
+ out = False
+
+ if not _is_1d_array(params):
+ out = False
+
+ for bound in bounds.lower, bounds.upper:
+ if not (_is_1d_array(bound) or bound is None):
+ out = False
+ return out
+
+
+def _is_1d_array(candidate: Any) -> bool:
+ return isinstance(candidate, np.ndarray) and candidate.ndim == 1
+
+
+def _get_fast_path_bounds(
+ params: PyTree, bounds: Bounds
+) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
+ if bounds.lower is None:
+ # faster than np.full
+ lower_bounds = np.array([-np.inf] * len(params))
+ else:
+ lower_bounds = bounds.lower.astype(float)
+
+ if bounds.upper is None:
+ # faster than np.full
+ upper_bounds = np.array([np.inf] * len(params))
+ else:
+ upper_bounds = bounds.upper.astype(float)
+
+ if (lower_bounds > upper_bounds).any():
+ msg = "Invalid bounds. Some lower bounds are larger than upper bounds."
+ raise InvalidBoundsError(msg)
+
+ return lower_bounds, upper_bounds
diff --git a/src/optimagic/parameters/constraint_tools.py b/src/optimagic/parameters/constraint_tools.py
index 5c790a210..d2e32f7c8 100644
--- a/src/optimagic/parameters/constraint_tools.py
+++ b/src/optimagic/parameters/constraint_tools.py
@@ -1,25 +1,46 @@
from optimagic.parameters.conversion import get_converter
+from optimagic.deprecations import replace_and_warn_about_deprecated_bounds
+from optimagic.parameters.bounds import pre_process_bounds
-def count_free_params(params, constraints=None, lower_bounds=None, upper_bounds=None):
+def count_free_params(
+ params,
+ constraints=None,
+ bounds=None,
+ # deprecated
+ lower_bounds=None,
+ upper_bounds=None,
+):
"""Count the (free) parameters of an optimization problem.
Args:
params (pytree): The parameters.
constraints (list): The constraints for the optimization problem. If constraints
are provided, only the free parameters are counted.
- lower_bounds (pytree): Lower bounds for params.
- upper_bounds (pytree): Upper bounds for params.
+ bounds: Lower and upper bounds on the parameters. The most general and preferred
+ way to specify bounds is an `optimagic.Bounds` object that collects lower,
+ upper, soft_lower and soft_upper bounds. The soft bounds are used for
+ sampling based optimizers but are not enforced during optimization. Each
+ bound type mirrors the structure of params. Check our how-to guide on bounds
+ for examples. If params is a flat numpy array, you can also provide bounds
+ via any format that is supported by scipy.optimize.minimize.
Returns:
int: Number of (free) parameters
"""
+ bounds = replace_and_warn_about_deprecated_bounds(
+ bounds=bounds,
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ )
+
+ bounds = pre_process_bounds(bounds)
+
_, internal_params = get_converter(
params=params,
constraints=constraints,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
func_eval=3,
primary_key="value",
scaling=False,
@@ -29,26 +50,44 @@ def count_free_params(params, constraints=None, lower_bounds=None, upper_bounds=
return int(internal_params.free_mask.sum())
-def check_constraints(params, constraints, lower_bounds=None, upper_bounds=None):
+def check_constraints(
+ params,
+ constraints,
+ bounds=None,
+ # deprecated
+ lower_bounds=None,
+ upper_bounds=None,
+):
"""Raise an error if constraints are invalid or not satisfied in params.
Args:
params (pytree): The parameters.
constraints (list): The constraints for the optimization problem.
- lower_bounds (pytree): Lower bounds for params.
- upper_bounds (pytree): Upper bounds for params.
-
+ bounds: Lower and upper bounds on the parameters. The most general and preferred
+ way to specify bounds is an `optimagic.Bounds` object that collects lower,
+ upper, soft_lower and soft_upper bounds. The soft bounds are used for
+ sampling based optimizers but are not enforced during optimization. Each
+ bound type mirrors the structure of params. Check our how-to guide on bounds
+ for examples. If params is a flat numpy array, you can also provide bounds
+ via any format that is supported by scipy.optimize.minimize.
Raises:
InvalidParamsError: If constraints are valid but not satisfied.
InvalidConstraintError: If constraints are invalid.
"""
+ bounds = replace_and_warn_about_deprecated_bounds(
+ bounds=bounds,
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ )
+
+ bounds = pre_process_bounds(bounds)
+
get_converter(
params=params,
constraints=constraints,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
func_eval=3,
primary_key="value",
scaling=False,
diff --git a/src/optimagic/parameters/conversion.py b/src/optimagic/parameters/conversion.py
index 9076b2bff..f9af25aa1 100644
--- a/src/optimagic/parameters/conversion.py
+++ b/src/optimagic/parameters/conversion.py
@@ -13,15 +13,12 @@
def get_converter(
params,
constraints,
- lower_bounds,
- upper_bounds,
+ bounds,
func_eval,
primary_key,
scaling,
scaling_options,
derivative_eval=None,
- soft_lower_bounds=None,
- soft_upper_bounds=None,
add_soft_bounds=False,
):
"""Get a converter between external and internal params and internal params.
@@ -74,20 +71,16 @@ def get_converter(
if fast_path:
return _get_fast_path_converter(
params=params,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
primary_key=primary_key,
)
tree_converter, internal_params = get_tree_converter(
params=params,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
func_eval=func_eval,
derivative_eval=derivative_eval,
primary_key=primary_key,
- soft_lower_bounds=soft_lower_bounds,
- soft_upper_bounds=soft_upper_bounds,
add_soft_bounds=add_soft_bounds,
)
@@ -216,7 +209,7 @@ def _fast_params_from_internal(x, return_type="tree"):
return x
-def _get_fast_path_converter(params, lower_bounds, upper_bounds, primary_key):
+def _get_fast_path_converter(params, bounds, primary_key):
def _fast_derivative_to_internal(
derivative_eval,
x, # noqa: ARG001
@@ -233,15 +226,15 @@ def _fast_derivative_to_internal(
has_transforming_constraints=False,
)
- if lower_bounds is None:
+ if bounds is None or bounds.lower is None:
lower_bounds = np.full(len(params), -np.inf)
else:
- lower_bounds = lower_bounds.astype(float)
+ lower_bounds = bounds.lower.astype(float)
- if upper_bounds is None:
+ if bounds is None or bounds.upper is None:
upper_bounds = np.full(len(params), np.inf)
else:
- upper_bounds = upper_bounds.astype(float)
+ upper_bounds = bounds.upper.astype(float)
internal_params = InternalParams(
values=params.astype(float),
diff --git a/src/optimagic/parameters/nonlinear_constraints.py b/src/optimagic/parameters/nonlinear_constraints.py
index 29cf25f8b..e51b2234a 100644
--- a/src/optimagic/parameters/nonlinear_constraints.py
+++ b/src/optimagic/parameters/nonlinear_constraints.py
@@ -89,8 +89,7 @@ def _process_nonlinear_constraint(
# process numdiff_options for numerical derivative
options = numdiff_options.copy()
- options.pop("lower_bounds", None)
- options.pop("upper_bounds", None)
+ options.pop("bounds", None)
if "derivative" in c:
if not callable(c["derivative"]):
diff --git a/src/optimagic/parameters/parameter_bounds.py b/src/optimagic/parameters/parameter_bounds.py
deleted file mode 100644
index f9286928a..000000000
--- a/src/optimagic/parameters/parameter_bounds.py
+++ /dev/null
@@ -1,165 +0,0 @@
-import numpy as np
-from pybaum import leaf_names, tree_map
-from pybaum import tree_just_flatten as tree_leaves
-
-from optimagic.exceptions import InvalidBoundsError
-from optimagic.parameters.tree_registry import get_registry
-
-
-def get_bounds(
- params,
- lower_bounds=None,
- upper_bounds=None,
- soft_lower_bounds=None,
- soft_upper_bounds=None,
- registry=None,
- add_soft_bounds=False,
-):
- """Consolidate lower/upper bounds with bounds available in params.
-
- Updates bounds defined in params. If no bounds are available the entry is set to
- -np.inf for the lower bound and np.inf for the upper bound. If a bound is defined in
- params and lower_bounds or upper_bounds, the bound from lower_bounds or upper_bounds
- will be used.
-
- Args:
- params (pytree): The parameter pytree.
- lower_bounds (pytree): Must be a subtree of params.
- upper_bounds (pytree): Must be a subtree of params.
- registry (dict): pybaum registry.
-
- Returns:
- np.ndarray: Consolidated and flattened lower_bounds.
- np.ndarray: Consolidated and flattened upper_bounds.
-
- """
- fast_path = _is_fast_path(
- params=params,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
- add_soft_bounds=add_soft_bounds,
- )
- if fast_path:
- return _get_fast_path_bounds(
- params=params,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
- )
-
- registry = get_registry(extended=True) if registry is None else registry
- n_params = len(tree_leaves(params, registry=registry))
-
- # Fill leaves with np.nan. If params contains a data frame with bounds as a column,
- # that column is NOT overwritten (as long as an extended registry is used).
- nan_tree = tree_map(lambda leaf: np.nan, params, registry=registry) # noqa: ARG005
-
- lower_flat = _update_bounds_and_flatten(
- nan_tree, lower_bounds, direction="lower_bound"
- )
- upper_flat = _update_bounds_and_flatten(
- nan_tree, upper_bounds, direction="upper_bound"
- )
-
- if len(lower_flat) != n_params:
- raise InvalidBoundsError("lower_bounds do not match dimension of params.")
- if len(upper_flat) != n_params:
- raise InvalidBoundsError("upper_bounds do not match dimension of params.")
-
- lower_flat[np.isnan(lower_flat)] = -np.inf
- upper_flat[np.isnan(upper_flat)] = np.inf
-
- if add_soft_bounds:
- lower_flat_soft = _update_bounds_and_flatten(
- nan_tree, soft_lower_bounds, direction="soft_lower_bound"
- )
- lower_flat_soft[np.isnan(lower_flat_soft)] = -np.inf
- lower_flat = np.maximum(lower_flat, lower_flat_soft)
-
- upper_flat_soft = _update_bounds_and_flatten(
- nan_tree, soft_upper_bounds, direction="soft_upper_bound"
- )
- upper_flat_soft[np.isnan(upper_flat_soft)] = np.inf
- upper_flat = np.minimum(upper_flat, upper_flat_soft)
-
- if (lower_flat > upper_flat).any():
- msg = "Invalid bounds. Some lower bounds are larger than upper bounds."
- raise InvalidBoundsError(msg)
-
- return lower_flat, upper_flat
-
-
-def _update_bounds_and_flatten(nan_tree, bounds, direction):
- registry = get_registry(extended=True, data_col=direction)
- flat_nan_tree = tree_leaves(nan_tree, registry=registry)
-
- if bounds is not None:
- registry = get_registry(extended=True)
- flat_bounds = tree_leaves(bounds, registry=registry)
-
- seperator = 10 * "$"
- params_names = leaf_names(nan_tree, registry=registry, separator=seperator)
- bounds_names = leaf_names(bounds, registry=registry, separator=seperator)
-
- flat_nan_dict = dict(zip(params_names, flat_nan_tree, strict=False))
-
- invalid = {"names": [], "bounds": []}
- for bounds_name, bounds_leaf in zip(bounds_names, flat_bounds, strict=False):
- # if a bounds leaf is None we treat it as saying the the corresponding
- # subtree of params has no bounds.
- if bounds_leaf is not None:
- if bounds_name in flat_nan_dict:
- flat_nan_dict[bounds_name] = bounds_leaf
- else:
- invalid["names"].append(bounds_name)
- invalid["bounds"].append(bounds_leaf)
-
- if invalid["bounds"]:
- msg = (
- f"{direction} could not be matched to params pytree. The bounds "
- f"{invalid['bounds']} with names {invalid['names']} are not part of "
- "params."
- )
- raise InvalidBoundsError(msg)
-
- flat_nan_tree = list(flat_nan_dict.values())
-
- updated = np.array(flat_nan_tree, dtype=np.float64)
- return updated
-
-
-def _is_fast_path(params, lower_bounds, upper_bounds, add_soft_bounds):
- out = True
- if add_soft_bounds:
- out = False
-
- if not _is_1d_array(params):
- out = False
-
- for bound in lower_bounds, upper_bounds:
- if not (_is_1d_array(bound) or bound is None):
- out = False
- return out
-
-
-def _is_1d_array(candidate):
- return isinstance(candidate, np.ndarray) and candidate.ndim == 1
-
-
-def _get_fast_path_bounds(params, lower_bounds, upper_bounds):
- if lower_bounds is None:
- # faster than np.full
- lower_bounds = np.array([-np.inf] * len(params))
- else:
- lower_bounds = lower_bounds.astype(float)
-
- if upper_bounds is None:
- # faster than np.full
- upper_bounds = np.array([np.inf] * len(params))
- else:
- upper_bounds = upper_bounds.astype(float)
-
- if (lower_bounds > upper_bounds).any():
- msg = "Invalid bounds. Some lower bounds are larger than upper bounds."
- raise InvalidBoundsError(msg)
-
- return lower_bounds, upper_bounds
diff --git a/src/optimagic/parameters/tree_conversion.py b/src/optimagic/parameters/tree_conversion.py
index 8640cb9ec..6c651b747 100644
--- a/src/optimagic/parameters/tree_conversion.py
+++ b/src/optimagic/parameters/tree_conversion.py
@@ -5,20 +5,17 @@
from optimagic.exceptions import InvalidFunctionError
from optimagic.parameters.block_trees import block_tree_to_matrix
-from optimagic.parameters.parameter_bounds import get_bounds
+from optimagic.parameters.bounds import get_internal_bounds
from optimagic.parameters.tree_registry import get_registry
from optimagic.utilities import isscalar
def get_tree_converter(
params,
- lower_bounds,
- upper_bounds,
+ bounds,
func_eval,
primary_key,
derivative_eval=None,
- soft_lower_bounds=None,
- soft_upper_bounds=None,
add_soft_bounds=False,
):
"""Get flatten and unflatten functions for criterion and its derivative.
@@ -55,21 +52,17 @@ def get_tree_converter(
_registry = get_registry(extended=True)
_params_vec, _params_treedef = tree_flatten(params, registry=_registry)
_params_vec = np.array(_params_vec).astype(float)
- _lower, _upper = get_bounds(
+ _lower, _upper = get_internal_bounds(
params=params,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
registry=_registry,
)
if add_soft_bounds:
- _soft_lower, _soft_upper = get_bounds(
+ _soft_lower, _soft_upper = get_internal_bounds(
params=params,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
registry=_registry,
- soft_lower_bounds=soft_lower_bounds,
- soft_upper_bounds=soft_upper_bounds,
add_soft_bounds=add_soft_bounds,
)
else:
diff --git a/src/optimagic/typing.py b/src/optimagic/typing.py
index f3fb9a5c1..9b71a71ec 100644
--- a/src/optimagic/typing.py
+++ b/src/optimagic/typing.py
@@ -1,4 +1,5 @@
-from typing import Any
+from typing import Any, Callable
PyTree = Any
+PyTreeRegistry = dict[type | str, dict[str, Callable[[Any], Any]]]
diff --git a/src/optimagic/visualization/slice_plot.py b/src/optimagic/visualization/slice_plot.py
index de8c4dbe1..144b958dc 100644
--- a/src/optimagic/visualization/slice_plot.py
+++ b/src/optimagic/visualization/slice_plot.py
@@ -11,13 +11,14 @@
from optimagic.parameters.conversion import get_converter
from optimagic.parameters.tree_registry import get_registry
from optimagic.visualization.plotting_utilities import combine_plots, get_layout_kwargs
+from optimagic.deprecations import replace_and_warn_about_deprecated_bounds
+from optimagic.parameters.bounds import pre_process_bounds
def slice_plot(
func,
params,
- lower_bounds=None,
- upper_bounds=None,
+ bounds=None,
func_kwargs=None,
selector=None,
n_cores=DEFAULT_N_CORES,
@@ -33,22 +34,29 @@ def slice_plot(
return_dict=False,
make_subplot_kwargs=None,
batch_evaluator="joblib",
+ # deprecated
+ lower_bounds=None,
+ upper_bounds=None,
):
"""Plot criterion along coordinates at given and random values.
Generates plots for each parameter and optionally combines them into a figure
with subplots.
+ # TODO: Use soft bounds to create the grid (if available).
+
Args:
criterion (callable): criterion function that takes params and returns a
scalar value or dictionary with the entry "value".
params (pytree): A pytree with parameters.
- lower_bounds (pytree): A pytree with same structure as params. Must be
- specified and finite for all parameters unless params is a DataFrame
- containing with "lower_bound" column.
- upper_bounds (pytree): A pytree with same structure as params. Must be
- specified and finite for all parameters unless params is a DataFrame
- containing with "lower_bound" column.
+ bounds: Lower and upper bounds on the parameters. The bounds are used to create
+ a grid over which slice plots are drawn. The most general and preferred
+ way to specify bounds is an `optimagic.Bounds` object that collects lower,
+ upper, soft_lower and soft_upper bounds. The soft bounds are not used for
+ slice_plots. Each bound type mirrors the structure of params. Check our
+ how-to guide on bounds for examples. If params is a flat numpy array, you
+ can also provide bounds via any format that is supported by
+ scipy.optimize.minimize.
selector (callable): Function that takes params and returns a subset
of params for which we actually want to generate the plot.
n_cores (int): Number of cores.
@@ -84,6 +92,13 @@ def slice_plot(
plots for each parameter or a plotly Figure combining the individual plots.
"""
+ bounds = replace_and_warn_about_deprecated_bounds(
+ lower_bounds=lower_bounds,
+ upper_bounds=upper_bounds,
+ bounds=bounds,
+ )
+
+ bounds = pre_process_bounds(bounds)
layout_kwargs = None
if title is not None:
@@ -99,8 +114,7 @@ def slice_plot(
converter, internal_params = get_converter(
params=params,
constraints=None,
- lower_bounds=lower_bounds,
- upper_bounds=upper_bounds,
+ bounds=bounds,
func_eval=func_eval,
primary_key="value",
scaling=False,
diff --git a/tests/estimagic/test_estimate_ml.py b/tests/estimagic/test_estimate_ml.py
index 2604b4de6..2fcec4d52 100644
--- a/tests/estimagic/test_estimate_ml.py
+++ b/tests/estimagic/test_estimate_ml.py
@@ -11,6 +11,7 @@
from numpy.testing import assert_array_equal
from scipy.stats import multivariate_normal
from statsmodels.base.model import GenericLikelihoodModel
+from optimagic.parameters.bounds import Bounds
def aaae(obj1, obj2, decimal=3):
@@ -388,7 +389,7 @@ def test_estimate_ml_general_pytree(normal_inputs):
params=start_params,
loglike_kwargs=kwargs,
optimize_options="scipy_lbfgsb",
- lower_bounds={"sd": 0.0001},
+ bounds=Bounds(lower={"sd": 0.0001}),
jacobian_kwargs=kwargs,
constraints=[{"selector": lambda p: p["sd"], "type": "sdcorr"}],
)
@@ -415,7 +416,7 @@ def test_to_pickle(normal_inputs, tmp_path):
params=start_params,
loglike_kwargs=kwargs,
optimize_options="scipy_lbfgsb",
- lower_bounds={"sd": 0.0001},
+ bounds=Bounds(lower={"sd": 0.0001}),
jacobian_kwargs=kwargs,
constraints=[{"selector": lambda p: p["sd"], "type": "sdcorr"}],
)
@@ -433,7 +434,7 @@ def test_caching(normal_inputs):
params=start_params,
loglike_kwargs=kwargs,
optimize_options="scipy_lbfgsb",
- lower_bounds={"sd": 0.0001},
+ bounds=Bounds(lower={"sd": 0.0001}),
jacobian_kwargs=kwargs,
constraints=[{"selector": lambda p: p["sd"], "type": "sdcorr"}],
)
diff --git a/tests/optimagic/differentiation/test_derivatives.py b/tests/optimagic/differentiation/test_derivatives.py
index 6272e752b..2eac8e8e5 100644
--- a/tests/optimagic/differentiation/test_derivatives.py
+++ b/tests/optimagic/differentiation/test_derivatives.py
@@ -29,6 +29,7 @@
from numpy.testing import assert_array_almost_equal as aaae
from pandas.testing import assert_frame_equal
from scipy.optimize._numdiff import approx_derivative
+from optimagic.parameters.bounds import Bounds
@pytest.fixture()
@@ -47,14 +48,18 @@ def test_first_derivative_jacobian(binary_choice_inputs, method):
fix = binary_choice_inputs
func = partial(logit_loglikeobs, y=fix["y"], x=fix["x"])
+ bounds = Bounds(
+ lower=np.full(fix["params_np"].shape, -np.inf),
+ upper=np.full(fix["params_np"].shape, np.inf),
+ )
+
calculated = first_derivative(
func=func,
method=method,
params=fix["params_np"],
n_steps=1,
base_steps=None,
- lower_bounds=np.full(fix["params_np"].shape, -np.inf),
- upper_bounds=np.full(fix["params_np"].shape, np.inf),
+ bounds=bounds,
min_steps=1e-8,
step_ratio=2.0,
f0=func(fix["params_np"]),
diff --git a/tests/optimagic/differentiation/test_generate_steps.py b/tests/optimagic/differentiation/test_generate_steps.py
index ecd3862ee..164f5ca95 100644
--- a/tests/optimagic/differentiation/test_generate_steps.py
+++ b/tests/optimagic/differentiation/test_generate_steps.py
@@ -8,6 +8,7 @@
generate_steps,
)
from numpy.testing import assert_array_almost_equal as aaae
+from optimagic.parameters.bounds import Bounds
def test_scalars_as_base_steps():
@@ -181,8 +182,7 @@ def test_generate_steps_binding_min_step():
n_steps=2,
target="first_derivative",
base_steps=np.array([0.1, 0.2, 0.3]),
- lower_bounds=np.full(3, -np.inf),
- upper_bounds=np.full(3, 2.5),
+ bounds=Bounds(lower=np.full(3, -np.inf), upper=np.full(3, 2.5)),
step_ratio=2.0,
min_steps=np.full(3, 1e-8),
scaling_factor=1.0,
@@ -202,8 +202,7 @@ def test_generate_steps_min_step_equals_base_step():
n_steps=2,
target="first_derivative",
base_steps=np.array([0.1, 0.2, 0.3]),
- lower_bounds=np.full(3, -np.inf),
- upper_bounds=np.full(3, 2.5),
+ bounds=Bounds(lower=np.full(3, -np.inf), upper=np.full(3, 2.5)),
step_ratio=2.0,
min_steps=None,
scaling_factor=1.0,
diff --git a/tests/optimagic/optimization/test_history_collection.py b/tests/optimagic/optimization/test_history_collection.py
index e3d2409a6..d25463bee 100644
--- a/tests/optimagic/optimization/test_history_collection.py
+++ b/tests/optimagic/optimization/test_history_collection.py
@@ -8,6 +8,7 @@
from numpy.testing import assert_array_almost_equal as aaae
from numpy.testing import assert_array_equal as aae
from optimagic.decorators import mark_minimizer
+from optimagic.parameters.bounds import Bounds
OPTIMIZERS = []
BOUNDED = []
@@ -32,8 +33,7 @@ def test_history_collection_with_parallelization(algorithm, tmp_path):
fun=lambda x: {"root_contributions": x, "value": x @ x},
params=np.arange(5),
algorithm=algorithm,
- lower_bounds=lb,
- upper_bounds=ub,
+ bounds=Bounds(lower=lb, upper=ub),
algo_options={"n_cores": 2, "stopping.max_iterations": 3},
logging=logging,
log_options={"if_database_exists": "replace", "fast_logging": True},
diff --git a/tests/optimagic/optimization/test_internal_criterion_and_derivative_template.py b/tests/optimagic/optimization/test_internal_criterion_and_derivative_template.py
index c529ce150..2314d30d7 100644
--- a/tests/optimagic/optimization/test_internal_criterion_and_derivative_template.py
+++ b/tests/optimagic/optimization/test_internal_criterion_and_derivative_template.py
@@ -69,8 +69,7 @@ def test_criterion_and_derivative_template(
converter, _ = get_converter(
params=base_inputs["params"],
constraints=None,
- lower_bounds=None,
- upper_bounds=None,
+ bounds=None,
func_eval=crit(base_inputs["params"]),
primary_key="value",
scaling=False,
@@ -118,8 +117,7 @@ def test_internal_criterion_with_penalty(base_inputs, direction):
converter, _ = get_converter(
params=base_inputs["params"],
constraints=None,
- lower_bounds=None,
- upper_bounds=None,
+ bounds=None,
func_eval=sos_scalar_criterion(base_inputs["params"]),
primary_key="value",
scaling=False,
diff --git a/tests/optimagic/optimization/test_many_algorithms.py b/tests/optimagic/optimization/test_many_algorithms.py
index 429354d06..4185890d8 100644
--- a/tests/optimagic/optimization/test_many_algorithms.py
+++ b/tests/optimagic/optimization/test_many_algorithms.py
@@ -13,6 +13,7 @@
from optimagic.algorithms import AVAILABLE_ALGORITHMS, GLOBAL_ALGORITHMS
from optimagic.optimization.optimize import minimize
from numpy.testing import assert_array_almost_equal as aaae
+from optimagic.parameters.bounds import Bounds
LOCAL_ALGORITHMS = {
key: value
@@ -54,8 +55,9 @@ def test_algorithm_on_sum_of_squares_with_binding_bounds(algorithm):
res = minimize(
fun=sos,
params=np.array([3, 2, -3]),
- lower_bounds=np.array([1, -np.inf, -np.inf]),
- upper_bounds=np.array([np.inf, np.inf, -1]),
+ bounds=Bounds(
+ lower=np.array([1, -np.inf, -np.inf]), upper=np.array([np.inf, np.inf, -1])
+ ),
algorithm=algorithm,
collect_history=True,
skip_checks=True,
@@ -77,8 +79,7 @@ def test_global_algorithms_on_sum_of_squares(algorithm):
res = minimize(
fun=sos,
params=np.array([0.35, 0.35]),
- lower_bounds=np.array([0.2, -0.5]),
- upper_bounds=np.array([1, 0.5]),
+ bounds=Bounds(lower=np.array([0.2, -0.5]), upper=np.array([1, 0.5])),
algorithm=algorithm,
collect_history=False,
skip_checks=True,
diff --git a/tests/optimagic/optimization/test_multistart.py b/tests/optimagic/optimization/test_multistart.py
index fbcd5c92d..886f0c0e5 100644
--- a/tests/optimagic/optimization/test_multistart.py
+++ b/tests/optimagic/optimization/test_multistart.py
@@ -3,253 +3,183 @@
import numpy as np
import pandas as pd
import pytest
-from optimagic.decorators import switch_sign
-from optimagic.examples.criterion_functions import (
- sos_dict_criterion,
- sos_scalar_criterion,
+from optimagic.optimization.multistart import (
+ _linear_weights,
+ _tiktak_weights,
+ draw_exploration_sample,
+ get_batched_optimization_sample,
+ run_explorations,
+ update_convergence_state,
)
-from optimagic.logging.load_database import load_database
-from optimagic.logging.read_from_database import read_new_rows
-from optimagic.logging.read_log import read_steps_table
-from optimagic.optimization.optimize import maximize, minimize
-from optimagic.optimization.optimize_result import OptimizeResult
from numpy.testing import assert_array_almost_equal as aaae
-criteria = [sos_scalar_criterion, sos_dict_criterion]
-
@pytest.fixture()
def params():
- params = pd.DataFrame()
- params["value"] = np.arange(4)
- params["soft_lower_bound"] = [-5] * 4
- params["soft_upper_bound"] = [10] * 4
- return params
-
-
-test_cases = product(criteria, ["maximize", "minimize"])
-
-
-@pytest.mark.parametrize("criterion, direction", test_cases)
-def test_multistart_minimize_with_sum_of_squares_at_defaults(
- criterion, direction, params
-):
- if direction == "minimize":
- res = minimize(
- fun=criterion,
- params=params,
- algorithm="scipy_lbfgsb",
- multistart=True,
- )
- else:
- res = maximize(
- fun=switch_sign(sos_dict_criterion),
- params=params,
- algorithm="scipy_lbfgsb",
- multistart=True,
- )
+ df = pd.DataFrame(index=["a", "b", "c"])
+ df["value"] = [0, 1, 2.0]
+ df["soft_lower_bound"] = [-1, 0, np.nan]
+ df["upper_bound"] = [2, 2, np.nan]
+ return df
- assert hasattr(res, "multistart_info")
- ms_info = res.multistart_info
- assert len(ms_info["exploration_sample"]) == 40
- assert len(ms_info["exploration_results"]) == 40
- assert all(isinstance(entry, float) for entry in ms_info["exploration_results"])
- assert all(isinstance(entry, OptimizeResult) for entry in ms_info["local_optima"])
- assert all(isinstance(entry, pd.DataFrame) for entry in ms_info["start_parameters"])
- assert np.allclose(res.fun, 0)
- aaae(res.params["value"], np.zeros(4))
-
-
-def test_multistart_with_existing_sample(params):
- sample = pd.DataFrame(
- np.arange(20).reshape(5, 4) / 10,
- columns=params.index,
- )
- options = {"sample": sample}
-
- res = minimize(
- fun=sos_dict_criterion,
- params=params,
- algorithm="scipy_lbfgsb",
- multistart=True,
- multistart_options=options,
- )
-
- calc_sample = _params_list_to_aray(res.multistart_info["exploration_sample"])
- aaae(calc_sample, options["sample"])
-
-
-def test_convergence_via_max_discoveries_works(params):
- options = {
- "convergence_relative_params_tolerance": np.inf,
- "convergence_max_discoveries": 2,
- }
-
- res = maximize(
- fun=switch_sign(sos_dict_criterion),
- params=params,
- algorithm="scipy_lbfgsb",
- multistart=True,
- multistart_options=options,
- )
-
- assert len(res.multistart_info["local_optima"]) == 2
+@pytest.fixture()
+def constraints():
+ return [{"type": "fixed", "loc": "c", "value": 2}]
+
+
+dim = 2
+distributions = ["uniform", "triangular"]
+rules = ["sobol", "halton", "latin_hypercube", "random"]
+lower = [np.zeros(dim), np.ones(dim) * 0.5, -np.ones(dim)]
+upper = [np.ones(dim), np.ones(dim) * 0.75, np.ones(dim) * 2]
+test_cases = list(product(distributions, rules, lower, upper))
+
+
+@pytest.mark.parametrize("dist, rule, lower, upper", test_cases)
+def test_draw_exploration_sample(dist, rule, lower, upper):
+ results = []
+
+ for _ in range(2):
+ results.append(
+ draw_exploration_sample(
+ x=np.ones_like(lower) * 0.5,
+ lower=lower,
+ upper=upper,
+ n_samples=3,
+ sampling_distribution=dist,
+ sampling_method=rule,
+ seed=1234,
+ )
+ )
-def test_steps_are_logged_as_skipped_if_convergence(params):
- options = {
- "convergence_relative_params_tolerance": np.inf,
- "convergence_max_discoveries": 2,
- }
+ aaae(results[0], results[1])
+ calculated = results[0]
+ assert calculated.shape == (3, 2)
+
+
+def test_run_explorations():
+ def _dummy(x, **kwargs):
+ assert set(kwargs) == {
+ "task",
+ "algo_info",
+ "error_handling",
+ "fixed_log_data",
+ }
+ if x.sum() == 5:
+ out = np.nan
+ else:
+ out = -x.sum()
+ return out
- minimize(
- fun=sos_dict_criterion,
- params=params,
- algorithm="scipy_lbfgsb",
- multistart=True,
- multistart_options=options,
- logging="logging.db",
+ calculated = run_explorations(
+ func=_dummy,
+ primary_key="value",
+ sample=np.arange(6).reshape(3, 2),
+ batch_evaluator="joblib",
+ n_cores=1,
+ step_id=0,
+ error_handling="raise",
)
- steps_table = read_steps_table("logging.db")
- expected_status = ["complete", "complete", "complete", "skipped", "skipped"]
- assert steps_table["status"].tolist() == expected_status
-
+ exp_values = np.array([-9, -1])
+ exp_sample = np.array([[4, 5], [0, 1]])
-def test_all_steps_occur_in_optimization_iterations_if_no_convergence(params):
- options = {"convergence_max_discoveries": np.inf}
+ aaae(calculated["sorted_values"], exp_values)
+ aaae(calculated["sorted_sample"], exp_sample)
- minimize(
- fun=sos_dict_criterion,
- params=params,
- algorithm="scipy_lbfgsb",
- multistart=True,
- multistart_options=options,
- logging="logging.db",
- )
- database = load_database(path_or_database="logging.db")
- iterations, _ = read_new_rows(
- database=database,
- table_name="optimization_iterations",
- last_retrieved=0,
- return_type="dict_of_lists",
+def test_get_batched_optimization_sample():
+ calculated = get_batched_optimization_sample(
+ sorted_sample=np.arange(12).reshape(6, 2),
+ n_optimizations=5,
+ batch_size=4,
)
+ expected = [[[0, 1], [2, 3], [4, 5], [6, 7]], [[8, 9]]]
- present_steps = set(iterations["step"])
+ assert len(calculated[0]) == 4
+ assert len(calculated[1]) == 1
+ assert len(calculated) == 2
- assert present_steps == {1, 2, 3, 4, 5}
+ for calc_batch, exp_batch in zip(calculated, expected, strict=False):
+ assert isinstance(calc_batch, list)
+ for calc_entry, exp_entry in zip(calc_batch, exp_batch, strict=False):
+ assert isinstance(calc_entry, np.ndarray)
+ assert calc_entry.tolist() == exp_entry
-def test_with_non_transforming_constraints(params):
- res = minimize(
- fun=sos_dict_criterion,
- params=params,
- constraints=[{"loc": [0, 1], "type": "fixed", "value": [0, 1]}],
- algorithm="scipy_lbfgsb",
- multistart=True,
- )
+def test_linear_weights():
+ calculated = _linear_weights(5, 10, 0.4, 0.8)
+ expected = 0.6
+ assert np.allclose(calculated, expected)
- aaae(res.params["value"].to_numpy(), np.array([0, 1, 0, 0]))
+def test_tiktak_weights():
+ assert np.allclose(0.3, _tiktak_weights(0, 10, 0.3, 0.8))
+ assert np.allclose(0.8, _tiktak_weights(10, 10, 0.3, 0.8))
-def test_error_is_raised_with_transforming_constraints(params):
- with pytest.raises(NotImplementedError):
- minimize(
- fun=sos_dict_criterion,
- params=params,
- constraints=[{"loc": [0, 1], "type": "probability"}],
- algorithm="scipy_lbfgsb",
- multistart=True,
- )
+@pytest.fixture()
+def current_state():
+ state = {
+ "best_x": np.ones(3),
+ "best_y": 5,
+ "best_res": None,
+ "x_history": [np.arange(3) - 1e-20, np.ones(3)],
+ "y_history": [6, 5],
+ "result_history": [],
+ "start_history": [],
+ }
-def _params_list_to_aray(params_list):
- data = [params["value"].tolist() for params in params_list]
- return np.array(data)
-
+ return state
-def test_multistart_with_numpy_params():
- res = minimize(
- fun=lambda params: params @ params,
- params=np.arange(5),
- algorithm="scipy_lbfgsb",
- soft_lower_bounds=np.full(5, -10),
- soft_upper_bounds=np.full(5, 10),
- multistart=True,
- )
- aaae(res.params, np.zeros(5))
+@pytest.fixture()
+def starts():
+ return [np.zeros(3)]
-def test_with_invalid_bounds():
- with pytest.raises(ValueError):
- minimize(
- fun=lambda x: x @ x,
- params=np.arange(5),
- algorithm="scipy_neldermead",
- multistart=True,
- )
+@pytest.fixture()
+def results():
+ return [{"solution_x": np.arange(3) + 1e-10, "solution_criterion": 4}]
-def test_with_scaling():
- def _crit(params):
- x = params - np.arange(len(params))
- return x @ x
+def test_update_state_converged(current_state, starts, results):
+ criteria = {
+ "xtol": 1e-3,
+ "max_discoveries": 2,
+ }
- res = minimize(
- fun=_crit,
- params=np.full(5, 10),
- soft_lower_bounds=np.full(5, -1),
- soft_upper_bounds=np.full(5, 11),
- algorithm="scipy_lbfgsb",
- multistart=True,
+ new_state, is_converged = update_convergence_state(
+ current_state=current_state,
+ starts=starts,
+ results=results,
+ convergence_criteria=criteria,
+ primary_key="value",
)
- aaae(res.params, np.arange(5))
+ aaae(new_state["best_x"], np.arange(3))
+ assert new_state["best_y"] == 4
+ assert new_state["y_history"] == [6, 5, 4]
+ assert new_state["result_history"][0]["solution_criterion"] == 4
+ aaae(new_state["start_history"][0], np.zeros(3))
+ assert new_state["best_res"].keys() == results[0].keys()
+ assert is_converged
-def test_with_ackley():
- def ackley(x):
- out = (
- -20 * np.exp(-0.2 * np.sqrt(np.mean(x**2)))
- - np.exp(np.mean(np.cos(2 * np.pi * x)))
- + 20
- + np.exp(1)
- )
- return out
-
- dim = 5
- kwargs = {
- "fun": ackley,
- "params": np.full(dim, -10),
- "lower_bounds": np.full(dim, -32),
- "upper_bounds": np.full(dim, 32),
- "algo_options": {"stopping.maxfun": 1000},
+def test_update_state_not_converged(current_state, starts, results):
+ criteria = {
+ "xtol": 1e-3,
+ "max_discoveries": 5,
}
- minimize(
- **kwargs,
- algorithm="scipy_lbfgsb",
- multistart=True,
- multistart_options={
- "n_samples": 200,
- "share_optimizations": 0.1,
- "convergence_max_discoveries": 10,
- },
- )
-
-
-def test_multistart_with_least_squares_optimizers():
- est = minimize(
- fun=sos_dict_criterion,
- params=np.array([-1, 1.0]),
- lower_bounds=np.full(2, -10.0),
- upper_bounds=np.full(2, 10.0),
- algorithm="scipy_ls_trf",
- multistart=True,
- multistart_options={"n_samples": 3, "share_optimizations": 1.0},
+ _, is_converged = update_convergence_state(
+ current_state=current_state,
+ starts=starts,
+ results=results,
+ convergence_criteria=criteria,
+ primary_key="value",
)
- aaae(est.params, np.zeros(2))
+ assert not is_converged
diff --git a/tests/optimagic/optimization/test_tiktak.py b/tests/optimagic/optimization/test_tiktak.py
deleted file mode 100644
index 122e513c3..000000000
--- a/tests/optimagic/optimization/test_tiktak.py
+++ /dev/null
@@ -1,185 +0,0 @@
-from itertools import product
-
-import numpy as np
-import pandas as pd
-import pytest
-from optimagic.optimization.tiktak import (
- _linear_weights,
- _tiktak_weights,
- draw_exploration_sample,
- get_batched_optimization_sample,
- run_explorations,
- update_convergence_state,
-)
-from numpy.testing import assert_array_almost_equal as aaae
-
-
-@pytest.fixture()
-def params():
- df = pd.DataFrame(index=["a", "b", "c"])
- df["value"] = [0, 1, 2.0]
- df["soft_lower_bound"] = [-1, 0, np.nan]
- df["upper_bound"] = [2, 2, np.nan]
- return df
-
-
-@pytest.fixture()
-def constraints():
- return [{"type": "fixed", "loc": "c", "value": 2}]
-
-
-dim = 2
-distributions = ["uniform", "triangular"]
-rules = ["sobol", "halton", "latin_hypercube", "random"]
-lower = [np.zeros(dim), np.ones(dim) * 0.5, -np.ones(dim)]
-upper = [np.ones(dim), np.ones(dim) * 0.75, np.ones(dim) * 2]
-test_cases = list(product(distributions, rules, lower, upper))
-
-
-@pytest.mark.parametrize("dist, rule, lower, upper", test_cases)
-def test_draw_exploration_sample(dist, rule, lower, upper):
- results = []
-
- for _ in range(2):
- results.append(
- draw_exploration_sample(
- x=np.ones_like(lower) * 0.5,
- lower=lower,
- upper=upper,
- n_samples=3,
- sampling_distribution=dist,
- sampling_method=rule,
- seed=1234,
- )
- )
-
- aaae(results[0], results[1])
- calculated = results[0]
- assert calculated.shape == (3, 2)
-
-
-def test_run_explorations():
- def _dummy(x, **kwargs):
- assert set(kwargs) == {
- "task",
- "algo_info",
- "error_handling",
- "fixed_log_data",
- }
- if x.sum() == 5:
- out = np.nan
- else:
- out = -x.sum()
- return out
-
- calculated = run_explorations(
- func=_dummy,
- primary_key="value",
- sample=np.arange(6).reshape(3, 2),
- batch_evaluator="joblib",
- n_cores=1,
- step_id=0,
- error_handling="raise",
- )
-
- exp_values = np.array([-9, -1])
- exp_sample = np.array([[4, 5], [0, 1]])
-
- aaae(calculated["sorted_values"], exp_values)
- aaae(calculated["sorted_sample"], exp_sample)
-
-
-def test_get_batched_optimization_sample():
- calculated = get_batched_optimization_sample(
- sorted_sample=np.arange(12).reshape(6, 2),
- n_optimizations=5,
- batch_size=4,
- )
- expected = [[[0, 1], [2, 3], [4, 5], [6, 7]], [[8, 9]]]
-
- assert len(calculated[0]) == 4
- assert len(calculated[1]) == 1
- assert len(calculated) == 2
-
- for calc_batch, exp_batch in zip(calculated, expected, strict=False):
- assert isinstance(calc_batch, list)
- for calc_entry, exp_entry in zip(calc_batch, exp_batch, strict=False):
- assert isinstance(calc_entry, np.ndarray)
- assert calc_entry.tolist() == exp_entry
-
-
-def test_linear_weights():
- calculated = _linear_weights(5, 10, 0.4, 0.8)
- expected = 0.6
- assert np.allclose(calculated, expected)
-
-
-def test_tiktak_weights():
- assert np.allclose(0.3, _tiktak_weights(0, 10, 0.3, 0.8))
- assert np.allclose(0.8, _tiktak_weights(10, 10, 0.3, 0.8))
-
-
-@pytest.fixture()
-def current_state():
- state = {
- "best_x": np.ones(3),
- "best_y": 5,
- "best_res": None,
- "x_history": [np.arange(3) - 1e-20, np.ones(3)],
- "y_history": [6, 5],
- "result_history": [],
- "start_history": [],
- }
-
- return state
-
-
-@pytest.fixture()
-def starts():
- return [np.zeros(3)]
-
-
-@pytest.fixture()
-def results():
- return [{"solution_x": np.arange(3) + 1e-10, "solution_criterion": 4}]
-
-
-def test_update_state_converged(current_state, starts, results):
- criteria = {
- "xtol": 1e-3,
- "max_discoveries": 2,
- }
-
- new_state, is_converged = update_convergence_state(
- current_state=current_state,
- starts=starts,
- results=results,
- convergence_criteria=criteria,
- primary_key="value",
- )
-
- aaae(new_state["best_x"], np.arange(3))
- assert new_state["best_y"] == 4
- assert new_state["y_history"] == [6, 5, 4]
- assert new_state["result_history"][0]["solution_criterion"] == 4
- aaae(new_state["start_history"][0], np.zeros(3))
- assert new_state["best_res"].keys() == results[0].keys()
-
- assert is_converged
-
-
-def test_update_state_not_converged(current_state, starts, results):
- criteria = {
- "xtol": 1e-3,
- "max_discoveries": 5,
- }
-
- _, is_converged = update_convergence_state(
- current_state=current_state,
- starts=starts,
- results=results,
- convergence_criteria=criteria,
- primary_key="value",
- )
-
- assert not is_converged
diff --git a/tests/optimagic/optimization/test_with_bounds.py b/tests/optimagic/optimization/test_with_bounds.py
new file mode 100644
index 000000000..8bfea6808
--- /dev/null
+++ b/tests/optimagic/optimization/test_with_bounds.py
@@ -0,0 +1,39 @@
+from optimagic.optimization.optimize import minimize, maximize
+from scipy.optimize import Bounds as ScipyBounds
+import numpy as np
+
+
+def test_minimize_with_scipy_bounds():
+ minimize(
+ lambda x: x @ x,
+ np.arange(3),
+ bounds=ScipyBounds(np.full(3, -1), np.full(3, 5)),
+ algorithm="scipy_lbfgsb",
+ )
+
+
+def test_minimize_with_sequence_bounds():
+ minimize(
+ lambda x: x @ x,
+ np.arange(3),
+ bounds=[(-1, 5)] * 3,
+ algorithm="scipy_lbfgsb",
+ )
+
+
+def test_maximize_with_scipy_bounds():
+ maximize(
+ lambda x: -x @ x,
+ np.arange(3),
+ bounds=ScipyBounds(np.full(3, -1), np.full(3, 5)),
+ algorithm="scipy_lbfgsb",
+ )
+
+
+def test_maximize_with_sequence_bounds():
+ maximize(
+ lambda x: -x @ x,
+ np.arange(3),
+ bounds=[(-1, 5)] * 3,
+ algorithm="scipy_lbfgsb",
+ )
diff --git a/tests/optimagic/optimization/test_with_constraints.py b/tests/optimagic/optimization/test_with_constraints.py
index 733b29d14..cc1fe0253 100644
--- a/tests/optimagic/optimization/test_with_constraints.py
+++ b/tests/optimagic/optimization/test_with_constraints.py
@@ -28,6 +28,7 @@
from optimagic.exceptions import InvalidConstraintError, InvalidParamsError
from optimagic.optimization.optimize import maximize, minimize
from numpy.testing import assert_array_almost_equal as aaae
+from optimagic.parameters.bounds import Bounds
def logit_loglike(params, y, x):
@@ -268,7 +269,7 @@ def return_all_but_working_hours(params):
"type": "increasing",
},
],
- lower_bounds={"work": {"hours": 0}},
+ bounds=Bounds(lower={"work": {"hours": 0}}),
)
assert np.allclose(res.params["work"]["hours"], start_params["time_budget"])
diff --git a/tests/optimagic/optimization/test_with_multistart.py b/tests/optimagic/optimization/test_with_multistart.py
new file mode 100644
index 000000000..45692a1e9
--- /dev/null
+++ b/tests/optimagic/optimization/test_with_multistart.py
@@ -0,0 +1,252 @@
+from itertools import product
+
+import numpy as np
+import pandas as pd
+import pytest
+from optimagic.decorators import switch_sign
+from optimagic.examples.criterion_functions import (
+ sos_dict_criterion,
+ sos_scalar_criterion,
+)
+from optimagic.logging.load_database import load_database
+from optimagic.logging.read_from_database import read_new_rows
+from optimagic.logging.read_log import read_steps_table
+from optimagic.optimization.optimize import maximize, minimize
+from optimagic.optimization.optimize_result import OptimizeResult
+from numpy.testing import assert_array_almost_equal as aaae
+from optimagic.parameters.bounds import Bounds
+
+criteria = [sos_scalar_criterion, sos_dict_criterion]
+
+
+@pytest.fixture()
+def params():
+ params = pd.DataFrame()
+ params["value"] = np.arange(4)
+ params["soft_lower_bound"] = [-5] * 4
+ params["soft_upper_bound"] = [10] * 4
+ return params
+
+
+test_cases = product(criteria, ["maximize", "minimize"])
+
+
+@pytest.mark.parametrize("criterion, direction", test_cases)
+def test_multistart_minimize_with_sum_of_squares_at_defaults(
+ criterion, direction, params
+):
+ if direction == "minimize":
+ res = minimize(
+ fun=criterion,
+ params=params,
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ )
+ else:
+ res = maximize(
+ fun=switch_sign(sos_dict_criterion),
+ params=params,
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ )
+
+ assert hasattr(res, "multistart_info")
+ ms_info = res.multistart_info
+ assert len(ms_info["exploration_sample"]) == 40
+ assert len(ms_info["exploration_results"]) == 40
+ assert all(isinstance(entry, float) for entry in ms_info["exploration_results"])
+ assert all(isinstance(entry, OptimizeResult) for entry in ms_info["local_optima"])
+ assert all(isinstance(entry, pd.DataFrame) for entry in ms_info["start_parameters"])
+ assert np.allclose(res.fun, 0)
+ aaae(res.params["value"], np.zeros(4))
+
+
+def test_multistart_with_existing_sample(params):
+ sample = pd.DataFrame(
+ np.arange(20).reshape(5, 4) / 10,
+ columns=params.index,
+ )
+ options = {"sample": sample}
+
+ res = minimize(
+ fun=sos_dict_criterion,
+ params=params,
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ multistart_options=options,
+ )
+
+ calc_sample = _params_list_to_aray(res.multistart_info["exploration_sample"])
+ aaae(calc_sample, options["sample"])
+
+
+def test_convergence_via_max_discoveries_works(params):
+ options = {
+ "convergence_relative_params_tolerance": np.inf,
+ "convergence_max_discoveries": 2,
+ }
+
+ res = maximize(
+ fun=switch_sign(sos_dict_criterion),
+ params=params,
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ multistart_options=options,
+ )
+
+ assert len(res.multistart_info["local_optima"]) == 2
+
+
+def test_steps_are_logged_as_skipped_if_convergence(params):
+ options = {
+ "convergence_relative_params_tolerance": np.inf,
+ "convergence_max_discoveries": 2,
+ }
+
+ minimize(
+ fun=sos_dict_criterion,
+ params=params,
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ multistart_options=options,
+ logging="logging.db",
+ )
+
+ steps_table = read_steps_table("logging.db")
+ expected_status = ["complete", "complete", "complete", "skipped", "skipped"]
+ assert steps_table["status"].tolist() == expected_status
+
+
+def test_all_steps_occur_in_optimization_iterations_if_no_convergence(params):
+ options = {"convergence_max_discoveries": np.inf}
+
+ minimize(
+ fun=sos_dict_criterion,
+ params=params,
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ multistart_options=options,
+ logging="logging.db",
+ )
+
+ database = load_database(path_or_database="logging.db")
+ iterations, _ = read_new_rows(
+ database=database,
+ table_name="optimization_iterations",
+ last_retrieved=0,
+ return_type="dict_of_lists",
+ )
+
+ present_steps = set(iterations["step"])
+
+ assert present_steps == {1, 2, 3, 4, 5}
+
+
+def test_with_non_transforming_constraints(params):
+ res = minimize(
+ fun=sos_dict_criterion,
+ params=params,
+ constraints=[{"loc": [0, 1], "type": "fixed", "value": [0, 1]}],
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ )
+
+ aaae(res.params["value"].to_numpy(), np.array([0, 1, 0, 0]))
+
+
+def test_error_is_raised_with_transforming_constraints(params):
+ with pytest.raises(NotImplementedError):
+ minimize(
+ fun=sos_dict_criterion,
+ params=params,
+ constraints=[{"loc": [0, 1], "type": "probability"}],
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ )
+
+
+def _params_list_to_aray(params_list):
+ data = [params["value"].tolist() for params in params_list]
+ return np.array(data)
+
+
+def test_multistart_with_numpy_params():
+ res = minimize(
+ fun=lambda params: params @ params,
+ params=np.arange(5),
+ algorithm="scipy_lbfgsb",
+ bounds=Bounds(soft_lower=np.full(5, -10), soft_upper=np.full(5, 10)),
+ multistart=True,
+ )
+
+ aaae(res.params, np.zeros(5))
+
+
+def test_with_invalid_bounds():
+ with pytest.raises(ValueError):
+ minimize(
+ fun=lambda x: x @ x,
+ params=np.arange(5),
+ algorithm="scipy_neldermead",
+ multistart=True,
+ )
+
+
+def test_with_scaling():
+ def _crit(params):
+ x = params - np.arange(len(params))
+ return x @ x
+
+ res = minimize(
+ fun=_crit,
+ params=np.full(5, 10),
+ bounds=Bounds(soft_lower=np.full(5, -1), soft_upper=np.full(5, 11)),
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ )
+
+ aaae(res.params, np.arange(5))
+
+
+def test_with_ackley():
+ def ackley(x):
+ out = (
+ -20 * np.exp(-0.2 * np.sqrt(np.mean(x**2)))
+ - np.exp(np.mean(np.cos(2 * np.pi * x)))
+ + 20
+ + np.exp(1)
+ )
+ return out
+
+ dim = 5
+
+ kwargs = {
+ "fun": ackley,
+ "params": np.full(dim, -10),
+ "bounds": Bounds(lower=np.full(dim, -32), upper=np.full(dim, 32)),
+ "algo_options": {"stopping.maxfun": 1000},
+ }
+
+ minimize(
+ **kwargs,
+ algorithm="scipy_lbfgsb",
+ multistart=True,
+ multistart_options={
+ "n_samples": 200,
+ "share_optimizations": 0.1,
+ "convergence_max_discoveries": 10,
+ },
+ )
+
+
+def test_multistart_with_least_squares_optimizers():
+ est = minimize(
+ fun=sos_dict_criterion,
+ params=np.array([-1, 1.0]),
+ bounds=Bounds(soft_lower=np.full(2, -10), soft_upper=np.full(2, 10)),
+ algorithm="scipy_ls_trf",
+ multistart=True,
+ multistart_options={"n_samples": 3, "share_optimizations": 1.0},
+ )
+
+ aaae(est.params, np.zeros(2))
diff --git a/tests/optimagic/optimization/test_with_nonlinear_constraints.py b/tests/optimagic/optimization/test_with_nonlinear_constraints.py
index a5fbe3d3c..2e85e7a19 100644
--- a/tests/optimagic/optimization/test_with_nonlinear_constraints.py
+++ b/tests/optimagic/optimization/test_with_nonlinear_constraints.py
@@ -7,6 +7,7 @@
from optimagic.config import IS_CYIPOPT_INSTALLED
from optimagic.algorithms import AVAILABLE_ALGORITHMS
from numpy.testing import assert_array_almost_equal as aaae
+from optimagic.parameters.bounds import Bounds
NLC_ALGORITHMS = [
name
@@ -89,8 +90,7 @@ def constraint_jac(x):
"criterion": criterion,
"params": np.array([0, np.sqrt(2)]),
"derivative": derivative,
- "lower_bounds": np.zeros(2),
- "upper_bounds": 2 * np.ones(2),
+ "bounds": Bounds(lower=np.zeros(2), upper=2 * np.ones(2)),
}
kwargs = {
@@ -128,8 +128,7 @@ def test_nonlinear_optimization(nlc_2d_example, algorithm, constr_type):
solution_x, kwargs = nlc_2d_example
if algorithm == "scipy_cobyla":
- del kwargs[constr_type]["lower_bounds"]
- del kwargs[constr_type]["upper_bounds"]
+ del kwargs[constr_type]["bounds"]
with warnings.catch_warnings():
warnings.simplefilter("ignore")
@@ -160,13 +159,11 @@ def test_documentation_example(algorithm):
pytest.skip(reason="Slow.")
kwargs = {
- "lower_bounds": np.zeros(6),
- "upper_bounds": 2 * np.ones(6),
+ "bounds": Bounds(lower=np.zeros(6), upper=2 * np.ones(6)),
}
if algorithm == "scipy_cobyla":
- del kwargs["lower_bounds"]
- del kwargs["upper_bounds"]
+ del kwargs["bounds"]
minimize(
fun=criterion,
diff --git a/tests/optimagic/parameters/test_parameter_bounds.py b/tests/optimagic/parameters/test_bounds.py
similarity index 55%
rename from tests/optimagic/parameters/test_parameter_bounds.py
rename to tests/optimagic/parameters/test_bounds.py
index 3f56e7af3..0727d4da7 100644
--- a/tests/optimagic/parameters/test_parameter_bounds.py
+++ b/tests/optimagic/parameters/test_bounds.py
@@ -2,7 +2,7 @@
import pandas as pd
import pytest
from optimagic.exceptions import InvalidBoundsError
-from optimagic.parameters.parameter_bounds import get_bounds
+from optimagic.parameters.bounds import get_internal_bounds, Bounds, pre_process_bounds
from numpy.testing import assert_array_equal
@@ -23,6 +23,28 @@ def array_params():
return np.arange(2)
+def test_pre_process_bounds_trivial_case():
+ got = pre_process_bounds(Bounds(lower=[0], upper=[1]))
+ expected = Bounds(lower=[0], upper=[1])
+ assert got == expected
+
+
+def test_pre_process_bounds_none_case():
+ assert pre_process_bounds(None) is None
+
+
+def test_pre_process_bounds_sequence():
+ got = pre_process_bounds([(0, 1), (None, 1)])
+ expected = Bounds(lower=[0, -np.inf], upper=[1, 1])
+ assert_array_equal(got.lower, expected.lower)
+ assert_array_equal(got.upper, expected.upper)
+
+
+def test_pre_process_bounds_invalid_type():
+ with pytest.raises(InvalidBoundsError):
+ pre_process_bounds(1)
+
+
def test_get_bounds_subdataframe(pytree_params):
upper_bounds = {
"utility": pd.DataFrame([[2]] * 2, index=["b", "c"], columns=["value"]),
@@ -31,31 +53,31 @@ def test_get_bounds_subdataframe(pytree_params):
"delta": 0,
"utility": pd.DataFrame([[1]] * 2, index=["a", "b"], columns=["value"]),
}
- lb, ub = get_bounds(
- pytree_params, lower_bounds=lower_bounds, upper_bounds=upper_bounds
- )
+
+ bounds = Bounds(lower=lower_bounds, upper=upper_bounds)
+
+ lb, ub = get_internal_bounds(pytree_params, bounds=bounds)
assert np.all(lb[1:3] == np.ones(2))
assert np.all(ub[2:4] == 2 * np.ones(2))
TEST_CASES = [
- ({"selector": lambda p: p["delta"], "lower_bounds": 0}, None),
- ({"delta": [0, -1]}, None),
- ({"probs": 1}, None),
- ({"probs": np.array([0, 1])}, None), # wrong size lower bounds
- (None, {"probs": np.array([0, 1])}), # wrong size upper bounds
+ Bounds(lower={"delta": [0, -1]}, upper=None),
+ Bounds(lower={"probs": 1}, upper=None),
+ Bounds(lower={"probs": np.array([0, 1])}, upper=None), # wrong size lower bounds
+ Bounds(lower=None, upper={"probs": np.array([0, 1])}), # wrong size upper bounds
]
-@pytest.mark.parametrize("lower_bounds, upper_bounds", TEST_CASES)
-def test_get_bounds_error(pytree_params, lower_bounds, upper_bounds):
+@pytest.mark.parametrize("bounds", TEST_CASES)
+def test_get_bounds_error(pytree_params, bounds):
with pytest.raises(InvalidBoundsError):
- get_bounds(pytree_params, lower_bounds=lower_bounds, upper_bounds=upper_bounds)
+ get_internal_bounds(pytree_params, bounds=bounds)
def test_get_bounds_no_arguments(pytree_params):
- got_lower, got_upper = get_bounds(pytree_params)
+ got_lower, got_upper = get_internal_bounds(pytree_params)
expected_lower = np.array([-np.inf] + 3 * [0] + 4 * [-np.inf])
expected_upper = np.full(8, np.inf)
@@ -67,7 +89,9 @@ def test_get_bounds_no_arguments(pytree_params):
def test_get_bounds_with_lower_bounds(pytree_params):
lower_bounds = {"delta": 0.1}
- got_lower, got_upper = get_bounds(pytree_params, lower_bounds=lower_bounds)
+ bounds = Bounds(lower=lower_bounds)
+
+ got_lower, got_upper = get_internal_bounds(pytree_params, bounds=bounds)
expected_lower = np.array([0.1] + 3 * [0] + 4 * [-np.inf])
expected_upper = np.full(8, np.inf)
@@ -80,7 +104,8 @@ def test_get_bounds_with_upper_bounds(pytree_params):
upper_bounds = {
"utility": pd.DataFrame([[1]] * 3, index=["a", "b", "c"], columns=["value"]),
}
- got_lower, got_upper = get_bounds(pytree_params, upper_bounds=upper_bounds)
+ bounds = Bounds(upper=upper_bounds)
+ got_lower, got_upper = get_internal_bounds(pytree_params, bounds=bounds)
expected_lower = np.array([-np.inf] + 3 * [0] + 4 * [-np.inf])
expected_upper = np.array([np.inf] + 3 * [1] + 4 * [np.inf])
@@ -90,7 +115,7 @@ def test_get_bounds_with_upper_bounds(pytree_params):
def test_get_bounds_numpy(array_params):
- got_lower, got_upper = get_bounds(array_params)
+ got_lower, got_upper = get_internal_bounds(array_params)
expected = np.array([np.inf, np.inf])
@@ -99,10 +124,10 @@ def test_get_bounds_numpy(array_params):
def test_get_bounds_numpy_error(array_params):
+ # lower bounds larger than upper bounds
+ bounds = Bounds(lower=np.ones_like(array_params), upper=np.zeros_like(array_params))
with pytest.raises(InvalidBoundsError):
- get_bounds(
+ get_internal_bounds(
array_params,
- # lower bounds larger than upper bounds
- lower_bounds=np.ones_like(array_params),
- upper_bounds=np.zeros_like(array_params),
+ bounds=bounds,
)
diff --git a/tests/optimagic/parameters/test_constraint_tools.py b/tests/optimagic/parameters/test_constraint_tools.py
index 6e1ed1698..86402e5dc 100644
--- a/tests/optimagic/parameters/test_constraint_tools.py
+++ b/tests/optimagic/parameters/test_constraint_tools.py
@@ -11,7 +11,7 @@ def test_count_free_params_no_constraints():
def test_count_free_params_with_constraints():
params = {"a": 1, "b": 2, "c": [3, 3]}
constraints = [{"selector": lambda x: x["c"], "type": "equality"}]
- assert count_free_params(params, constraints) == 3
+ assert count_free_params(params, constraints=constraints) == 3
def test_check_constraints():
@@ -19,4 +19,4 @@ def test_check_constraints():
constraints = [{"selector": lambda x: x["c"], "type": "equality"}]
with pytest.raises(InvalidParamsError):
- check_constraints(params, constraints)
+ check_constraints(params, constraints=constraints)
diff --git a/tests/optimagic/parameters/test_conversion.py b/tests/optimagic/parameters/test_conversion.py
index b24dfff23..5ecc28836 100644
--- a/tests/optimagic/parameters/test_conversion.py
+++ b/tests/optimagic/parameters/test_conversion.py
@@ -7,14 +7,14 @@
get_converter,
)
from numpy.testing import assert_array_almost_equal as aaae
+from optimagic.parameters.bounds import Bounds
def test_get_converter_fast_case():
converter, internal = get_converter(
params=np.arange(3),
constraints=None,
- lower_bounds=None,
- upper_bounds=None,
+ bounds=None,
func_eval=3,
derivative_eval=2 * np.arange(3),
primary_key="value",
@@ -36,11 +36,14 @@ def test_get_converter_fast_case():
def test_get_converter_with_constraints_and_bounds():
+ bounds = Bounds(
+ lower=np.array([-1, -np.inf, -np.inf]),
+ upper=np.array([np.inf, 10, np.inf]),
+ )
converter, internal = get_converter(
params=np.arange(3),
constraints=[{"loc": 2, "type": "fixed"}],
- lower_bounds=np.array([-1, -np.inf, -np.inf]),
- upper_bounds=np.array([np.inf, 10, np.inf]),
+ bounds=bounds,
func_eval=3,
derivative_eval=2 * np.arange(3),
primary_key="value",
@@ -62,11 +65,14 @@ def test_get_converter_with_constraints_and_bounds():
def test_get_converter_with_scaling():
+ bounds = Bounds(
+ lower=np.arange(3) - 1,
+ upper=np.arange(3) + 1,
+ )
converter, internal = get_converter(
params=np.arange(3),
constraints=None,
- lower_bounds=np.arange(3) - 1,
- upper_bounds=np.arange(3) + 1,
+ bounds=bounds,
func_eval=3,
derivative_eval=2 * np.arange(3),
primary_key="value",
@@ -92,8 +98,7 @@ def test_get_converter_with_trees():
converter, internal = get_converter(
params=params,
constraints=None,
- lower_bounds=None,
- upper_bounds=None,
+ bounds=None,
func_eval={"contributions": {"d": 1, "e": 2}},
derivative_eval={"a": 0, "b": 2, "c": 4},
primary_key="value",
diff --git a/tests/optimagic/parameters/test_process_constraints.py b/tests/optimagic/parameters/test_process_constraints.py
index aba4aac37..a2059d0cc 100644
--- a/tests/optimagic/parameters/test_process_constraints.py
+++ b/tests/optimagic/parameters/test_process_constraints.py
@@ -8,6 +8,7 @@
from optimagic.parameters.process_constraints import (
_replace_pairwise_equality_by_equality,
)
+from optimagic.parameters.bounds import Bounds
def test_replace_pairwise_equality_by_equality():
@@ -37,6 +38,6 @@ def test_to_many_bounds_in_increasing_constraint_raise_good_error():
with pytest.raises(InvalidConstraintError):
check_constraints(
params=np.arange(3),
- lower_bounds=np.arange(3) - 1,
+ bounds=Bounds(lower=np.arange(3) - 1),
constraints={"loc": [0, 1, 2], "type": "increasing"},
)
diff --git a/tests/optimagic/parameters/test_tree_conversion.py b/tests/optimagic/parameters/test_tree_conversion.py
index 269c35237..0b2860d7f 100644
--- a/tests/optimagic/parameters/test_tree_conversion.py
+++ b/tests/optimagic/parameters/test_tree_conversion.py
@@ -3,6 +3,7 @@
import pytest
from optimagic.parameters.tree_conversion import get_tree_converter
from numpy.testing import assert_array_equal as aae
+from optimagic.parameters.bounds import Bounds
@pytest.fixture()
@@ -31,10 +32,12 @@ def upper_bounds():
@pytest.mark.parametrize("func_eval", FUNC_EVALS)
def test_tree_converter_primary_key_is_value(params, upper_bounds, func_eval):
+ bounds = Bounds(
+ upper=upper_bounds,
+ )
converter, flat_params = get_tree_converter(
params=params,
- lower_bounds=None,
- upper_bounds=upper_bounds,
+ bounds=bounds,
func_eval=func_eval,
derivative_eval=params,
primary_key="value",
@@ -73,8 +76,7 @@ def test_tree_conversion_fast_path(primary_entry):
converter, flat_params = get_tree_converter(
params=np.arange(3),
- lower_bounds=None,
- upper_bounds=None,
+ bounds=None,
func_eval=func_eval,
derivative_eval=derivative_eval,
primary_key=primary_entry,
diff --git a/tests/test_deprecations.py b/tests/optimagic/test_deprecations.py
similarity index 77%
rename from tests/test_deprecations.py
rename to tests/optimagic/test_deprecations.py
index a492b9f5a..c674a8405 100644
--- a/tests/test_deprecations.py
+++ b/tests/optimagic/test_deprecations.py
@@ -25,7 +25,9 @@
from estimagic import OptimizeLogReader, OptimizeResult
from estimagic import criterion_plot, params_plot
import optimagic as om
+import estimagic as em
import warnings
+from optimagic.parameters.bounds import Bounds
# ======================================================================================
# Deprecated in 0.5.0, remove in 0.6.0
@@ -92,8 +94,7 @@ def test_estimagic_slice_plot_is_deprecated():
slice_plot(
func=lambda x: x @ x,
params=np.arange(3),
- lower_bounds=np.zeros(3),
- upper_bounds=np.ones(3) * 5,
+ bounds=Bounds(lower=np.zeros(3), upper=np.ones(3) * 5),
)
@@ -388,3 +389,119 @@ def test_deprecated_attributes_of_optimize_result():
with pytest.warns(FutureWarning, match=msg):
_ = res.start_criterion
+
+
+BOUNDS_KWARGS = [
+ {"lower_bounds": np.full(3, -1)},
+ {"upper_bounds": np.full(3, 2)},
+]
+
+SOFT_BOUNDS_KWARGS = [
+ {"soft_lower_bounds": np.full(3, -1)},
+ {"soft_upper_bounds": np.full(3, 1)},
+]
+
+
+@pytest.mark.parametrize("bounds_kwargs", BOUNDS_KWARGS + SOFT_BOUNDS_KWARGS)
+def test_old_bounds_are_deprecated_in_minimize(bounds_kwargs):
+ msg = "Specifying bounds via the arguments"
+ with pytest.warns(FutureWarning, match=msg):
+ om.minimize(
+ lambda x: x @ x,
+ np.arange(3),
+ algorithm="scipy_lbfgsb",
+ **bounds_kwargs,
+ )
+
+
+@pytest.mark.parametrize("bounds_kwargs", BOUNDS_KWARGS + SOFT_BOUNDS_KWARGS)
+def test_old_bounds_are_deprecated_in_maximize(bounds_kwargs):
+ msg = "Specifying bounds via the arguments"
+ with pytest.warns(FutureWarning, match=msg):
+ om.maximize(
+ lambda x: -x @ x,
+ np.arange(3),
+ algorithm="scipy_lbfgsb",
+ **bounds_kwargs,
+ )
+
+
+@pytest.mark.parametrize("bounds_kwargs", BOUNDS_KWARGS)
+def test_old_bounds_are_deprecated_in_first_derivative(bounds_kwargs):
+ msg = "Specifying bounds via the arguments"
+ with pytest.warns(FutureWarning, match=msg):
+ om.first_derivative(
+ lambda x: x @ x,
+ np.arange(3),
+ **bounds_kwargs,
+ )
+
+
+@pytest.mark.parametrize("bounds_kwargs", BOUNDS_KWARGS)
+def test_old_bounds_are_deprecated_in_second_derivative(bounds_kwargs):
+ msg = "Specifying bounds via the arguments"
+ with pytest.warns(FutureWarning, match=msg):
+ om.second_derivative(
+ lambda x: x @ x,
+ np.arange(3),
+ **bounds_kwargs,
+ )
+
+
+@pytest.mark.parametrize("bounds_kwargs", BOUNDS_KWARGS)
+def test_old_bounds_are_deprecated_in_estimate_ml(bounds_kwargs):
+ msg = "Specifying bounds via the arguments"
+ with pytest.warns(FutureWarning, match=msg):
+ em.estimate_ml(
+ loglike=lambda x: {"contributions": -(x**2), "value": -x @ x},
+ params=np.arange(3),
+ optimize_options={"algorithm": "scipy_lbfgsb"},
+ **bounds_kwargs,
+ )
+
+
+@pytest.mark.parametrize("bounds_kwargs", BOUNDS_KWARGS)
+def test_old_bounds_are_deprecated_in_estimate_msm(bounds_kwargs):
+ msg = "Specifying bounds via the arguments"
+ with pytest.warns(FutureWarning, match=msg):
+ em.estimate_msm(
+ simulate_moments=lambda x: x,
+ empirical_moments=np.zeros(3),
+ moments_cov=np.eye(3),
+ params=np.arange(3),
+ optimize_options={"algorithm": "scipy_lbfgsb"},
+ **bounds_kwargs,
+ )
+
+
+@pytest.mark.parametrize("bounds_kwargs", BOUNDS_KWARGS)
+def test_old_bounds_are_deprecated_in_count_free_params(bounds_kwargs):
+ msg = "Specifying bounds via the arguments"
+ with pytest.warns(FutureWarning, match=msg):
+ om.count_free_params(
+ np.arange(3),
+ constraints=[{"loc": 0, "type": "fixed"}],
+ **bounds_kwargs,
+ )
+
+
+@pytest.mark.parametrize("bounds_kwargs", BOUNDS_KWARGS)
+def test_old_bounds_are_deprecated_in_check_constraints(bounds_kwargs):
+ msg = "Specifying bounds via the arguments"
+ with pytest.warns(FutureWarning, match=msg):
+ om.check_constraints(
+ np.arange(3),
+ constraints=[{"loc": 0, "type": "fixed"}],
+ **bounds_kwargs,
+ )
+
+
+def test_old_bounds_are_deprecated_in_slice_plot():
+ msg = "Specifying bounds via the arguments"
+ with pytest.warns(FutureWarning, match=msg):
+ om.slice_plot(
+ lambda x: x @ x,
+ np.arange(3),
+ lower_bounds=np.full(3, -1),
+ upper_bounds=np.full(3, 2),
+ )
diff --git a/tests/optimagic/visualization/test_history_plots.py b/tests/optimagic/visualization/test_history_plots.py
index a31c7ecdc..26d7bb090 100644
--- a/tests/optimagic/visualization/test_history_plots.py
+++ b/tests/optimagic/visualization/test_history_plots.py
@@ -4,10 +4,12 @@
import pytest
from optimagic.optimization.optimize import minimize
from optimagic.visualization.history_plots import criterion_plot, params_plot
+from optimagic.parameters.bounds import Bounds
@pytest.fixture()
def minimize_result():
+ bounds = Bounds(soft_lower=np.full(5, -1), soft_upper=np.full(5, 6))
out = {}
for multistart in [True, False]:
res = []
@@ -16,8 +18,7 @@ def minimize_result():
fun=lambda x: x @ x,
params=np.arange(5),
algorithm=algorithm,
- soft_lower_bounds=np.full(5, -1),
- soft_upper_bounds=np.full(5, 6),
+ bounds=bounds,
multistart=multistart,
multistart_options={
"n_samples": 1000,
@@ -94,13 +95,13 @@ def test_criterion_plot_wrong_results():
def test_criterion_plot_different_input_types():
+ bounds = Bounds(soft_lower=np.full(5, -1), soft_upper=np.full(5, 6))
# logged result
minimize(
fun=lambda x: x @ x,
params=np.arange(5),
algorithm="scipy_lbfgsb",
- soft_lower_bounds=np.full(5, -1),
- soft_upper_bounds=np.full(5, 6),
+ bounds=bounds,
multistart=True,
multistart_options={"n_samples": 1000, "convergence.max_discoveries": 5},
log_options={"fast_logging": True},
@@ -111,8 +112,7 @@ def test_criterion_plot_different_input_types():
fun=lambda x: x @ x,
params=np.arange(5),
algorithm="scipy_lbfgsb",
- soft_lower_bounds=np.full(5, -1),
- soft_upper_bounds=np.full(5, 6),
+ bounds=bounds,
multistart=True,
multistart_options={"n_samples": 1000, "convergence.max_discoveries": 5},
)
diff --git a/tests/optimagic/visualization/test_slice_plot.py b/tests/optimagic/visualization/test_slice_plot.py
index 71015f6e5..de812ba45 100644
--- a/tests/optimagic/visualization/test_slice_plot.py
+++ b/tests/optimagic/visualization/test_slice_plot.py
@@ -1,18 +1,20 @@
import numpy as np
import pytest
from optimagic.visualization.slice_plot import slice_plot
+from optimagic.parameters.bounds import Bounds
@pytest.fixture()
def fixed_inputs():
params = {"alpha": 0, "beta": 0, "gamma": 0, "delta": 0}
- lower_bounds = {name: -5 for name in params}
- upper_bounds = {name: i + 2 for i, name in enumerate(params)}
+ bounds = Bounds(
+ lower={name: -5 for name in params},
+ upper={name: i + 2 for i, name in enumerate(params)},
+ )
out = {
"params": params,
- "lower_bounds": lower_bounds,
- "upper_bounds": upper_bounds,
+ "bounds": bounds,
}
return out