Nelder-Mead法をC++に移植

目的関数の勾配を使用しないで最適化するアルゴリズム。
[JuliaによるNelder-Meadアルゴリズムの実装](http://bicycle1885.hatenablog.com/entry/2015/03/05/040431)
JuliaからC++に移植してみた。簡略化してヘッダーに実装も記述。
「[std::vectorを拡張してみた(その後)](https://qiita.com/fred55/items/a64c2e2dca06cc5e3ccf)」で作成した`my_vector.h`を使用して、`std::vector`の四則演算を実行。

```cpp
#include <algorithm>
#include "my_vector.h"

// Adaptive Nelder - Mead Simplex(ANMS) algorithm

typedef double optimfn(int n, const double *x0, void *ex);

class nelder_mead {
private:
    class vertex {
    public:
        std::vector<double> x;
        double v;
    public:
        vertex(const std::vector<double>&x, double v) : x(x), v(v) {}
        static bool compare(const vertex *p1, const vertex *p2) {
            return (p1->v < p2->v);
        }
    };
private:
    std::vector<vertex*> simplex;
    void resize(int m) {
        if (!simplex.empty()) {
            for (auto e : simplex) delete e;
            simplex.resize(0);
        }
        if (m > 0) simplex.resize(m, NULL);
    }
    std::vector<double> centroid(int h) {
        int n = simplex.size() - 1;
        std::vector<double> c(n, 0.0);
        int m = 0;
        for (int i = 0; i < n + 1; i++) {
            if (i == h) continue;
            c += simplex[i]->x;
            m++;
        }
        return c / m;
    }
public:
    int iterations = 0;
    int fncount = 0;
    std::vector<double> xout;
    double fmin = 0;
public:
    nelder_mead() = default;
    nelder_mead(const nelder_mead&) = delete;
    nelder_mead& operator=(const nelder_mead&) = delete;
    ~nelder_mead() {
        resize(0);
    }
public:
    void minimize(optimfn fminfn, void *ex, const std::vector<double>& x0,
        int maxit, double ftol, double xtol) {

        // parameters of transformations
        int n = x0.size();
        double alpha = 1.0;
        double beta = 1.0 + 2.0 / n;
        double gamma = 0.75 - 1.0 / 2.0 / n;
        double delta = 1.0 - 1.0 / n;

        // number of function calls
        fncount = 0;
        auto fcall = [&](const std::vector<double>& x, void *ex) {
            fncount++;
            return fminfn(x.size(), x.data(), ex);
        };

        // initialize a simplex and function values
        resize(n + 1);
        double fvalue = fcall(x0, ex);
        for (auto& e : simplex) {
            e = new vertex(x0, fvalue);
        }
        for (int i = 1; i < n + 1; i++) {
            double tau = (x0[i - 1] == 0.0 ? 0.00025 : 0.05 * x0[i - 1]);
            simplex[i]->x[i - 1] += tau;
            simplex[i]->v = fcall(simplex[i]->x, ex);
        }
        std::sort(simplex.begin(), simplex.end(), vertex::compare);

        // centroid cache
        std::vector<double> c = centroid(n);

        // lowest index
        std::vector<double> xl = simplex[0]->x;
        double fl = simplex[0]->v;

        // stopping criteria
        iterations = 0;  // number of iterations
        bool domconv = false;  // domain convergence
        bool fvalconv = false;  // function-value convergence

        while ((iterations < maxit) && !(fvalconv && domconv)) {
            // highest and second highest indices
            std::vector<double> xh = simplex[n]->x;
            double fh = simplex[n]->v;
            double fs = simplex[n - 1]->v;

            // reflect
            std::vector<double> xr = c + (c - xh) * alpha;
            double fr = fcall(xr, ex);
            bool doshrink = false;
            std::vector<double> x;
            double fvalue = 0;

            if (fr < fl) { // <= fs
                // expand
                std::vector<double> xe = c + (xr - c) * beta;
                double fe = fcall(xe, ex);
                if (fe < fr) {
                    x = xe;
                    fvalue = fe;
                }
                else {
                    x = xr;
                    fvalue = fr;
                }
            }
            else if (fr < fs) {
                x = xr;
                fvalue = fr;
            }
            else { // fs <= fr
                // contract
                if (fr < fh) {
                    // outside
                    std::vector<double> xc = c + (xr - c) * gamma;
                    double fc = fcall(xc, ex);
                    if (fc <= fr) {
                        x = xc;
                        fvalue = fc;
                    }
                    else {
                        doshrink = true;
                    }
                }
                else {
                    // inside
                    std::vector<double> xc = c - (xr - c) * gamma;
                    double fc = fcall(xc, ex);
                    if (fc < fh) {
                        x = xc;
                        fvalue = fc;
                    }
                    else {
                        doshrink = true;
                    }
                }
            }
            // update simplex, function values and centroid cache
            // shrinkage almost never happen in practice
            if (doshrink) {
                // shrink
                for (int i = 1; i < n + 1; i++) {
                    simplex[i]->x = xl + (simplex[i]->x - xl) * delta;
                    simplex[i]->v = fcall(simplex[i]->x, ex);
                }
                std::sort(simplex.begin(), simplex.end(), vertex::compare);
                c = centroid(n);
            }
            else {
                // insert the new function value into an ordered position
                simplex[n]->x = x;
                simplex[n]->v = fvalue;
                for (int i = n; i > 0; i--) {
                    if (simplex[i - 1]->v > simplex[i]->v) {
                        vertex *tmp = simplex[i - 1];
                        simplex[i - 1] = simplex[i];
                        simplex[i] = tmp;
                    }
                    else {
                        break;
                    }
                }
                // add the new vertex, and subtract the highest vertex
                xh = simplex[n]->x;
                c += (x - xh) / n;
            }

            xl = simplex[0]->x;
            fl = simplex[0]->v;

            // check convergence
            fvalconv = true;
            for (int i = 1; i < n + 1; i++) {
                if (std::abs(simplex[i]->v - fl) > ftol) {
                    fvalconv = false;
                    break;
                }
            }
            domconv = true;
            for (int i = 1; i < n + 1; i++) {
                for (int j = 0; j < n; j++) {
                    if (std::abs(simplex[i]->x[j] - xl[j]) > xtol) {
                        domconv = false;
                        break;
                    }
                }
                if (!domconv) break;
            }

            iterations++;
        }

        // return the lowest vertex (or the centroid of the simplex) and the function value
        c = centroid(-1);
        double fcent = fcall(c, ex);
        if (fcent < fl) {
            xout = c;
            fmin = fcent;
        }
        else {
            xout = xl;
            fmin = fl;
        }
    }
};
```

使用例

```cpp
double rosenbrock(int n, const double *x, void *ex) {
    double *params = (double*)ex;
    double a = params[0];
    double b = params[1];
    double ret = 0;
    for (int i = 0; i < n - 1; i += 2) {
        double s = a - x[i];
        double t = x[i + 1] - x[i] * x[i];
        ret += s * s + b * t * t;
    }
    if (n % 2 == 1) {
        double s = a - x[n - 1];
        ret += s * s;
    }
    return ret;
}

double quadratic(int n, const double *x, void *ex) {
    double ret = 0;
    for (int i = 0; i < n; i++) {
        ret += x[i] * x[i];
    }
    return ret;
}

void test_anms() {
    std::cout << "test_anms" << std::endl;
    int maxit = 1000000;
    double ftol = 1.0e-8;
    double xtol = 1.0e-8;
    nelder_mead obj;

    for (int n = 2; n <= 10; n++) {
        double a = 1.0;
        double b = 100.0;
        std::vector<double> params = { a, b };
        std::vector<double> x0(n, 0);
        std::vector<double> x1(n, a);
        for (int i = 0; i < n - 1; i += 2) {
            x1[i + 1] = a * a;
        }
        void *ex = params.data();
        obj.minimize(rosenbrock, ex, x0, maxit, ftol, xtol);
        std::cout << "rosenbrock,";
        std::cout << n << ",";
        std::cout << std::boolalpha << (norm(obj.xout - x1) < xtol) << ",";
        std::cout << std::boolalpha << (std::abs(obj.fmin) < ftol) << ",";
        std::cout << obj.fncount << std::endl;
    }

    for (int n = 2; n <= 10; n++) {
        std::vector<double> x0(n, 1.0);
        std::vector<double> x1(n, 0.0);
        void *ex = NULL;
        obj.minimize(quadratic, ex, x0, maxit, ftol, xtol);
        std::cout << "quadratic,";
        std::cout << n << ",";
        std::cout << std::boolalpha << (norm(obj.xout - x1) < xtol) << ",";
        std::cout << std::boolalpha << (std::abs(obj.fmin) < ftol) << ",";
        std::cout << obj.fncount << std::endl;
    }
}
```

実行結果

```
test_anms
rosenbrock,2,true,true,207
rosenbrock,3,true,true,472
rosenbrock,4,true,true,790
rosenbrock,5,true,true,1155
rosenbrock,6,true,true,1828
rosenbrock,7,true,true,2012
rosenbrock,8,true,true,2795
rosenbrock,9,true,true,3293
rosenbrock,10,true,true,5371
quadratic,2,true,true,128
quadratic,3,true,true,285
quadratic,4,true,true,424
quadratic,5,true,true,593
quadratic,6,true,true,743
quadratic,7,true,true,957
quadratic,8,true,true,1133
quadratic,9,true,true,1321
quadratic,10,true,true,1494
```

参考文献

- [Fuchang Gao and Lixing Han (2010). Implementing the Nelder-Mead simplex algorithm with adaptive parameters]
(http://link.springer.com/article/10.1007%2Fs10589-010-9329-3)
- [Saša Singer and John Nelder (2009). Nelder-Mead algorithm]
(http://www.scholarpedia.org/article/Nelder-Mead_algorithm)