Repository: wangronin/Bayesian-Optimization

Issue:
Eval_type 'list' causing errors in mixed-space example
**Describe the bug**
When setting the eval_type to 'list' in the following code, it throws an error, while it works properly with 'dict'.

**To Reproduce**
`def test_mix_space(eval_type):
    dim_r = 2  # dimension of the real values
    def obj_fun(x):
        x_r = np.array([x['continuous_%d'%i] for i in range(dim_r)])
        x_i = x['ordinal']
        x_d = x['nominal']
        _ = 0 if x_d == 'OK' else 1
        return np.sum(x_r ** 2) + abs(x_i - 10) / 123. + _ * 2

    search_space = ContinuousSpace([-5, 5], var_name='continuous') * dim_r + \
        OrdinalSpace([5, 15], var_name='ordinal') + \
        NominalSpace(['OK', 'A', 'B', 'C', 'D', 'E', 'F', 'G'], var_name='nominal')

    model = RandomForest(levels=search_space.levels)

    opt = ParallelBO(
        search_space=search_space, 
        obj_fun=obj_fun, 
        model=model, 
        max_FEs=6, 
        DoE_size=3,    # the initial DoE size
        eval_type=eval_type,
        acquisition_fun='MGFI',
        acquisition_par={'t' : 2},
        n_job=3,       # number of processes
        n_point=3,     # number of the candidate solution proposed in each iteration
        verbose=True   # turn this off, if you prefer no output
    )
    xopt, fopt, stop_dict = opt.run()

    print('xopt: {}'.format(xopt))
    print('fopt: {}'.format(fopt))
    print('stop criteria: {}'.format(stop_dict))`


Discussion:

Patch:
diff --git a/bayes_optim/base.py b/bayes_optim/base.py
index 91c6d97..94c0436 100644
--- a/bayes_optim/base.py
+++ b/bayes_optim/base.py
@@ -608,7 +608,10 @@ class baseBO(ABC):
             func_vals = self.parallel_obj_fun(X)
         else:
             if self.n_job > 1: # or by ourselves..
-                func_vals = Parallel(n_jobs=self.n_job)(delayed(self.obj_fun)(x) for x in X)
+                if self._eval_type == 'dataframe':
+                    func_vals = Parallel(n_jobs=self.n_job)(delayed(self.obj_fun)(x) for _, x in X.iterrows())
+                else:
+                    func_vals = Parallel(n_jobs=self.n_job)(delayed(self.obj_fun)(x) for x in X)
             else:              # or sequential execution
                 func_vals = [self.obj_fun(x) for x in X]
 


Test patch:
diff --git a/unittest/test_BO.py b/unittest/test_BO.py
index 88bdfd5..c5b12b5 100644
--- a/unittest/test_BO.py
+++ b/unittest/test_BO.py
@@ -124,13 +124,23 @@ def test_continuous():
 @pytest.mark.parametrize("eval_type", ['list', 'dict', 'dataframe'])  # type: ignore
 def test_mix_space(eval_type):
     dim_r = 2  # dimension of the real values
-    def obj_fun(x):
-        x_r = np.array([x['continuous_%d'%i] for i in range(dim_r)])
-        x_i = x['ordinal']
-        x_d = x['nominal']
-        _ = 0 if x_d == 'OK' else 1
-        return np.sum(x_r ** 2) + abs(x_i - 10) / 123. + _ * 2
-
+    if eval_type == 'dict' or eval_type == 'dataframe':
+        def obj_fun(x):
+            #Do explicit type-casting since dataframe rows might be strings otherwise
+            x_r = np.array([float(x['continuous_%d'%i]) for i in range(dim_r)])
+            x_i = int(x['ordinal'])
+            x_d = x['nominal']
+            _ = 0 if x_d == 'OK' else 1
+            return np.sum(x_r ** 2) + abs(x_i - 10) / 123. + _ * 2
+    elif eval_type == 'list':
+        def obj_fun(x):
+            x_r = np.array([x[i] for i in range(dim_r)])
+            x_i = x[-2]
+            x_d = x[-1]
+            _ = 0 if x_d == 'OK' else 1
+            return np.sum(x_r ** 2) + abs(x_i - 10) / 123. + _ * 2
+    else:
+        raise NotImplemented
     search_space = ContinuousSpace([-5, 5], var_name='continuous') * dim_r + \
         OrdinalSpace([5, 15], var_name='ordinal') + \
         NominalSpace(['OK', 'A', 'B', 'C', 'D', 'E', 'F', 'G'], var_name='nominal')