/******************************************************************************
* Copyright (C) 2017 Weird Constructor
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
******************************************************************************/

#include "utillib.h"
#include "csv.h"
#include <modules/vval_util.h>
#include <boost/locale/encoding.hpp>
#if USE_STD_REGEX
#   include <regex>
#   define RE_PREFIX std
#else
#   include <boost/regex.hpp>
#   define RE_PREFIX boost
#endif

using namespace VVal;
using namespace std;

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

// Taken from the internet, originally from Thomas Wang.
// Found on https://naml.us/blog/tag/thomas-wang:
static uint64_t int64hash(uint64_t key)
{
    key = (~key) + (key << 21); // key = (key << 21) - key - 1;
    key = key ^ (key >> 24);
    key = (key + (key << 3)) + (key << 8); // key * 265
    key = key ^ (key >> 14);
    key = (key + (key << 2)) + (key << 4); // key * 21
    key = key ^ (key >> 28);
    key = key + (key << 31);
    return key;
}
//---------------------------------------------------------------------------

// Function taken from the Internet:
//
// A xorshift* generator takes an xorshift generator, and performs an
// invertible multiplication (modulo the word size) to its output as a
// non-linear transformation, as suggested by Marsaglia.[1] The following
// 64-bit generator with 64 bits of state has a maximal period of 264 − 1[5]
// and fails only the MatrixRank test of BigCrush:
//
// http://en.wikipedia.org/wiki/Xorshift#Xorshift.2A
static uint64_t xorshift64star(uint64_t x)
{
	x ^= x >> 12; // a
	x ^= x << 25; // b
	x ^= x >> 27; // c
	return x * 2685821657736338717ULL;
}
//---------------------------------------------------------------------------

static double int64todouble(uint64_t x)
{
    x = x >> 11; // Most significant bits to 53bit
    return (double) x / (double) (UINT64_MAX >> 11);
}
//---------------------------------------------------------------------------

VV_CLOSURE_DOC(util_int_to_ratio,
"@util procedure (util-int-to-ratio _num_)\n"
"@util procedure (util-int-to-ratio _list-of-num_)\n"
"\n"
    "    Converts a 64 Bit (actually 53 bit) integer to a double in the range 0 to 1\n"
)
{
    if (vv_args->_(0)->is_list())
    {
        VV out(vv_list());

        for (auto n : *(vv_args->_(0)))
        {
            out << int64todouble((uint64_t) n->i());
        }

        return out;
    }
    else
    {
        return vv(int64todouble((uint64_t) vv_args->_i(0)));
    }
}
//---------------------------------------------------------------------------

VV_CLOSURE_DOC(util_xorshift,
"@util procedure (util-xorshift _list-of-nums_)\n"
"@util procedure (util-xorshift _num_)\n"
"@util procedure (util-xorshift _num_ _num-times_)\n"
"@util procedure (util-xorshift _num_ _num-times_ #t)\n"
"\n"
"A 64-Bit XOR-Shift implementation with maximal period of 2^64-1.\n"
"The initial number should not be zero, and preverable should have\n"
"multiple one bits to lead to more 'interesting' numbers.\n"
"The _num-times_ repeat application of xorshift to the same number multiple times.\n"
"If the third argument is #true, all intermediate values are kept and returned\n"
"in a list."
)
{
    if (vv_args->_(0)->is_list())
    {
        VV out(vv_list());
        for (auto n : *(vv_args->_(0)))
            out << (int64_t) xorshift64star((uint64_t) n->i());
        return out;
    }
    else if (vv_args->size() > 2 && vv_args->_b(2))
    {
        VV out(vv_list());
        uint64_t x = (uint64_t) vv_args->_i(0);
        for (int i = 1; i <= vv_args->_i(1); i++)
        {
            x = xorshift64star(x);
            out << (int64_t) x;
        }
        return out;
    }
    else if (vv_args->size() > 1)
    {
        uint64_t x = (uint64_t) vv_args->_i(0);
        for (int i = 1; i <= vv_args->_i(1); i++)
            x = xorshift64star(x);
        return vv((int64_t) x);
    }
    else
    {
        return vv((int64_t) xorshift64star((uint64_t) vv_args->_i(0)));
    }
}
//---------------------------------------------------------------------------

VV_CLOSURE_DOC(util_hash64,
"@util procedure (util-hash64 _list-of-num_)\n"
"@util procedure (util-hash64 _num_)\n"
"@util procedure (util-hash64 _num_ _num-times_)\n"
"@util procedure (util-hash64 _num_ _num-times_ #t)\n"
"\n"
"    A 64-Bit hash function implementation. See also `util-xorshift`.\n"
)
{
    if (vv_args->_(0)->is_list())
    {
        VV out(vv_list());
        for (auto n : *(vv_args->_(0)))
            out << (int64_t) int64hash((uint64_t) n->i());
        return out;
    }
    else if (vv_args->size() > 2 && vv_args->_b(2))
    {
        VV out(vv_list());
        uint64_t x = (uint64_t) vv_args->_i(0);
        for (int i = 1; i <= vv_args->_i(1); i++)
        {
            x = int64hash(x);
            out << (int64_t) x;
        }
        return out;
    }
    else if (vv_args->size() > 1)
    {
        uint64_t x = (uint64_t) vv_args->_i(0);
        for (int i = 1; i <= vv_args->_i(1); i++)
            x = int64hash(x);
        return vv((int64_t) x);
    }
    else
    {
        return vv((int64_t) int64hash((uint64_t) vv_args->_i(0)));
    }
}
//---------------------------------------------------------------------------

VV_CLOSURE_DOC(util_from_csv,
"@util procedure (util-from-csv _csv-string_ _field-sep_ _row-sep_)\n"
"@util procedure (util-from-csv _csv-string_ _field-sep_)\n"
"@util procedure (util-from-csv _csv-string_)\n\n"
"Returns a table like data structure that contains the contents of\n"
"the CSV formatted _csv-string_.\n"
"_field-sep_ contains the field separator, usually something like `,` or `;` (default).\n"
"_row-sep_ contains the row/field set separator,\n"
"usually something like `\"\\r\\n\"` (default).\n"
"\n"
"    (util-from-csv \"a,b,c\\r\\nd,e,f\" \",\" \"\\r\\n\")\n"
"    ;=> [[\"a\",\"b\",\"c\"],[\"d\",\"e\",\"f\"]]\n"
)
{
    string sep     = vv_args->_s(1);
    string row_sep = vv_args->_s(2);
    if (sep.empty()) sep = ",";
    if (row_sep.empty()) row_sep = "\r\n";
    return VVal::csv::from_csv(vv_args->_s(0), sep[0], row_sep);
}
//---------------------------------------------------------------------------

VV_CLOSURE_DOC(util_to_csv,
"@util procedure (util-to-csv _data_ _field-sep_ _row-sep_)\n"
"@util procedure (util-to-csv _data_ _field-sep_)\n"
"@util procedure (util-to-csv _data_)\n\n"
"Serializes the table data structure _data_ as CSV.\n"
"_field-sep_ contains the field separator, usually something like `,` or `;` (default).\n"
"_row-sep_ contains the row/field set separator,\n"
"usually something like `\"\\r\\n\"` (default).\n"
"\n"
"    (util-from-csv \"a,b,c\\r\\nd,e,f\" \",\" \"\\r\\n\")\n"
"    ;=> [[\"a\",\"b\",\"c\"],[\"d\",\"e\",\"f\"]]\n"
)
{
    string sep     = vv_args->_s(1);
    string row_sep = vv_args->_s(2);
    if (sep.empty()) sep = ",";
    if (row_sep.empty()) row_sep = "\r\n";
    return vv(VVal::csv::to_csv(vv_args->_(0), sep[0], row_sep));
}
//---------------------------------------------------------------------------

VV_CLOSURE_DOC(util_to_json,
"@util procedure (util-to-json _data_ _do-indent-bool_)\n"
"@util procedure (util-to-json _data_)\n\n"
"Returns the _data_ structure as JSON and UTF-8 encoded string.\n"
"If the optional parameter _do-indent-bool_ is true, the output will be pretty printed.\n"
)
{
    return vv(VVal::as_json(vv_args->_(0), vv_args->_b(1)));
}
//---------------------------------------------------------------------------

VV_CLOSURE_DOC(util_from_json,
"@util procedure (util-from-json _string_)\n\n"
"Interpretes _string_ as UTF-8 encoded JSON.\n"
)
{
    return VVal::from_json(vv_args->_s(0));
}
//---------------------------------------------------------------------------

VV_CLOSURE_DOC(util_to_utf8,
"@util procedure (util-to-utf8 _string-or-bytes_ _source-encoding-name_)\n\n"
"Reencodes the character set of _string-or-bytes_ by interpreting it as\n"
"if it is encoded in _source-encoding-name_.\n"
"Returns an UTF8 encoded text string.\n"
"\n"
"Possible _encoding-names_:\n"
"    - Latin1\n"
"    - ISO-8859-8\n"
"    - UTF-16\n"
"    - UTF-16BE\n"
"    - UTF-16LE\n"
"    - UTF-32\n"
"    - UTF-32BE\n"
"    - UTF-32LE\n"
"    - CP1250 / WINDOWS-1250\n"
"    - 850 / CP850\n"
"And many others that are supported by boost::local (iconv/icu)\n"
)
{
    return vv(boost::locale::conv::to_utf<char>(vv_args->_s(0), vv_args->_s(1)));
}
//---------------------------------------------------------------------------

VV_CLOSURE_DOC(util_from_utf8,
"@util:bklisp-util procedure (util-from-utf8 _string_ _dest-encoding-name_)\n\n"
"Decodes the UTF8 _string_ into the _dest-encoding-name_\n"
"Returns the bytes of the result.\n"
"See also `util-to-utf8`.\n"
)
{
    return vv_bytes(boost::locale::conv::from_utf<char>(vv_args->_s(0), vv_args->_s(1)));
}
//---------------------------------------------------------------------------

struct regex_matcher
{
    std::string m_str;
    RE_PREFIX::regex *m_r;
    bool        m_keep_first_submatch;
    bool        m_match_whole_string;
    bool        m_global_match;
    bool        m_with_token;
    std::string m_token;
    bool        m_split;
    regex_matcher()
        : m_r(nullptr),
          m_keep_first_submatch(false),
          m_match_whole_string(false),
          m_global_match(false),
          m_with_token(false),
          m_split(false) {}

    ~regex_matcher()
    {
        if (m_r) delete m_r;
    }

    void new_regex(const std::string &s, RE_PREFIX::regex::flag_type flags)
    {
        m_str = s;
        m_r = new RE_PREFIX::regex(m_str, flags);
    }

