Browse code

resolve some comments

Jorn Hoofwijk authored on 05/07/2018 17:26:40 • Bas Nijholt committed on 11/07/2018 05:27:21
Showing 2 changed files
... ...
@@ -11,6 +11,7 @@ from ..notebook_integration import ensure_holoviews
11 11
 from .base_learner import BaseLearner
12 12
 
13 13
 from .triangulation import Triangulation
14
+import math
14 15
 
15 16
 
16 17
 def find_initial_simplex(pts, ndim):
... ...
@@ -59,7 +60,7 @@ def default_loss(simplex, ys):
59 60
     return std_loss(simplex, ys) + longest_edge * 0.1
60 61
 
61 62
 
62
-def choose_point_in_simplex(simplex, metric=None):
63
+def choose_point_in_simplex(simplex, transform=None):
63 64
     """Choose a new point in inside a simplex.
64 65
 
65 66
     Pick the center of the longest edge of this simplex
... ...
@@ -68,7 +69,7 @@ def choose_point_in_simplex(simplex, metric=None):
68 69
     ----------
69 70
     simplex : numpy array
70 71
         The coordinates of a triangle with shape (N+1, N)
71
-    metric : N*N matrix
72
+    transform : N*N matrix
72 73
         The multiplication to apply to the simplex before choosing the new point
73 74
 
74 75
     Returns
... ...
@@ -78,15 +79,15 @@ def choose_point_in_simplex(simplex, metric=None):
78 79
     """
79 80
 
80 81
     # TODO find a better selection algorithm
81
-    if metric is not None:
82
-        simplex = np.dot(simplex, metric)
82
+    if transform is not None:
83
+        simplex = np.dot(simplex, transform)
83 84
 
84 85
     distances = scipy.spatial.distance.pdist(simplex)
85 86
     distance_matrix = scipy.spatial.distance.squareform(distances)
86 87
     i, j = np.unravel_index(np.argmax(distance_matrix), distance_matrix.shape)
87 88
 
88 89
     point = (simplex[i, :] + simplex[j, :]) / 2
89
-    return np.dot(point, np.linalg.inv(metric))
90
+    return np.linalg.solve(transform, point)
90 91
 
91 92
 
92 93
 class LearnerND(BaseLearner):
... ...
@@ -154,7 +155,7 @@ class LearnerND(BaseLearner):
154 155
 
155 156
         self._subtriangulations = dict()  # simplex -> triangulation
156 157
 
157
-        self._metric = np.linalg.inv(np.diag(np.diff(bounds).flat))
158
+        self._transform = np.linalg.inv(np.diag(np.diff(bounds).flat))
158 159
 
159 160
     @property
160 161
     def npoints(self):
... ...
@@ -194,7 +195,7 @@ class LearnerND(BaseLearner):
194 195
         to_add = [p for i, p in enumerate(self.points) if i not in initial_simplex]
195 196
 
196 197
         for p in to_add:
197
-            self._tri.add_point(p, metric=self._metric)
198
+            self._tri.add_point(p, transform=self._transform)
198 199
         # TODO also compute losses of initial simplex
199 200
 
200 201
     @property
... ...
@@ -234,7 +235,7 @@ class LearnerND(BaseLearner):
234 235
             simplex = self._pending_to_simplex.get(point, None)
235 236
             if simplex is not None and not self._simplex_exists(simplex):
236 237
                 simplex = None
237
-            to_delete, to_add = self._tri.add_point(point, simplex, metric=self._metric)
238
+            to_delete, to_add = self._tri.add_point(point, simplex, transform=self._transform)
238 239
             self.update_losses(to_delete, to_add)
239 240
 
240 241
     def _simplex_exists(self, simplex):
... ...
@@ -339,7 +340,7 @@ class LearnerND(BaseLearner):
339 340
                     simplex = real_simp
340 341
                     loss = pend_loss
341 342
 
342
-            point_new = tuple(choose_point_in_simplex(points, metric=self._metric))  # choose a new point in the simplex
343
+            point_new = tuple(choose_point_in_simplex(points, transform=self._transform))  # choose a new point in the simplex
343 344
             self._pending_to_simplex[point_new] = simplex
344 345
 
345 346
             new_points.append(point_new)
... ...
@@ -390,7 +391,8 @@ class LearnerND(BaseLearner):
390 391
     def remove_unfinished(self):
391 392
         # TODO implement this method
