def maximize_expanding_volume_L(self, gp_params):
        """
        Expanding volume following L ~ MaxIter

        Input parameters
        ----------

        gp_params: parameter for Gaussian Process

        Returns
        -------
        x: recommented point for evaluation
        """

        if self.acq['name'] == 'random':
            x_max = [
                np.random.uniform(x[0], x[1], size=1) for x in self.bounds
            ]
            x_max = np.asarray(x_max)
            x_max = x_max.T
            self.X_original = np.vstack((self.X_original, x_max))
            # evaluate Y using original X
            self.Y_original = np.append(self.Y_original, self.f(x_max))

            # update Y after change Y_original
            self.Y = (self.Y_original - np.mean(self.Y_original)) / (
                np.max(self.Y_original) - np.min(self.Y_original))

            self.time_opt = np.hstack((self.time_opt, 0))
            return

        # init a new Gaussian Process
        self.gp = PradaGaussianProcess(gp_params)

        # scale the data before updating the GP
        # convert it to scaleX
        self.max_min_gap = self.bounds[:, 1] - self.bounds[:, 0]
        temp = np.divide((self.X_original - self.bounds[:, 0]),
                         self.max_min_gap)
        self.X = np.asarray(temp)

        ur = unique_rows(self.X)
        self.gp.fit(self.X[ur], self.Y[ur])

        # Set acquisition function
        start_opt = time.time()

        y_max = self.Y.max()

        #self.L=self.estimate_L(self.scalebounds)
        # select the acquisition function

        self.acq_func = AcquisitionFunction(self.acq)

        # consider the expansion step

        # backup the previous bounds
        self.bounds_bk = self.bounds.copy()
        self.scalebounds_bk = self.scalebounds.copy()

        # the region considered is computed as follows: NewVol~OldVol*T/t
        # alternatively, we compute the radius NewL~Oldl*pow(T/t,1/d)
        new_radius = self.l_radius * np.power(
            self.MaxIter / len(self.Y_original), 1.0 / self.dim)
        # extra proportion
        extra_proportion = new_radius * 1.0 / self.l_radius

        #extra_radius=(new_radius-self.l_radius)/2

        if extra_proportion < 1:
            extra_proportion = 1

        max_bounds = self.bounds.copy()

        # expand half to the lower bound and half to the upper bound
        max_bounds[:, 0] = max_bounds[:, 0] - self.max_min_gap * (
            extra_proportion - 1) * 0.5
        max_bounds[:, 1] = max_bounds[:, 1] + self.max_min_gap * (
            extra_proportion - 1) * 0.5

        # make sure it is within the limit
        if not (self.b_limit_lower is None):
            temp_max_bounds_lower = [
                np.maximum(max_bounds[idx, 0], self.b_limit_lower[idx])
                for idx in xrange(self.dim)
            ]
            max_bounds[:, 0] = temp_max_bounds_lower

        if not (self.b_limit_upper is None):
            temp_max_bounds_upper = [
                np.minimum(max_bounds[idx, 1], self.b_limit_upper[idx])
                for idx in xrange(self.dim)
            ]
            max_bounds[:, 1] = temp_max_bounds_upper

        temp = [(max_bounds[d, :] - self.bounds_bk[d, 0]) * 1.0 /
                self.max_min_gap[d] for d in xrange(self.dim)]
        self.scalebounds = np.asarray(temp)

        # perform standard BO on the new bound (scaled)
        x_max_scale = acq_max(ac=self.acq_func.acq_kind,
                              gp=self.gp,
                              y_max=y_max,
                              bounds=self.scalebounds,
                              opt_toolbox=self.opt_toolbox)

        #val_acq=self.acq_func.acq_kind(x_max_scale,self.gp,y_max)
        #print "alpha[x_max]={:.5f}".format(np.ravel(val_acq)[0])

        # record the optimization time
        finished_opt = time.time()
        elapse_opt = finished_opt - start_opt
        self.time_opt = np.hstack((self.time_opt, elapse_opt))

        # Test if x_max is repeated, if it is, draw another one at random
        if np.any((self.X - x_max_scale).sum(axis=1) == 0):
            x_max_scale = np.random.uniform(self.scalebounds[:, 0],
                                            self.scalebounds[:, 1],
                                            size=self.scalebounds.shape[0])

        # check if the estimated data point is in the old bound or new for cropping
        IsCropping = 0
        if IsCropping == 1:
            flagOutside = 0
            for d in xrange(self.dim):
                if x_max_scale[d] > self.scalebounds_bk[d, 1] or x_max_scale[
                        d] < self.scalebounds_bk[d, 0]:  #outside the old bound
                    flagOutside = 1
                    self.scalebounds[d,
                                     0] = np.minimum(x_max_scale[d],
                                                     self.scalebounds_bk[d, 0])
                    self.scalebounds[d,
                                     1] = np.maximum(x_max_scale[d],
                                                     self.scalebounds_bk[d, 1])

                    # now the scalebounds is no longer 0-1

            if flagOutside == 0:  # not outside the old bound
                self.scalebounds = self.scalebounds_bk
                self.bounds = self.bounds_bk.copy()
            else:  # inside the old bound => recompute bound
                temp = [
                    self.scalebounds[d, :] * self.max_min_gap[d] +
                    self.bounds_bk[d, 0] for d in xrange(self.dim)
                ]
                if self.dim > 1:
                    self.bounds = np.reshape(temp, (self.dim, 2))
                else:
                    self.bounds = np.array(temp)
        else:
            temp = [
                self.scalebounds[d, :] * self.max_min_gap[d] +
                self.bounds_bk[d, 0] for d in xrange(self.dim)
            ]
            if self.dim > 1:
                self.bounds = np.reshape(temp, (self.dim, 2))
            else:
                self.bounds = np.array(temp)

        # compute X in original scale
        temp_X_new_original = x_max_scale * self.max_min_gap + self.bounds_bk[:,
                                                                              0]
        self.X_original = np.vstack((self.X_original, temp_X_new_original))

        # clone the self.X for updating GP
        self.max_min_gap = self.bounds[:, 1] - self.bounds[:, 0]
        temp = np.divide((self.X_original - self.bounds[:, 0]),
                         self.max_min_gap)
        self.X = np.asarray(temp)

        scalebounds = np.array([np.zeros(self.dim), np.ones(self.dim)])
        self.scalebounds = scalebounds.T
        # evaluate Y using original X

        self.Y_original = np.append(self.Y_original,
                                    self.f(temp_X_new_original))

        # update Y after change Y_original
        self.Y = (self.Y_original - np.mean(self.Y_original)) / (
            np.max(self.Y_original) - np.min(self.Y_original))

        # for plotting
        self.gp = PradaGaussianProcess(gp_params)
        ur = unique_rows(self.X)
        self.gp.fit(self.X[ur], self.Y[ur])

        # update volume and radius
        #self.vol=prod(self.max_min_gap)
        #self.l_radius=np.power(self.vol,1/self.dim)
        self.l_radius = np.exp(1.0 * np.sum(np.log(self.max_min_gap)) /
                               self.dim)
    def maximize_volume_doubling(self, gp_params):
        """
        Volume Doubling, double the volume (e.g., gamma=2) after every 3d evaluations

        Input parameters
        ----------
        gp_params: parameter for Gaussian Process
        Returns
        -------
        x: recommented point for evaluation
        """

        if self.acq['name'] == 'random':
            x_max = [
                np.random.uniform(x[0], x[1], size=1) for x in self.bounds
            ]
            x_max = np.asarray(x_max)
            x_max = x_max.T
            self.X_original = np.vstack((self.X_original, x_max))
            # evaluate Y using original X
            self.Y_original = np.append(self.Y_original, self.f(x_max))

            # update Y after change Y_original
            self.Y = (self.Y_original - np.mean(self.Y_original)) / (
                np.max(self.Y_original) - np.min(self.Y_original))

            self.time_opt = np.hstack((self.time_opt, 0))
            return

        # init a new Gaussian Process
        self.gp = PradaGaussianProcess(gp_params)

        # scale the data before updating the GP
        # convert it to scaleX
        self.max_min_gap = self.bounds[:, 1] - self.bounds[:, 0]
        temp = np.divide((self.X_original - self.bounds[:, 0]),
                         self.max_min_gap)
        self.X = np.asarray(temp)

        ur = unique_rows(self.X)
        self.gp.fit(self.X[ur], self.Y[ur])

        # Find unique rows of X to avoid GP from breaking

        # Set acquisition function
        start_opt = time.time()

        y_max = self.Y.max()

        # select the acquisition function
        self.acq_func = AcquisitionFunction(self.acq)

        self.scalebounds_bk = self.scalebounds.copy()
        self.bounds_bk = self.bounds

        # consider the expansion step after 3 iterations

        if (len(self.Y) % 3) == 0:
            new_radius = 2.0 * self.l_radius
            extra_radius = (new_radius - self.l_radius) / 2

            max_bounds = self.bounds.copy()
            max_bounds[:, 0] = max_bounds[:, 0] - extra_radius
            max_bounds[:, 1] = max_bounds[:, 1] + extra_radius

            # make sure it is within the limit
            if not (self.b_limit_lower is None):
                temp_max_bounds_lower = [
                    np.maximum(max_bounds[idx, 0], self.b_limit_lower[idx])
                    for idx in xrange(self.dim)
                ]
                max_bounds[:, 0] = temp_max_bounds_lower

            if not (self.b_limit_upper is None):
                temp_max_bounds_upper = [
                    np.minimum(max_bounds[idx, 1], self.b_limit_upper[idx])
                    for idx in xrange(self.dim)
                ]
                max_bounds[:, 1] = temp_max_bounds_upper

            self.bounds = np.asarray(max_bounds).copy()

            temp = [(max_bounds[d, :] - self.bounds_bk[d, 0]) * 1.0 /
                    self.max_min_gap[d] for d in xrange(self.dim)]
            self.scalebounds = np.asarray(temp)

        # perform standard BO on the new bound (scaled)
        x_max_scale = acq_max(ac=self.acq_func.acq_kind,
                              gp=self.gp,
                              y_max=y_max,
                              bounds=self.scalebounds,
                              opt_toolbox=self.opt_toolbox)

        #val_acq=self.acq_func.acq_kind(x_max_scale,self.gp,y_max)
        #print "alpha[x_max]={:.5f}".format(np.ravel(val_acq)[0])

        # record the optimization time
        finished_opt = time.time()
        elapse_opt = finished_opt - start_opt
        self.time_opt = np.hstack((self.time_opt, elapse_opt))

        # Test if x_max is repeated, if it is, draw another one at random
        if np.any((self.X - x_max_scale).sum(axis=1) == 0):
            x_max_scale = np.random.uniform(self.scalebounds[:, 0],
                                            self.scalebounds[:, 1],
                                            size=self.scalebounds.shape[0])

        # compute X in original scale
        temp_X_new_original = x_max_scale * self.max_min_gap + self.bounds_bk[:,
                                                                              0]
        self.X_original = np.vstack((self.X_original, temp_X_new_original))

        # clone the self.X for updating GP
        self.max_min_gap = self.bounds[:, 1] - self.bounds[:, 0]
        temp = np.divide((self.X_original - self.bounds[:, 0]),
                         self.max_min_gap)
        self.X = np.asarray(temp)

        scalebounds = np.array([np.zeros(self.dim), np.ones(self.dim)])
        self.scalebounds = scalebounds.T
        # evaluate Y using original X

        self.Y_original = np.append(self.Y_original,
                                    self.f(temp_X_new_original))

        # update Y after change Y_original
        self.Y = (self.Y_original - np.mean(self.Y_original)) / (
            np.max(self.Y_original) - np.min(self.Y_original))

        # for plotting
        self.gp = PradaGaussianProcess(gp_params)
        ur = unique_rows(self.X)

        try:
            self.gp.fit(self.X[ur], self.Y[ur])
        except:
            print "bug"

        # update volume and radius
        #self.vol=prod(self.max_min_gap)
        #self.l_radius=np.power(self.vol,1/self.dim)
        self.l_radius = np.exp(1.0 * np.sum(np.log(self.max_min_gap)) /
                               self.dim)
    def maximize_unbounded_regularizer(self, gp_params):
        """
        Unbounded Regularizer AISTAST 2016 Bobak

        Input parameters
        ----------
        gp_params: parameter for Gaussian Process
        Returns
        -------
        x: recommented point for evaluation
        """

        if self.acq['name'] == 'random':
            x_max = [
                np.random.uniform(x[0], x[1], size=1) for x in self.bounds
            ]
            x_max = np.asarray(x_max)
            x_max = x_max.T
            self.X_original = np.vstack((self.X_original, x_max))
            # evaluate Y using original X
            self.Y_original = np.append(self.Y_original, self.f(x_max))

            # update Y after change Y_original
            self.Y = (self.Y_original - np.mean(self.Y_original)) / (
                np.max(self.Y_original) - np.min(self.Y_original))

            self.time_opt = np.hstack((self.time_opt, 0))
            return

        # init a new Gaussian Process
        self.gp = PradaGaussianProcess(gp_params)

        # scale the data before updating the GP
        self.max_min_gap = self.bounds[:, 1] - self.bounds[:, 0]
        temp = np.divide((self.X_original - self.bounds[:, 0]),
                         self.max_min_gap)
        self.X = np.asarray(temp)

        ur = unique_rows(self.X)
        self.gp.fit(self.X[ur], self.Y[ur])

        # Find unique rows of X to avoid GP from breaking

        # Set acquisition function
        start_opt = time.time()

        y_max = self.Y.max()

        self.scalebounds_bk = self.scalebounds.copy()
        self.bounds_bk = self.bounds
        # consider the expansion step after 3 iterations

        if (len(self.Y) % 3) == 0:
            new_radius = 2.0 * self.l_radius
            extra_radius = (new_radius - self.l_radius) / 2

            max_bounds = self.bounds.copy()
            max_bounds[:, 0] = max_bounds[:, 0] - extra_radius
            max_bounds[:, 1] = max_bounds[:, 1] + extra_radius

            # make sure it is within the limit
            if not (self.b_limit_lower is None):
                temp_max_bounds_lower = [
                    np.maximum(max_bounds[idx, 0], self.b_limit_lower[idx])
                    for idx in xrange(self.dim)
                ]
                max_bounds[:, 0] = temp_max_bounds_lower

            if not (self.b_limit_upper is None):
                temp_max_bounds_upper = [
                    np.minimum(max_bounds[idx, 1], self.b_limit_upper[idx])
                    for idx in xrange(self.dim)
                ]
                max_bounds[:, 1] = temp_max_bounds_upper

            self.bounds = np.asarray(max_bounds)

            temp = [(max_bounds[d, :] - self.bounds[d, 0]) * 1.0 /
                    self.max_min_gap[d] for d in xrange(self.dim)]
            self.scalebounds = np.asarray(temp)

        # select the acquisition function
        self.acq['x_bar'] = np.mean(self.bounds)
        self.acq['R'] = np.power(self.l_radius, 1.0 / self.dim)

        self.acq_func = AcquisitionFunction(self.acq)

        # mean of the domain

        #acq['R']

        x_max_scale = acq_max(ac=self.acq_func.acq_kind,
                              gp=self.gp,
                              y_max=y_max,
                              bounds=self.scalebounds,
                              opt_toolbox=self.opt_toolbox)

        #val_acq=self.acq_func.acq_kind(x_max_scale,self.gp,y_max)
        #print "alpha[x_max]={:.5f}".format(np.ravel(val_acq)[0])

        # record the optimization time
        finished_opt = time.time()
        elapse_opt = finished_opt - start_opt
        self.time_opt = np.hstack((self.time_opt, elapse_opt))

        # Test if x_max is repeated, if it is, draw another one at random
        if np.any((self.X - x_max_scale).sum(axis=1) == 0):
            x_max_scale = np.random.uniform(self.scalebounds[:, 0],
                                            self.scalebounds[:, 1],
                                            size=self.scalebounds.shape[0])

        # check if the estimated data point is in the old bound or new
        flagOutside = 0
        for d in xrange(self.dim):
            if x_max_scale[d] > self.scalebounds_bk[d, 1] or x_max_scale[
                    d] < self.scalebounds_bk[d, 0]:  #outside the old bound
                flagOutside = 1
                self.scalebounds[d, 0] = np.minimum(x_max_scale[d],
                                                    self.scalebounds_bk[d, 0])
                self.scalebounds[d, 1] = np.maximum(x_max_scale[d],
                                                    self.scalebounds_bk[d, 1])

                # now the scalebounds is no longer 0-1

        if flagOutside == 0:  # not outside the old bound
            self.scalebounds = self.scalebounds_bk
        else:  # inside the old bound => recompute bound
            temp = [
                self.scalebounds[d, :] * self.max_min_gap[d] +
                self.bounds_bk[d, 0] for d in xrange(self.dim)
            ]
            if self.dim > 1:
                self.bounds = np.reshape(temp, (self.dim, 2))
            else:
                self.bounds = np.array(temp)

        # compute X in original scale
        temp_X_new_original = x_max_scale * self.max_min_gap + self.bounds_bk[:,
                                                                              0]
        self.X_original = np.vstack((self.X_original, temp_X_new_original))

        # clone the self.X for updating GP
        self.max_min_gap = self.bounds[:, 1] - self.bounds[:, 0]
        temp = np.divide((self.X_original - self.bounds[:, 0]),
                         self.max_min_gap)
        self.X = np.asarray(temp)

        scalebounds = np.array([np.zeros(self.dim), np.ones(self.dim)])
        self.scalebounds = scalebounds.T
        # evaluate Y using original X

        self.Y_original = np.append(self.Y_original,
                                    self.f(temp_X_new_original))

        # update Y after change Y_original
        self.Y = (self.Y_original - np.mean(self.Y_original)) / (
            np.max(self.Y_original) - np.min(self.Y_original))

        # for plotting
        self.gp = PradaGaussianProcess(gp_params)
        ur = unique_rows(self.X)
        self.gp.fit(self.X[ur], self.Y[ur])

        # update volume and radius
        #self.vol=prod(self.max_min_gap)
        #self.l_radius=np.power(self.vol,1/self.dim)
        self.l_radius = np.exp(1.0 * np.sum(np.log(self.max_min_gap)) /
                               self.dim)
    def run_FBO(self, gp_params):
        """
        Main optimization method for filtering strategy for BO.

        Input parameters
        ----------

        gp_params: parameter for Gaussian Process

        Returns
        -------
        x: recommented point for evaluation
        """

        # for random approach
        if self.acq['name'] == 'random':
            x_max = [
                np.random.uniform(x[0], x[1], size=1) for x in self.bounds
            ]
            x_max = np.asarray(x_max)
            x_max = x_max.T
            self.X_original = np.vstack((self.X_original, x_max))
            # evaluate Y using original X
            self.Y_original = np.append(self.Y_original, self.f(x_max))

            # update Y after change Y_original
            self.Y = (self.Y_original - np.mean(self.Y_original)) / (
                np.max(self.Y_original) - np.min(self.Y_original))

            self.time_opt = np.hstack((self.time_opt, 0))
            return

        # init a new Gaussian Process
        self.gp = PradaGaussianProcess(gp_params)

        # scale the data before updating the GP
        # convert it to scaleX
        self.max_min_gap = self.bounds[:, 1] - self.bounds[:, 0]
        temp = np.divide((self.X_original - self.bounds[:, 0]),
                         self.max_min_gap)
        self.X = np.asarray(temp)

        ur = unique_rows(self.X)
        self.gp.fit(self.X[ur], self.Y[ur])

        # Set acquisition function
        start_opt = time.time()

        # obtain the maximum on the observed set (for EI)
        y_max = self.Y.max()

        #self.L=self.estimate_L(self.scalebounds)
        # select the acquisition function

        self.acq_func = AcquisitionFunction(self.acq)

        # consider the expansion step

        # finding the maximum over the lower bound
        # mu(x)-kappa x sigma(x)
        mu_acq = {}
        mu_acq['name'] = 'lcb'
        mu_acq['dim'] = self.dim
        mu_acq['kappa'] = 2
        acq_mu = AcquisitionFunction(mu_acq)

        # obtain the argmax(lcb), make sure the scale bound vs original bound
        x_lcb_max = acq_max(ac=acq_mu.acq_kind,
                            gp=self.gp,
                            y_max=y_max,
                            bounds=self.scalebounds,
                            opt_toolbox=self.opt_toolbox)

        # obtain the max(lcb)
        max_lcb = acq_mu.acq_kind(x_lcb_max, gp=self.gp, y_max=y_max)
        max_lcb = np.ravel(max_lcb)

        # finding the region outside the box, that has the ucb > max_lcb
        self.max_min_gap_bk = self.max_min_gap.copy()
        self.bounds_bk = self.bounds.copy()
        self.scalebounds_bk = self.scalebounds.copy()
        self.X_invasion = []

        # the region considered is computed as follows: NewVol~OldVol*T/t
        # alternatively, we compute the radius NewL~Oldl*pow(T/t,1/d)
        new_radius = self.l_radius * np.power(
            self.MaxIter / len(self.Y_original), 1.0 / self.dim)

        # extra proportion
        extra_proportion = new_radius * 1.0 / self.l_radius

        #extra_radius=(new_radius-self.l_radius)/2

        # check if extra radius is negative
        if extra_proportion < 1:
            extra_proportion = 1

        max_bounds = self.bounds.copy()

        # expand half to the lower bound and half to the upper bound, X'_t
        max_bounds[:, 0] = max_bounds[:, 0] - self.max_min_gap * (
            extra_proportion - 1)
        max_bounds[:, 1] = max_bounds[:, 1] + self.max_min_gap * (
            extra_proportion - 1)

        #max_bounds[:,0]=max_bounds[:,0]-extra_radius
        #max_bounds[:,1]=max_bounds[:,1]+extra_radius

        # make sure the max_bounds is within the limit
        if not (self.b_limit_lower is None):
            temp_max_bounds_lower = [
                np.maximum(max_bounds[idx, 0], self.b_limit_lower[idx])
                for idx in xrange(self.dim)
            ]
            max_bounds[:, 0] = temp_max_bounds_lower

        if not (self.b_limit_upper is None):
            temp_max_bounds_upper = [
                np.minimum(max_bounds[idx, 1], self.b_limit_upper[idx])
                for idx in xrange(self.dim)
            ]
            max_bounds[:, 1] = temp_max_bounds_upper

        temp = [
            (max_bounds[d, :] - self.bounds[d, 0]) * 1.0 / self.max_min_gap[d]
            for d in xrange(self.dim)
        ]
        max_bounds_scale = np.asarray(temp)

        # find suitable candidates in new regions
        # ucb(x) > max_lcb st max L(x)

        # new bound in scale space
        # we note that the scalebound will be changed inside this function
        self.max_volume(self.gp, max_bounds_scale, max_lcb)

        #print "new bounds scale"
        #print self.scalebounds

        # perform standard BO on the new bound (scaled)
        x_max_scale = acq_max(ac=self.acq_func.acq_kind,
                              gp=self.gp,
                              y_max=y_max,
                              bounds=self.scalebounds,
                              opt_toolbox=self.opt_toolbox)

        val_acq = self.acq_func.acq_kind(x_max_scale, self.gp, y_max)

        # record the optimization time
        finished_opt = time.time()
        elapse_opt = finished_opt - start_opt
        self.time_opt = np.hstack((self.time_opt, elapse_opt))

        # Test if x_max is repeated, if it is, draw another one at random
        if np.any((self.X - x_max_scale).sum(axis=1) == 0):
            x_max_scale = np.random.uniform(self.scalebounds[:, 0],
                                            self.scalebounds[:, 1],
                                            size=self.scalebounds.shape[0])

        # check if the estimated data point is in the old bound or new
        flagOutside = 0
        for d in xrange(self.dim):
            if x_max_scale[d] > self.scalebounds_bk[d, 1] or x_max_scale[
                    d] < self.scalebounds_bk[d, 0]:  #outside the old bound
                flagOutside = 1
                self.scalebounds[d, 0] = np.minimum(x_max_scale[d],
                                                    self.scalebounds_bk[d, 0])
                self.scalebounds[d, 1] = np.maximum(x_max_scale[d],
                                                    self.scalebounds_bk[d, 1])
            else:
                self.scalebounds[d, :] = self.scalebounds_bk[d, :]

                # now the scalebounds is no longer 0-1

        if flagOutside == 0:  # not outside the old bound, use the old bound
            self.scalebounds = self.scalebounds_bk
            self.bounds = self.bounds_bk.copy()
        else:  # outside the old bound => expand the bound as the minimum bound containing the old bound and the selected point
            temp = [
                self.scalebounds[d, :] * self.max_min_gap[d] +
                self.bounds_bk[d, 0] for d in xrange(self.dim)
            ]
            if self.dim > 1:
                self.bounds = np.reshape(temp, (self.dim, 2))
            else:
                self.bounds = np.array(temp)

        self.bounds_list = np.hstack((self.bounds_list, self.bounds))

        # compute X in original scale
        temp_X_new_original = x_max_scale * self.max_min_gap + self.bounds_bk[:,
                                                                              0]
        self.X_original = np.vstack((self.X_original, temp_X_new_original))

        # clone the self.X for updating GP
        self.max_min_gap = self.bounds[:, 1] - self.bounds[:, 0]
        temp = np.divide((self.X_original - self.bounds[:, 0]),
                         self.max_min_gap)
        self.X = np.asarray(temp)

        scalebounds = np.array([np.zeros(self.dim), np.ones(self.dim)])
        self.scalebounds = scalebounds.T
        # evaluate Y using original X

        self.Y_original = np.append(self.Y_original,
                                    self.f(temp_X_new_original))

        # update Y after change Y_original
        self.Y = (self.Y_original - np.mean(self.Y_original)) / (
            np.max(self.Y_original) - np.min(self.Y_original))

        # for plotting
        self.gp = PradaGaussianProcess(gp_params)
        ur = unique_rows(self.X)
        self.gp.fit(self.X[ur], self.Y[ur])

        # update volume and radius
        #self.vol=prod(self.max_min_gap)
        #self.l_radius=np.power(self.vol,1/self.dim)
        self.l_radius = np.exp(1.0 * np.sum(np.log(self.max_min_gap)) /
                               self.dim)
	def maximize(self,
                 init_points=5,
                 n_iter=25,
                 acq='ucb',
                 kappa=2.576,
                 **gp_params):
		"""
		Main optimization method.

		Parameters
		----------
		:param init_points:
			Number of randomly chosen points to sample the
			target function before fitting the gp.

		:param n_iter:
			Total number of times the process is to repeated. Note that
			currently this methods does not have stopping criteria (due to a
			number of reasons), therefore the total number of points to be
			sampled must be specified.

		:param acq:
			Acquisition function to be used, defaults to Expected Improvement.

		:param gp_params:
			Parameters to be passed to the Scikit-learn Gaussian Process object

		Returns
		-------
		:return: Nothing
		"""
		# Reset timer
		#self.plog.reset_timer()

		# Set acquisition function
		self.util = UtilityFunction(kind=self.acq, kappa=kappa)

		# Initialize x, y and find current y_max
		if not self.initialized:
			#if self.verbose:
				#self.plog.print_header()
			self.init(init_points)

		y_max = self.Y.max()

		self.theta=gp_params['theta']

		# Set parameters if any was passed
		#self.gp.set_params(**gp_params)
		self.gp=PradaMultipleGaussianProcess(**gp_params)

		# Find unique rows of X to avoid GP from breaking
		ur = unique_rows(self.X)
		self.gp.fit(self.X[ur], self.Y[ur])

		# Finding argmax of the acquisition function.
		x_max = acq_max(ac=self.acq_func.acq_kind,gp=self.gp,
						y_max=y_max,bounds=self.bounds)

		#print "start acq max nlopt"
		#x_max,f_max = acq_max_nlopt(f=self.acq_func.acq_kind,gp=self.gp,y_max=y_max,
							  #bounds=self.bounds)
		#print "end acq max nlopt"

		# Test if x_max is repeated, if it is, draw another one at random
		# If it is repeated, print a warning
		#pwarning = False
		if np.any((self.X - x_max).sum(axis=1) == 0):
			#print "x max uniform random"

			x_max = np.random.uniform(self.bounds[:, 0],
									  self.bounds[:, 1],
									  size=self.bounds.shape[0])
									
		#print "start append X,Y"
		self.X = np.vstack((self.X, x_max.reshape((1, -1))))
		#self.Y = np.append(self.Y, self.f(**dict(zip(self.keys, x_max))))
		self.Y = np.append(self.Y, self.f(x_max))


		#print "end append X,Y"
		#print 'x_max={:f}'.format(x_max[0])

		#print "start fitting GP"

		# Updating the GP.
		ur = unique_rows(self.X)
		self.gp.fit(self.X[ur], self.Y[ur])

		#print "end fitting GP"
		# Update maximum value to search for next probe point.
		if self.Y[-1] > y_max:
			y_max = self.Y[-1]
    def maximize(self,gp_params,kappa=2):
        """
        Main optimization method.

        Input parameters
        ----------

        kappa: parameter for UCB acquisition only.

        gp_params: parameter for Gaussian Process

        Returns
        -------
        x: recommented point for evaluation
        """

        # init a new Gaussian Process
        self.gp=PradaGaussianProcess(gp_params)
        
        # Find unique rows of X to avoid GP from breaking
        ur = unique_rows(self.X)
        self.gp.fit(self.X[ur], self.Y[ur])
        

        # Set acquisition function
        start_opt=time.time()

        acq=self.acq
        
        # select the acquisition function
        if acq=='nei':
            self.L=self.estimate_L(self.bounds)
            self.util = AcquisitionFunction(kind=self.acq, L=self.L)
        else:
            if acq=="ucb":
                self.acq_func = AcquisitionFunction(kind=self.acq, kappa=kappa)
            else:
                self.acq_func = AcquisitionFunction(kind=self.acq, kappa=kappa)

        y_max = self.Y.max()
        
        # select the optimization toolbox        
        if self.opt=='nlopt':
            x_max,f_max = acq_max_nlopt(ac=self.acq_func.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds)
        if self.opt=='scipy':
            x_max = acq_max(ac=self.acq_func.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds)
        if self.opt=='direct':
            x_max = acq_max_direct(ac=self.acq_func.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds)

        # record the optimization time
        finished_opt=time.time()
        elapse_opt=finished_opt-start_opt
        self.time_opt=np.hstack((self.time_opt,elapse_opt))
        
        # Test if x_max is repeated, if it is, draw another one at random
        if np.any((self.X - x_max).sum(axis=1) == 0):

            x_max = np.random.uniform(self.scalebounds[:, 0],
                                      self.scalebounds[:, 1],
                                      size=self.scalebounds.shape[0])
                                     
        # store X                                     
        self.X = np.vstack((self.X, x_max.reshape((1, -1))))

        # compute X in original scale
        temp_X_new_original=x_max*self.max_min_gap+self.bounds[:,0]
        self.X_original=np.vstack((self.X_original, temp_X_new_original))
        # evaluate Y using original X
        self.Y = np.append(self.Y, self.f(temp_X_new_original))
    def maximize(self,gp_params):
        """
        Main optimization method.

        Input parameters
        ----------
        gp_params: parameter for Gaussian Process

        Returns
        -------
        x: recommented point for evaluation
        """

        if self.stop_flag==1:
            return
            
        if self.acq['name']=='random':
            x_max = [np.random.uniform(x[0], x[1], size=1) for x in self.scalebounds]
            x_max=np.asarray(x_max)
            x_max=x_max.T
            self.X_original=np.vstack((self.X_original, x_max))
            # evaluate Y using original X
            
            self.Y_original = np.append(self.Y_original, self.f(x_max))
            
            # update Y after change Y_original
            self.Y=(self.Y_original-np.mean(self.Y_original))/np.std(self.Y_original)
            
            self.time_opt=np.hstack((self.time_opt,0))
            return         

        # init a new Gaussian Process
        self.gp=PradaGaussianProcess(gp_params)
        if self.gp.KK_x_x_inv ==[]:
            # Find unique rows of X to avoid GP from breaking
            ur = unique_rows(self.X)
            self.gp.fit(self.X[ur], self.Y[ur])

 
        acq=self.acq

        if acq['debug']==1:
            logmarginal=self.gp.log_marginal_lengthscale(gp_params['theta'],gp_params['noise_delta'])
            print(gp_params['theta'])
            print("log marginal before optimizing ={:.4f}".format(logmarginal))
            self.logmarginal=logmarginal
                
            if logmarginal<-999999:
                logmarginal=self.gp.log_marginal_lengthscale(gp_params['theta'],gp_params['noise_delta'])

        if self.optimize_gp==1 and len(self.Y)%2*self.dim==0 and len(self.Y)>5*self.dim:

            print("Initial length scale={}".format(gp_params['theta']))
            newtheta = self.gp.optimize_lengthscale(gp_params['theta'],gp_params['noise_delta'],self.scalebounds)
            gp_params['theta']=newtheta
            print("New length scale={}".format(gp_params['theta']))

            # init a new Gaussian Process after optimizing hyper-parameter
            self.gp=PradaGaussianProcess(gp_params)
            # Find unique rows of X to avoid GP from breaking
            ur = unique_rows(self.X)
            self.gp.fit(self.X[ur], self.Y[ur])

            
        # Modify search space based on selected method
        if self.expandSS=='expandBoundsDDB_MAP':
			self.expandBoundsDDB_MAP()
        if self.expandSS=='expandBoundsDDB_FB':
			self.expandBoundsDDB_FB()            
        if self.expandSS=='expandBoundsFiltering':
            self.expandBoundsFiltering()
        if self.expandSS=='volumeDoubling' and len(self.Y)%3*self.dim==0:
            self.volumeDoubling()
        # Prevent bounds from breaching maximum limit
        for d in range(0,self.dim):
            if self.scalebounds[d,0]<0:
                print('Lower bound of {} in dimention {} exceeded minimum bound of {}. Scaling up.'.format(self.scalebounds[d,0],d,0))
                self.scalebounds[d,0]=0
                print('bound set to {}'.format(self.scalebounds))
            if self.scalebounds[d,1]>max_bound_size:
                print('Upper bound of {} in dimention {} exceeded maximum bound of {}. Scaling down.'.format(self.scalebounds[d,1],d,max_bound_size))
                self.scalebounds[d,1]=max_bound_size
                self.scalebounds[d,0]=min(self.scalebounds[d,0],self.scalebounds[d,1]-np.sqrt(3*self.gp.lengthscale))
                print('bound set to {}'.format(self.scalebounds))
        
        # Set acquisition function
        start_opt=time.time()

        y_max = self.Y.max()
        
        if acq['name'] in ['consensus','mes']: 
            ucb_acq_func={}
            ucb_acq_func['name']='ucb'
            ucb_acq_func['kappa']=np.log(len(self.Y))
            ucb_acq_func['dim']=self.dim
            ucb_acq_func['scalebounds']=self.scalebounds
        
            myacq=AcquisitionFunction(ucb_acq_func)
            xt_ucb = acq_max(ac=myacq.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds)
            
            xstars=[]
            xstars.append(xt_ucb)
            
            ei_acq_func={}
            ei_acq_func['name']='ei'
            ei_acq_func['dim']=self.dim
            ei_acq_func['scalebounds']=self.scalebounds
        
            myacq=AcquisitionFunction(ei_acq_func)
            xt_ei = acq_max(ac=myacq.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds)
            xstars.append(xt_ei)
                 
            
            pes_acq_func={}
            pes_acq_func['name']='pes'
            pes_acq_func['dim']=self.dim
            pes_acq_func['scalebounds']=self.scalebounds
        
            myacq=AcquisitionFunction(pes_acq_func)
            xt_pes = acq_max(ac=myacq.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds)
            xstars.append(xt_pes)
            
            
            self.xstars=xstars            
            
        if acq['name']=='vrs':
            print("please call the maximize_vrs function")
            return
                      
        if 'xstars' not in globals():
            xstars=[]
            
        self.xstars=xstars

        self.acq['xstars']=xstars
        self.acq['WW']=False
        self.acq['WW_dim']=False
        self.acq_func = AcquisitionFunction(self.acq,self.bb_function)

        if acq['name']=="ei_mu":
            #find the maximum in the predictive mean
            mu_acq={}
            mu_acq['name']='mu'
            mu_acq['dim']=self.dim
            acq_mu=AcquisitionFunction(mu_acq)
            x_mu_max = acq_max(ac=acq_mu.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds,opt_toolbox=self.opt_toolbox)
            # set y_max = mu_max
            y_max=acq_mu.acq_kind(x_mu_max,gp=self.gp, y_max=y_max)

        
        x_max = acq_max(ac=self.acq_func.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds,opt_toolbox=self.opt_toolbox,seeds=self.xstars)

        if acq['name']=='consensus' and acq['debug']==1: # plot the x_max and xstars
            fig=plt.figure(figsize=(5, 5))

            plt.scatter(xt_ucb[0],xt_ucb[1],marker='s',color='g',s=200,label='Peak')
            plt.scatter(xt_ei[0],xt_ei[1],marker='s',color='k',s=200,label='Peak')
            plt.scatter(x_max[0],x_max[1],marker='*',color='r',s=300,label='Peak')
            plt.xlim(0,1)
            plt.ylim(0,1)
            strFileName="acquisition_functions_debug.eps"
            fig.savefig(strFileName, bbox_inches='tight')

        if acq['name']=='vrs' and acq['debug']==1: # plot the x_max and xstars
            fig=plt.figure(figsize=(5, 5))

            plt.scatter(xt_ucb[0],xt_ucb[1],marker='s',color='g',s=200,label='Peak')
            plt.scatter(xt_ei[0],xt_ei[1],marker='s',color='k',s=200,label='Peak')
            plt.scatter(x_max[0],x_max[1],marker='*',color='r',s=300,label='Peak')
            plt.xlim(0,1)
            plt.ylim(0,1)
            strFileName="vrs_acquisition_functions_debug.eps"
            #fig.savefig(strFileName, bbox_inches='tight')
            
            
        val_acq=self.acq_func.acq_kind(x_max,self.gp,y_max)
        #print x_max
        #print val_acq
        if self.stopping_criteria!=0 and val_acq<self.stopping_criteria:
            val_acq=self.acq_func.acq_kind(x_max,self.gp,y_max)

            self.stop_flag=1
            print("Stopping Criteria is violated. Stopping Criteria is {:.15f}".format(self.stopping_criteria))
        
        
        self.alpha_Xt= np.append(self.alpha_Xt,val_acq)
        
        mean,var=self.gp.predict(x_max, eval_MSE=True)
        var.flags['WRITEABLE']=True
        var[var<1e-20]=0
        #self.Tau_Xt= np.append(self.Tau_Xt,val_acq/var)
       
        # record the optimization time
        finished_opt=time.time()
        elapse_opt=finished_opt-start_opt
        self.time_opt=np.hstack((self.time_opt,elapse_opt))
        
        # store X                                     
        self.X = np.vstack((self.X, x_max.reshape((1, -1))))

        # evaluate Y using original X
        self.Y_original = np.append(self.Y_original, self.f(x_max))
        
        # update Y after change Y_original
        self.Y=(self.Y_original-np.mean(self.Y_original))/np.std(self.Y_original)
        
        if self.gp.flagIncremental==1:
            self.gp.fit_incremental(x_max,self.Y[-1])