    bool apply_regex_replace(const VV &str, VV &results)
    {
        std::string in(str->_s(0));
        std::string repl(str->_s(1));
        VV s = vv(RE_PREFIX::regex_replace(in, *m_r, repl));

        if (m_with_token)
        {
            results = vv_list();
            if (m_with_token) results << vv(m_token);
            results << s;
        }
        else
            results = s;

        return true;
    }

    bool apply_regex_global(const VV &str, VV &results)
    {
        std::string in(str->s());
        RE_PREFIX::regex_iterator<std::string::iterator> rit (in.begin(), in.end(), *m_r);
        RE_PREFIX::regex_iterator<std::string::iterator> rend;
        if (rit == rend)
            return false;

        results = vv_list();
        if (m_with_token) results << vv(m_token);
        while (rit != rend)
        {
            int start_idx = (m_keep_first_submatch ? 0 : 1);

            if (flat_submatch_result())
                results << vv((*rit)[start_idx].str());
            else
            {
                VV sub(vv_list());
                for (size_t i = start_idx; i < rit->size(); i++)
                    sub << vv((*rit)[i].str());
                results << sub;
            }
            ++rit;
        }

        return true;
    }

    bool apply_regex_split(const VV &str, VV &results)
    {
        std::string in(str->s());
        RE_PREFIX::sregex_token_iterator rit(in.begin(), in.end(), *m_r, -1);
        RE_PREFIX::sregex_token_iterator rend;
        if (rit == rend)
        {
            results = vv_list();
            if (m_with_token) results << vv(m_token);
            results << vv(in);
            return true;
        }

        results = vv_list();
        if (m_with_token) results << vv(m_token);
        while (rit != rend)
        {
            results << vv(*rit);
            ++rit;
        }

        return true;
    }

    bool apply_regex_match(const VV &str, VV &results)
    {
        bool b = false;
        std::string in(str->s());
        RE_PREFIX::smatch m;
        if (m_match_whole_string)
            b = RE_PREFIX::regex_match(in, m, *m_r);
        else
            b = RE_PREFIX::regex_search(in, m, *m_r);

        if (b)
        {
            int start_idx = (m_keep_first_submatch ? 0 : 1);

            if (!m_with_token && flat_submatch_result())
            {
                results = vv(m[start_idx].str());
            }
            else
            {
                results = vv_list();
                if (m_with_token) results << vv(m_token);
                for (size_t i = start_idx; i < m.size(); i++)
                {
                    results << vv(m[i].str());
                }
            }
        }
        return b;
    }

    bool operator()(const VV &str, VV &results)
    {
        if (str->is_list())      return apply_regex_replace(str, results);
        else if (m_global_match) return apply_regex_global(str, results);
        else if (m_split)        return apply_regex_split(str, results);
        else                     return apply_regex_match(str, results);
    }

    bool flat_submatch_result() const
    {
        return
            (m_keep_first_submatch && m_r->mark_count() == 0)
            || (!m_keep_first_submatch && m_r->mark_count() == 1);
    }
};
//---------------------------------------------------------------------------

static regex_matcher *build_single_regex_matcher(bool with_token, const VV &desc)
{
    RE_PREFIX::regex::flag_type flags = RE_PREFIX::regex::ECMAScript;
    regex_matcher *o = new regex_matcher;

    if (desc->is_list() && desc->_(1)->is_string())
    {
        std::string f = desc->_s(1);
        if (f.find_first_of("0") != std::string::npos) flags |= RE_PREFIX::regex::ECMAScript;
        if (f.find_first_of("1") != std::string::npos) flags |= RE_PREFIX::regex::extended;
        if (f.find_first_of("2") != std::string::npos) flags |= RE_PREFIX::regex::awk;
        if (f.find_first_of("3") != std::string::npos) flags |= RE_PREFIX::regex::grep;
        if (f.find_first_of("4") != std::string::npos) flags |= RE_PREFIX::regex::egrep;
        if (f.find_first_of("5") != std::string::npos) flags |= RE_PREFIX::regex::basic;
#if !USE_STD_REGEX
        if (f.find_first_of("6") != std::string::npos) flags |= RE_PREFIX::regex::perl;
#endif
        if (f.find_first_of("i") != std::string::npos) flags |= RE_PREFIX::regex::icase;
        if (f.find_first_of("o") != std::string::npos) flags |= RE_PREFIX::regex::optimize;
        if (f.find_first_of("c") != std::string::npos) flags |= RE_PREFIX::regex::collate;
        if (f.find_first_of("k") != std::string::npos) o->m_keep_first_submatch = true;
        if (f.find_first_of("a") != std::string::npos) o->m_match_whole_string  = true;
        if (f.find_first_of("g") != std::string::npos) o->m_global_match        = true;
        if (f.find_first_of("s") != std::string::npos) o->m_split               = true;
    }
    else
    {
        flags = RE_PREFIX::regex::ECMAScript;
    }

//    L_TRACE << "MKRE[" << (desc->is_list() ? desc->_s(0) : desc->s()) << "]";
    o->new_regex(desc->is_list() ? desc->_s(0) : desc->s(), flags);

    o->m_with_token = with_token;
    o->m_token      = desc->_s(2);

    return o;
}
//---------------------------------------------------------------------------

static void build_regex_from_desc(const VV &desc, std::list<regex_matcher *> &regexes)
{
    if (!desc->_(0)->is_list())
    {
        regexes.push_back(
            build_single_regex_matcher(desc->_(2)->is_defined(), desc));
    }
    else
    {
        bool with_token = false;
        for (auto i : *desc)
        {
            if (i->is_list() && i->_(2)->is_defined())
            {
                with_token = true;
                break;
            }
        }

        for (auto i : *desc)
            regexes.push_back(build_single_regex_matcher(with_token, i));
    }
}

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

VV_CLOSURE_DOC(util_re,
"@util procedure (util-re _strings_ _regexes_ _return-format-mode-str_)\n\n"
"Matches _strings_ using _regexes_. Can also be just a single string and\n"
"a single regex. Regex format is ECMAScript standard like C++11 uses it.\n"
"\n"
"_strings_ may look like in these examples:\n"
"   _strings_ = _string_\n"
"   _strings_ = [_string_, ...]\n"
"   _strings_ = [[_string_, _replacement_], ...]\n"
"_regexes_ may look like in these examples:\n"
"   _regexes_ = _regex_\n"
"   _regexes_ = [_regex_, _flags_, _match-token_]\n"
"   _regexes_ = [[_regex_, _flags_, _match-token_], ...]\n"
"Where _flags_ and _match-token_ are optional.\n"
"_match-token_ contains some value, that is used to identify the regex that was\n"
"matched.\n"
"_flags_ may be one or multiple of:\n"
"   _flags_:\n"
"       \"i\"   - case insensitive match\n"
"       \"o\"   - optimize for matching fast\n"
"       \"c\"   - collate according to current locale\n"
"       \"w\"   - widestring match (for UTF-8 input).\n"
"       \"k\"   - keep first submatch (the string itself) in the output.\n"
"       \"0\"   - Regex Grammar: ECMAScript (default)\n"
"       \"1\"   - Regex Grammar: Extended POSIX\n"
"       \"2\"   - Regex Grammar: Awk POSIX\n"
"       \"3\"   - Regex Grammar: Grep POSIX\n"
"       \"4\"   - Regex Grammar: Egrep POSIX\n"
"       \"5\"   - Regex Grammar: Basic POSIX\n"
"       \"6\"   - Regex Grammar: Perl Regex syntax (only with boost::regex)\n"
"       \"a\"   - will match the whole string, instead of just a part of it\n"
"                 against the regex.\n"
"       \"g\"   - globally match the pattern multiple times.\n"
"                 (does not work with replacements.)\n"
"       \"s\"   - split up the string using this regex.\n"
"                 (does not work with replacements.)\n"
//"       \"r\"   - required match (this makes matching this regex\n"
//"                 a requirement for matching any string.)\n"
//"       \"R\"   - must not match (no string matches, that matches\n"
//"                 this regex.)\n"
"_replacement_ may contain:\n"
"       $n      - backreference\n"
"       $&      - entire match\n"
"       $`      - prefix\n"
"       $´      - suffix\n"
"       $$      - '$' itself\n"
"_return-format-mode-str_ may be empty or contain:\n"
"       \"n\"   - string index-numbers\n"
"\n"
"Examples:\n"
"\n"
"  \n; Searching for a match inside a string:\n"
"    (util-re \"foobar\" \"(o+)\")  ;=> \"oo\"\n"
"  \n; Matching a complete string:\n"
"    (util-re \"foobar\" [\"f(o+)bar\" \"a\"]) ;=> \"oo\"\n"
"    ; or:\n"
"    (util-re \"foobar\" \"^f(o+)bar$\") ;=> \"oo\"\n"
"  \n; Multiple sub-matches:\n"
"    (util-re \"foobar\" \"(o+).*(a+)\")  ;=> (\"oo\" \"a\")\n"
"  \n; Global match:\n"
"    (util-re \"foobarfoobfbffoooorer\" [\"f(o+)\" \"g\"])  ;=> (\"oo\" \"oo\" \"oooo\")\n"
"    (util-re \"foobarfoobfbffoooorer\" [\"(f(o+))\" \"g\"])\n"
"    ;=> ((\"foo\" \"oo\") (\"foo\" \"oo\") (\"foooo\" \"oooo\"))\n"
"  \n; Split string:\n"
"    (util-re \"foo, bar ,  baz,boo\" [\"\\s*,\\s*\" \"s\"])  ;=> (\"foo\" \"bar\" \"baz\" \"boo\")\n"
)
{
    std::list<regex_matcher *> matchers;

    std::string ret_mode = vv_args->_s(2);
    bool with_string_idxs = ret_mode.find_first_of("n") != std::string::npos;

    build_regex_from_desc(vv_args->_(1), matchers);

    VV ret = vv_list();
    int string_idx = 0;
    for (auto s : *(vv_args->_(0)))
    {
        for (auto m : matchers)
        {
            VV results;
            if ((*m)(s, results))
            {
                if (with_string_idxs)
                {
                    if (!results->is_list())
                        results = vv_list() << results;
                    results->unshift(vv(string_idx));
                }
                ret << results;
            }
        }
        string_idx++;
    }

    if (ret->size() == 0)
        ret = vv_undef();
    else if (matchers.size() == 1 && string_idx <= 1)
        ret = ret->_(0);

    for (auto m : matchers) delete m;

    return ret;
}
//---------------------------------------------------------------------------

