diff --git a/figures.ipynb b/figures.ipynb index 93d0a7807642961037a35e4b725558c75fd03bee..0ec869533be667fa64ed9e54d79ac250f934a5bb 100644 --- a/figures.ipynb +++ b/figures.ipynb @@ -39,7 +39,16 @@ "}\n", "\n", "plt.rcParams.update(params)\n", - "plt.rc(\"text.latex\", preamble=[r\"\\usepackage{xfrac}\", r\"\\usepackage{siunitx}\"])" + "plt.rc(\"text.latex\", preamble=[r\"\\usepackage{xfrac}\", r\"\\usepackage{siunitx}\"])\n", + "\n", + "import adaptive\n", + "from scipy import interpolate\n", + "import functools\n", + "import itertools\n", + "import adaptive\n", + "import holoviews.plotting.mpl\n", + "import time\n", + "import matplotlib.tri as mtri" ] }, { @@ -113,9 +122,6 @@ "metadata": {}, "outputs": [], "source": [ - "import adaptive\n", - "\n", - "\n", "def f(x, offset=0.123):\n", " a = 0.02\n", " return x + a ** 2 / (a ** 2 + (x - offset) ** 2)\n", @@ -129,33 +135,35 @@ " return np.sin(100 * x) * np.exp(-x ** 2 / 0.1 ** 2)\n", "\n", "\n", - "funcs = [\n", + "funcs_1D = [\n", " dict(function=f, bounds=(-1, 1), title=\"peak\"),\n", " dict(function=g, bounds=(-1, 1), title=\"tanh\"),\n", " dict(function=h, bounds=(-0.3, 0.3), title=\"wave packet\"),\n", "]\n", - "fig, axs = plt.subplots(2, len(funcs), figsize=(fig_width, 1.5 * fig_height))\n", + "fig, axs = plt.subplots(2, len(funcs_1D), figsize=(fig_width, 1.5 * fig_height))\n", "n_points = 50\n", "for i, ax in enumerate(axs.T.flatten()):\n", " ax.xaxis.set_ticks([])\n", " ax.yaxis.set_ticks([])\n", - " if i % 2 == 0:\n", - " d = funcs[i // 2]\n", - " # homogeneous\n", - " xs = np.linspace(*d[\"bounds\"], n_points)\n", - " ys = d[\"function\"](xs)\n", + " kind = \"homogeneous\" if i % 2 == 0 else \"adaptive\"\n", + " index = i // 2 if kind == \"homogeneous\" else (i - 1) // 2\n", + " d = funcs_1D[index]\n", + " bounds = d[\"bounds\"]\n", + " f = d[\"function\"]\n", + "\n", + " if kind == \"homogeneous\":\n", + " xs = np.linspace(*bounds, n_points)\n", + " ys = f(xs)\n", " ax.set_title(rf\"\\textrm{{{d['title']}}}\")\n", - " else:\n", - " d = funcs[(i - 1) // 2]\n", + " elif kind == \"adaptive\":\n", " loss = adaptive.learner.learner1D.curvature_loss_function()\n", " learner = adaptive.Learner1D(\n", - " d[\"function\"], bounds=d[\"bounds\"], loss_per_interval=loss\n", + " f, bounds=bounds, loss_per_interval=loss\n", " )\n", " adaptive.runner.simple(learner, goal=lambda l: l.npoints >= n_points)\n", - " # adaptive\n", " xs, ys = zip(*sorted(learner.data.items()))\n", - " xs_dense = np.linspace(*d[\"bounds\"], 1000)\n", - " ax.plot(xs_dense, d[\"function\"](xs_dense), c=\"red\", alpha=0.3, lw=0.5)\n", + " xs_dense = np.linspace(*bounds, 1000)\n", + " ax.plot(xs_dense, f(xs_dense), c=\"red\", alpha=0.3, lw=0.5)\n", " ax.scatter(xs, ys, s=0.5, c=\"k\")\n", "\n", "axs[0][0].set_ylabel(r\"$\\textrm{homogeneous}$\")\n", @@ -176,13 +184,6 @@ "metadata": {}, "outputs": [], "source": [ - "from scipy import interpolate\n", - "import functools\n", - "import itertools\n", - "import adaptive\n", - "import holoviews.plotting.mpl\n", - "import matplotlib.tri as mtri\n", - "\n", "\n", "def f(xy, offset=0.123):\n", " a = 0.2\n", @@ -310,134 +311,23 @@ "metadata": {}, "outputs": [], "source": [ - "from scipy import interpolate\n", - "import functools\n", - "import itertools\n", - "import adaptive\n", - "import holoviews.plotting.mpl\n", - "import matplotlib.tri as mtri\n", - "\n", - "\n", - "def f(xy, offset=0.123):\n", - " a = 0.2\n", - " x, y = xy\n", - " return x + np.exp(-(x ** 2 + y ** 2 - 0.75 ** 2) ** 2 / a ** 4)\n", - "\n", - "\n", - "@functools.lru_cache()\n", - "def g_setup(fname):\n", - " data = adaptive.utils.load(fname)\n", - " points = np.array(list(data.keys()))\n", - " values = np.array(list(data.values()), dtype=float)\n", - " bounds = [\n", - " (points[:, 0].min(), points[:, 0].max()),\n", - " (points[:, 1].min(), points[:, 1].max()),\n", - " ]\n", - " ll, ur = np.reshape(bounds, (2, 2)).T\n", - " inds = np.all(np.logical_and(ll <= points, points <= ur), axis=1)\n", - " points, values = points[inds], values[inds].reshape(-1, 1)\n", - " return interpolate.LinearNDInterpolator(points, values), bounds\n", - "\n", - "\n", - "def g(xy, fname):\n", - " ip, _ = g_setup(fname)\n", - " return np.round(ip(xy))\n", - "\n", - "\n", - "def density(x, eps=0):\n", - " e = [0.8, 0.2]\n", - " delta = [0.5, 0.5, 0.5]\n", - " c = 3\n", - " omega = [0.02, 0.05]\n", - "\n", - " H = np.array(\n", - " [\n", - " [e[0] + 1j * omega[0], delta[0], delta[1]],\n", - " [delta[0], e[1] + c * x + 1j * omega[1], delta[1]],\n", - " [delta[1], delta[2], e[1] - c * x + 1j * omega[1]],\n", - " ]\n", - " )\n", - " H += np.eye(3) * eps\n", - " return np.trace(np.linalg.inv(H)).imag\n", - "\n", - "\n", - "def h(xy):\n", - " x, y = xy\n", - " return density(x, y) + y\n", - "\n", - "\n", - "funcs = [\n", - " dict(function=f, bounds=[(-1, 1), (-1, 1)], npoints=33),\n", - " dict(\n", - " function=g,\n", - " bounds=g_setup(\"phase_diagram.pickle\")[1],\n", - " npoints=100,\n", - " fname=\"phase_diagram.pickle\",\n", - " ),\n", - " dict(function=h, bounds=[(-1, 1), (-3, 3)], npoints=50),\n", - "]\n", - "fig, axs = plt.subplots(len(funcs), 2, figsize=(fig_width, 2 * fig_height))\n", - "\n", - "plt.subplots_adjust(hspace=0.1, wspace=0.1)\n", - "\n", - "with_tri = False\n", - "\n", - "for i, ax in enumerate(axs.flatten()):\n", - " label = \"abcdef\"[i]\n", - " ax.text(\n", - " -0.03,\n", - " 0.98,\n", - " f\"$\\mathrm{{({label})}}$\",\n", - " transform=ax.transAxes,\n", - " horizontalalignment=\"right\",\n", - " verticalalignment=\"top\",\n", - " )\n", - " ax.xaxis.set_ticks([])\n", - " ax.yaxis.set_ticks([])\n", - " kind = \"homogeneous\" if i % 2 == 0 else \"adaptive\"\n", - " d = funcs[i // 2] if kind == \"homogeneous\" else funcs[(i - 1) // 2]\n", - " bounds = d[\"bounds\"]\n", - " npoints = d[\"npoints\"]\n", - " f = d[\"function\"]\n", - " fname = d.get(\"fname\")\n", - " if fname is not None:\n", - " f = functools.partial(f, fname=fname)\n", - "\n", - " if kind == \"homogeneous\":\n", - " xs, ys = [np.linspace(*bound, npoints) for bound in bounds]\n", - " data = {xy: f(xy) for xy in itertools.product(xs, ys)}\n", - " learner = adaptive.Learner2D(f, bounds=bounds)\n", - " learner.data = data\n", - " d[\"learner_hom\"] = learner\n", - " elif kind == \"adaptive\":\n", - " learner = adaptive.Learner2D(f, bounds=bounds)\n", - " if fname is not None:\n", - " learner.load(fname)\n", - " learner.data = {\n", - " k: v for i, (k, v) in enumerate(learner.data.items()) if i <= npoints ** 2\n", - " }\n", - " adaptive.runner.simple(learner, goal=lambda l: l.npoints >= npoints ** 2)\n", - " d[\"learner\"] = learner\n", - "\n", - " if with_tri:\n", - " tri = learner.ip().tri\n", - " triang = mtri.Triangulation(*tri.points.T, triangles=tri.vertices)\n", - " ax.triplot(triang, c=\"w\", lw=0.2, alpha=0.8)\n", - "\n", - " values = np.array(list(learner.data.values()))\n", - " ax.imshow(\n", - " learner.plot(npoints if kind == \"homogeneous\" else None).Image.I.data,\n", - " extent=(-0.5, 0.5, -0.5, 0.5),\n", - " interpolation=\"none\",\n", - " )\n", - " ax.set_xticks([])\n", - " ax.set_yticks([])\n", - "\n", - "axs[0][0].set_title(r\"$\\textrm{homogeneous}$\")\n", - "axs[0][1].set_title(r\"$\\textrm{adaptive}$\")\n", - "\n", - "plt.savefig(\"figures/adaptive_2D.pdf\", bbox_inches=\"tight\", transparent=True)" + "learner = adaptive.Learner1D(funcs_1D[0]['function'], bounds=funcs_1D[0]['bounds'])\n", + "times = []\n", + "for i in range(10000):\n", + " t_start = time.time()\n", + " points, _ = learner.ask(1)\n", + " t_end = time.time()\n", + " times.append(t_end - t_start)\n", + " learner.tell(points[0], learner.function(points[0]))\n", + "plt.plot(np.cumsum(times))" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/paper.md b/paper.md index adc66dc304902a0744fe844724b37a02031c7425..ec4765729df596178771c0d61f135306431c0ca0 100755 --- a/paper.md +++ b/paper.md @@ -128,8 +128,16 @@ A more complex loss function that also takes the first neighboring intervals int Figure @fig:adaptive_vs_grid shows a comparison between a result using this loss and a function that is sampled on a grid. #### In general local loss functions only have a logarithmic overhead. +<!-- Bas: not sure what to write here --> #### With many points, due to the loss being local, parallel sampling incurs no additional cost. +<!-- Bas: the text below doesn't really describe what's written above, but is an essential part nonetheless --> +So far, the description of the general algorithm did not include parallelism. +It needs to be able to suggest multiple points at the same time and remember which points it suggests. +When a new point $\bm{x}_\textrm{new}$ with the largest loss $L_\textrm{max}$ is suggested, the interval it belongs to splits up into $N$ new intervals (here $N$ depends on the dimensionality of the function $f$.) +A temporary loss $L_\textrm{temp} = L_\textrm{max}/N$ is assigned to these newly created intervals until $f(\bm{x})$ is calculated and the temporary loss can be replaced by the actual loss $L \equiv L((\bm{x},\bm{y})_\textrm{new}, (\bm{x},\bm{y})_\textrm{neigbors})$ of these new intervals, where $L \ge L_\textrm{temp}$. +For a one-dimensional scalar function, this procedure is equivalent to temporarily using the function values of the neighbors of $x_\textrm{new}$ and assign the interpolated value to $y_\textrm{new}$ until it is known. +When querying $n>1$ points, the former procedure simply repeats $n$ times. # Loss function design