    // Copyright (C) 2016  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_fIND_OPTIMAL_PARAMETERS_Hh_
    #define DLIB_fIND_OPTIMAL_PARAMETERS_Hh_

    #include "../matrix.h"
    #include "find_optimal_parameters_abstract.h"
    #include "optimization_bobyqa.h"
    #include "optimization_line_search.h"

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------

        template <
            typename funct
            >
        double find_optimal_parameters (
            double initial_search_radius,
            double eps,
            const unsigned int max_f_evals,
            matrix<double,0,1>& x,
            const matrix<double,0,1>& x_lower,
            const matrix<double,0,1>& x_upper,
            const funct& f
        )
        {
            DLIB_CASSERT(x.size() == x_lower.size() && x_lower.size() == x_upper.size() && x.size() > 0,
                "\t double find_optimal_parameters()"
                << "\n\t x.size():       " << x.size()
                << "\n\t x_lower.size(): " << x_lower.size()
                << "\n\t x_upper.size(): " << x_upper.size()
                );

            // check the requirements.  Also split the assert up so that the error message isn't huge.
            DLIB_CASSERT(max_f_evals > 1 && eps > 0 && initial_search_radius > eps,
                "\t double find_optimal_parameters()"
                << "\n\t Invalid arguments have been given to this function"
                << "\n\t initial_search_radius: " << initial_search_radius
                << "\n\t eps:                   " << eps
                << "\n\t max_f_evals:           " << max_f_evals
            );

            DLIB_CASSERT( min(x_upper - x_lower) > 0 &&
                         min(x - x_lower) >= 0 && min(x_upper - x) >= 0,
                "\t double find_optimal_parameters()"
                << "\n\t The bounds constraints have to make sense and also contain the starting point."
                << "\n\t min(x_upper - x_lower):                         " << min(x_upper - x_lower)
                << "\n\t min(x - x_lower) >= 0 && min(x_upper - x) >= 0: " << (min(x - x_lower) >= 0 && min(x_upper - x) >= 0)
            );

            // if the search radius is too big then shrink it so it fits inside the bounds.
            if (initial_search_radius*2 >= min(x_upper-x_lower))
                initial_search_radius = 0.5*min(x_upper-x_lower)*0.99;

            double objective_val = std::numeric_limits<double>::infinity();
            size_t num_iter_used = 0;
            if (x.size() == 1)
            {
                // BOBYQA requires x to have at least 2 variables in it.  So we can't call it in
                // this case.  Instead we call find_min_single_variable().
                matrix<double,0,1> temp(1);
                auto ff = [&](const double& xx)
                {
                    temp = xx;
                    double obj = f(temp);
                    ++num_iter_used;
                    // keep track of the best x.
                    if (obj < objective_val)
                    {
                        objective_val = obj;
                        x = temp;
                    }
                    return obj;
                };
                try
                {
                    double dx = x(0);
                    find_min_single_variable(ff, dx, x_lower(0), x_upper(0), eps, max_f_evals, initial_search_radius);
                } catch (optimize_single_variable_failure& )
                {
                }
            }
            else
            {
                auto ff = [&](const matrix<double,0,1>& xx)
                {
                    double obj = f(xx);
                    ++num_iter_used;
                    // keep track of the best x.
                    if (obj < objective_val)
                    {
                        objective_val = obj;
                        x = xx;
                    }
                    return obj;
                };
                try
                {
                    matrix<double,0,1> start_x = x;
                    find_min_bobyqa(ff, start_x, 2*x.size()+1, x_lower, x_upper, initial_search_radius, eps, max_f_evals);
                } catch (bobyqa_failure& )
                {
                }
            }

            return objective_val;
        }

    // ----------------------------------------------------------------------------------------

    }

    #endif // DLIB_fIND_OPTIMAL_PARAMETERS_Hh_