392 393
         self._pending = set()
393
-        raise NotImplementedError("method 'LearnerND.remove_unfinished()' is not yet fully implemented")
394
+        self._subtriangulations = dict()
395
+        self._pending_to_simplex = dict()
394 396
 
395 397
     def plot(self, n=None, tri_alpha=0):
396 398
         hv = ensure_holoviews()
... ...
@@ -488,7 +490,7 @@ class LearnerND(BaseLearner):
488 490
                 raise NotImplementedError('multidimensional output not yet supported by plotSlice')
489 491
 
490 492
             # Plot with 5% empty margins such that the boundary points are visible
491
-            margin = 0.05 / np.diag(self._metric)[ind]
493
+            margin = 0.05 / np.diag(self._transform)[ind]
492 494
             plot_bounds = (a - margin, b + margin)
493 495
 
494 496
             return p.redim(x=dict(range=plot_bounds))
... ...
@@ -5,18 +5,23 @@ import numpy as np
5 5
 from scipy import linalg
6 6
 import math
7 7
 
8
-# WIP:
8
+def fast_norm(v):
9
+    # notice this method can be even more optimised
10
+    return math.sqrt(np.dot(v,v))
11
+
12
+def fast_norm_2d(v):
13
+    return math.sqrt(v[0]*v[0] + v[1]*v[1])
9 14
 
10 15
 def fast_2d_point_in_simplex(point, simplex, eps=1e-8):
11 16
     (p0x, p0y), (p1x, p1y), (p2x, p2y) = simplex
12 17
     px, py = point
13 18
 
14
-    Area = 0.5 * (-p1y * p2x + p0y * (-p1x + p2x) + p0x * (p1y - p2y) + p1x * p2y)
19
+    area = 0.5 * (-p1y * p2x + p0y * (-p1x + p2x) + p0x * (p1y - p2y) + p1x * p2y)
15 20
 
16
-    s = 1 / (2 * Area) * (p0y * p2x - p0x * p2y + (p2y - p0y) * px + (p0x - p2x) * py)
21
+    s = 1 / (2 * area) * (p0y * p2x - p0x * p2y + (p2y - p0y) * px + (p0x - p2x) * py)
17 22
     if s < -eps or s > 1+eps:
18 23
         return
19
-    t = 1 / (2 * Area) * (p0x * p1y - p0y * p1x + (p0y - p1y) * px + (p1x - p0x) * py)
24
+    t = 1 / (2 * area) * (p0x * p1y - p0y * p1x + (p0y - p1y) * px + (p1x - p0x) * py)
20 25
 
21 26
     return (t >= -eps) and (s + t <= 1+eps)
22 27
 
... ...
@@ -90,7 +95,7 @@ def fast_3d_circumcircle(points):
90 95
     a = 2*aa
91 96
 
92 97
     center = [dx/a, -dy/a, dz/a]
93
-    radius = np.sqrt(np.dot(center, center))
98
+    radius = fast_norm(center)
94 99
     center = np.add(center, points[0])
95 100
 
96 101
     return tuple(center), radius
... ...
@@ -168,11 +173,11 @@ class Triangulation:
168 173
         for vertex in simplex:
169 174
             self.vertex_to_simplices[vertex].add(simplex)
170 175
 
171
-    def get_vertices(self, indices, metric=None):
172
-        if metric is None:
176
+    def get_vertices(self, indices, transform=None):
177
+        if transform is None:
173 178
             return [self.vertices[i] for i in indices]
174 179
         else:
175
-            return np.dot(self.get_vertices(indices), metric)
180
+            return np.dot(self.get_vertices(indices), transform)
176 181
 
177 182
     def point_in_simplex(self, point, simplex, eps=1e-8):
