2121import unittest
2222from unittest .mock import patch , MagicMock
2323
24+ import numpy as np
2425import numpy .testing as npt
2526import pandas as pd
27+ from bayes_opt import BayesianOptimization , UtilityFunction , Events
28+ from scipy .optimize import NonlinearConstraint
2629
2730from climada .util .calibrate import Input , BayesianOptimizer , BayesianOptimizerController
31+ from climada .util .calibrate .bayesian_optimizer import (
32+ Improvement ,
33+ StopEarly ,
34+ BayesianOptimizerOutput ,
35+ )
2836
2937from .test_base import hazard , exposure
3038
@@ -44,12 +52,26 @@ def input():
4452class TestBayesianOptimizerController (unittest .TestCase ):
4553 """Tests for the controller of the BayesianOptimizer"""
4654
55+ def setUp (self ):
56+ """Create a optimization instance"""
57+ self .bayes_opt = BayesianOptimization (
58+ f = lambda x : - (x ** 2 ),
59+ pbounds = {"x" : (- 10 , 10 )},
60+ constraint = NonlinearConstraint (fun = lambda x : x , lb = - 0.5 , ub = np .inf ),
61+ verbose = 0 ,
62+ allow_duplicate_points = True ,
63+ )
64+
65+ def _make_step (self , x , controller ):
66+ self .bayes_opt .probe ({"x" : x }, lazy = False )
67+ controller .update (Events .OPTIMIZATION_STEP , self .bayes_opt )
68+
4769 def test_kappa_decay (self ):
4870 """Check correct values for kappa_decay"""
49- contr = BayesianOptimizerController (kappa = 3 , kappa_min = 3 , n_iter = 10 )
71+ contr = BayesianOptimizerController (0 , kappa = 3 , kappa_min = 3 , n_iter = 10 )
5072 self .assertAlmostEqual (contr .kappa_decay , 1.0 )
5173
52- contr = BayesianOptimizerController (kappa = 3 , kappa_min = 1 , n_iter = 10 )
74+ contr = BayesianOptimizerController (0 , kappa = 3 , kappa_min = 1 , n_iter = 10 )
5375 self .assertAlmostEqual (contr .kappa * (contr .kappa_decay ** 10 ), 1.0 )
5476
5577 def test_from_input (self ):
@@ -62,6 +84,142 @@ def test_from_input(self):
6284 self .assertEqual (contr .init_points , 3 ** 2 )
6385 self .assertEqual (contr .n_iter , 3 ** 2 )
6486
87+ def test_optimizer_params (self ):
88+ """Test BayesianOptimizerController.optimizer_params"""
89+ contr = BayesianOptimizerController (
90+ 1 , 2 , kappa = 3 , utility_func_kwargs = {"xi" : 1.11 , "kind" : "ei" }
91+ )
92+ result = contr .optimizer_params ()
93+
94+ self .assertDictContainsSubset (
95+ {
96+ "init_points" : 1 ,
97+ "n_iter" : 2 ,
98+ },
99+ result ,
100+ )
101+ util_func = result ["acquisition_function" ]
102+ self .assertEqual (util_func .kappa , 3 )
103+ self .assertEqual (util_func ._kappa_decay , contr ._calc_kappa_decay ())
104+ self .assertEqual (util_func .xi , 1.11 )
105+ self .assertEqual (util_func .kind , "ei" )
106+
107+ def test_update_step (self ):
108+ """Test the update for STEP events"""
109+ contr = BayesianOptimizerController (3 , 2 )
110+
111+ # Regular step
112+ self ._make_step (3.0 , contr )
113+ self .assertEqual (contr .steps , 1 )
114+ best = Improvement (
115+ iteration = 0 , sample = 0 , random = True , target = - 9.0 , improvement = np .inf
116+ )
117+ self .assertEqual (len (contr ._improvements ), 1 )
118+ self .assertTupleEqual (contr ._improvements [- 1 ], best )
119+
120+ # Step that has no effect due to constraints
121+ self ._make_step (- 2.0 , contr )
122+ self .assertEqual (contr .steps , 2 )
123+ self .assertEqual (len (contr ._improvements ), 1 )
124+ self .assertTupleEqual (contr ._improvements [- 1 ], best )
125+
126+ # Step that is not new max
127+ self ._make_step (4.0 , contr )
128+ self .assertEqual (contr .steps , 3 )
129+ self .assertEqual (len (contr ._improvements ), 1 )
130+ self .assertTupleEqual (contr ._improvements [- 1 ], best )
131+
132+ # Two minimal increments, therefore we should see a StopEarly
133+ self ._make_step (2.999 , contr )
134+ self .assertEqual (contr .steps , 4 )
135+ self .assertEqual (len (contr ._improvements ), 2 )
136+
137+ with self .assertRaises (StopEarly ):
138+ self ._make_step (2.998 , contr )
139+ self .assertEqual (contr .steps , 5 )
140+ self .assertEqual (len (contr ._improvements ), 3 )
141+
142+ def test_update_end (self ):
143+ """Test the update for END events"""
144+ contr = BayesianOptimizerController (1 , 1 )
145+
146+ # One step with improvement, then stop
147+ self ._make_step (3.0 , contr )
148+ contr .update (Events .OPTIMIZATION_END , self .bayes_opt )
149+ self .assertEqual (contr ._last_it_improved , 0 )
150+ self .assertEqual (contr ._last_it_end , 1 )
151+
152+ # One step with no more improvement
153+ self ._make_step (4.0 , contr )
154+ with self .assertRaises (StopIteration ):
155+ contr .update (Events .OPTIMIZATION_END , self .bayes_opt )
156+
157+ def test_improvements (self ):
158+ """Test ouput of BayesianOptimizerController.improvements"""
159+ contr = BayesianOptimizerController (1 , 1 )
160+ self ._make_step (3.0 , contr )
161+ self ._make_step (2.0 , contr )
162+ contr .update (Events .OPTIMIZATION_END , self .bayes_opt )
163+ self ._make_step (2.5 , contr ) # Not better
164+ self ._make_step (1.0 , contr )
165+ contr .update (Events .OPTIMIZATION_END , self .bayes_opt )
166+ self ._make_step (- 0.9 , contr ) # Constrained
167+
168+ df = contr .improvements ()
169+ pd .testing .assert_frame_equal (
170+ df ,
171+ pd .DataFrame .from_dict (
172+ data = {
173+ "iteration" : [0 , 0 , 1 ],
174+ "sample" : [0 , 1 , 3 ],
175+ "random" : [True , False , False ],
176+ "target" : [- 9.0 , - 4.0 , - 1.0 ],
177+ "improvement" : [np .inf , 9.0 / 4.0 - 1 , 3.0 ],
178+ }
179+ ).set_index ("sample" ),
180+ )
181+
182+
183+ class TestBayesianOptimizerOutput (unittest .TestCase ):
184+ """Tests for the output class of BayesianOptimizer"""
185+
186+ def test_p_space_to_dataframe (self ):
187+ """"""
188+ bayes_opt = BayesianOptimization (
189+ f = lambda x : - (x ** 2 ),
190+ pbounds = {"x" : (- 10 , 10 )},
191+ constraint = NonlinearConstraint (fun = lambda x : x , lb = - 0.5 , ub = np .inf ),
192+ verbose = 0 ,
193+ allow_duplicate_points = True ,
194+ )
195+ bayes_opt .probe ({"x" : 2.0 }, lazy = False )
196+ bayes_opt .probe ({"x" : 1.0 }, lazy = False )
197+ bayes_opt .probe ({"x" : - 0.9 }, lazy = False )
198+
199+ output = BayesianOptimizerOutput (
200+ params = bayes_opt .max ["params" ],
201+ target = bayes_opt .max ["target" ],
202+ p_space = bayes_opt .space ,
203+ )
204+ self .assertDictEqual (output .params , {"x" : 1.0 })
205+ self .assertEqual (output .target , - 1.0 )
206+
207+ idx = pd .MultiIndex .from_tuples (
208+ [
209+ ("Parameters" , "x" ),
210+ ("Calibration" , "Cost Function" ),
211+ ("Calibration" , "Constraints Function" ),
212+ ("Calibration" , "Allowed" ),
213+ ]
214+ )
215+ df = pd .DataFrame (data = None , columns = idx )
216+ df ["Parameters" , "x" ] = [2.0 , 1.0 , - 0.9 ]
217+ df ["Calibration" , "Cost Function" ] = [4.0 , 1.0 , 0.9 ** 2 ]
218+ df ["Calibration" , "Constraints Function" ] = df ["Parameters" , "x" ]
219+ df ["Calibration" , "Allowed" ] = [True , True , False ]
220+ df .index .rename ("Iteration" , inplace = True )
221+ pd .testing .assert_frame_equal (output .p_space_to_dataframe (), df )
222+
65223
66224class TestBayesianOptimizer (unittest .TestCase ):
67225 """Tests for the optimizer based on bayes_opt.BayesianOptimization"""
@@ -121,4 +279,10 @@ def test_target_func(self, _):
121279# Execute Tests
122280if __name__ == "__main__" :
123281 TESTS = unittest .TestLoader ().loadTestsFromTestCase (TestBayesianOptimizer )
282+ TESTS .addTests (
283+ unittest .TestLoader ().loadTestsFromTestCase (TestBayesianOptimizerOutput )
284+ )
285+ TESTS .addTests (
286+ unittest .TestLoader ().loadTestsFromTestCase (TestBayesianOptimizerController )
287+ )
124288 unittest .TextTestRunner (verbosity = 2 ).run (TESTS )
0 commit comments