{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Mixed problems" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The `pymoo` interface supports the solution of mixed integer/float problems via the genetic algorithm `MixedVariableGA`. Here we demonstrate this by maximizing the number if points that fit inside a circle and lie on a regular grid.\n", "\n", "These are the imports:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "from iwopy import Problem, Objective\n", "from iwopy.interfaces.pymoo import Optimizer_pymoo" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The problem describes points on a regular grid of size `nx * ny`. Points that lie outside of the given radius will be marked by a `False` value in a boolean array called `valid`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class GridProblem(Problem):\n", "\n", " def __init__(self, n_row_max, radius, min_dist, ctol=1e-3):\n", " super().__init__(name=\"grid_problem\")\n", "\n", " self.n_row_max = n_row_max\n", " self.radius = float(radius)\n", " self.min_dist = float(min_dist)\n", " self.max_dist = 2*radius\n", "\n", " self.xy = None\n", " self.valid = None\n", " \n", " def initialize(self, verbosity=1):\n", " super().initialize(verbosity)\n", " self.apply_individual(self.initial_values_int(), self.initial_values_float())\n", "\n", " def var_names_int(self):\n", " return [\"nx\", \"ny\"]\n", "\n", " def initial_values_int(self):\n", " return [2, 2]\n", "\n", " def min_values_int(self):\n", " return [1, 1]\n", "\n", " def max_values_int(self):\n", " return [self.n_row_max, self.n_row_max]\n", "\n", " def var_names_float(self):\n", " return [\"x0\", \"y0\", \"dx\", \"dy\", \"alpha\"]\n", "\n", " def initial_values_float(self):\n", " return [0., 0., self.min_dist, self.min_dist, 0.]\n", "\n", " def min_values_float(self):\n", " return [-2*self.radius, -2*self.radius, self.min_dist, self.min_dist, 0.]\n", "\n", " def max_values_float(self):\n", " return [self.radius, self.radius, self.max_dist, self.max_dist, 90.]\n", "\n", " def apply_individual(self, vars_int, vars_float):\n", " \"\"\" Create points on a regular grid, and evaluate their validity \"\"\"\n", "\n", " nx, ny = vars_int\n", " x0, y0, dx, dy, alpha = vars_float\n", "\n", " a = np.deg2rad(alpha)\n", " nax = np.array([np.cos(a), np.sin(a), 0.])\n", " naz = np.array([0., 0., 1.])\n", " nay = np.cross(naz, nax)\n", "\n", " self.xy = np.zeros((nx, ny, 2))\n", " self.xy[:] = np.array([x0, y0])[None, None, :]\n", " self.xy[:] += np.arange(nx)[:, None, None] * dx * nax[None, None, :2]\n", " self.xy[:] += np.arange(ny)[None, :, None] * dy * nay[None, None, :2]\n", "\n", " self.valid = np.linalg.norm(self.xy, axis=-1) <= self.radius\n", "\n", " return self.xy, self.valid\n", "\n", " def apply_population(self, vars_int, vars_float):\n", " \"\"\" Create points on a regular grid, and evaluate their validity \"\"\"\n", "\n", " n_pop = vars_int.shape[0]\n", " nx = vars_int[:, 0]\n", " ny = vars_int[:, 1]\n", " x0 = vars_float[:, 0]\n", " y0 = vars_float[:, 1]\n", " dx = vars_float[:, 2]\n", " dy = vars_float[:, 3]\n", " alpha = vars_float[:, 4]\n", "\n", " a = np.deg2rad(alpha)\n", " nax = np.stack([np.cos(a), np.sin(a), np.zeros(a.shape)], axis=-1)\n", " naz = np.zeros_like(nax)\n", " naz[:, 2] = 1\n", " nay = np.cross(naz, nax)\n", "\n", " mx = np.max(nx)\n", " my = np.max(ny)\n", " self.xy = np.full((n_pop, mx, my, 2), -2*self.radius)\n", " for i in range(n_pop):\n", " self.xy[i, :nx[i], :ny[i]] = np.array([x0[i], y0[i]])[None, None, :]\n", " self.xy[i, :nx[i], :ny[i]] += np.arange(nx[i])[:, None, None] * dx[i, None, None, None] * nax[i, None, None, :2]\n", " self.xy[i, :nx[i], :ny[i]] += np.arange(ny[i])[None, :, None] * dy[i, None, None, None] * nay[i, None, None, :2]\n", "\n", " self.valid = np.linalg.norm(self.xy, axis=-1) <= self.radius\n", "\n", " return self.xy, self.valid\n", "\n", " def get_fig(self, xy=None, valid=None):\n", "\n", " if xy is None:\n", " xy = self.xy\n", " if valid is None:\n", " valid = self.valid\n", "\n", " nx, ny = xy.shape[:2]\n", " xy = xy.reshape(nx*ny, 2)[valid.reshape(nx*ny)]\n", "\n", " fig, ax = plt.subplots()\n", " ax.scatter(xy[:, 0], xy[:, 1], color=\"orange\")\n", " ax.add_patch(plt.Circle((0, 0), self.radius, color=\"darkred\", fill=False))\n", " ax.set_aspect(\"equal\", adjustable=\"box\")\n", " ax.set_xlabel(\"x\")\n", " ax.set_ylabel(\"y\")\n", " ax.set_title(f\"N = {len(xy)}, min_dist = {self.min_dist}\")\n", "\n", " return fig" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the `calculate_individual` and `calculate_population` functions return the current grid points and the validity array. This problem has two integer variables `nx` and `ny`, determining the number of grid points in x and y directions, respectively, and 5 float type variables.\n", "\n", "Our objective is the maximization of the total number of points within the circle:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "class MaxN(Objective):\n", " def __init__(self, problem):\n", " super().__init__(problem, \"maxN\", vnames_int=problem.var_names_int(), \n", " vnames_float=problem.var_names_float())\n", "\n", " def n_components(self):\n", " return 1\n", "\n", " def maximize(self):\n", " return [True]\n", "\n", " def calc_individual(self, vars_int, vars_float, problem_results, cmpnts=None):\n", " xy, valid = problem_results\n", " return np.sum(valid)\n", "\n", " def calc_population(self, vars_int, vars_float, problem_results, cmpnts=None):\n", " xy, valid = problem_results\n", " return np.sum(valid, axis=(1, 2))[:, None]" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "This objective makes use of the returned validity array. We can now solve this with the appropriate algorithm from `pymoo`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "problem = GridProblem(n_row_max=1000, radius=5, min_dist=0.5)\n", "problem.add_objective(MaxN(problem))\n", "problem.initialize()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "solver = Optimizer_pymoo(\n", " problem,\n", " problem_pars=dict(\n", " vectorize=True,\n", " ),\n", " algo_pars=dict(\n", " type=\"MixedVariableGA\",\n", " pop_size=40,\n", " seed=42,\n", " ),\n", " setup_pars=dict(\n", " ),\n", " term_pars=('n_eval', 1000),\n", ")\n", "solver.initialize()\n", "solver.print_info()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here `tol` is an `IPOPT` parameter that defines the convergence tolerance. Now we are finally ready - let's solve the problem!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "results = solver.solve()\n", "solver.finalize(results)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "These are the results:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(results)\n", "\n", "fig = problem.get_fig()\n", "plt.show()\n", "plt.close(fig)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Clearly the circle is fully filled with points on a regular grid." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10 (default, Nov 14 2022, 12:59:47) \n[GCC 9.4.0]" }, "vscode": { "interpreter": { "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" } } }, "nbformat": 4, "nbformat_minor": 2 }