#        if (self.acq['name']=='ei_regularizerH') or (self.acq['name']=='ei_regularizerQ'):
#            self.scalebounds[:,0]=self.scalebounds[:,0]+1
#            self.scalebounds[:,1]=self.scalebounds[:,1]-1
#        self.acq['scalebounds']=self.scalebounds
        self.experiment_num=self.experiment_num+1
    def maximize(self, gp_params, kappa=2):
        """
        Main optimization method.

        Input parameters
        ----------

        kappa: parameter for UCB acquisition only.

        gp_params: parameter for Gaussian Process

        Returns
        -------
        x: recommented point for evaluation
        """

        if self.acq['name'] == 'random':
            x_max = [
                np.random.uniform(x[0], x[1], size=1) for x in self.bounds
            ]
            x_max = np.asarray(x_max)
            x_max = x_max.T
            self.X_original = np.vstack((self.X_original, x_max))
            # evaluate Y using original X

            #self.Y = np.append(self.Y, self.f(temp_X_new_original))
            self.Y_original = np.append(self.Y_original, self.f(x_max))

            # update Y after change Y_original
            self.Y = (self.Y_original - np.mean(self.Y_original)) / (
                np.max(self.Y_original) - np.min(self.Y_original))

            self.time_opt = np.hstack((self.time_opt, 0))
            return

        # init a new Gaussian Process
        self.gp = PradaGaussianProcess(gp_params)

        # Find unique rows of X to avoid GP from breaking
        ur = unique_rows(self.X)
        self.gp.fit(self.X[ur], self.Y[ur])

        # Set acquisition function
        start_opt = time.time()

        acq = self.acq
        y_max = self.Y.max()

        #self.L=self.estimate_L(self.scalebounds)
        # select the acquisition function
        if acq['name'] == 'nei':
            self.L = self.estimate_L(self.scalebounds)
            self.acq_func = AcquisitionFunction(kind=self.acq, L=self.L)
        else:
            self.acq_func = AcquisitionFunction(self.acq)

            if acq['name'] == "ei_mu":
                #find the maximum in the predictive mean
                mu_acq = {}
                mu_acq['name'] = 'mu'
                mu_acq['dim'] = self.dim
                acq_mu = AcquisitionFunction(mu_acq)
                x_mu_max = acq_max(ac=acq_mu.acq_kind,
                                   gp=self.gp,
                                   y_max=y_max,
                                   bounds=self.scalebounds,
                                   opt_toolbox=self.opt_toolbox)
                # set y_max = mu_max
                y_max = acq_mu.acq_kind(x_mu_max, gp=self.gp, y_max=y_max)

        x_max = acq_max(ac=self.acq_func.acq_kind,
                        gp=self.gp,
                        y_max=y_max,
                        bounds=self.scalebounds,
                        opt_toolbox=self.opt_toolbox)

        val_acq = self.acq_func.acq_kind(x_max, self.gp, y_max)
        #print "alpha[x_max]={:.5f}".format(np.ravel(val_acq)[0])
        # check the value alpha(x_max)==0
        #if val_acq<0.0001:
        #self.stop_flag=1
        #return

        # select the optimization toolbox
        """      
        if self.opt=='nlopt':
            x_max,f_max = acq_max_nlopt(ac=self.acq_func.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds)
        if self.opt=='scipy':
            
        if self.opt=='direct':
            x_max = acq_max_direct(ac=self.acq_func.acq_kind,gp=self.gp,y_max=y_max,bounds=self.scalebounds)
        """

        # record the optimization time
        finished_opt = time.time()
        elapse_opt = finished_opt - start_opt
        self.time_opt = np.hstack((self.time_opt, elapse_opt))

        # Test if x_max is repeated, if it is, draw another one at random
        if np.any((self.X - x_max).sum(axis=1) == 0):

            x_max = np.random.uniform(self.scalebounds[:, 0],
                                      self.scalebounds[:, 1],
                                      size=self.scalebounds.shape[0])

        # store X
        self.X = np.vstack((self.X, x_max.reshape((1, -1))))

        # compute X in original scale
        temp_X_new_original = x_max * self.max_min_gap + self.bounds[:, 0]
        self.X_original = np.vstack((self.X_original, temp_X_new_original))
        # evaluate Y using original X

        #self.Y = np.append(self.Y, self.f(temp_X_new_original))
        self.Y_original = np.append(self.Y_original,
                                    self.f(temp_X_new_original))

        # update Y after change Y_original
        self.Y = (self.Y_original - np.mean(self.Y_original)) / (
            np.max(self.Y_original) - np.min(self.Y_original))