1
1
# -*- coding: utf-8 -*-
2
2
3
3
import itertools
4
+ import warnings
4
5
from collections import OrderedDict
5
6
from copy import copy
6
7
from math import sqrt
@@ -26,7 +27,7 @@ def deviations(ip):
26
27
27
28
Returns
28
29
-------
29
- numpy array
30
+ deviations : list
30
31
The deviation per triangle.
31
32
"""
32
33
values = ip .values / (ip .values .ptp (axis = 0 ).max () or 1 )
@@ -64,7 +65,7 @@ def areas(ip):
64
65
65
66
Returns
66
67
-------
67
- numpy array
68
+ areas : numpy.ndarray
68
69
The area per triangle in ``ip.tri``.
69
70
"""
70
71
p = ip .tri .points [ip .tri .vertices ]
@@ -78,16 +79,27 @@ def uniform_loss(ip):
78
79
79
80
Works with `~adaptive.Learner2D` only.
80
81
82
+ Parameters
83
+ ----------
84
+ ip : `scipy.interpolate.LinearNDInterpolator` instance
85
+
86
+ Returns
87
+ -------
88
+ losses : numpy.ndarray
89
+ Loss per triangle in ``ip.tri``.
90
+
81
91
Examples
82
92
--------
83
93
>>> from adaptive.learner.learner2D import uniform_loss
84
94
>>> def f(xy):
85
95
... x, y = xy
86
96
... return x**2 + y**2
87
97
>>>
88
- >>> learner = adaptive.Learner2D(f,
89
- ... bounds=[(-1, -1), (1, 1)],
90
- ... loss_per_triangle=uniform_loss)
98
+ >>> learner = adaptive.Learner2D(
99
+ ... f,
100
+ ... bounds=[(-1, -1), (1, 1)],
101
+ ... loss_per_triangle=uniform_loss,
102
+ ... )
91
103
>>>
92
104
"""
93
105
return np .sqrt (areas (ip ))
@@ -102,17 +114,18 @@ def resolution_loss_function(min_distance=0, max_distance=1):
102
114
The arguments `min_distance` and `max_distance` should be in between 0 and 1
103
115
because the total area is normalized to 1.
104
116
117
+ Returns
118
+ -------
119
+ loss_function : callable
120
+
105
121
Examples
106
122
--------
107
123
>>> def f(xy):
108
124
... x, y = xy
109
125
... return x**2 + y**2
110
126
>>>
111
127
>>> loss = resolution_loss_function(min_distance=0.01, max_distance=1)
112
- >>> learner = adaptive.Learner2D(f,
113
- ... bounds=[(-1, -1), (1, 1)],
114
- ... loss_per_triangle=loss)
115
- >>>
128
+ >>> learner = adaptive.Learner2D(f, bounds=[(-1, -1), (1, 1)], loss_per_triangle=loss)
116
129
"""
117
130
118
131
def resolution_loss (ip ):
@@ -132,12 +145,21 @@ def resolution_loss(ip):
132
145
133
146
134
147
def minimize_triangle_surface_loss (ip ):
135
- """Loss function that is similar to the default loss function in the
148
+ """Loss function that is similar to the distance loss function in the
136
149
`~adaptive.Learner1D`. The loss is the area spanned by the 3D
137
150
vectors of the vertices.
138
151
139
152
Works with `~adaptive.Learner2D` only.
140
153
154
+ Parameters
155
+ ----------
156
+ ip : `scipy.interpolate.LinearNDInterpolator` instance
157
+
158
+ Returns
159
+ -------
160
+ losses : numpy.ndarray
161
+ Loss per triangle in ``ip.tri``.
162
+
141
163
Examples
142
164
--------
143
165
>>> from adaptive.learner.learner2D import minimize_triangle_surface_loss
@@ -169,6 +191,19 @@ def _get_vectors(points):
169
191
170
192
171
193
def default_loss (ip ):
194
+ """Loss function that combines `deviations` and `areas` of the triangles.
195
+
196
+ Works with `~adaptive.Learner2D` only.
197
+
198
+ Parameters
199
+ ----------
200
+ ip : `scipy.interpolate.LinearNDInterpolator` instance
201
+
202
+ Returns
203
+ -------
204
+ losses : numpy.ndarray
205
+ Loss per triangle in ``ip.tri``.
206
+ """
172
207
dev = np .sum (deviations (ip ), axis = 0 )
173
208
A = areas (ip )
174
209
losses = dev * np .sqrt (A ) + 0.3 * A
@@ -186,15 +221,15 @@ def choose_point_in_triangle(triangle, max_badness):
186
221
187
222
Parameters
188
223
----------
189
- triangle : numpy array
190
- The coordinates of a triangle with shape (3, 2)
224
+ triangle : numpy.ndarray
225
+ The coordinates of a triangle with shape (3, 2).
191
226
max_badness : int
192
227
The badness at which the point is either chosen on a edge or
193
228
in the middle.
194
229
195
230
Returns
196
231
-------
197
- point : numpy array
232
+ point : numpy.ndarray
198
233
The x and y coordinate of the suggested new point.
199
234
"""
200
235
a , b , c = triangle
@@ -230,7 +265,6 @@ class Learner2D(BaseLearner):
230
265
triangle area, to determine the loss. See the notes
231
266
for more details.
232
267
233
-
234
268
Attributes
235
269
----------
236
270
data : dict
@@ -248,12 +282,6 @@ class Learner2D(BaseLearner):
248
282
triangles will be stretched along ``x``, otherwise
249
283
along ``y``.
250
284
251
- Methods
252
- -------
253
- data_combined : dict
254
- Sampled points and values so far including
255
- the unknown interpolated points in `pending_points`.
256
-
257
285
Notes
258
286
-----
259
287
Adapted from an initial implementation by Pauli Virtanen.
@@ -345,6 +373,38 @@ def bounds_are_done(self):
345
373
(p in self .pending_points or p in self ._stack ) for p in self ._bounds_points
346
374
)
347
375
376
+ def interpolated_on_grid (self , n = None ):
377
+ """Get the interpolated data on a grid.
378
+
379
+ Parameters
380
+ ----------
381
+ n : int, optional
382
+ Number of points in x and y. If None (default) this number is
383
+ evaluated by looking at the size of the smallest triangle.
384
+
385
+ Returns
386
+ -------
387
+ xs : 1D numpy.ndarray
388
+ ys : 1D numpy.ndarray
389
+ interpolated_on_grid : 2D numpy.ndarray
390
+ """
391
+ ip = self .interpolator (scaled = True )
392
+ if n is None :
393
+ # Calculate how many grid points are needed.
394
+ # factor from A=√3/4 * a² (equilateral triangle)
395
+ n = int (0.658 / sqrt (areas (ip ).min ()))
396
+ n = max (n , 10 )
397
+
398
+ # The bounds of the linspace should be (-0.5, 0.5) but because of
399
+ # numerical precision problems it could (for example) be
400
+ # (-0.5000000000000001, 0.49999999999999983), then any point at exact
401
+ # boundary would be outside of the domain. See #181.
402
+ eps = 1e-13
403
+ xs = ys = np .linspace (- 0.5 + eps , 0.5 - eps , n )
404
+ zs = ip (xs [:, None ], ys [None , :] * self .aspect_ratio ).squeeze ()
405
+ xs , ys = self ._unscale (np .vstack ([xs , ys ]).T ).T
406
+ return xs , ys , zs
407
+
348
408
def _data_in_bounds (self ):
349
409
if self .data :
350
410
points = np .array (list (self .data .keys ()))
@@ -358,7 +418,8 @@ def _data_interp(self):
358
418
if self .pending_points :
359
419
points = list (self .pending_points )
360
420
if self .bounds_are_done :
361
- values = self .ip ()(self ._scale (points ))
421
+ ip = self .interpolator (scaled = True )
422
+ values = ip (self ._scale (points ))
362
423
else :
363
424
# Without the bounds the interpolation cannot be done properly,
364
425
# so we just set everything to zero.
@@ -375,23 +436,47 @@ def _data_combined(self):
375
436
values_combined = np .vstack ([values , values_interp ])
376
437
return points_combined , values_combined
377
438
378
- def data_combined (self ):
379
- """Like `data`, however this includes the points in
380
- `pending_points` for which the values are interpolated."""
381
- # Interpolate the unfinished points
382
- points , values = self ._data_combined ()
383
- return {tuple (k ): v for k , v in zip (points , values )}
384
-
385
439
def ip (self ):
440
+ """Deprecated, use `self.interpolator(scaled=True)`"""
441
+ warnings .warn (
442
+ "`learner.ip()` is deprecated, use `learner.interpolator(scaled=True)`."
443
+ " This will be removed in v1.0." ,
444
+ DeprecationWarning ,
445
+ )
446
+ return self .interpolator (scaled = True )
447
+
448
+ def interpolator (self , * , scaled = False ):
386
449
"""A `scipy.interpolate.LinearNDInterpolator` instance
387
- containing the learner's data."""
388
- if self ._ip is None :
450
+ containing the learner's data.
451
+
452
+ Parameters
453
+ ----------
454
+ scaled : bool
455
+ Use True if all points are inside the
456
+ unit-square [(-0.5, 0.5), (-0.5, 0.5)] or False if
457
+ the data points are inside the ``learner.bounds``.
458
+
459
+ Returns
460
+ -------
461
+ interpolator : `scipy.interpolate.LinearNDInterpolator`
462
+
463
+ Examples
464
+ --------
465
+ >>> xs, ys = [np.linspace(*b, num=100) for b in learner.bounds]
466
+ >>> ip = learner.interpolator()
467
+ >>> zs = ip(xs[:, None], ys[None, :])
468
+ """
469
+ if scaled :
470
+ if self ._ip is None :
471
+ points , values = self ._data_in_bounds ()
472
+ points = self ._scale (points )
473
+ self ._ip = interpolate .LinearNDInterpolator (points , values )
474
+ return self ._ip
475
+ else :
389
476
points , values = self ._data_in_bounds ()
390
- points = self ._scale (points )
391
- self ._ip = interpolate .LinearNDInterpolator (points , values )
392
- return self ._ip
477
+ return interpolate .LinearNDInterpolator (points , values )
393
478
394
- def ip_combined (self ):
479
+ def _interpolator_combined (self ):
395
480
"""A `scipy.interpolate.LinearNDInterpolator` instance
396
481
containing the learner's data *and* interpolated data of
397
482
the `pending_points`."""
@@ -428,7 +513,7 @@ def _fill_stack(self, stack_till=1):
428
513
raise ValueError ("too few points..." )
429
514
430
515
# Interpolate
431
- ip = self .ip_combined ()
516
+ ip = self ._interpolator_combined ()
432
517
433
518
losses = self .loss_per_triangle (ip )
434
519
@@ -496,7 +581,7 @@ def ask(self, n, tell_pending=True):
496
581
def loss (self , real = True ):
497
582
if not self .bounds_are_done :
498
583
return np .inf
499
- ip = self .ip ( ) if real else self .ip_combined ()
584
+ ip = self .interpolator ( scaled = True ) if real else self ._interpolator_combined ()
500
585
losses = self .loss_per_triangle (ip )
501
586
return losses .max ()
502
587
@@ -541,21 +626,8 @@ def plot(self, n=None, tri_alpha=0):
541
626
lbrt = x [0 ], y [0 ], x [1 ], y [1 ]
542
627
543
628
if len (self .data ) >= 4 :
544
- ip = self .ip ()
545
-
546
- if n is None :
547
- # Calculate how many grid points are needed.
548
- # factor from A=√3/4 * a² (equilateral triangle)
549
- n = int (0.658 / sqrt (areas (ip ).min ()))
550
- n = max (n , 10 )
551
-
552
- # The bounds of the linspace should be (-0.5, 0.5) but because of
553
- # numerical precision problems it could (for example) be
554
- # (-0.5000000000000001, 0.49999999999999983), then any point at exact
555
- # boundary would be outside of the domain. See #181.
556
- eps = 1e-13
557
- x = y = np .linspace (- 0.5 + eps , 0.5 - eps , n )
558
- z = ip (x [:, None ], y [None , :] * self .aspect_ratio ).squeeze ()
629
+ ip = self .interpolator (scaled = True )
630
+ x , y , z = self .interpolated_on_grid (n )
559
631
560
632
if self .vdim > 1 :
561
633
ims = {
0 commit comments