178 183
         """Check whether vertex lies within a simplex.
... ...
@@ -191,10 +196,11 @@ class Triangulation:
191 196
         alpha = np.linalg.solve(vectors.T, point - x0)
192 197
         if any(alpha < -eps) or sum(alpha) > 1 + eps:
193 198
             return []
194
-        result = (
195
-            ([0] if sum(alpha)  < 1 - eps else [])
196
-            + [i + 1 for i, a in enumerate(alpha) if a > eps]
197
-        )
199
+
200
+        result = [i for i, a in enumerate(alpha, 1) if a > eps]
201
+        if sum(alpha) < 1 - eps:
202
+            result.insert(0, 0)
203
+
198 204
         return [simplex[i] for i in result]
199 205
 
200 206
     def fast_point_in_simplex(self, point, simplex, eps=1e-8):
... ...
@@ -293,7 +299,7 @@ class Triangulation:
293 299
             if orientation_inside == -orientation_new_point:
294 300
                 # if the orientation of the new vertex is zero or directed
295 301
                 # towards the center, do not add the simplex
296
-                self.add_simplex(face + (pt_index,))
302
+                self.add_simplex((*face, pt_index))
297 303
                 faces_to_check.add(face)
298 304
 
299 305
         multiplicities = Counter(face for face in
... ...
@@ -312,20 +318,20 @@ class Triangulation:
312 318
         self.hull = self.compute_hull(vertices=self.hull, check=False)
313 319
 
314 320
 
315
-    def circumscribed_circle(self, simplex, metric):
321
+    def circumscribed_circle(self, simplex, transform):
316 322
         """
317 323
         Compute the centre and radius of the circumscribed circle of a simplex
318 324
         :param simplex: the simplex to investigate
319 325
         :return: tuple (centre point, radius)
320 326
         """
321 327
         if self.dim == 2:
322
-            return fast_2d_circumcircle(self.get_vertices(simplex, metric))
328
+            return fast_2d_circumcircle(self.get_vertices(simplex, transform))
323 329
         if self.dim == 3:
324
-            return fast_3d_circumcircle(self.get_vertices(simplex, metric))
330
+            return fast_3d_circumcircle(self.get_vertices(simplex, transform))
325 331
 
326 332
         # Modified from http://mathworld.wolfram.com/Circumsphere.html
327 333
         mat = []
328
-        pts = self.get_vertices(simplex, metric)
334
+        pts = self.get_vertices(simplex, transform)
329 335
         for pt in pts:
330 336
             length_squared = np.sum(np.square(pt))
331 337
             row = np.array([length_squared, *pt, 1])
... ...
@@ -342,45 +348,46 @@ class Triangulation:
342 348
 
343 349
         x0 = self.vertices[next(iter(simplex))]
344 350
         vec = np.subtract(center, x0)
345
-        radius = np.sqrt(np.dot(vec, vec))
351
+        radius = fast_norm(vec)
346 352
 
347 353
         return tuple(center), radius
348 354
 
349 355
 
350
-    def point_in_cicumcircle(self, pt_index, simplex, metric):
351
-        # return self.fast_point_in_circumcircle(pt_index, simplex, metric)
356
+    def point_in_cicumcircle(self, pt_index, simplex, transform):
357
+        # return self.fast_point_in_circumcircle(pt_index, simplex, transform)
352 358
         eps = 1e-8
353 359
 
354
-        center, radius = self.circumscribed_circle(simplex, metric)
355
-        pt = self.get_vertices([pt_index], metric)[0]
360
+        center, radius = self.circumscribed_circle(simplex, transform)
361
+        pt = self.get_vertices([pt_index], transform)[0]
356 362
 
357 363
         return np.linalg.norm(center - pt) < (radius * (1 + eps))
358 364
 
359
-    def fast_point_in_circumcircle(self, pt_index, simplex, metric):
360
-        # Construct the matrix
361
-        eps = 1e-10
362
-        indices = simplex + (pt_index,)
363
-        original_points = self.get_vertices(indices)
364
-        points = np.dot(original_points, metric)
365
-        l_squared = np.sum(np.square(points), axis=1)
366
-
367
-        M = [*np.transpose(points), l_squared, np.ones(l_squared.shape)]
368
-
369
-        # Compute the determinant
370
-        det = np.linalg.det(M)
371
-        if np.abs(det) < eps:
372
-            return True
373
-
374
-        M2 = [*np.transpose(points[:-1]), np.ones(len(simplex))]
375
-        det_inside = np.linalg.det(M2)
376
-
377
-        return np.sign(det) == np.sign(det_inside)
365
+    # WIP: currently this is slower than computing the circumcircle
366
+    # def fast_point_in_circumcircle(self, pt_index, simplex, transform):
367
+    #     # Construct the matrix
368
+    #     eps = 1e-10
369
+    #     indices = simplex + (pt_index,)
370
+    #     original_points = self.get_vertices(indices)
371
+    #     points = np.dot(original_points, transform)
372
+    #     l_squared = np.sum(np.square(points), axis=1)
373
+    #
374
+    #     M = np.array([*np.transpose(points), l_squared, np.ones(l_squared.shape)], dtype=float)
375
+    #
376
+    #     # Compute the determinant
377
+    #     det = np.linalg.det(M)
378
+    #     if np.abs(det) < eps:
379
+    #         return True
380
+    #
381
+    #     M2 = [*np.transpose(points[:-1]), np.ones(len(simplex))]
382
+    #     det_inside = np.linalg.det(M2)
383
+    #
384
+    #     return np.sign(det) == np.sign(det_inside)
378 385
 
379 386
     @property
380
-    def default_metric(self):
387
+    def default_transform(self):
381 388
         return np.eye(self.dim)
382 389
 
383
-    def bowyer_watson(self, pt_index, containing_simplex=None, metric=None):
390
+    def bowyer_watson(self, pt_index, containing_simplex=None, transform=None):
384 391
         """
