... | ... |
@@ -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.""" |