//  [python]
def get_lower_bound(self):
        '''
        Obtains the lower bound on the expected loss for the fitted classifier.

        Returns
        -------
        lower_bound : `float`
            Lower bound of the error for the fitted classifier.
        '''

        # Classifier should be fitted to obtain the lower bound
        check_is_fitted(self, "is_fitted_")

        # Learned feature mappings
        phi = self.lowerPhiConfigs
        phi_mu = np.dot(phi, self.mu_)
        hy_x = np.clip(1 + phi_mu + self.nu_, 0., None)

        # Variables
        n = phi.shape[0]
        m = phi.shape[2]

        if self.loss == '0-1':

            # To define the objective function and
            # the gradient for the 0-1 loss function.
            # epsilon
            eps = np.clip(1 + phi @ self.mu_ + self.nu_, 0, None)
            c = np.sum(eps, axis=1)
            zeros = np.isclose(c, 0)
            c[zeros] = 1
            eps[zeros, :] = 1 / self.n_classes
            eps = eps / (c[:, np.newaxis])
            # Using negative of epsilon
            # for the nesterov accelerated optimization
            eps = eps - 1

            # Reshape it for the optimization function
            eps = eps.reshape((n * self.n_classes,))

        elif self.loss == 'log':

            # To define the objective function and
            # the gradient for the log loss function.
            # Using negative of epsilon
            # for the nesterov accelerated optimization
            eps = phi @ self.mu_ - \
                np.max(phi @ self.mu_, axis=1)[:, np.newaxis] - \
                np.log(np.sum(np.exp(phi @ self.mu_ - np.max(phi @ self.mu_, axis=1)[:, np.newaxis]), axis=1))[:, np.newaxis]
            eps = eps.reshape((n * self.n_classes,))

        else:
            raise ValueError('The given loss function is not available ' +
                             'for this classifier')

        phi = phi.reshape((n * self.n_classes, m))

        if not self.deterministic:

            if self.solver == 'cvx':
                # Use CVXpy for the convex optimization of the MRC

                low_mu = cvx.Variable(m)

                # Objective function
                objective = cvx.Minimize(self.lambda_ @ cvx.abs(low_mu) -
                                         self.tau_ @ low_mu +
                                         cvx.max(phi @ low_mu + eps))

                self.mu_l_, self.lower_ = \
                    try_solvers(objective, None, low_mu, self.cvx_solvers)

                # Maximize the function
                self.lower_ = (-1) * self.lower_

            elif self.solver == 'subgrad' or self.solver == 'cg':
                # Use the subgradient approach for the convex optimization of MRC

                # Defining the partial objective and its gradient.
                def f_(mu):
                    return phi @ mu + eps

                def g_(mu, idx):
                    return phi.transpose()[:, idx]

                # Lower bound
                self.lower_params_ = nesterov_optimization_mrc(self.tau_,
                                                               self.lambda_,
                                                               m,
                                                               f_,
                                                               g_,
                                                               self.max_iters)

                self.mu_l_ = self.lower_params_['mu']
                self.lower_ = self.lower_params_['best_value']

                # Maximize the function
                # as the nesterov optimization gives the minimum
                self.lower_ = -1 * self.lower_

            else:
                raise ValueError('Unexpected solver ... ')

        elif self.deterministic:

            hy_x_det = np.zeros((n, self.n_classes))
            for i in range(n):
                hy_x_det[i, np.argmax(phi_mu[i,:])] = 1

            hy_x_det = np.reshape(hy_x_det, (n * self.n_classes,))

            mu_l_ = cvx.Variable(m)
            objective = cvx.Maximize(1 - self.tau_ @ mu_l_ - \
                            self.lambda_ @ cvx.abs(mu_l_) + \
                            cvx.min(phi @ mu_l_ - hy_x_det))

            self.mu_l_, self.lower_ = try_solvers(objective,
                                                     None,
                                                     mu_l_,
                                                     self.cvx_solvers)

        return self.lower_