385 392
         Modified Bowyer-Watson point adding algorithm
386 393
 
... ...
@@ -392,7 +399,7 @@ class Triangulation:
392 399
         queue = set()
393 400
         done_simplices = set()
394 401
 
395
-        metric = self.default_metric if metric is None else metric
402
+        transform = self.default_transform if transform is None else transform
396 403
 
397 404
         if containing_simplex is None:
398 405
             queue.update(self.vertex_to_simplices[pt_index])
... ...
@@ -407,7 +414,7 @@ class Triangulation:
407 414
             simplex = queue.pop()
408 415
             done_simplices.add(simplex)
409 416
 
410
-            if self.point_in_cicumcircle(pt_index, simplex, metric):
417
+            if self.point_in_cicumcircle(pt_index, simplex, transform):
411 418
                 self.delete_simplex(simplex)
412 419
                 todo_points = set(simplex) - done_points
413 420
                 done_points.update(simplex)
... ...
@@ -431,14 +438,14 @@ class Triangulation:
431 438
 
432 439
         return bad_triangles, self.vertex_to_simplices[pt_index]
433 440
 
434
-    def add_point(self, point, simplex=None, metric=None):
441
+    def add_point(self, point, simplex=None, transform=None):
435 442
         """Add a new vertex and create simplices as appropriate.
436 443
 
437 444
         Parameters
438 445
         ----------
439 446
         point : float vector
440 447
             Coordinates of the point to be added.
441
-        metric : N*N matrix of floats
448
+        transform : N*N matrix of floats
442 449
             Multiplication matrix to apply to the point (and neighbouring
443 450
             simplices) when running the Bowyer Watson method.
444 451
         simplex : tuple of ints, optional
... ...
@@ -457,7 +464,7 @@ class Triangulation:
457 464
 
458 465
             pt_index = len(self.vertices) - 1
459 466
             # self.bowyer_watson(pt_index)
460
-            return self.bowyer_watson(pt_index, metric=metric)
467
+            return self.bowyer_watson(pt_index, transform=transform)
461 468
         else:
462 469
             reduced_simplex = self.point_in_simplex(point, simplex)
463 470
             if not reduced_simplex:
... ...
@@ -472,7 +479,7 @@ class Triangulation:
472 479
         else:
473 480
             pt_index = len(self.vertices)
474 481
             self.vertices.append(point)
475
-            return self.bowyer_watson(pt_index, actual_simplex, metric)
482
+            return self.bowyer_watson(pt_index, actual_simplex, transform)
476 483
 
477 484
     def add_point_inside_simplex(self, point, simplex):
478 485
         if len(self.point_in_simplex(point, simplex)) != self.dim + 1:
... ...
@@ -483,7 +490,7 @@ class Triangulation:
483 490
 
484 491
         new = []
485 492
         for others in combinations(simplex, len(simplex) - 1):
486
-            tri = others + (pt_index,)
493
+            tri = (*others, pt_index)
487 494
             self.add_simplex(tri)
488 495
             new.append(tri)
489 496
 
... ...
@@ -506,7 +513,7 @@ class Triangulation:
506 513
             opposing = tuple(pt for pt in simplex if pt not in face)
507 514
 
508 515
             for others in combinations(face, len(face) - 1):
509
-                self.add_simplex(others + opposing + (pt_index,))
516
+                self.add_simplex((*others, *opposing, pt_index))
510 517
 
511 518
     def flip(self, face):
512 519
         """Flip the face shared between several simplices."""