:param x0: initial point for a local optimizer (x0 can be defined with or without the context included).
    :param f: function to optimize.
    :param df: gradient of the function to optimize.
    :param f_df: returns both the function to optimize and its gradient.
    :param duplicate_manager: logic to check for duplicate (always operates in the full space, context included)
    :param context_manager: If provided, x0 (and the optimizer) operates in the space without the context
    :param space: GPyOpt class design space.

def apply_optimizer(optimizer, x0, f=None, df=None, f_df=None, duplicate_manager=None, context_manager=None, space=None):
    """
    :param x0: initial point for a local optimizer (x0 can be defined with or without the context included).
    :param f: function to optimize.
    :param df: gradient of the function to optimize.
    :param f_df: returns both the function to optimize and its gradient.
    :param duplicate_manager: logic to check for duplicate (always operates in the full space, context included)
    :param context_manager: If provided, x0 (and the optimizer) operates in the space without the context
    :param space: GPyOpt class design space.
    """
    x0 = np.atleast_2d(x0)

    ## --- Compute a new objective that inputs non context variables but that takes into account the values of the context ones.
    ## --- It does nothing if no context is passed
    problem = OptimizationWithContext(x0=x0, f=f, df=df, f_df=f_df, context_manager=context_manager)


    if context_manager:
        add_context = lambda x : context_manager._expand_vector(x)
    else:
        add_context = lambda x : x

    if duplicate_manager and duplicate_manager.is_unzipped_x_duplicate(x0):
        raise ValueError("The starting point of the optimizer cannot be a duplicate.")

    ## --- Optimize point
    optimized_x, _ = optimizer.optimize(problem.x0_nocontext, problem.f_nocontext, problem.df_nocontext, problem.f_df_nocontext)

    ## --- Add context and round according to the type of variables of the design space
    suggested_x_with_context = add_context(optimized_x)
    suggested_x_with_context_rounded = space.round_optimum(suggested_x_with_context)

    ## --- Run duplicate_manager
    if duplicate_manager and duplicate_manager.is_unzipped_x_duplicate(suggested_x_with_context_rounded):
        suggested_x, suggested_fx = x0, np.atleast_2d(f(x0))
    else:
        suggested_x, suggested_fx = suggested_x_with_context_rounded, f(suggested_x_with_context_rounded)

    return suggested_x, suggested_fx