15
15
import pytest
16
16
import scipy .spatial
17
17
18
- from ..learner import (AverageLearner , BalancingLearner , DataSaver ,
18
+ import adaptive
19
+ from adaptive .learner import (AverageLearner , BalancingLearner , DataSaver ,
19
20
IntegratorLearner , Learner1D , Learner2D , LearnerND )
20
- from . .runner import simple
21
+ from adaptive .runner import simple
21
22
22
23
23
24
try :
24
25
import skopt
25
- from . .learner import SKOptLearner
26
+ from adaptive .learner import SKOptLearner
26
27
except ModuleNotFoundError :
27
28
SKOptLearner = None
28
29
29
30
31
+ LOSS_FUNCTIONS = {
32
+ Learner1D : ('loss_per_interval' , (
33
+ adaptive .learner .learner1D .default_loss ,
34
+ adaptive .learner .learner1D .uniform_loss ,
35
+ adaptive .learner .learner1D .curvature_loss_function (),
36
+ )),
37
+ Learner2D : ('loss_per_triangle' , (
38
+ adaptive .learner .learner2D .default_loss ,
39
+ adaptive .learner .learner2D .uniform_loss ,
40
+ adaptive .learner .learner2D .minimize_triangle_surface_loss ,
41
+ adaptive .learner .learner2D .resolution_loss_function (),
42
+ )),
43
+ LearnerND : ('loss_per_simplex' , (
44
+ adaptive .learner .learnerND .default_loss ,
45
+ adaptive .learner .learnerND .std_loss ,
46
+ adaptive .learner .learnerND .uniform_loss ,
47
+ )),
48
+ }
49
+
50
+
30
51
def generate_random_parametrization (f ):
31
52
"""Return a realization of 'f' with parameters bound to random values.
32
53
@@ -74,7 +95,6 @@ def maybe_skip(learner):
74
95
# All parameters except the first must be annotated with a callable that
75
96
# returns a random value for that parameter.
76
97
77
-
78
98
@learn_with (Learner1D , bounds = (- 1 , 1 ))
79
99
def quadratic (x , m : uniform (0 , 10 ), b : uniform (0 , 1 )):
80
100
return m * x ** 2 + b
@@ -108,20 +128,33 @@ def gaussian(n):
108
128
109
129
# Decorators for tests.
110
130
111
- def run_with (* learner_types ):
131
+
132
+ # Create a sequence of learner parameters by adding all
133
+ # possible loss functions to an existing parameter set.
134
+ def add_loss_to_params (learner_type , existing_params ):
135
+ if learner_type not in LOSS_FUNCTIONS :
136
+ return [existing_params ]
137
+ loss_param , loss_functions = LOSS_FUNCTIONS [learner_type ]
138
+ loss_params = [{loss_param : f } for f in loss_functions ]
139
+ return [dict (** existing_params , ** lp ) for lp in loss_params ]
140
+
141
+
142
+ def run_with (* learner_types , with_all_loss_functions = True ):
112
143
pars = []
113
144
for l in learner_types :
114
145
has_marker = isinstance (l , tuple )
115
146
if has_marker :
116
147
marker , l = l
117
148
for f , k in learner_function_combos [l ]:
118
- # Check if learner was marked with our `xfail` decorator
119
- # XXX: doesn't work when feeding kwargs to xfail.
120
- if has_marker :
121
- pars .append (pytest .param (l , f , dict (k ),
122
- marks = [marker ]))
123
- else :
124
- pars .append ((l , f , dict (k )))
149
+ ks = add_loss_to_params (l , k ) if with_all_loss_functions else [k ]
150
+ for k in ks :
151
+ # Check if learner was marked with our `xfail` decorator
152
+ # XXX: doesn't work when feeding kwargs to xfail.
153
+ if has_marker :
154
+ pars .append (pytest .param (l , f , dict (k ),
155
+ marks = [marker ]))
156
+ else :
157
+ pars .append ((l , f , dict (k )))
125
158
return pytest .mark .parametrize ('learner_type, f, learner_kwargs' , pars )
126
159
127
160
@@ -196,22 +229,19 @@ def f(x):
196
229
simple (learner , goal = lambda l : l .npoints > 10 )
197
230
198
231
199
- @run_with (xfail ( Learner1D ) , Learner2D , LearnerND )
232
+ @run_with (Learner1D , Learner2D , LearnerND )
200
233
def test_adding_existing_data_is_idempotent (learner_type , f , learner_kwargs ):
201
234
"""Adding already existing data is an idempotent operation.
202
235
203
236
Either it is idempotent, or it is an error.
204
237
This is the only sane behaviour.
205
-
206
- This test will fail for the Learner1D because the losses are normalized by
207
- _scale which is updated after every point. After one iteration of adding
208
- points, the _scale could be different from what it was when calculating
209
- the losses of the intervals. Readding the points a second time means
210
- that the losses are now all normalized by the correct _scale.
211
238
"""
212
239
f = generate_random_parametrization (f )
213
240
learner = learner_type (f , ** learner_kwargs )
214
241
control = learner_type (f , ** learner_kwargs )
242
+ if learner_type is Learner1D :
243
+ learner ._recompute_losses_factor = 1
244
+ control ._recompute_losses_factor = 1
215
245
216
246
N = random .randint (10 , 30 )
217
247
control .ask (N )
@@ -265,14 +295,11 @@ def test_adding_non_chosen_data(learner_type, f, learner_kwargs):
265
295
assert set (pls ) == set (cpls )
266
296
267
297
268
- @run_with (xfail ( Learner1D ) , xfail (Learner2D ), xfail (LearnerND ), AverageLearner )
298
+ @run_with (Learner1D , xfail (Learner2D ), xfail (LearnerND ), AverageLearner )
269
299
def test_point_adding_order_is_irrelevant (learner_type , f , learner_kwargs ):
270
300
"""The order of calls to 'tell' between calls to 'ask'
271
301
is arbitrary.
272
302
273
- This test will fail for the Learner1D for the same reason as described in
274
- the doc-string in `test_adding_existing_data_is_idempotent`.
275
-
276
303
This test will fail for the Learner2D because
277
304
`interpolate.interpnd.estimate_gradients_2d_global` will give different
278
305
outputs based on the order of the triangles and values in
@@ -282,6 +309,10 @@ def test_point_adding_order_is_irrelevant(learner_type, f, learner_kwargs):
282
309
learner = learner_type (f , ** learner_kwargs )
283
310
control = learner_type (f , ** learner_kwargs )
284
311
312
+ if learner_type is Learner1D :
313
+ learner ._recompute_losses_factor = 1
314
+ control ._recompute_losses_factor = 1
315
+
285
316
N = random .randint (10 , 30 )
286
317
control .ask (N )
287
318
xs , _ = learner .ask (N )
@@ -353,7 +384,7 @@ def test_learner_performance_is_invariant_under_scaling(learner_type, f, learner
353
384
learner = learner_type (lambda x : yscale * f (np .array (x ) / xscale ),
354
385
** l_kwargs )
355
386
356
- npoints = random .randrange (1000 , 2000 )
387
+ npoints = random .randrange (300 , 500 )
357
388
358
389
for n in range (npoints ):
359
390
cxs , _ = control .ask (1 )
@@ -366,10 +397,11 @@ def test_learner_performance_is_invariant_under_scaling(learner_type, f, learner
366
397
assert np .allclose (xs_unscaled , cxs )
367
398
368
399
# Check if the losses are close
369
- assert abs (learner .loss () - control .loss ()) / learner . loss () < 1e-11
400
+ assert math . isclose (learner .loss (), control .loss (), rel_tol = 1e-10 )
370
401
371
402
372
- @run_with (Learner1D , Learner2D , LearnerND , AverageLearner )
403
+ @run_with (Learner1D , Learner2D , LearnerND , AverageLearner ,
404
+ with_all_loss_functions = False )
373
405
def test_balancing_learner (learner_type , f , learner_kwargs ):
374
406
"""Test if the BalancingLearner works with the different types of learners."""
375
407
learners = [learner_type (generate_random_parametrization (f ), ** learner_kwargs )
@@ -403,19 +435,22 @@ def test_balancing_learner(learner_type, f, learner_kwargs):
403
435
404
436
405
437
@run_with (Learner1D , Learner2D , LearnerND , AverageLearner ,
406
- maybe_skip (SKOptLearner ), IntegratorLearner )
438
+ maybe_skip (SKOptLearner ), IntegratorLearner ,
439
+ with_all_loss_functions = False )
407
440
def test_saving (learner_type , f , learner_kwargs ):
408
441
f = generate_random_parametrization (f )
409
442
learner = learner_type (f , ** learner_kwargs )
410
443
control = learner_type (f , ** learner_kwargs )
444
+ if learner_type is Learner1D :
445
+ learner ._recompute_losses_factor = 1
446
+ control ._recompute_losses_factor = 1
411
447
simple (learner , lambda l : l .npoints > 100 )
412
448
fd , path = tempfile .mkstemp ()
413
449
try :
414
450
learner .save (path )
415
451
control .load (path )
416
- if learner_type is not Learner1D :
417
- # Because different scales result in differnt losses
418
- np .testing .assert_almost_equal (learner .loss (), control .loss ())
452
+
453
+ np .testing .assert_almost_equal (learner .loss (), control .loss ())
419
454
420
455
# Try if the control is runnable
421
456
simple (control , lambda l : l .npoints > 200 )
@@ -424,24 +459,29 @@ def test_saving(learner_type, f, learner_kwargs):
424
459
425
460
426
461
@run_with (Learner1D , Learner2D , LearnerND , AverageLearner ,
427
- maybe_skip (SKOptLearner ), IntegratorLearner )
462
+ maybe_skip (SKOptLearner ), IntegratorLearner ,
463
+ with_all_loss_functions = False )
428
464
def test_saving_of_balancing_learner (learner_type , f , learner_kwargs ):
429
465
f = generate_random_parametrization (f )
430
466
learner = BalancingLearner ([learner_type (f , ** learner_kwargs )])
431
467
control = BalancingLearner ([learner_type (f , ** learner_kwargs )])
432
468
469
+ if learner_type is Learner1D :
470
+ for l , c in zip (learner .learners , control .learners ):
471
+ l ._recompute_losses_factor = 1
472
+ c ._recompute_losses_factor = 1
473
+
433
474
simple (learner , lambda l : l .learners [0 ].npoints > 100 )
434
475
folder = tempfile .mkdtemp ()
435
476
436
477
def fname (learner ):
437
478
return folder + 'test'
438
479
439
480
try :
440
- learner .save (fname )
441
- control .load (fname )
442
- if learner_type is not Learner1D :
443
- # Because different scales result in differnt losses
444
- np .testing .assert_almost_equal (learner .loss (), control .loss ())
481
+ learner .save (fname = fname )
482
+ control .load (fname = fname )
483
+
484
+ np .testing .assert_almost_equal (learner .loss (), control .loss ())
445
485
446
486
# Try if the control is runnable
447
487
simple (control , lambda l : l .learners [0 ].npoints > 200 )
@@ -450,21 +490,27 @@ def fname(learner):
450
490
451
491
452
492
@run_with (Learner1D , Learner2D , LearnerND , AverageLearner ,
453
- maybe_skip (SKOptLearner ), IntegratorLearner )
493
+ maybe_skip (SKOptLearner ), IntegratorLearner ,
494
+ with_all_loss_functions = False )
454
495
def test_saving_with_datasaver (learner_type , f , learner_kwargs ):
455
496
f = generate_random_parametrization (f )
456
497
g = lambda x : {'y' : f (x ), 't' : random .random ()}
457
498
arg_picker = operator .itemgetter ('y' )
458
499
learner = DataSaver (learner_type (g , ** learner_kwargs ), arg_picker )
459
500
control = DataSaver (learner_type (g , ** learner_kwargs ), arg_picker )
501
+
502
+ if learner_type is Learner1D :
503
+ learner .learner ._recompute_losses_factor = 1
504
+ control .learner ._recompute_losses_factor = 1
505
+
460
506
simple (learner , lambda l : l .npoints > 100 )
461
507
fd , path = tempfile .mkstemp ()
462
508
try :
463
509
learner .save (path )
464
510
control .load (path )
465
- if learner_type is not Learner1D :
466
- # Because different scales result in differnt losses
467
- np . testing . assert_almost_equal ( learner . loss (), control . loss ())
511
+
512
+ np . testing . assert_almost_equal ( learner . loss (), control . loss ())
513
+
468
514
assert learner .extra_data == control .extra_data
469
515
470
516
# Try if the control is runnable
0 commit comments