From 8b0c5472c3544b6d38f3cb5d98e93098be15724a Mon Sep 17 00:00:00 2001 From: Jorn Hoofwijk <jornhoofwijk@gmail.com> Date: Wed, 17 Oct 2018 12:21:37 +0200 Subject: [PATCH] also add an isoline feature --- adaptive/learner/learnerND.py | 101 ++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/adaptive/learner/learnerND.py b/adaptive/learner/learnerND.py index df9e622c..217898ac 100644 --- a/adaptive/learner/learnerND.py +++ b/adaptive/learner/learnerND.py @@ -463,6 +463,10 @@ class LearnerND(BaseLearner): self._subtriangulations = dict() self._pending_to_simplex = dict() + ########################## + # Plotting related stuff # + ########################## + def plot(self, n=None, tri_alpha=0): """Plot the function we want to learn, only works in 2D. @@ -639,6 +643,103 @@ class LearnerND(BaseLearner): ) return vertices, faces + + def _get_isoline(self, level=0.0): + # Very similar to _get_isosurface, maybe merge the two functions + if self.ndim != 2 or self.vdim != 1: + raise Exception('Isoline plotting is only supported' + ' for a 2D input and 1D output') + + vertices = [] # index -> (x,y,z) + lines = [] # tuple of indices of the corner points + from_line_to_vertex = {} # the interpolated vertex (index) between two known points + + def _get_vertex_index(a, b): + if (a, b) in from_line_to_vertex: + return from_line_to_vertex[(a, b)] + + # Otherwise compute it and cache the result. + vertex_a = self.tri.vertices[a] + vertex_b = self.tri.vertices[b] + value_a = self.data[vertex_a] + value_b = self.data[vertex_b] + + da = abs(value_a - level) + db = abs(value_b - level) + dab = da + db + + new_pt = (db / dab * np.array(vertex_a) + + da / dab * np.array(vertex_b)) + + new_index = len(vertices) + vertices.append(new_pt) + from_line_to_vertex[(a, b)] = new_index + return new_index + + for simplex in self.tri.simplices: + line = [] + for a, b in itertools.combinations(simplex, 2): + va = self.data[self.tri.vertices[a]] + vb = self.data[self.tri.vertices[b]] + if min(va, vb) < level <= max(va, vb): + vi = _get_vertex_index(a, b) + should_add = True + for pi in line: + if np.allclose(vertices[vi], vertices[pi]): + should_add = False + if should_add: + line.append(vi) + + if len(line) == 2: + lines.append(line) + + if len(lines) == 0: + r_min = min(self.data[v] for v in self.tri.vertices) + r_max = max(self.data[v] for v in self.tri.vertices) + + raise ValueError( + f"Could not draw isosurface for level={level}, as" + " this value is not inside the function range. Please choose" + f" a level strictly inside interval ({r_min}, {r_max})" + ) + + return vertices, lines + + def plot_isoline(self, level=0.0, n=None): + """Plot the isoline at a specific level of the function we want to + learn, only works in 2D. + + Parameters + ---------- + level : float + the value of the function at which you would like to see the isoline + n : int + the number of boxes in the interpolation grid along each axis + """ + hv = ensure_holoviews() + + vertices, lines = self._get_isoline(level) + paths = [[vertices[i], vertices[j]] for i,j in lines] + contour = hv.Path(paths) + + x, y = self.bounds + lbrt = x[0], y[0], x[1], y[1] + + if n is None: + # Calculate how many grid points are needed. + # factor from A=√3/4 * a² (equilateral triangle) + n = int(0.658 / np.sqrt(np.min(self.tri.volumes()))) + + xs = ys = np.linspace(0, 1, n) + xs = xs * (x[1] - x[0]) + x[0] + ys = ys * (y[1] - y[0]) + y[0] + z = self.ip()(xs[:, None], ys[None, :]).squeeze() + + im = hv.Image(np.rot90(z), bounds=lbrt) + + im_opts = dict(cmap='viridis') + contour_opts = dict(color='black') + return im.opts(style=im_opts) * contour.opts(style=contour_opts) def plot_isosurface(self, level=0.0, hull_opacity=0.2): """Plots the linearly interpolated isosurface of the function, -- GitLab