VV_CLOSURE(util_init)
{
    return vv_undef();
}
//---------------------------------------------------------------------------

VV_CLOSURE(util_destroy)
{
    return vv_undef();
}
//---------------------------------------------------------------------------

BukaLISPModule init_utillib()
{
    VV reg(vv_list() << vv("util"));
    VV obj(vv_map());
    VV mod(vv_list());
    reg << mod;

#define SET_FUNC(functionName, closureName) \
    mod << (vv_list() << vv(#functionName) << VVC_NEW_##closureName(obj))

    SET_FUNC(__INIT__,      util_init);
    SET_FUNC(__DESTROY__,   util_destroy);

    SET_FUNC(int-to-ratio,  util_int_to_ratio);
    SET_FUNC(xorshift,      util_xorshift);
    SET_FUNC(hash64,        util_hash64);
    SET_FUNC(from-csv,      util_from_csv);
    SET_FUNC(to-csv,        util_to_csv);
    SET_FUNC(to-utf8,       util_to_utf8);
    SET_FUNC(from-utf8,     util_from_utf8);
    SET_FUNC(to-json,       util_to_json);
    SET_FUNC(from-json,     util_from_json);
    SET_FUNC(re,            util_re);

    return BukaLISPModule(reg);
}
//---------------------------------------------------------------------------


/* boost random/mersenne_twister.hpp header file
 *
 * Copyright Jens Maurer 2000-2001
 * Permission to use, copy, modify, sell, and distribute this software
 * is hereby granted without fee provided that the above copyright notice
 * appears in all copies and that both that copyright notice and this
 * permission notice appear in supporting documentation,
 *
 * Jens Maurer makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * See http://www.boost.org for most recent version including documentation.
 *
 * $Id: mersenne_twister.hpp 12208 2002-01-03 22:21:34Z jmaurer $
 *
 * Revision history
 *  2001-02-18  moved to individual header files
 */

#ifndef BOOST_RANDOM_MERSENNE_TWISTER_HPP
#define BOOST_RANDOM_MERSENNE_TWISTER_HPP

#include <iostream>
#include <algorithm>     // std::copy
#include <boost/config.hpp>
#include <boost/limits.hpp>
#include <boost/static_assert.hpp>
#include <boost/integer_traits.hpp>
#include <boost/cstdint.hpp>
#include <boost/random/linear_congruential.hpp>

namespace boost {
namespace random {

// http://www.math.keio.ac.jp/matumoto/emt.html
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
class mersenne_twister
{
public:
  typedef DataType result_type;
  BOOST_STATIC_CONSTANT(int, state_size = n);
  BOOST_STATIC_CONSTANT(int, shift_size = m);
  BOOST_STATIC_CONSTANT(int, mask_bits = r);
  BOOST_STATIC_CONSTANT(DataType, parameter_a = a);
  BOOST_STATIC_CONSTANT(int, output_u = u);
  BOOST_STATIC_CONSTANT(int, output_s = s);
  BOOST_STATIC_CONSTANT(DataType, output_b = b);
  BOOST_STATIC_CONSTANT(int, output_t = t);
  BOOST_STATIC_CONSTANT(DataType, output_c = c);
  BOOST_STATIC_CONSTANT(int, output_l = l);
#ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION
  static const bool has_fixed_range = true;
  static const result_type min_value = integer_traits<result_type>::const_min;
  static const result_type max_value = integer_traits<result_type>::const_max;
#else
  BOOST_STATIC_CONSTANT(bool, has_fixed_range = false);
#endif
  result_type min() const { return std::numeric_limits<result_type>::min(); }
  result_type max() const { return std::numeric_limits<result_type>::max(); }

  mersenne_twister() { seed(); }

#if defined(__SUNPRO_CC) && (__SUNPRO_CC <= 0x520)
  // Work around overload resolution problem (Gennadiy E. Rozental)
  explicit mersenne_twister(const DataType& value)
#else
  explicit mersenne_twister(DataType value)
#endif
  { seed(value); }

  template<class Generator>
  explicit mersenne_twister(Generator & gen) { seed(gen); }

  // compiler-generated copy ctor and assignment operator are fine

  void seed() { seed(DataType(4357)); }

#if defined(__SUNPRO_CC) && (__SUNPRO_CC <= 0x520)
  // Work around overload resolution problem (Gennadiy E. Rozental)
  void seed(const DataType& value)
#else
  void seed(DataType value)
#endif
  {
    random::linear_congruential<uint32_t, 69069, 0, 0, /* unknown */ 0>
      gen(value);
    seed(gen);
  }

  // For GCC, moving this function out-of-line prevents inlining, which may
  // reduce overall object code size.  However, MSVC does not grok
  // out-of-line definitions of member function templates.
  template<class Generator>
  void seed(Generator & gen)
  {
#ifndef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS
    BOOST_STATIC_ASSERT(!std::numeric_limits<result_type>::is_signed);
#endif
    // I could have used std::generate_n, but it takes "gen" by value
    for(int j = 0; j < n; j++)
      x[j] = gen();
    i = n;
  }

  result_type operator()();
  bool validation(result_type v) const { return val == v; }

#ifndef BOOST_NO_OPERATORS_IN_NAMESPACE
  friend std::ostream& operator<<(std::ostream& os, const mersenne_twister& mt)
  {
    os << mt.i << " ";
    std::copy(mt.x, mt.x+n, std::ostream_iterator<data_type>(os, " "));
    return os;
  }
  friend std::istream& operator>>(std::istream& is, mersenne_twister& mt)
  {
    is >> mt.i >> std::ws;
    for(int i = 0; i < mt.state_size; ++i)
      is >> mt.x[i] >> std::ws;
    return is;
  }
  friend bool operator==(const mersenne_twister& x, const mersenne_twister& y)
  { return x.i == y.i && std::equal(x.x, x.x+n, y.x); }
#else
  // Use a member function; Streamable concept not supported.
  bool operator==(const mersenne_twister& rhs) const
  { return i == rhs.i && std::equal(x, x+n, rhs.x); }
#endif

private:
  typedef DataType data_type;
  void twist();
  int i;
  data_type x[n];
};

#ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION
//  A definition is required even for integral static constants
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const bool mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::has_fixed_range;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const typename mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::result_type mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::min_value;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const typename mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::result_type mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::max_value;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const int mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::state_size;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const int mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::shift_size;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const int mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::mask_bits;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const DataType mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::parameter_a;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const int mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::output_u;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const int mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::output_s;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const DataType mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::output_b;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const int mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::output_t;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const DataType mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::output_c;
template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
const int mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::output_l;
#endif

template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
void mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::twist()
{
  const data_type upper_mask = (~0u) << r;
  const data_type lower_mask = ~upper_mask;
  /*
  for(int j = 0; j < n; j++) {
    // Step 2
    data_type y = (x[j] & upper_mask) | (x[(j+1)%n] & lower_mask);
    // Step 3
    x[j] = x[(j+m)%n] ^ (y >> 1) ^ (y&1 ? a : 0);
  }
  */
  // split loop to avoid costly modulo operations
  {  // extra scope for MSVC brokenness w.r.t. for scope
  for(int j = 0; j < n-m; j++) {
    data_type y = (x[j] & upper_mask) | (x[j+1] & lower_mask);
    x[j] = x[j+m] ^ (y >> 1) ^ (y&1 ? a : 0);
  }
  }

  for(int j = n-m; j < n-1; j++) {
    data_type y = (x[j] & upper_mask) | (x[j+1] & lower_mask);
    x[j] = x[j+m-n] ^ (y >> 1) ^ (y&1 ? a : 0);
  }
  // last iteration
  data_type y = (x[n-1] & upper_mask) | (x[0] & lower_mask);
  x[n-1] = x[n-1+m-n] ^ (y >> 1) ^ (y&1 ? a : 0);

  i = 0;
}

template<class DataType, int n, int m, int r, DataType a, int u,
  int s, DataType b, int t, DataType c, int l, DataType val>
inline typename mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::result_type
mersenne_twister<DataType,n,m,r,a,u,s,b,t,c,l,val>::operator()()
{
  if(i >= n)
    twist();
  // Step 4
  data_type z = x[i];
  ++i;
  z ^= (z >> u);
  z ^= ((z << s) & b);
  z ^= ((z << t) & c);
  z ^= (z >> l);
  return z;
}

} // namespace random

typedef random::mersenne_twister<uint32_t,351,175,19,0xccab8ee7,11,
  7,0x31b6ab00,15,0xffe50000,17, /* unknown */ 0> mt11213b;

// validation by experiment from mt19937.c
typedef random::mersenne_twister<uint32_t,624,397,31,0x9908b0df,11,
  7,0x9d2c5680,15,0xefc60000,18, 3346425566U> mt19937;

} // namespace boost

#endif // BOOST_RANDOM_MERSENNE_TWISTER_HPP


/**
 * @file barConnectionInfoTest.cpp
 * @author Luiz Victor Linhares Rocha (luizvictorlrocha@gmail.com)
 * @brief test the Pmu connection
 * @version 0.1
 * @date 2018-10-08
 *
 * @copyright Copyright (c) 2018
 *
 */

#define BOOST_TEST_DYN_LINK

#include <random>
#include <fstream>
#include <vector>
#include <boost/test/unit_test.hpp>
#include <boost/test/test_tools.hpp>
#include "../rede/neuralFluxDeclare.hpp"
#include "../rede/cdfReader.hpp"
#include "../rede/powerNet.hpp"
#include "../rede/netStateHandler.hpp"
#include "../rede/pmuConnector.hpp"
#include "../rede/netSolutionPrinter.hpp"
#include "../rede/powerFlowSolver.hpp"
#include "../rede/gausSiedelSolver.hpp"
#include "../rede/barConnectionInfo.hpp"

using neuralFlux::PowerNetPtr;
using neuralFlux::NetStateHandler;
using neuralFlux::PmuConnector;
using neuralFlux::CdfReader;
using neuralFlux::NetSolutionPrinter;
using neuralFlux::GausSeidelSolver;
using neuralFlux::BarConnectionInfoPtr;
using std::ifstream;
using std::ofstream;
using std::vector;

BOOST_AUTO_TEST_CASE(net_solution_printer_complete_solution) {
    CdfReader reader;
    ifstream input("/home/luiz/projects/neuralPowerFlow/netData/ieee14cdf.txt");
    PowerNetPtr net = reader.readNet(input);
    PmuConnector conn(net);
    std::default_random_engine eng(0);
    std::vector<BarConnectionInfoPtr> pmuData = conn.insertPmus(&eng);
    std::vector<int> pmuPos = conn.getPmuBars(pmuData);
    NetStateHandler stateHandler(net);
    GausSeidelSolver solver(net);
    stateHandler.shuffleNet(
        0.01,
        0.20,
        0.01,
        0.02,
        0.005,
        0.005,
        0.01,
        0.01,
        &eng);
    solver.solveNet(true, 0.00001, 50000);
    ofstream inputFile("input.csv");
    ofstream resolutionFile("output.csv");
    NetSolutionPrinter printer(net);
    printer.printHeaderWithPmu(inputFile, resolutionFile, pmuPos);
    printer.printSolutionsWithPmu(inputFile, resolutionFile, pmuPos);
}


/*
@copyright Louis Dionne 2014
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/integer_list.hpp>

#include <boost/hana/detail/assert.hpp>
#include <boost/hana/integral.hpp>
using namespace boost::hana;

int main() {
    BOOST_HANA_CONSTANT_ASSERT(head(integer_list<int, 0>) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(head(integer_list<int, 0, 1>) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(head(integer_list<int, 0, 1, 2>) == int_<0>);
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// This file was modified by Oracle on 2014.
// Modifications copyright (c) 2014 Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <geometry_test_common.hpp>

#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/radius.hpp>
#include <boost/geometry/core/srs.hpp>

#include <boost/geometry/algorithms/make.hpp>

template <std::size_t I, typename G>
void test_get_set()
{
    typedef typename bg::radius_type<G>::type radius_type;

    G g;
    bg::set_radius<I>(g, radius_type(5));

    radius_type x = bg::get_radius<I>(g);

    BOOST_CHECK_CLOSE(double(x), 5.0, 0.0001);
}

template <typename T>
void test_all()
{
    typedef bg::srs::spheroid<T> Sd;
    test_get_set<0, Sd>();
    test_get_set<2, Sd>();

    typedef bg::srs::sphere<T> Se;
    test_get_set<0, Se>();
}

int test_main(int, char* [])
{
    test_all<int>();
    test_all<float>();
    test_all<double>();

    return 0;
}


// libgp - Gaussian process library for Machine Learning
// Copyright (c) 2011, Manuel Blum <mblum@informatik.uni-freiburg.de>
// All rights reserved.

#include "cov_factory.h"

#include <Eigen/Dense>
#include <gtest/gtest.h>

#if GTEST_HAS_PARAM_TEST

using ::testing::TestWithParam;
using ::testing::Values;

class GradientTest : public TestWithParam<std::string> {
  protected:
    virtual void SetUp() {
      n = 3;
      e = 1e-8;
      covf = factory.create(n, GetParam());
      param_dim = covf->get_param_dim();
      params = Eigen::VectorXd::Random(param_dim);
      x1 = Eigen::VectorXd::Random(n);
      x2 = Eigen::VectorXd::Random(n);
      covf->set_loghyper(params);
    }
    virtual void TearDown() {
      delete covf;
    }
    int n, param_dim;
    libgp::CovFactory factory;
    libgp::CovarianceFunction * covf;
    double e;
    Eigen::VectorXd params;
    Eigen::VectorXd x1;
    Eigen::VectorXd x2;
    Eigen::VectorXd gradient() {
      Eigen::VectorXd grad(param_dim);
      covf->grad(x1, x2, grad);
      return grad;
    }
    double numerical_gradient(int i) {
      double theta = params(i);
      params(i) = theta - e;
      covf->set_loghyper(params);
      double j1 = covf->get(x1, x2);
      params(i) = theta + e;
      covf->set_loghyper(params);
      double j2 = covf->get(x1, x2);
      params(i) = theta;
      return (j2-j1)/(2*e);
    }
};

TEST_P(GradientTest, EqualToNumerical) {
  Eigen::VectorXd grad = gradient();
  for (int i=0; i<param_dim; ++i) {
    if (grad(i) == 0.0) ASSERT_NEAR(numerical_gradient(i), 0.0, 1e-2);
    else ASSERT_NEAR((numerical_gradient(i)-grad(i))/grad(i), 0.0, 1e-2);
  }
}

INSTANTIATE_TEST_CASE_P(CovarianceFunction, GradientTest, Values(
          "CovLinearard",
          "CovLinearone",
          "CovMatern3iso",
          "CovMatern5iso",
          "CovNoise",
          "CovProd(CovSEiso, CovMatern3iso)",
          "CovRQiso",
          "CovSEard",
          "CovSEiso",
          "CovSum(CovSEiso, CovNoise)",
          "CovSum(CovLinearard, CovNoise)",
          "InputDimFilter(1/CovSEiso)",
          "InputDimFilter(0/CovSum(CovSEiso, CovNoise))"
          ));

TEST(FilterTest, EqualToVector) {

}

#else

// Google Test may not support value-parameterized tests with some
// compilers. If we use conditional compilation to compile out all
// code referring to the gtest_main library, MSVC linker will not link
// that library at all and consequently complain about missing entry
// point defined in that library (fatal error LNK1561: entry point
// must be defined). This dummy test keeps gtest_main linked in.
TEST(DummyTest, ValueParameterizedTestsAreNotSupportedOnThisPlatform) {}

#endif  // GTEST_HAS_PARAM_TEST


/* auto_encoder_trainer.cc
   Jeremy Barnes, 11 November 2009
   Copyright (c) 2009 Jeremy Barnes.  All rights reserved.

   Trainer for an auto-encoder.
*/

#include "auto_encoder_trainer.h"
#include "jml/utils/configuration.h"
#include "jml/arch/threads.h"

#include <boost/progress.hpp>
#include "jml/utils/worker_task.h"
#include <boost/tuple/tuple.hpp>
#include "jml/utils/guard.h"
#include "jml/utils/configuration.h"
#include "jml/arch/timers.h"
#include <boost/bind.hpp>
#include "auto_encoder_stack.h"
#include "jml/utils/check_not_nan.h"
#include "jml/stats/distribution_ops.h"

using namespace std;

namespace ML {

/*****************************************************************************/
/* AUTO_ENCODER_TRAINER                                                      */
/*****************************************************************************/

Auto_Encoder_Trainer::
Auto_Encoder_Trainer()
{
    defaults();
}

void
Auto_Encoder_Trainer::
defaults()
{
    learning_rate = 0.75;
    minibatch_size = 512;
    niter = 50;
    prob_cleared = 0.10;
    verbosity = 2;
    randomize_order = true;
    sample_proportion = 0.8;
    test_every = 1;
    prob_any_noise = 0.5;
    stack_backprop_iter = 0;
    individual_learning_rates = false;
    weight_decay_l1 = 0.0;
    weight_decay_l2 = 0.0;
    dump_testing_output = 0;
}

void
Auto_Encoder_Trainer::
configure(const std::string & name, const Configuration & config)
{
    config.get(prob_cleared, "prob_cleared");
    config.get(learning_rate, "learning_rate");
    config.get(minibatch_size, "minibatch_size");
    config.get(niter, "niter");
    config.get(verbosity, "verbosity");
    config.get(randomize_order, "randomize_order");
    config.get(sample_proportion, "sample_proportion");
    config.get(test_every, "test_every");
    config.get(prob_any_noise, "prob_any_noise");
    config.get(stack_backprop_iter, "stack_backprop_iter");
    config.get(individual_learning_rates, "individual_learning_rates");
    config.get(weight_decay_l1, "weight_decay_l1");
    config.get(weight_decay_l2, "weight_decay_l2");
    config.get(dump_testing_output, "dump_testing_output");
}

template<typename Float>
distribution<Float>
Auto_Encoder_Trainer::
add_noise(const distribution<Float> & inputs,
          Thread_Context & context,
          bool force_noise) const
{
    distribution<Float> result = inputs;

    if (!force_noise
        && (prob_any_noise == 0.0
            || context.random01() > prob_any_noise)) return result;

    for (unsigned i = 0;  i < inputs.size();  ++i)
        if (context.random01() < prob_cleared)
            result[i] = std::numeric_limits<float>::quiet_NaN();

    return result;
}

std::pair<double, double>
Auto_Encoder_Trainer::
train_example(const Auto_Encoder & encoder,
              const distribution<float> & inputs_,
              Parameters & updates,
              Thread_Context & context) const
{
    // What precision do we do the calculations in?
    typedef double Float;

    distribution<Float> inputs(inputs_);

    distribution<Float> noisy_inputs
        = add_noise(inputs, context, false /* force_noise */);

    size_t temp_space_size = encoder.rfprop_temporary_space_required();

    Float temp_space[temp_space_size];

    distribution<Float> reconstruction(inputs.size());

    // Forward propagate (calculate the reconstruction from the noisy
    // input)

    encoder.rfprop(&noisy_inputs[0], temp_space, temp_space_size,
                   &reconstruction[0]);

    // Calculate the error (difference between the reconstruction and the
    // input) and the error gradient

    distribution<Float> error = inputs - reconstruction;
    distribution<Float> derror = -2.0 * error;

    // Backpropagate the error gradient through the parameters

    encoder.rbprop(&noisy_inputs[0], &reconstruction[0],
                   temp_space, temp_space_size,
                   &derror[0], 0 /* input_errors_out */, updates, 1.0);

    // Calculate the exact error as well
    distribution<Float> exact_error;

    if (!equivalent(noisy_inputs, inputs)) {
        distribution<Float> exact_reconstruction
            = encoder.reconstruct(inputs);

        exact_error = inputs - exact_reconstruction;
    }
    else exact_error = error;

    return make_pair(exact_error.dotprod(exact_error), error.dotprod(error));
}

namespace {

struct Train_Examples_Job {

    const Auto_Encoder_Trainer & trainer;
    const Auto_Encoder & layer;
    const vector<distribution<float> > & data;
    int first;
    int last;
    const vector<int> & examples;
    const Thread_Context & context;
    int random_seed;
    Parameters_Copy<double> & updates;
    Lock & update_lock;
    double & error_exact;
    double & error_noisy;
    boost::progress_display * progress;

    Train_Examples_Job(const Auto_Encoder_Trainer & trainer,
                       const Auto_Encoder & layer,
                       const vector<distribution<float> > & data,
                       int first, int last,
                       const vector<int> & examples,
                       const Thread_Context & context,
                       int random_seed,
                       Parameters_Copy<double> & updates,
                       Lock & update_lock,
                       double & error_exact,
                       double & error_noisy,
                       boost::progress_display * progress)
        : trainer(trainer),
          layer(layer), data(data), first(first), last(last),
          examples(examples),
          context(context), random_seed(random_seed), updates(updates),
          update_lock(update_lock),
          error_exact(error_exact), error_noisy(error_noisy),
          progress(progress)
    {
    }

    void operator () ()
    {
        Thread_Context thread_context(context);
        thread_context.seed(random_seed);

        double total_error_exact = 0.0, total_error_noisy = 0.0;

        Parameters_Copy<double> local_updates(layer, 0.0);

        for (unsigned x = first;  x < last;  ++x) {

            double eex, eno;
            boost::tie(eex, eno)
                = trainer.train_example(layer, data[x],
                                        local_updates,
                                        thread_context);

            total_error_exact += eex;
            total_error_noisy += eno;
        }

        Guard guard(update_lock);

        //cerr << "applying local updates" << endl;
        updates.values += local_updates.values;

        error_exact += total_error_exact;
        error_noisy += total_error_noisy;

        if (progress)
            (*progress) += (last - first);
    }
};

} // file scope

std::pair<double, double>
Auto_Encoder_Trainer::
train_iter(Auto_Encoder & encoder,
           const std::vector<distribution<float> > & data,
           Thread_Context & thread_context,
           double learning_rate) const
{
    Worker_Task & worker = thread_context.worker();

    int nx = data.size();
    int ni JML_UNUSED = encoder.inputs();
    int no JML_UNUSED = encoder.outputs();

    int microbatch_size = minibatch_size / (num_threads() * 4);

    Lock update_lock;

    double total_mse_exact = 0.0, total_mse_noisy = 0.0;

    vector<int> examples;
    for (unsigned x = 0;  x < nx;  ++x) {
        // Randomly exclude some samples
        if (thread_context.random01() >= sample_proportion)
            continue;
        examples.push_back(x);
    }

    if (randomize_order) {
        Thread_Context::RNG_Type rng = thread_context.rng();
        std::random_shuffle(examples.begin(), examples.end(), rng);
    }

    int nx2 = examples.size();

    std::unique_ptr<boost::progress_display> progress;
    if (verbosity >= 3) progress.reset(new boost::progress_display(nx2, cerr));

    for (unsigned x = 0;  x < nx2;  x += minibatch_size) {

        Parameters_Copy<double> updates(encoder.parameters(), 0.0);

        // Now, submit it as jobs to the worker task to be done
        // multithreaded
        int group;
        {
            int parent = -1;  // no parent group
            group = worker.get_group(NO_JOB, "dump user results task",
                                     parent);

            // Make sure the group gets unlocked once we've populated
            // everything
            Call_Guard guard(boost::bind(&Worker_Task::unlock_group,
                                         boost::ref(worker),
                                         group));

            for (unsigned x2 = x;  x2 < nx2 && x2 < x + minibatch_size;
                 x2 += microbatch_size) {

                Train_Examples_Job job(*this,
                                       encoder,
                                       data,
                                       x2,
                                       min<int>(nx2,
                                                min(x + minibatch_size,
                                                    x2 + microbatch_size)),
                                       examples,
                                       thread_context,
                                       thread_context.random(),
                                       updates,
                                       update_lock,
                                       total_mse_exact,
                                       total_mse_noisy,
                                       progress.get());

                // Send it to a thread to be processed
                worker.add(job, "blend job", group);
            }
        }

        worker.run_until_finished(group);

        // If we have weight decay, include that in the parameter updates
        // * l1 weight decay: we move each parameter towards zero by the
        //   same amount;
        // * l2 weight decay: we move each parameter towards zero by an
        //   amount proportional to the value of the parameter
        // ...

        //cerr << "applying minibatch updates" << endl;

        encoder.parameters().update(updates, -learning_rate);
    }

    return make_pair(sqrt(total_mse_exact / nx2), sqrt(total_mse_noisy / nx2));
}

std::pair<double, double>
Auto_Encoder_Trainer::
train_iter(Auto_Encoder & encoder,
           const std::vector<distribution<float> > & data,
           Thread_Context & thread_context,
           const Parameters_Copy<float> & learning_rates) const
{
    Worker_Task & worker = thread_context.worker();

    int nx = data.size();
    int ni JML_UNUSED = encoder.inputs();
    int no JML_UNUSED = encoder.outputs();

    int microbatch_size = minibatch_size / (num_threads() * 4);

    Lock update_lock;

    double total_mse_exact = 0.0, total_mse_noisy = 0.0;

    vector<int> examples;
    for (unsigned x = 0;  x < nx;  ++x) {
        // Randomly exclude some samples
        if (thread_context.random01() >= sample_proportion)
            continue;
        examples.push_back(x);
    }

    if (randomize_order) {
        Thread_Context::RNG_Type rng = thread_context.rng();
        std::random_shuffle(examples.begin(), examples.end(), rng);
    }

    int nx2 = examples.size();

    std::unique_ptr<boost::progress_display> progress;
    if (verbosity >= 3) progress.reset(new boost::progress_display(nx2, cerr));

    for (unsigned x = 0;  x < nx2;  x += minibatch_size) {

        Parameters_Copy<double> updates(encoder.parameters(), 0.0);

        // Now, submit it as jobs to the worker task to be done
        // multithreaded
        int group;
        {
            int parent = -1;  // no parent group
            group = worker.get_group(NO_JOB, "dump user results task",
                                     parent);

            // Make sure the group gets unlocked once we've populated
            // everything
            Call_Guard guard(boost::bind(&Worker_Task::unlock_group,
                                         boost::ref(worker),
                                         group));

            for (unsigned x2 = x;  x2 < nx2 && x2 < x + minibatch_size;
                 x2 += microbatch_size) {

                Train_Examples_Job job(*this,
                                       encoder,
                                       data,
                                       x2,
                                       min<int>(nx2,
                                                min(x + minibatch_size,
                                                    x2 + microbatch_size)),
                                       examples,
                                       thread_context,
                                       thread_context.random(),
                                       updates,
                                       update_lock,
                                       total_mse_exact,
                                       total_mse_noisy,
                                       progress.get());

                // Send it to a thread to be processed
                worker.add(job, "blend job", group);
            }
        }

        worker.run_until_finished(group);

        //cerr << "applying minibatch updates" << endl;

        updates.values *= -1.0 / minibatch_size;

        encoder.parameters().update(updates, learning_rates);
    }

    return make_pair(sqrt(total_mse_exact / nx2), sqrt(total_mse_noisy / nx2));
}

void
Auto_Encoder_Trainer::
train(Auto_Encoder & encoder,
      const std::vector<distribution<float> > & training_data,
      const std::vector<distribution<float> > & testing_data,
      Thread_Context & thread_context,
      int niter) const
{
    if (niter == -1) niter = this->niter;

    double learning_rate = this->learning_rate;

    int nx = training_data.size();
    int nxt = testing_data.size();

    if (verbosity == 2)
        cerr << "iter      lr  -- inst train--  ---- train ----  ---- test -----\n"
             << "----  ------    exact   noisy    exact   noisy    exact   noisy\n";

    Parameters_Copy<float> learning_rates;

    for (unsigned iter = 0;  iter < niter;  ++iter) {

        if (verbosity >= 2)
            cerr << format("%4d", iter) << flush;

        if (iter % 5 == 0 && !individual_learning_rates) {
#if 0
            learning_rate
                = calc_learning_rate(encoder, training_data, thread_context);
            //cerr << "optimal learning rate calculated was " << learning_rate
            //     << endl;

            learning_rate /= 3.0 * nx * sample_proportion;

            cerr << "calculated learning rate: " << learning_rate
                 << " heuristic learning rate: "
                 << (0.75 / (nx * sample_proportion))
                 << " ratio "
                 << (learning_rate / (0.75 / (nx * sample_proportion)))
                 << endl;
#endif

            learning_rate = this->learning_rate / (nx * sample_proportion);

            //cerr << "learning_rate = " << learning_rate << " nx = " << nx
            //     << endl;
        }
        else if (iter % 5 == 0 && individual_learning_rates) {
            learning_rates
                = calc_learning_rates(encoder, training_data, thread_context);
        }

        if (verbosity >= 3)
            cerr << "iter " << iter << " training on " << nx << " examples"
                 << endl;
        else if (verbosity >= 2)
            cerr << format("  %6.4f", learning_rate) << flush;
        Timer timer;

        double train_error_exact, train_error_noisy;

        if (!individual_learning_rates)
            boost::tie(train_error_exact, train_error_noisy)
                = train_iter(encoder, training_data, thread_context,
                             learning_rate);
        else
            boost::tie(train_error_exact, train_error_noisy)
                = train_iter(encoder, training_data, thread_context,
                             learning_rates);

        if (verbosity >= 3) {
            cerr << "rmse of iteration: exact " << train_error_exact
                 << " noisy " << train_error_noisy << endl;
            if (verbosity >= 3) cerr << timer.elapsed() << endl;
        }
        else if (verbosity == 2)
            cerr << format("  %7.5f %7.5f",
                           train_error_exact, train_error_noisy)
                 << flush;

        if (iter % test_every == (test_every - 1)
            || iter == niter - 1) {
            timer.restart();

            double train_error_exact = 0.0, train_error_noisy = 0.0;

            if (verbosity >= 3)
                cerr << "testing on " << nxt << " examples"
                     << endl;

            boost::tie(train_error_exact, train_error_noisy)
                = test(encoder, training_data, thread_context);

            if (verbosity >= 3) {
                cerr << "training rmse of iteration: exact "
                     << train_error_exact << " noisy " << train_error_noisy
                     << endl;
                cerr << timer.elapsed() << endl;
            }
            else if (verbosity == 2)
                cerr << format("  %7.5f %7.5f",
                               train_error_exact, train_error_noisy);

            double test_error_exact = 0.0, test_error_noisy = 0.0;

            if (verbosity >= 3)
                cerr << "testing on " << nxt << " examples"
                     << endl;

            boost::tie(test_error_exact, test_error_noisy)
                = test(encoder, testing_data, thread_context);

            if (verbosity >= 3) {
                cerr << "testing rmse of iteration: exact "
                     << test_error_exact << " noisy " << test_error_noisy
                     << endl;
                cerr << timer.elapsed() << endl;
            }
            else if (verbosity == 2)
                cerr << format("  %7.5f %7.5f",
                               test_error_exact, test_error_noisy);
        }

        if (verbosity == 2) cerr << endl;
    }
}

double
Auto_Encoder_Trainer::
calc_learning_rate(const Auto_Encoder & layer,
                   const std::vector<distribution<float> > & training_data,
                   Thread_Context & thread_context) const
{
    // See http://videolectures.net/eml07_lecun_wia/ and the slides at
    // http://carbon.videolectures.net/2007/pascal/eml07_whistler/lecun_yann/eml07_lecun_wia_01.pdf especially slide 48.

    // 1.  Pick an initial eigenvector estimate at random

    Parameters_Copy<double> eig(layer);
    for (unsigned i = 0;  i < eig.values.size();  ++i)
        eig.values[i] = 1 / sqrt(eig.values.size());

    // Get a mutable version so that we can modify its parameters
    unique_ptr<Auto_Encoder> modified(layer.deep_copy());

    // Current set of parameters
    Parameters_Copy<double> params(*modified);

    // Temporary space for the fprop/bprop
    size_t temp_space_size = layer.rfprop_temporary_space_required();
    float temp_space[temp_space_size];

    double alpha = 0.0001;
    double gamma = 0.05;

    for (unsigned i = 0;  i < 500;  ++i) {
        if (i == 100) gamma = 0.01;
        if (i == 300) gamma = 0.005;

        //cerr << "i = " << i << " two_norm = " << eig.values.two_norm()
        //     << endl;

        int example_num = min<int>(training_data.size() - 1,
                                   thread_context.random01()
                                   * training_data.size());

        const distribution<float> & exact_input = training_data[example_num];
        distribution<float> noisy_input
            = add_noise(exact_input, thread_context, false /* force_noise */);

        distribution<float> reconstructed(layer.inputs());

        Parameters_Copy<double> gradient1(eig, 0.0);

        layer.rfprop(&noisy_input[0], temp_space, temp_space_size,
                     &reconstructed[0]);

        distribution<float> errors = -2.0 * (exact_input - reconstructed);

        layer.rbprop(&noisy_input[0], &reconstructed[0],
                     temp_space, temp_space_size, &errors[0], 0,
                     gradient1, 1.0);

        Parameters_Copy<double> new_params = params;
        new_params.values += alpha / eig.values.two_norm() * eig.values;

        modified->parameters().set(new_params);

        modified->rfprop(&noisy_input[0], temp_space, temp_space_size,
                         &reconstructed[0]);

        errors = -2.0 * (exact_input - reconstructed);

        Parameters_Copy<double> gradient2(eig, 0.0);

        modified->rbprop(&noisy_input[0], &reconstructed[0],
                         temp_space, temp_space_size, &errors[0], 0,
                         gradient2, 1.0);

        distribution<double> dgradient = gradient2.values - gradient1.values;
        dgradient *= (gamma / alpha);

        eig.values = (1.0 - gamma) * eig.values + gamma * dgradient;

        //distribution<float> values(eig.values.begin(), eig.values.begin() + 20);
        //cerr << "values = " << values << endl;
    }

    //distribution<float> values(eig.values.begin(), eig.values.begin() + 20);
    //cerr << "values = " << values << endl;

    return 1.0 / eig.values.two_norm();
}

Parameters_Copy<float>
Auto_Encoder_Trainer::
calc_learning_rates(const Auto_Encoder & layer,
                    const std::vector<distribution<float> > & training_data,
                    Thread_Context & thread_context) const
{
    // Where we store the average hessian
    Parameters_Copy<double> avg_hessian_diag(layer, 0.0);

    // 1.  Pick an initial eigenvector estimate at random

    Parameters_Copy<double> eig(layer);
    for (unsigned i = 0;  i < eig.values.size();  ++i)
        eig.values[i] = 1 / sqrt(eig.values.size());

    // Get a mutable version so that we can modify its parameters
    unique_ptr<Auto_Encoder> modified(layer.deep_copy());

    // Current set of parameters
    Parameters_Copy<double> params(*modified);

    // Temporary space for the fprop/bprop
    size_t temp_space_size = layer.rfprop_temporary_space_required();
    float temp_space[temp_space_size];

    double alpha = 0.0001;
    double gamma = 0.05;

    for (unsigned i = 0;  i < 500;  ++i) {
        if (i == 100) gamma = 0.01;
        if (i == 300) gamma = 0.005;

        //cerr << "i = " << i << " two_norm = " << eig.values.two_norm()
        //     << endl;

        int example_num = min<int>(training_data.size() - 1,
                                   thread_context.random01()
                                   * training_data.size());

        const distribution<float> & exact_input = training_data[example_num];
        distribution<float> noisy_input
            = add_noise(exact_input, thread_context, false /* force_noise */);

        distribution<float> reconstructed(layer.inputs());

        Parameters_Copy<double> gradient1(eig, 0.0);

        layer.rfprop(&noisy_input[0], temp_space, temp_space_size,
                     &reconstructed[0]);

        distribution<float> errors = 2.0 * (reconstructed - exact_input);

        distribution<float> derrors(layer.inputs(), 2.0);

        layer.rbbprop(&noisy_input[0], &reconstructed[0],
                      temp_space, temp_space_size, &errors[0],
                      &derrors[0], 0, 0,
                      gradient1, &avg_hessian_diag, 1.0);

        Parameters_Copy<double> new_params = params;
        new_params.values += alpha / eig.values.two_norm() * eig.values;

        modified->parameters().set(new_params);

        modified->rfprop(&noisy_input[0], temp_space, temp_space_size,
                         &reconstructed[0]);

        errors = -2.0 * (exact_input - reconstructed);

        Parameters_Copy<double> gradient2(eig, 0.0);

        modified->rbprop(&noisy_input[0], &reconstructed[0],
                         temp_space, temp_space_size, &errors[0], 0,
                         gradient2, 1.0);

        distribution<double> dgradient = gradient2.values - gradient1.values;
        dgradient *= (gamma / alpha);

        eig.values = (1.0 - gamma) * eig.values + dgradient;

        //distribution<float> values(eig.values.begin(), eig.values.begin() + 20);
        //cerr << "values = " << values << endl;
    }

    //distribution<float> values(eig.values.begin(), eig.values.begin() + 20);
    //cerr << "values = " << values << endl;

    distribution<float> ahd_values(avg_hessian_diag.values.begin(),
                                   avg_hessian_diag.values.begin() + 20);
    ahd_values /= 500;
    cerr << "avg_hessian_diag = " << ahd_values << endl;

    double base_rate = 1.0 / eig.values.two_norm();
    Parameters_Copy<float> result(params, 0.0);

    // Limit our updates to 10 times faster than the base rate
    double mu = 0.1;

    result.values = base_rate / ((abs(avg_hessian_diag.values) / 500) + mu);

    double avg_result = result.values.mean();

    cerr << "avg_result = " << avg_result << endl;

    cerr << "base_rate = " << base_rate << endl;

    cerr << "result.values = "
         << distribution<float>(result.values.begin(),
                                result.values.begin() + 20)
         << endl;

    result.values *= 2.0 * base_rate / avg_result;

    //result.values.fill(base_rate);

    return result;
}

void
Auto_Encoder_Trainer::
train_stack(Auto_Encoder_Stack & stack,
            const std::vector<distribution<float> > & training_data,
            const std::vector<distribution<float> > & testing_data,
            Thread_Context & thread_context) const
{
    int nx = training_data.size();
    int nxt = testing_data.size();

    if (nx == 0)
        throw Exception("can't train on no data");

    int nlayers = stack.size();

    vector<distribution<float> > layer_train = training_data;
    vector<distribution<float> > layer_test = testing_data;

    // Learning rate is per-example
    double learning_rate = this->learning_rate / nx;

    // Compensate for the example proportion
    learning_rate /= sample_proportion;

    Auto_Encoder_Stack test_stack("test");

    for (unsigned layer_num = 0;  layer_num < nlayers;  ++layer_num) {
        cerr << endl << endl << endl << "--------- LAYER " << layer_num
             << " ---------" << endl << endl;

        Auto_Encoder & layer = stack[layer_num];

        vector<distribution<float> > next_layer_train, next_layer_test;

        int ni = layer.inputs();

        if (ni != layer_train[0].size())
            throw Exception("ni is wrong");

        train(layer, layer_train, layer_test, thread_context, niter);

        next_layer_train.resize(nx);
        next_layer_test.resize(nxt);

        // Add it to the testing stack so that we can test up to here
        test_stack.add(make_unowned_sp(layer));

        if (stack_backprop_iter > 0 && layer_num != 0) {

            // Test the layer stack
            if (verbosity >= 3)
                cerr << "calculating whole stack testing performance on "
                     << nxt << " examples" << endl;

            if (verbosity >= 1) {
                double test_error_exact = 0.0, test_error_noisy = 0.0;
                boost::tie(test_error_exact, test_error_noisy)
                    = test(test_stack, testing_data, thread_context);

                cerr << "testing rmse of stack: exact "
                     << test_error_exact << " noisy " << test_error_noisy
                     << endl;

                cerr << endl << endl << "training whole stack backprop" << endl;
                train(test_stack, training_data, testing_data, thread_context,
                      stack_backprop_iter);
            }
        }

        // Calculate the inputs to the next layer

        if (verbosity >= 3)
            cerr << "calculating next layer training inputs on "
                 << nx << " examples" << endl;
        double train_error_exact = 0.0, train_error_noisy = 0.0;
        boost::tie(train_error_exact, train_error_noisy)
            = test_and_update(layer, layer_train, next_layer_train,
                              thread_context);

        if (verbosity >= 2)
            cerr << "training rmse of layer: exact "
                 << train_error_exact << " noisy " << train_error_noisy
                 << endl;

        if (verbosity >= 3)
            cerr << "calculating next layer testing inputs on "
                 << nxt << " examples" << endl;
        double test_error_exact = 0.0, test_error_noisy = 0.0;
        boost::tie(test_error_exact, test_error_noisy)
            = test_and_update(layer, layer_test, next_layer_test,
                              thread_context);

        if (verbosity >= 2)
            cerr << "testing rmse of layer: exact "
                 << test_error_exact << " noisy " << test_error_noisy
                 << endl;

        layer_train.swap(next_layer_train);
        layer_test.swap(next_layer_test);

        // Test the layer stack
        if (verbosity >= 3)
            cerr << "calculating whole stack testing performance on "
                 << nxt << " examples" << endl;

        if (verbosity >= 1) {
            boost::tie(test_error_exact, test_error_noisy)
                = test(test_stack, testing_data, thread_context);

            cerr << "testing rmse of stack: exact "
                 << test_error_exact << " noisy " << test_error_noisy
                 << endl;
        }
    }
}

namespace {

std::string print_dist(const distribution<float> & dist)
{
    string result;
    for (unsigned i = 0;  i < min<int>(dist.size(), 12);  ++i)
        result += format("%6.3f ", dist[i]);
    if (dist.size() > 20) result += "...";
    return result;
}

struct Test_Examples_Job {

    const Auto_Encoder_Trainer & trainer;
    const Auto_Encoder & layer;
    const vector<distribution<float> > & data_in;
    vector<distribution<float> > & data_out;
    int first;
    int last;
    const Thread_Context & context;
    int random_seed;
    Lock & update_lock;
    double & error_exact;
    double & error_noisy;
    boost::progress_display * progress;

    Test_Examples_Job(const Auto_Encoder_Trainer & trainer,
                      const Auto_Encoder & layer,
                      const vector<distribution<float> > & data_in,
                      vector<distribution<float> > & data_out,
                      int first, int last,
                      const Thread_Context & context,
                      int random_seed,
                      Lock & update_lock,
                      double & error_exact,
                      double & error_noisy,
                      boost::progress_display * progress)
        : trainer(trainer), layer(layer), data_in(data_in), data_out(data_out),
          first(first), last(last),
          context(context), random_seed(random_seed),
          update_lock(update_lock),
          error_exact(error_exact), error_noisy(error_noisy),
          progress(progress)
    {
    }

    void operator () ()
    {
        Thread_Context thread_context(context);
        thread_context.seed(random_seed);

        double test_error_exact = 0.0, test_error_noisy = 0.0;

        for (unsigned x = first;  x < last;  ++x) {
            int ni JML_UNUSED = layer.inputs();
            int no JML_UNUSED = layer.outputs();

            // Present this input
            distribution<float> model_input(data_in[x]);

            distribution<bool> was_cleared;

            // Add noise
            distribution<float> noisy_input
                = trainer.add_noise(model_input, thread_context,
                                    true /* force_noise */);

            // Apply the layer
            distribution<float> hidden_rep
                = layer.apply(noisy_input);

            // Reconstruct the input
            distribution<float> denoised_input
                = layer.iapply(hidden_rep);

            // Error signal
            distribution<float> diff
                = model_input - denoised_input;

            // Overall error
            double error = pow(diff.two_norm(), 2);

            test_error_noisy += error;

            // Apply the layer
            distribution<float> hidden_rep2
                = layer.apply(model_input);

            if (!data_out.empty())
                data_out.at(x) = hidden_rep2.cast<float>();

            // Reconstruct the input
            distribution<float> reconstructed_input
                = layer.iapply(hidden_rep2);

            // Error signal
            distribution<float> diff2
                = model_input - reconstructed_input;

            // Overall error
            double error2 = pow(diff2.two_norm(), 2);

            if (x < trainer.dump_testing_output) {
                cerr << endl
                     << "example " << x << ": error exact " << error2
                     << " noisy " << error << endl
                     << "  input: " << print_dist(model_input) << endl
                     << "  output:" << print_dist(reconstructed_input) << endl
                     << "  diff:  " << print_dist(diff2) << endl;
                if (isnan(noisy_input).any()) {
                    cerr << "  noisy: " << print_dist(noisy_input) << endl
                         << "  dnoisd:" << print_dist(denoised_input) << endl
                         << "  diff:  " << print_dist(diff)
                         << endl;
                }
            }

            test_error_exact += error2;
        }

        Guard guard(update_lock);
        error_exact += test_error_exact;
        error_noisy += test_error_noisy;
        if (progress)
            (*progress) += (last - first);
    }
};

} // file scope

std::pair<double, double>
Auto_Encoder_Trainer::
test_and_update(const Auto_Encoder & encoder,
                const std::vector<distribution<float> > & data_in,
                std::vector<distribution<float> > & data_out,
                Thread_Context & thread_context) const
{
    Lock update_lock;
    double error_exact = 0.0;
    double error_noisy = 0.0;

    int nx = data_in.size();

    std::unique_ptr<boost::progress_display> progress;
    if (verbosity >= 3) progress.reset(new boost::progress_display(nx, cerr));

    Worker_Task & worker = thread_context.worker();

    // Now, submit it as jobs to the worker task to be done
    // multithreaded
    int group;
    {
        int parent = -1;  // no parent group
        group = worker.get_group(NO_JOB, "dump user results task",
                                 parent);

        // Make sure the group gets unlocked once we've populated
        // everything
        Call_Guard guard(boost::bind(&Worker_Task::unlock_group,
                                     boost::ref(worker),
                                     group));

        // 20 jobs per CPU
        int batch_size = nx / (num_threads() * 20);

        for (unsigned x = 0; x < nx;  x += batch_size) {

            Test_Examples_Job job(*this, encoder, data_in, data_out,
                                  x, min<int>(x + batch_size, nx),
                                  thread_context,
                                  thread_context.random(),
                                  update_lock,
                                  error_exact, error_noisy,
                                  progress.get());

            // Send it to a thread to be processed
            worker.add(job, "blend job", group);
        }
    }

    worker.run_until_finished(group);

    return make_pair(sqrt(error_exact / nx),
                     sqrt(error_noisy / nx));
}

} // namespace ML

#if 0

            cerr << "weights: " << endl;
            for (unsigned i = 0;  i < 10;  ++i) {
                for (unsigned j = 0;  j < 10;  ++j) {
                    cerr << format("%7.4f", layer.weights[i][j]);
                }
                cerr << endl;
            }

            double max_abs_weight = 0.0;
            double total_abs_weight = 0.0;
            double total_weight_sqr = 0.0;
            for (unsigned i = 0;  i < ni;  ++i) {
                for (unsigned j = 0;  j < nh;  ++j) {
                    double abs_weight = abs(layer.weights[i][j]);
                    max_abs_weight = std::max(max_abs_weight, abs_weight);
                    total_abs_weight += abs_weight;
                    total_weight_sqr += abs_weight * abs_weight;
                }
            }

            double avg_abs_weight = total_abs_weight / (ni * nh);
            double rms_avg_weight = sqrt(total_weight_sqr / (ni * nh));

            cerr << "max = " << max_abs_weight << " avg = "
                 << avg_abs_weight << " rms avg = " << rms_avg_weight
                 << endl;

            //cerr << "iscales: " << layer.iscales << endl;
            //cerr << "hscales: " << layer.hscales << endl;
            //cerr << "bias: " << layer.bias << endl;
            //cerr << "ibias: " << layer.ibias << endl;

            distribution<LFloat> svalues(min(ni, nh));
            boost::multi_array<LFloat, 2> layer2 = layer.weights;
            int nvalues = std::min(ni, nh);

            boost::multi_array<LFloat, 2> rvectors(boost::extents[ni][nvalues]);
            boost::multi_array<LFloat, 2> lvectorsT(boost::extents[nvalues][nh]);

            int result = LAPack::gesdd("S", nh, ni,
                                       layer2.data(), nh,
                                       &svalues[0],
                                       &lvectorsT[0][0], nh,
                                       &rvectors[0][0], nvalues);
            if (result != 0)
                throw Exception("gesdd returned non-zero");

            if (false) {
                boost::multi_array<LFloat, 2> weights2
                    = rvectors * diag(svalues) * lvectorsT;

                cerr << "weights2: " << endl;
                for (unsigned i = 0;  i < 10;  ++i) {
                    for (unsigned j = 0;  j < 10;  ++j) {
                        cerr << format("%7.4f", weights2[i][j]);
                    }
                    cerr << endl;
                }
            }

            //if (iter == 0) layer.weights = rvectors * lvectorsT;

            //if (iter == 0) layer.weights = rvectors * lvectorsT;

            //cerr << "svalues = " << svalues << endl;

        if (init_with_svd) {
            // Initialize with a SVD
            SVD_Decomposition init;
            init.train(layer_train, nh);

            for (unsigned i = 0;  i < ni;  ++i) {
                distribution<float> init_i(&init.lvectors[i][0],
                                            &init.lvectors[i][0] + nh);
                //init_i /= sqrt(init.singular_values_order);
                //init_i *= init.singular_values_order / init.singular_values_order[0];

                std::copy(init_i.begin(), init_i.end(),
                          &layer.weights[i][0]);
                layer.bias.fill(0.0);
                layer.ibias.fill(0.0);
                layer.iscales.fill(1.0);
                layer.hscales.fill(1.0);
                //layer.hscales = init.singular_values_order;
            }
        }

#endif


#ifndef __CONTROL_HH__
#define __CONTROL_HH__
/********************************* TRICK HEADER *******************************
PURPOSE:
      (Describe the CONTROL Module On Board)
LIBRARY DEPENDENCY:
      ((../src/Control.cpp))
PROGRAMMERS:
      (((Chung-Fan Yang) () () () ))
*******************************************************************************/
#include <armadillo>
#include "aux.hh"
#include "datadeck.hh"
#include "env/atmosphere76.hh"
#include "env/atmosphere_nasa2002.hh"
#include "math_utility.hh"
#include "matrix/utility.hh"

class Control {
  TRICK_INTERFACE(Control);

 public:
  Control();
  Control(const Control& other);

  Control& operator=(const Control& other);

  void initialize();

  void control(double int_step);
  // double control_pitch_rate(double qqdx);
  // double control_gamma(double thtvdcomx);
  void set_close_loop_pole(double in1, double in2);
  void set_factor(double in1, double in2);
  void set_feedforward_gain(double in1);
  void set_thtvdcomx(double in);
  // void set_maut(double in);
  void set_delimx(double in);
  void set_drlimx(double in);
  void set_pgam(double in);
  void set_wgam(double in);
  void set_zgam(double in);
  void pitch_down(double pitchcmd, double int_step);
  void roll_control(double rollcmd, double int_step);
  void yaw_control(double yawcmd, double int_step);
  void AOA_control(double aoacmd, double int_step);
  void S2_B_pseudo_G(arma::vec3 cmd, double int_step);
  void S3_B_pseudo_G(arma::vec3 cmd, double int_step);

  void set_aero_coffe(double in1, double in2, double in3);
  void set_IBBB0(double in1, double in2, double in3);
  void set_IBBB1(double in1, double in2, double in3);
  void set_controller_var(double in1, double in2, double in3, double in4,
                          double in5, double in6, double in7);
  void set_S2_PITCH_DOWN_I();
  void set_S2_PITCH_DOWN_II();
  void set_S3_PITCH_DOWN();
  void set_S2_ROLL_CONTROL();
  void set_NO_CONTROL();
  void set_S2_AOA();
  void set_S3_AOA();
  void set_acc_control();
  void set_kpp(double in);
  void set_kpi(double in);
  void set_kppp(double in);
  void set_krp(double in);
  void set_kri(double in);
  void set_krpp(double in);
  void set_kyp(double in);
  void set_kyi(double in);
  void set_kypp(double in);
  void set_attcmd(double in1, double in2, double in3);
  void set_kaoap(double in);
  void set_kaoai(double in);
  void set_kaoapp(double in);
  void set_kaoad(double in);
  void set_aoacmd(double in);
  void set_engnum(double in);

  void get_control_gain(double in1, double in2, double in3, double in4,
                        double in5, double in6, double in7, double in8,
                        double in9, double in10, double in11, double in12,
                        double in13, double in14, double in15, double in16,
                        double in17, double in18, double in19, double in20);

  double get_theta_a_cmd();
  double get_theta_b_cmd();
  double get_theta_c_cmd();
  double get_theta_d_cmd();
  void set_ierror_zero();
  void set_reference_point(double in);
  void set_engine_d(double in);
  void load_aerotable(const char* filename);
  void atmosphere_use_nasa();
  void atmosphere_use_public();
  void pitch_down_test(double pitchcmd, double int_step);

  arma::vec3 euler_angle(arma::mat33 TBD);

  void set_ancomx(double in);
  void set_alcomx(double in);

  enum CONTROL_TYPE {
    NO_CONTROL = 0,
    S2_PITCH_DOWN_I,
    S2_PITCH_DOWN_II,
    S2_ROLL_CONTROL,
    S3_PITCH_DOWN,
    S2_AOA,
    S3_AOA,
    ACC_CONTROL_ON
  };
  ///////////////////////////////////////////////////////////////////////////////
  // Definition of control module-variables
  // Member function of class 'Hyper'
  // Module-variable locations are assigned to hyper[500-599]
  //
  // maut = |mauty|mautp|
  //
  // mauty = 0 no control, fixed control surfaces
  //       = 5 yaw acceleration control for yaw-to-turn
  //
  // mautp = 0 no control, fixed control surfaces
  //       = 3 pitch acceleration control
  //
  // 030520 Created by Peter H Zipfel
  // 091214 Modified for ROCKET6, PZi
  ///////////////////////////////////////////////////////////////////////////////

  // std::function<int()>    grab_mprop;

  // std::function<double()> grab_dvbe;

  // std::function<double()> grab_gymax;
  // std::function<double()> grab_dyb;
  // std::function<double()> grab_dnb;
  // std::function<double()> grab_dnr;
  // std::function<double()> grab_dndr;
  // std::function<double()> grab_dla;
  // std::function<double()> grab_dma;
  // std::function<double()> grab_dmq;
  // std::function<double()> grab_dmde;
  // std::function<double()> grab_dnd;
  // std::function<double()> grab_dlde;

  // std::function<double()> grab_pdynmc;
  std::function<int()> grab_thrust_state;
  std::function<double()> grab_dvbec;
  std::function<double()> grab_thtvdcx;
  std::function<double()> grab_thtbdcx;
  std::function<double()> grab_phibdcx;
  std::function<double()> grab_psibdcx;
  std::function<double()> grab_alphacx;
  std::function<double()> grab_altc;

  std::function<double()> grab_qqcx;
  std::function<double()> grab_rrcx;
  std::function<double()> grab_phipcx;
  std::function<double()> grab_alppcx;

  std::function<arma::vec3()> grab_FSPCB;

  std::function<arma::vec3()> grab_computed_WBIB;
  std::function<arma::vec4()> grab_TBDQ;
  std::function<arma::mat33()> grab_TBD;
  std::function<arma::mat33()> grab_TBICI;
  std::function<arma::mat33()> grab_TBIC;
  std::function<arma::vec3()> grab_WBECB;
  std::function<arma::vec3()> grab_ABICB;

  double get_delecx();
  double get_delrcx();

  arma::mat33 build_321_rotation_matrix(arma::vec3 angle);
  void Quaternion_cmd(double int_step);
  void calculate_xcg_thrust(double int_step);

 private:
  cad::Atmosphere* atmosphere;

  double control_normal_accel(double ancomx, double int_step);
  double control_yaw_accel(double alcomx, double int_step);
  void aerodynamics_der();
  void default_data();

  double delecx; /* *io (d)      Pitch command deflection */  // n
  double delrcx; /* *io (d)      Yaw command deflection */    // n

  enum CONTROL_TYPE maut; /* *io (--)     maut=|mauty|mautp| see table */
  int mfreeze;   /* *io (--)     =0:Unfreeze; =1:Freeze; increment for more */
  double waclp;  /* *io (r/s)    Nat freq of accel close loop complex pole */
  double zaclp;  /* *io (--)     Damping of accel close loop complex pole */
  double paclp;  /* *io (--)     Close loop real pole */
  double delimx; /* *io (d)      Pitch command limiter */
  double drlimx; /* *io (d)      Yaw command limiter */
  double yyd;    /* *io (m/s2)   Yaw feed-forward derivative variable */
  double yy;     /* *io (m/s)    Yaw feed-forward integration variable */
  double zzd;    /* *io (m/s2)   Pitch feed-forward derivative variable */
  double zz;     /* *io (m/s)    Pitch feed-forward integration variable */
  double alcomx_actual; /* *io (--)     Later accel com limited by 'betalimx' */
  double ancomx_actual; /* *io (--)     Normal accel com limited by 'alplimx' */
  arma::vec GAINFP;  /* *io (--)     Feedback gains of pitch accel controller */
  double _GAINFP[3]; /* *io (--)     Feedback gains of pitch accel controller */
  double gainp;  /* *io (s2/m)   Proportional gain in pitch acceleration loop */
  double gainl;  /* *io (--)     Gain in lateral acceleration loop */
  double gkp;    /* *io (s)      Gain of roll rate feedback */
  double gkphi;  /* *io (--)     Gain of roll angle feedback */
  double isetc2; /* *io (--)     Flag to print freeze variables */
  double wacly;  /* *io (r/s)    Nat freq of accel close loop pole, yaw */
  double zacly;  /* *io (--)     Damping of accel close loop pole, yaw */
  double pacly;  /* *io (--)     Close loop real pole, yaw */
  double gainy;  /* *io (--)     Gain in lateral acceleration loop */
  arma::vec GAINFY;  /* *io (--)     Feedback gains of yaw accel controller */
  double _GAINFY[3]; /* *io (--)     Feedback gains of yaw accel controller */
  double factwaclp; /* *io (--)     Factor to mod 'waclp': waclp*(1+factwacl) */
  double factwacly; /* *io (--)     Factor to mod 'wacly': wacly*(1+factwacl) */
  double alcomx;    /* *io (--)     Lateral (horizontal) acceleration command */
  double ancomx;    /* *io (--)     Pitch (normal) acceleration command */
  double qqdx;      /* *io (--)     Desired pitch rate", "control */
  double grate;     /* *io (--)     grate */
  double gainff;    /* *io  (--)    gain */
  double thtvdcomx; /* *io (d)      Flight path angle command */
  arma::mat GAINGAM;     /* *io  (--)    Gain */
  double _GAINGAM[3][1]; /* *io  (--)    Gain */
  double pgam;           /*  *io  (--)    Gain */
  double wgam;           /*  *io  (--)    Gain */
  double zgam;           /*  *io  (--)    Gain */

  double fmasse;
  double mdot;
  double fmass0;
  double xcg_0;
  double xcg_1;
  double isp;
  double vmass;
  double vmass0;

  arma::vec IBBB0;
  double _IBBB0[3];

  arma::vec IBBB1;
  double _IBBB1[3];

  arma::vec IBBB2;
  double _IBBB2[3];

  arma::vec CONTROLCMD;
  double _CONTROLCMD[3];

  arma::vec CMDQ;
  double _CMDQ[4];

  arma::vec TCMDQ;
  double _TCMDQ[4];

  // pitch
  double thterror;
  double perrori;
  double perrorp;
  double perror_old;
  double pitchiout_old;
  double pN;
  double pdout_old;
  double kpp;
  double kpi;
  double kpd;
  double kppp;
  // /////////test////////////
  double thterrort;
  double perrorit;
  double perrorpt;
  double perror_oldt;
  double pitchiout_oldt;
  double pdout_oldt;

  // roll
  double rollerror;
  double rerrori;
  double rerrorp;
  double rerror_old;
  double rolliout_old;
  double rN;
  double rdout_old;
  double krp;
  double kri;
  double krd;
  double krpp;

  // yaw
  double yawerror;
  double yerrori;
  double yerrorp;
  double yerror_old;
  double yawiout_old;
  double yN;
  double ydout_old;
  double kyp;
  double kyi;
  double kyd;
  double kypp;

  // aoa
  double aoaerror;
  double aoaerrori;
  double aoaerrorp;
  double aoaerror_old;
  double aoaiout_old;
  double aoaN;
  double aoadout_old;
  double kaoap;
  double kaoai;
  double kaoad;
  double kaoapp;
  double aoaerror_smooth_old;
  double aoaerror_new;
  double aoaerror_out_old;

  double theta_a_cmd;
  double theta_b_cmd;
  double theta_c_cmd;
  double theta_d_cmd;
  double lx;

  double rollcmd;
  double pitchcmd;
  double yawcmd;
  double aoacmd;

  double pitchcmd_new;
  double pitchcmd_old;
  double pitchcmd_out_old;

  arma::vec delta_euler;
  double _delta_euler[3];

  arma::vec euler;
  double _euler[3];

  double xcg;
  double thrust;
  double mass_ratio;

  arma::vec WBICBT;
  double _WBICBT[3];

  arma::vec CMDG;
  double _CMDG[3];

  double reference_point;
  double d;

  // Aerodynamics def()
  Datadeck aerotable;

  double dla;
  double dlde;
  double dma;
  double dmq;
  double dmde;
  double dyb;
  double dydr;
  double dnb;
  double dnr;
  double dndr;
  double dllp;
  double dllda;
  double dnd;
  double cla;
  double clde;
  double cyb;
  double cydr;
  double cllda;
  double cllp;
  double cma;
  double cmde;
  double cmq;
  double cnb;
  double cndr;
  double cnr;
  double cn0;
  // diagnostics
  double stmarg_yaw;
  double stmarg_pitch;
  double realp1;
  double realp2;
  double wnp;
  double zetp;
  double rpreal;
  double realy1;
  double realy2;
  double wny;
  double zety;
  double ryreal;
  double refa;
  double refd;
  double xcp;
  double pdynmc;
  double vmach;

  double eng_num;
};

#endif  // __CONTROL_HH__


#include "ompu/music/concrete/interval.hpp"

#include <boost/assert.hpp>

namespace ompu { namespace music { namespace concrete {

Interval::Interval(std::size_t semitones) noexcept
    : semitones_(semitones)
{
    BOOST_ASSERT(semitones <= 12);
}

Interval make_interval(note_height_type a, note_height_type b)
{
    return Interval{(std::max(a, b) - std::min(a, b)) % 12};
}

}}} // ompu