11"""Calibration with Bayesian Optimization"""
22
33from dataclasses import dataclass , InitVar
4- from typing import Mapping , Optional , Any , Union , List
4+ from typing import Mapping , Optional , Any , Union , List , Tuple
55from numbers import Number
66from itertools import combinations , repeat
77
@@ -80,6 +80,7 @@ def __post_init__(
8080 self .optimizer = BayesianOptimization (
8181 f = self ._opt_func ,
8282 pbounds = self .input .bounds ,
83+ constraint = self .input .constraints ,
8384 verbose = verbose ,
8485 random_state = random_state ,
8586 allow_duplicate_points = allow_duplicate_points ,
@@ -153,13 +154,26 @@ def p_space_to_dataframe(self):
153154 function value (``Cost Function``) and whose rows are the optimizer
154155 iterations.
155156 """
156- # TODO: Handle constraints!!!
157- data = {
158- self .p_space .keys [i ]: self .p_space .params [..., i ]
159- for i in range (self .p_space .dim )
160- }
161- data ["Cost Function" ] = - self .p_space .target
162- data = pd .DataFrame .from_dict (data )
157+ # Build MultiIndex for columns
158+ index = pd .MultiIndex .from_tuples (
159+ [("Parameters" , p ) for p in self .p_space .keys ]
160+ + [("Calibration" , "Cost Function" )]
161+ )
162+
163+ # Create DataFrame and fill
164+ data = pd .DataFrame (data = None , columns = index )
165+ for i in range (self .p_space .dim ):
166+ data ["Parameters" , self .p_space .keys [i ]] = self .p_space .params [..., i ]
167+ data ["Calibration" , "Cost Function" ] = - self .p_space .target
168+
169+ # Constraints
170+ if self .p_space .constraint is not None :
171+ data ["Calibration" , "Constraints Function" ] = self .p_space .constraint_values
172+ data ["Calibration" , "Allowed" ] = self .p_space .constraint .allowed (
173+ self .p_space .constraint_values
174+ )
175+
176+ # Rename index and return
163177 data .index .rename ("Iteration" , inplace = True )
164178 return data
165179
@@ -168,7 +182,7 @@ def plot_p_space(
168182 p_space_df : Optional [pd .DataFrame ] = None ,
169183 x : Optional [str ] = None ,
170184 y : Optional [str ] = None ,
171- min_def : Optional [str ] = "Cost Function" ,
185+ min_def : Optional [Union [ str , Tuple [ str , str ]] ] = "Cost Function" ,
172186 min_fmt : str = "x" ,
173187 min_color : str = "r" ,
174188 ** plot_kwargs
@@ -206,13 +220,19 @@ def plot_p_space(
206220 if p_space_df is None :
207221 p_space_df = self .p_space_to_dataframe ()
208222
223+ if min_def is not None and not isinstance (min_def , tuple ):
224+ min_def = ("Calibration" , min_def )
225+
209226 # Plot defaults
210227 cmap = plot_kwargs .pop ("cmap" , "viridis_r" )
211228 s = plot_kwargs .pop ("s" , 40 )
212- c = plot_kwargs .pop ("c" , "Cost Function" )
229+ c = ( "Calibration" , plot_kwargs .pop ("c" , "Cost Function" ) )
213230
214231 def plot_single (x , y ):
215232 """Plot a single combination of parameters"""
233+ x = ("Parameters" , x )
234+ y = ("Parameters" , y )
235+
216236 # Plot scatter
217237 ax = p_space_df .plot (
218238 kind = "scatter" ,
@@ -231,14 +251,8 @@ def plot_single(x, y):
231251
232252 return ax
233253
234- # Ignore cost dimension
235- params = p_space_df .columns .tolist ()
236- try :
237- params .remove (c )
238- except ValueError :
239- pass
240-
241254 # Option 0: Only one parameter
255+ params = p_space_df .columns .to_list ()
242256 if len (params ) < 2 :
243257 return plot_single (x = params [0 ], y = repeat (0 ))
244258
0 commit comments