/* boosting_training_tool.cc                                       -*- C++ -*-
   Jeremy Barnes, 12 June 2003
   Copyright (c) 2003 Jeremy Barnes.  All rights reserved.
   $Source$

   Tool to use to train boosting with.
*/

#include "jml/boosting/boosted_stumps.h"
#include "jml/boosting/naive_bayes.h"

#include "jml/boosting/training_data.h"
#include "jml/boosting/dense_features.h"
#include "jml/boosting/sparse_features.h"
#include "jml/boosting/probabilizer.h"
#include "jml/boosting/decoded_classifier.h"
#include "jml/boosting/boosting_tool_common.h"
#include "jml/boosting/weighted_training.h"
#include "jml/boosting/training_index.h"
#include "jml/utils/vector_utils.h"
#include "jml/utils/command_line.h"
#include <boost/progress.hpp>
#include <boost/timer.hpp>
#include "jml/stats/moments.h"
#include "datasets.h"

#include <iterator>
#include <iostream>
#include <set>

using namespace std;

using namespace ML;
using namespace Math;
using namespace Stats;

#if 0
// Stump representation
    Feature feature;                   ///< which feature to operate on?
    Feature_Type type;           ///< what type of feature is it?
    float arg;                         ///< p ::=  x > arg
    float Z;                           ///< Z score of the rule
    distribution<float> pred_true;     ///< predictions if p holds
    distribution<float> pred_false;    ///< predictions if p doesn't hold
    distribution<float> pred_missing;  ///< predictions if feature absent

// Boosted stumps representation
    typedef std::map<std::pair<Feature, float>, Stump> stumps_type;
    stumps_type stumps;

// Naive bayes representation
    /** This structure holds information about a feature used by Naive Bayes
        to turn a (feature, value) pair into a predicate.
    */
    struct Bayes_Feature {
        Bayes_Feature(const Feature & feature = Feature(), float arg = 0)
            : feature(feature), arg(arg)
        {
        }

        Feature feature;    ///< Feature to split on
        float arg;          ///< Value to split on

        bool operator < (const Bayes_Feature & other) const
        {
            if (feature < other.feature) return true;
            else if (feature == other.feature && arg < other.arg) return true;
            else return false;
        }
    };

    std::vector<Bayes_Feature> features;
    boost::multi_array<float, 3> probs;  /* num features x 3 x num labels matrix */
    distribution<float> label_priors;
    distribution<float> missing_total; /* sum of all missing distributions. */
#endif

Boosted_Stumps
bayes_to_boost(const Naive_Bayes & bayes)
{
    Boosted_Stumps result(bayes.feature_space(), bayes.label_count());
    int nl = bayes.label_count();
    static const int MISSING=2;

    cerr << "nl = " << nl << " bayes.features.size() = "
         << bayes.features.size() << " bayes.shape()[0] = " << bayes.probs.shape()[0]
         << "  bayes.shape()[1] = " << bayes.probs.shape()[1] << " bayes.dim(2) = "
         << bayes.probs.dim(2) << endl;

    for (unsigned f = 0;  f < bayes.features.size();  ++f) {
        Stump this_stump(bayes.feature_space(), nl);
        this_stump.feature = bayes.features[f].feature;
        this_stump.arg     = bayes.features[f].arg;
        this_stump.pred_false
            = distribution<float>(&bayes.probs[f][false][0],
                                  &bayes.probs[f][false][0] + nl);

        this_stump.pred_true
            = distribution<float>(&bayes.probs[f][true][0],
                                  &bayes.probs[f][true][0] + nl);
        this_stump.pred_missing
            = distribution<float>(&bayes.probs[f][MISSING][0],
                                  &bayes.probs[f][MISSING][0] + nl);

        result.insert(this_stump);
    }

    result.bias = log(bayes.label_priors);

    return result;
}

Boosted_Stumps
run_boosting(const Training_Data & training_set,
             const Training_Data & validation_set_,
             unsigned max_iter, unsigned min_iter, int verbosity,
             double equalize_beta, vector<Feature> features,
             const vector<Feature> & equalize_features,
             const vector<float> & equalize_betas, bool true_only,
             bool profile, bool fair,
             float feature_prop, int committee_size, int trace,
             int cost_function, int output_function, int update_alg,
             float ignore_highest, float sample_prop, int weight_function,
             const vector<Classifier_Impl::Weight_Spec> & weight_spec)
{
    boost::timer timer;
    unsigned nl = training_set.label_count();
    std::shared_ptr<const Feature_Space> feature_space
        = training_set.feature_space();

    Boosted_Stumps stumps(feature_space, nl);
    Boosted_Stumps best(feature_space, nl);
    float best_acc = 0.0;
    int best_iter = 0;

    bool validate_is_train = false;
    if (validation_set_.example_count() == 0)
        validate_is_train = true;

    const Training_Data & validation_set
        = (validate_is_train ? training_set : validation_set_);

    //cerr << "true_only = " << true_only << endl;
    //cerr << "trace = " << trace << endl;
    Training_Params params;
    params["true_only"] = true_only;
    params["rule_proportion"] = feature_prop;
    params["sample_proportion"] = sample_prop;
    if (trace) params["trace"] = trace;
    params["cost_function"] = Boosted_Stumps::Cost_Function(cost_function);
    stumps.output = Boosted_Stumps::Output(output_function);
    params["update_alg"] = update_alg;
    params["ignore_highest"] = ignore_highest;
    params["weight_function"] = weight_function;

    boost::multi_array<float, 2> training_output(training_set.example_count(), nl);
    training_output.fill(0.0);

    boost::multi_array<float, 2> validation_output(validation_set.example_count(),
                                            nl);
    validation_output.fill(0.0);

    distribution<float> training_ex_weights
        = Classifier_Impl::apply_weight_spec(training_set, weight_spec);
    distribution<float> validate_ex_weights
        = Classifier_Impl::apply_weight_spec(validation_set, weight_spec);

    boost::multi_array<float, 2> weights
        = Classifier_Impl::expand_weights(training_set, training_ex_weights);

    float validate_acc = 0.0;
    float train_acc = 0.0;

#if 1
    cerr << "training bayes... ";
    Naive_Bayes bayes(training_set.feature_space(), nl);
    Training_Params bayes_params;
    if (trace) bayes_params["trace"] = trace;
    bayes_params["features"] = features;
    bayes.train_weighted(training_set, bayes_params, weights);
    cerr << "done." << endl;

    train_acc = bayes.accuracy(training_set);
    validate_acc = bayes.accuracy(validation_set);

    cerr << "bayes accuracy: " << train_acc << " training "
         << validate_acc << " validation" << endl;

#if 0
    cerr << "converting to boosted stumps... ";
    stumps = bayes_to_boost(bayes);
    cerr << "done." << endl;

    cerr << "updating weights... ";
    for (Boosted_Stumps::const_iterator it = stumps.begin();
         it != stumps.end();  ++it) {
        stumps.update_scores(training_output, training_set, *it);
        stumps.update_scores(validation_output, validation_set, *it);
    }
    cerr << "done." << endl;
#endif
#endif

#if 1
    distribution<float> bias(nl, 0.0);
    for (unsigned i = 0;  i < 5;  ++i) {
        Stump stump = Stump::get_bias(training_set, params, weights);
        bias += stump.pred_missing;
        stumps.update_weights(weights, stump, training_set, params);
        stumps.update_scores(training_output, training_set, stump);
        stumps.update_scores(validation_output, validation_set, stump);
    }

    cerr << "bias = " << bias << endl;
    stumps.bias = bias;
#endif

    boost::scoped_ptr<boost::progress_display> progress;

    if (verbosity <= 5) {
        cerr << "training " << features.size() << " features..." << endl;
        progress.reset(new boost::progress_display(features.size(), cerr));
    }

#if 1
    boost::multi_array<float, 2> start_weights = weights;

    for (unsigned i = 0;  i < features.size();  ++i) {
        vector<Feature> f(1, features[i]);
        Stump stump(feature_space, nl);
        Training_Params params2 = params;
        params2["features"] = f;

        stump.train_weighted(training_set, params2, start_weights);
        stumps.insert(stump);

        stumps.update_scores(training_output, training_set, stump);
        stumps.update_scores(validation_output, validation_set, stump);

        train_acc = stumps.accuracy(training_output, training_set);
        if (validate_is_train) validate_acc = train_acc;
        else validate_acc
                 = stumps.accuracy(validation_output, validation_set);

        if (verbosity > 5) {
            cerr << format("%4d %6.2f%% %6.2f%% %5.3f ",
                           i, train_acc * 100.0, validate_acc * 100.0,
                           stump.Z);
            if (stump.label_count() == 2
                && (abs(stump.pred_true[0]+stump.pred_true[1]) < 0.001)) {
                cerr << format(" %5.2f %5.2f", stump.pred_true[1],
                               stump.pred_false[1]);
                if (abs(stump.pred_missing[1]) > 0.01)
                    cerr << format(" %5.2f", stump.pred_missing[1]);
                else cerr << "      ";
            }
            else if (nl <= 5)
                for (unsigned i = 0;  i < std::min(nl, 5U);  ++i)
                    cerr << format("%6.2f", stump.pred_true[i]);
            else if (verbosity > 6)
                for (unsigned i = 0;  i < nl;  ++i)
                    cerr << format("%6.2f", stump.pred_true[i]);

            cerr << format("%9.4g %-16s", stump.arg,
                           training_set.feature_space()
                           ->print(stump.feature).c_str());
            cerr << endl;
        }
        else ++(*progress);
    }

    cerr << "after bayesian part: train_acc = " << train_acc
         << " validate_acc = " << validate_acc << endl;
#endif

    if (verbosity == 1 || verbosity == 2) {
        cerr << "training " << max_iter << " iterations..." << endl;
        progress.reset(new boost::progress_display(max_iter, cerr));
    }

    if (min_iter > max_iter)
        throw Exception("min_iter is greater than max_iter");

    if (verbosity > 2) {
        cerr << "  it   train     val   Z    ";
        if (nl == 2) cerr << "true false  miss";
        else if (nl <= 5)
            for (unsigned i = 0;  i < std::min(nl, 5U);  ++i)
                cerr << format("lbl%d  ", i);
        else if (verbosity > 3)
            for (unsigned i = 0;  i < nl;  ++i)
                cerr << format("lbl%-3d", i);
        cerr << "       arg feature" << endl;
    }

    for (unsigned i = 0;  i < max_iter;  ++i) {
        if (progress) ++(*progress);
        //vector<ML::Feature> features;

        if (!fair) {
#if 0
            vector<Stump> all_stumps
                = stumps.train_iteration(training_set, params, weights,
                                         features, committee_size);
            stumps.update_scores(training_output, training_set, all_stumps);
            stumps.update_scores(validation_output, validation_set,all_stumps);

            train_acc = stumps.accuracy(training_output, training_set);
            validate_acc
                = stumps.accuracy(validation_output, validation_set);

            if (verbosity > 2) {
                cerr << format("%4d %6.2f%% %6.2f%% ",
                               i, train_acc * 100.0, validate_acc * 100.0);
                for (unsigned i = 0;  i < all_stumps.size();  ++i) {
                    if (i != 0) cerr << "                     ";
                    cerr << format("%6.4f", all_stumps[i].Z);
                    const Stump & stump = all_stumps[i];
                    if (stump.label_count() == 2
                        && (abs(stump.pred_true[0]+stump.pred_true[1])
                            < 0.001)) {
                        cerr << format(" %5.2f %5.2f", stump.pred_true[1],
                                       stump.pred_false[1]);
                        if (abs(stump.pred_missing[1]) > 0.01)
                            cerr << format(" %5.2f", stump.pred_missing[1]);
                        else cerr << "      ";
                    }
                    else {
                        if (nl <= 5 || verbosity > 3)
                            for (unsigned l = 0;  l < stump.pred_true.size();
                                 ++l)
                                cerr << format(" %5.2f", stump.pred_true[l]);
                    }
                    cerr << format("%8.4f %s", stump.arg,
                                   training_set.feature_space()
                                   ->print(stump.feature).c_str());
                    if (i != all_stumps.size() - 1) cerr << ",";
                    cerr << endl;
                }
            }

#else
            Stump stump;
            stump = stumps
                .train_iteration(training_set, params, weights, features);

            stumps.update_scores(training_output, training_set, stump);
            stumps.update_scores(validation_output, validation_set, stump);

            train_acc = stumps.accuracy(training_output, training_set);
            if (validate_is_train) validate_acc = train_acc;
            else validate_acc
                     = stumps.accuracy(validation_output, validation_set);

#if 0
            cerr << "train_acc = " << train_acc << "  validate_acc = "
                 << validate_acc << endl;

            train_acc = stumps.accuracy(training_set);
            validate_acc = stumps.accuracy(validation_set);

            cerr << "train_acc = " << train_acc << "  validate_acc = "
                 << validate_acc << endl;
#endif

            if (verbosity > 2) {
                cerr << format("%4d %6.2f%% %6.2f%% %5.3f ",
                               i, train_acc * 100.0, validate_acc * 100.0,
                               stump.Z);
                if (stump.label_count() == 2
                    && (abs(stump.pred_true[0]+stump.pred_true[1]) < 0.001)) {
                    cerr << format(" %5.2f %5.2f", stump.pred_true[1],
                                   stump.pred_false[1]);
                    if (abs(stump.pred_missing[1]) > 0.01)
                        cerr << format(" %5.2f", stump.pred_missing[1]);
                    else cerr << "      ";
                }
                else if (nl <= 5)
                    for (unsigned i = 0;  i < std::min(nl, 5U);  ++i)
                        cerr << format("%6.2f", stump.pred_true[i]);
                else if (verbosity > 3)
                    for (unsigned i = 0;  i < nl;  ++i)
                        cerr << format("%6.2f", stump.pred_true[i]);

                cerr << format("%9.4g %-16s", stump.arg,
                               training_set.feature_space()
                                   ->print(stump.feature).c_str());
                cerr << endl;
            }
#endif
        }
        else {
            vector<Stump> all_stumps
                = stumps.train_iteration_fair(training_set, params, weights,
                                              features, committee_size);
            stumps.update_scores(training_output, training_set, all_stumps);
            stumps.update_scores(validation_output, validation_set,all_stumps);

            float train_acc = stumps.accuracy(training_output, training_set);
            validate_acc
                = stumps.accuracy(validation_output, validation_set);

            if (verbosity > 2) {
                cerr << format("%4d %6.2f%% %6.2f%% ",
                               i, train_acc * 100.0, validate_acc * 100.0);
                for (unsigned i = 0;  i < all_stumps.size();  ++i) {
                    if (i != 0) cerr << "                     ";
                    cerr << format("%6.4f", all_stumps[i].Z);
                    const Stump & stump = all_stumps[i];
                    if (stump.label_count() == 2
                        && (abs(stump.pred_true[0]+stump.pred_true[1])
                            < 0.001)) {
                        cerr << format(" %5.2f %5.2f", stump.pred_true[1],
                                       stump.pred_false[1]);
                        if (abs(stump.pred_missing[1]) > 0.01)
                            cerr << format(" %5.2f", stump.pred_missing[1]);
                        else cerr << "      ";
                    }
                    else {
                        if (nl <= 5 || verbosity > 3)
                            for (unsigned l = 0;  l < stump.pred_true.size();
                                 ++l)
                                cerr << format(" %5.2f", stump.pred_true[l]);
                        if (verbosity > 4) {
                            cerr << endl;
                            cerr << "                           ";
                            for (unsigned l = 0;  l < stump.pred_true.size();
                                 ++l)
                                cerr << format(" %5.2f", stump.pred_false[l]);
                            cerr << endl;
                            cerr << "                           ";
                            for (unsigned l = 0;  l < stump.pred_true.size();
                                 ++l)
                                cerr << format(" %5.2f", stump.pred_missing[l]);
                        }
                    }
                    cerr << format("%8.4f %s", stump.arg,
                                   training_set.feature_space()
                                   ->print(stump.feature).c_str());
                    if (i != all_stumps.size() - 1) cerr << ",";
                    cerr << endl;
                }
            }
        }

        if (validate_acc > best_acc && i >= min_iter) {
            best = stumps;
            best_acc = validate_acc;
            best_iter = i;
        }
    }

    if (profile)
        cerr << "training time: " << timer.elapsed() << "s" << endl;

    if (verbosity > 0) {
        cerr << format("best was %6.2f%% on iteration %d", best_acc * 100.0,
                       best_iter)
             << endl;
    }

    cerr << "best accuracy = " << best.accuracy(validation_set) << endl;

    best.calc_sum_missing();

    return best;
}

int main(int argc, char ** argv)
try
{
    ios::sync_with_stdio(false);

    int max_iter            = 1000;
    int min_iter            = 5;
    float validation_split  = 0.0;
    float testing_split     = 0.0;
    bool randomize_order    = false;
    int probabilize_mode    = 1;
    int probabilize_data    = 0;
    bool probabilize_weighted = false;
    bool variable_header    = false;
    bool data_is_sparse     = false;
    int label_count         = -1;
    float equalize_beta     = 0.0;
    string weight_spec      = "";
    int draw_graphs         = false;
    //Stop_At stop_at        = BEST_RECOG;
    string scale_output;
    string scale_input;
    string output_file;
    string variable_name_file;
    string equalize_name = "label";
    int verbosity           = 1;
    vector<string> ignore_features;
    vector<string> optional_features;
    int cross_validate      = 1;
    int repeat_trials       = 1;
    bool val_with_test      = false;
    bool dump_testing       = false;
    bool true_only          = false;
    int min_feature_count   = 1;
    bool remove_aliased     = false;
    bool profile            = false;
    bool fair               = false;
    int committee_size      = 1;
    float feature_prop      = 1.0;
    int print_confusion     = false;
    int trace               = 0;
    string probabilize_link = "logit";
    int cost_function       = 0;
    int output_function     = 0;
    int update_alg          = 0;
    int weight_function     = 0;
    float ignore_highest    = 0.0;
    float sample_prop       = 1.0;
    bool is_regression      = false;  // Set to be a regression?
    bool is_regression_set  = false;  // Value of is_regression has been set?
    int num_buckets         = Training_Data::DEFAULT_NUM_BUCKETS;
    string group_feature_name = "";
    bool eval_by_group      = false;  // Evaluate a group at a time?
    bool eval_by_group_set  = false;  // Value of eval_by_group has been set?

    vector<string> extra;
    {
        using namespace CmdLine;

        static const Option data_options[] = {
            { "validation-split", 'V', validation_split, validation_split,
              false, "split X% of training data for validation", "0-100" },
            { "testing-split", 'T', testing_split, testing_split,
              false, "split X% of training data for testing", "0-100" },
            { "validate-with-testing", 0, NO_ARG, flag(val_with_test),
              false, "(CHEAT) use testing data for validation" },
            { "randomize-order", 'R', NO_ARG, flag(randomize_order),
              false, "randomize the order of data before splitting" },
            { "variable-names", 0, variable_name_file, variable_name_file,
              false, "read variables names from FILE", "FILE" },
            { "variable-header", 'd', NO_ARG, flag(variable_header),
              false, "use first line of file as a variable header" },
            { "label-count", 'L', label_count, label_count,
              false, "force number of labels to be N", "N" },
            { "ignore-var", 'z', string(), push_back(ignore_features),
              false,"ignore variable with name matching REGEX","REGEX|@FILE" },
            { "optional-feature", 'O', string(), push_back(optional_features),
              false, "feature with name REGEX is optional", "REGEX|@FILE" },
            { "sparse-data", 'S', NO_ARG, flag(data_is_sparse),
              false, "dataset is in sparse format" },
            { "min-feature-count", 'c', min_feature_count, min_feature_count,
              true, "don't consider features seen < NUM times", "NUM" },
            { "remove-aliased", 'A', NO_ARG, flag(remove_aliased),
              false, "remove aliased training rows from the training data" },
            { "regression", 'N', NO_ARG, flag(is_regression,is_regression_set),
              false, "force dataset to be a regression problem" },
            { "num-buckets", '1', num_buckets, num_buckets,
              true, "number of buckets to discretize into (0=off)", "INT" },
            { "group-feature", 'g', group_feature_name, group_feature_name,
              false, "use FEATURE to group examples in dataset", "FEATURE" },
            Last_Option
        };

        static const Option training_options[] = {
            { "max-iterations", 'm', max_iter, max_iter,
              true, "run no more than NUM iterations", "NUM" },
            { "min-iterations", 'n', min_iter, min_iter,
              true, "output must be trained on at least N iterations", "NUM" },
            { "cross-validate", 'X', cross_validate, cross_validate,
              false, "run N-fold cross validation", "INT" },
            { "repeat-trials", 'r', repeat_trials, repeat_trials,
              false, "repeat experiment N times", "INT" },
            { "true-only", '9', NO_ARG, flag(true_only),
              false, "don't false or missing predicates to infer labels" },
            { "fair", 'f', NO_ARG, flag(fair),
              false, "treat mutiple equal features in a fair manner" },
            { "feature-proportion", '3', feature_prop, feature_prop,
              false, "lazy train on this proportion of features", "0.0-1.0" },
            { "committee-size", '4', committee_size, committee_size,
              true, "(fair=1 only): learn committees of N classifiers", "INT"},
            { "cost-function", '5', cost_function, cost_function,
              true, "cost function: 0 = boosting, 1 = logitboost", "0-1" },
            { "output-function", '6', output_function, output_function,
              true, "output fn: 0 = raw, 1 = prob, 2 = probnorm", "0-2" },
            { "update-alg", '7', update_alg, update_alg,
              true, "update alg: 0 = boosting, 1 = gentle", "0-1" },
            { "ignore-highest", '8', ignore_highest, ignore_highest,
              true, "ignore highest x proportion of weights", "0.0-1.0" },
            { "sample-proportion", '9', sample_prop, sample_prop,
              true, "use only X proportion of samples each iter", "0.0-1.0" },
            { "weight-function", 'a', weight_function, weight_function,
              true, "weight function: 0 = normal, 1 = conf", "0,1" },
            Last_Option
        };

        static const Option weight_options[] = {
            { "equalize-beta", 'E', equalize_beta, equalize_beta,
              true, "equalize labels (b in w=freq^(-b); 0.0=off)", "0.0-1.0" },
            { "equalize-feature", 'F', equalize_name, equalize_name,
              true, "equalize based upon feature FEATURE","FEATURE||'label'" },
            { "weight-spec", 'W', weight_spec, weight_spec,
              false, "use SPEC for weights", "VAR1(BETA1),VAR2(BETA2)..." },
            Last_Option
        };

        static const Option probabilize_options[] = {
            { "probabilize-mode", 'p', probabilize_mode, probabilize_mode,
              true, "prob mode: 0=matrix, 1=pervar, 2=oneonly, 3=off", "0-3" },
            { "probabilize-link", 'K', probabilize_link, probabilize_link,
              true, "prob link function", "logit|log" },
            { "probabilize-data", 'P', probabilize_data, probabilize_data,
              true, "data for prob: 0 = train, 1 = validate", "0|1" },
            { "probabilize-weighted", 'Q', NO_ARG, flag(probabilize_weighted),
              false, "train probabilizer using weights also" },
            Last_Option
        };

        static const Option output_options[] = {
            { "output-file", 'o', output_file, output_file,
              false, "write output network to FILE", "FILE" },
            { "quiet", 'q', NO_ARG, assign(verbosity, 0),
              false, "don't write any non-fatal output" },
            { "verbosity", 'v', optional(2), verbosity,
              false, "set verbosity to LEVEL (0-3)", "LEVEL" },
            { "profile", 'l', NO_ARG, flag(profile),
              false, "profile execution time" },
            { "draw-graphs", 'G', NO_ARG, increment(draw_graphs),
              false, "draw graphs for two-class predictor" },
            { "dump-testing", 'D', NO_ARG, flag(dump_testing),
              false, "dump output of classifier on testing sets" },
            { "print-confusion", 'C', NO_ARG, increment(print_confusion),
              false, "print confusion matrix" },
            { "trace", 't', trace, trace,
              false, "set trace level to LEVEL (0 off)", "LEVEL" },
            { "eval-by-group", 0, NO_ARG,flag(eval_by_group,eval_by_group_set),
              false, "evaluate by group rather than by example" },
            Last_Option
        };

        static const Option options[] = {
            group("Data options", data_options),
            group("Weight options", weight_options),
            group("Training options", training_options),
            group("Probabilizer options", probabilize_options),
            group("Output options",   output_options),
            Help_Options,
            Last_Option };

        Command_Line_Parser parser("boosting_training_tool", argc, argv,
                                   options);

        bool res = parser.parse();
        if (res == false) exit(1);

        extra.insert(extra.end(), parser.extra_begin(), parser.extra_end());
    }

    if (val_with_test && validation_split != 0.0)
        throw Exception("can't validate with testing and validation data");

    /* The first extra argument is the training set.  The second, if it
       exists, is the validation set.  Any further ones are testing sets.

       We can also choose to split our training data according to a given
       percentage, for the case where we only have one set.
    */

    if (extra.empty()) {
        cerr << "error: need to specify (at least) training data" << endl;
        exit(1);
    }

    Datasets datasets;
    datasets.init(extra, verbosity, profile);

    std::shared_ptr<Feature_Space> feature_space = datasets.feature_space;

    vector<Feature> features;
    map<string, Feature> feature_index;

    boost::tie(features, feature_index)
        = do_features(*data[0], feature_space, ignore_features,
                      optional_features, min_feature_count, verbosity);

    if (group_feature_name != "") {
        if (!feature_index.count(group_feature_name))
            throw Exception("grouping feature " + group_feature_name
                            + " not found in data");
        Feature group_feature = feature_index[group_feature_name];
        data[0]->group_feature_ = group_feature;

        if (!eval_by_group_set) {
            if (verbosity > 0)
                cerr << "note: overriding eval-by-group=1, use "
                     << "--no-eval-by-group to avoid" << endl;
            eval_by_group = true;
        }
    }

    float training_split = 100.0 - validation_split - testing_split;
    datasets.split(training_split, validation_split, testing_split,
                   randomize_order, group_feature);

    /* Write a null classifier, if there is no training data or we can't
       train for some reason. */
    if (training->example_count() == 0
        || (label_count <= 1 && !regression_problem)) {
        write_null_classifier(output_file, label_count, verbosity);
        return 0;
    }

    vector<Feature> equalize_features;
    vector<float> equalize_betas;

    boost::tie(equalize_betas, equalize_features)
        = parse_weight_spec(feature_index, equalize_name, equalize_beta,
                            weight_spec);

    vector<Classifier_Impl::Weight_Spec> trained_weight_spec
        = Classifier_Impl::get_weight_spec(*training, equalize_betas,
                                           equalize_features);

    /* Now for the training. */

    vector<distribution<float> > accum_acc(testing.size());

    for (unsigned i = 0;  i < repeat_trials;  ++i) {
        if (repeat_trials > 1) cerr << "trial " << (i + 1) << endl;

        if (!training->finished()) {
            boost::timer timer;
            training->finish(num_buckets);
            if (profile)
                cerr << "[finish training data: " << timer.elapsed() << "s]"
                     << endl;
        }

        if (verbosity > 0) {
            unsigned testing_rows = 0;
            for (unsigned i = 0;  i < testing.size();  ++i) {
                testing_rows += testing[i]->example_count();
            }

            cerr << "data sizes: training " << training->example_count()
                 << "  validation: " << validation->example_count()
                 << "  testing: " << testing_rows << endl;
        }

        if (remove_aliased)
            remove_aliased_examples(*training, verbosity, profile);

        vector<Feature> equalize_features;
        vector<float> equalize_betas;

        boost::tie(equalize_betas, equalize_features)
            = parse_weight_spec(feature_index, equalize_name, equalize_beta,
                                weight_spec);

        Boosted_Stumps current
            = run_boosting(*training, *validation, max_iter, min_iter,
                           verbosity, equalize_beta, features,
                           equalize_features, equalize_betas,
                           true_only,
                           profile, fair, feature_prop, committee_size, trace,
                           cost_function, output_function, update_alg,
                           ignore_highest, sample_prop, weight_function,
                           trained_weight_spec);
        GLZ_Probabilizer prob;
        Training_Params params;
        params["glz_probabilizer_mode"] = probabilize_mode;
        params["glz_probabilizer_link"] = probabilize_link;

        if (probabilize_data < 0 || probabilize_data > 1)
            throw Exception("probabilize-data must be 0 or 1 (currently "
                            + ostream_format(probabilize_data) + ")");
        std::shared_ptr<const Training_Data> prob_set
            = (probabilize_data == 0 ? training : validation);

        distribution<float> pr_weights(prob_set->example_count(), 1.0);

        if (probabilize_weighted)
            pr_weights
                = Classifier_Impl::
                    apply_weight_spec(*prob_set, trained_weight_spec);

        prob.train(*prob_set, params, current, pr_weights);

        if (repeat_trials == 1 || verbosity > 2) {
            cerr << "Stats over training set: " << endl;
            calc_stats(current, prob, *training, draw_graphs, dump_testing,
                       print_confusion, eval_by_group);

            cerr << "Stats over validation set: " << endl;
            calc_stats(current, prob, *validation, draw_graphs, dump_testing,
                       print_confusion, eval_by_group);
        }

        Classifier output_wrapped(current);
        Decoded_Classifier output(output_wrapped, Decoder(prob));
        Classifier cl(output);

        if (output_file != "") {
            if (verbosity > 0)
                cerr << "writing to \'" << output_file << "\'... ";
            cl.save(output_file);
            if (verbosity > 0) cerr << "done." << endl;
        }

        /* Test all of the testing datasets. */
        for (unsigned j = 0;  j < testing.size();  ++j) {
            cerr << "Stats over testing set " << j << ":" << endl;

            calc_stats(current, prob, *testing[j], draw_graphs,
                       dump_testing, print_confusion, eval_by_group);

            if (repeat_trials > 1) {
                float acc = current.accuracy(*testing[j]);
                accum_acc[j].push_back(acc);
                cerr << "trial " << i + 1 << ": accuracy " << acc * 100.0
                     << "%, average over all trials = "
                     << accum_acc[j].total() * 100.0 / (i + 1)
                     << "%" << endl;
            }
        }

        if (i != repeat_trials - 1) {
            /* Re-shuffle the data. */
            datasets.resuffle();
        }
    }

    if (repeat_trials > 1) {
        for (unsigned j = 0;  j < accum_acc.size();  ++j) {
            float mean = Stats::mean(accum_acc[j].begin(), accum_acc[j].end(),
                                     0.0);
            float std_dev
                = Stats::std_dev(accum_acc[j].begin(), accum_acc[j].end(),
                                 mean);
            cerr << "testing set " << j << ": " << repeat_trials
                 << " trials accuracy: mean "
                 << mean * 100.0 << "% std " << std_dev * 100.0 << "%."
                 << endl;
        }
    }
}
catch (const std::exception & exc) {
    cerr << "error: " << exc.what() << endl;
    exit(1);
}


/*

Copyright (c) 2007-2012, Un Shyam & Arvid Norberg
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in
      the documentation and/or other materials provided with the distribution.
    * Neither the name of the author nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef TORRENT_DISABLE_ENCRYPTION

#include <boost/cstdint.hpp>
#include <algorithm>

#if defined TORRENT_USE_GCRYPT
#include <gcrypt.h>
#elif defined TORRENT_USE_OPENSSL
#include <openssl/bn.h>
#include "libtorrent/random.hpp"
#elif defined TORRENT_USE_TOMMATH
extern "C" {
#include "libtorrent/tommath.h"
}
#include "libtorrent/random.hpp"
#endif

#include "libtorrent/pe_crypto.hpp"
#include "libtorrent/hasher.hpp"
#include "libtorrent/assert.hpp"

namespace libtorrent
{
	namespace
	{
		const unsigned char dh_prime[96] = {
			0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
			0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
			0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
			0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
			0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
			0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
			0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
			0xA6, 0x3A, 0x36, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x05, 0x63
		};
	}

	// Set the prime P and the generator, generate local public key
	dh_key_exchange::dh_key_exchange()
	{
#ifdef TORRENT_USE_GCRYPT
		// create local key
		gcry_randomize(m_dh_local_secret, sizeof(m_dh_local_secret), GCRY_STRONG_RANDOM);

		// build gcrypt big ints from the prime and the secret
		gcry_mpi_t prime = 0;
		gcry_mpi_t secret = 0;
		gcry_mpi_t key = 0;
		gcry_error_t e;

		e = gcry_mpi_scan(&prime, GCRYMPI_FMT_USG, dh_prime, sizeof(dh_prime), 0);
		if (e) goto get_out;
		e = gcry_mpi_scan(&secret, GCRYMPI_FMT_USG, m_dh_local_secret, sizeof(m_dh_local_secret), 0);
		if (e) goto get_out;

		key = gcry_mpi_new(8);

		// generator is 2
		gcry_mpi_set_ui(key, 2);
		// key = (2 ^ secret) % prime
		gcry_mpi_powm(key, key, secret, prime);

		// key is now our local key
		size_t written;
		gcry_mpi_print(GCRYMPI_FMT_USG, (unsigned char*)m_dh_local_key
			, sizeof(m_dh_local_key), &written, key);
		if (written < 96)
		{
			memmove(m_dh_local_key + (sizeof(m_dh_local_key) - written), m_dh_local_key, written);
			memset(m_dh_local_key, 0, sizeof(m_dh_local_key) - written);
		}

get_out:
		if (key) gcry_mpi_release(key);
		if (prime) gcry_mpi_release(prime);
		if (secret) gcry_mpi_release(secret);

#elif defined TORRENT_USE_OPENSSL
		// create local key
		for (int i = 0; i < sizeof(m_dh_local_secret); ++i)
			m_dh_local_secret[i] = random();

		BIGNUM* prime = 0;
		BIGNUM* secret = 0;
		BIGNUM* key = 0;
		BN_CTX* ctx = 0;
		int size;

		prime = BN_bin2bn(dh_prime, sizeof(dh_prime), 0);
		if (prime == 0) goto get_out;
		secret = BN_bin2bn((unsigned char*)m_dh_local_secret, sizeof(m_dh_local_secret), 0);
		if (secret == 0) goto get_out;

		key = BN_new();
		if (key == 0) goto get_out;
		// generator is 2
		BN_set_word(key, 2);

		ctx = BN_CTX_new();
		if (ctx == 0) goto get_out;
		BN_mod_exp(key, key, secret, prime, ctx);
		BN_CTX_free(ctx);

		// print key to m_dh_local_key
		size = BN_num_bytes(key);
		memset(m_dh_local_key, 0, sizeof(m_dh_local_key) - size);
		BN_bn2bin(key, (unsigned char*)m_dh_local_key + sizeof(m_dh_local_key) - size);

get_out:
		if (key) BN_free(key);
		if (secret) BN_free(secret);
		if (prime) BN_free(prime);
#elif defined TORRENT_USE_TOMMATH
		// create local key
		for (int i = 0; i < int(sizeof(m_dh_local_secret)); ++i)
			m_dh_local_secret[i] = random();

		mp_int prime;
		mp_int secret;
		mp_int key;
		int e;
		int size;

		mp_init(&prime);
		mp_init(&secret);
		mp_init(&key);

		e = mp_read_unsigned_bin(&prime, dh_prime, sizeof(dh_prime));
		if (e) goto get_out;
		e = mp_read_unsigned_bin(&secret, (unsigned char*)m_dh_local_secret, sizeof(m_dh_local_secret));
		if (e) goto get_out;

		// generator is 2
		mp_set_int(&key, 2);
		// key = (2 ^ secret) % prime
		e = mp_exptmod(&key, &secret, &prime, &key);
		if (e) goto get_out;

		// key is now our local key
		size = mp_unsigned_bin_size(&key);
		memset(m_dh_local_key, 0, sizeof(m_dh_local_key) - size);
		mp_to_unsigned_bin(&key, (unsigned char*)m_dh_local_key + sizeof(m_dh_local_key) - size);

get_out:
		mp_clear(&key);
		mp_clear(&prime);
		mp_clear(&secret);
#else
#error you must define which bigint library to use
#endif
	}

	char const* dh_key_exchange::get_local_key() const
	{
		return m_dh_local_key;
	}

	// compute shared secret given remote public key
	int dh_key_exchange::compute_secret(char const* remote_pubkey)
	{
		TORRENT_ASSERT(remote_pubkey);
		int ret = 0;
#ifdef TORRENT_USE_GCRYPT

		gcry_mpi_t prime = 0;
		gcry_mpi_t remote_key = 0;
		gcry_mpi_t secret = 0;
		size_t written;
		gcry_error_t e;

		e = gcry_mpi_scan(&prime, GCRYMPI_FMT_USG, dh_prime, sizeof(dh_prime), 0);
		if (e != 0) { ret = 1; goto get_out; }
		e = gcry_mpi_scan(&remote_key, GCRYMPI_FMT_USG, remote_pubkey, 96, 0);
		if (e != 0) { ret = 1; goto get_out; }
		e = gcry_mpi_scan(&secret, GCRYMPI_FMT_USG, (unsigned char const*)m_dh_local_secret
			, sizeof(m_dh_local_secret), 0);
		if (e != 0) { ret = 1; goto get_out; }

		gcry_mpi_powm(remote_key, remote_key, secret, prime);

		// remote_key is now the shared secret
		e = gcry_mpi_print(GCRYMPI_FMT_USG, (unsigned char*)m_dh_shared_secret
			, sizeof(m_dh_shared_secret), &written, remote_key);
		if (e != 0) { ret = 1; goto get_out; }

		if (written < 96)
		{
			memmove(m_dh_shared_secret, m_dh_shared_secret
				+ (sizeof(m_dh_shared_secret) - written), written);
			memset(m_dh_shared_secret, 0, sizeof(m_dh_shared_secret) - written);
		}

get_out:
		if (prime) gcry_mpi_release(prime);
		if (remote_key) gcry_mpi_release(remote_key);
		if (secret) gcry_mpi_release(secret);

#elif defined TORRENT_USE_OPENSSL

		BIGNUM* prime = 0;
		BIGNUM* secret = 0;
		BIGNUM* remote_key = 0;
		BN_CTX* ctx = 0;
		int size;

		prime = BN_bin2bn(dh_prime, sizeof(dh_prime), 0);
		if (prime == 0) { ret = 1; goto get_out; }
		secret = BN_bin2bn((unsigned char*)m_dh_local_secret, sizeof(m_dh_local_secret), 0);
		if (secret == 0) { ret = 1; goto get_out; }
		remote_key = BN_bin2bn((unsigned char*)remote_pubkey, 96, 0);
		if (remote_key == 0) { ret = 1; goto get_out; }

		ctx = BN_CTX_new();
		if (ctx == 0) { ret = 1; goto get_out; }
		BN_mod_exp(remote_key, remote_key, secret, prime, ctx);
		BN_CTX_free(ctx);

		// remote_key is now the shared secret
		size = BN_num_bytes(remote_key);
		memset(m_dh_shared_secret, 0, sizeof(m_dh_shared_secret) - size);
		BN_bn2bin(remote_key, (unsigned char*)m_dh_shared_secret + sizeof(m_dh_shared_secret) - size);

get_out:
		BN_free(remote_key);
		BN_free(secret);
		BN_free(prime);
#elif defined TORRENT_USE_TOMMATH
		mp_int prime;
		mp_int secret;
		mp_int remote_key;
		int size;
		int e;

		mp_init(&prime);
		mp_init(&secret);
		mp_init(&remote_key);

		e = mp_read_unsigned_bin(&prime, dh_prime, sizeof(dh_prime));
		if (e) { ret = 1; goto get_out; }
		e = mp_read_unsigned_bin(&secret, (unsigned char*)m_dh_local_secret, sizeof(m_dh_local_secret));
		if (e) { ret = 1; goto get_out; }
		e = mp_read_unsigned_bin(&remote_key, (unsigned char*)remote_pubkey, 96);
		if (e) { ret = 1; goto get_out; }

		e = mp_exptmod(&remote_key, &secret, &prime, &remote_key);
		if (e) goto get_out;

		// remote_key is now the shared secret
		size = mp_unsigned_bin_size(&remote_key);
		memset(m_dh_shared_secret, 0, sizeof(m_dh_shared_secret) - size);
		mp_to_unsigned_bin(&remote_key, (unsigned char*)m_dh_shared_secret + sizeof(m_dh_shared_secret) - size);

get_out:
		mp_clear(&remote_key);
		mp_clear(&secret);
		mp_clear(&prime);
#else
#error you must define which bigint library to use
#endif

		// calculate the xor mask for the obfuscated hash
		hasher h;
		h.update("req3", 4);
		h.update(m_dh_shared_secret, sizeof(m_dh_shared_secret));
		m_xor_mask = h.final();
		return ret;
	}

} // namespace libtorrent

#if !defined TORRENT_USE_OPENSSL && !defined TORRENT_USE_GCRYPT

// All this code is based on libTomCrypt (http://www.libtomcrypt.com/)
// this library is public domain and has been specially
// tailored for libtorrent by Arvid Norberg

void rc4_init(const unsigned char* in, unsigned long len, rc4 *state)
{
	unsigned char key[256], tmp, *s;
	int keylen, x, y, j;

	TORRENT_ASSERT(state != 0);
	TORRENT_ASSERT(len <= 256);

	state->x = 0;
	while (len--) {
		state->buf[state->x++] = *in++;
	}

	/* extract the key */
	s = state->buf;
	memcpy(key, s, 256);
	keylen = state->x;

	/* make RC4 perm and shuffle */
	for (x = 0; x < 256; x++) {
		s[x] = x;
	}

	for (j = x = y = 0; x < 256; x++) {
		y = (y + state->buf[x] + key[j++]) & 255;
		if (j == keylen) {
			j = 0;
		}
		tmp = s[x]; s[x] = s[y]; s[y] = tmp;
	}
	state->x = 0;
	state->y = 0;
}

unsigned long rc4_encrypt(unsigned char *out, unsigned long outlen, rc4 *state)
{
	unsigned char x, y, *s, tmp;
	unsigned long n;

	TORRENT_ASSERT(out != 0);
	TORRENT_ASSERT(state != 0);

	n = outlen;
	x = state->x;
	y = state->y;
	s = state->buf;
	while (outlen--) {
		x = (x + 1) & 255;
		y = (y + s[x]) & 255;
		tmp = s[x]; s[x] = s[y]; s[y] = tmp;
		tmp = (s[x] + s[y]) & 255;
		*out++ ^= s[tmp];
	}
	state->x = x;
	state->y = y;
	return n;
}

#endif

#endif // #ifndef TORRENT_DISABLE_ENCRYPTION


/*    Copyright (c) 2010-2019, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 *
 */

#include <boost/bind.hpp>

#include "Tudat/Astrodynamics/BasicAstrodynamics/physicalConstants.h"
#include "Tudat/Mathematics/Interpolators/cubicSplineInterpolator.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/timeConversions.h"
#include "Tudat/Astrodynamics/EarthOrientation/precessionNutationCalculator.h"
#include "Tudat/External/SofaInterface/sofaTimeConversions.h"

namespace tudat
{

namespace earth_orientation
{

//! Constructor for (CIO-based) precession-nutation calculation object.
PrecessionNutationCalculator::PrecessionNutationCalculator(
        basic_astrodynamics::IAUConventions precessionNutationTheory,
        std::shared_ptr< interpolators::OneDimensionalInterpolator< double, Eigen::Vector2d > >
        dailyCorrectionInterpolator ):
    precessionNutationTheory_( precessionNutationTheory ),
    dailyCorrectionInterpolator_( dailyCorrectionInterpolator )
{
    // Link selected SOFA function wrapper for direct calculation of precession-nutation.
    nominalCipPositionFunction_ =
            std::bind( sofa_interface::getPositionOfCipInGcrs,
                         std::placeholders::_1, basic_astrodynamics::JULIAN_DAY_ON_J2000, precessionNutationTheory );
}

//! Function to calculate the position of CIP in GCRS (CIO-based precession-nutation) and CIO-locator.
std::pair< Eigen::Vector2d, double > PrecessionNutationCalculator::getPositionOfCipInGcrs(
        const double terrestrialTime )
{
    // Calculate current UTC from SOFA.
    double utc = sofa_interface::convertTTtoUTC( terrestrialTime );

    // Call function to compte precession-nutation from UTC and TT.
    return getPositionOfCipInGcrs( terrestrialTime, utc );
}

//! Function to calculate the position of CIP in GCRS (CIO-based precession-nutation) and CIO-locator.
std::pair< Eigen::Vector2d, double > PrecessionNutationCalculator::getPositionOfCipInGcrs(
        const double terrestrialTime,
        const double utc )
{
    // Calculate nominal precession-nutation values.
    std::pair< Eigen::Vector2d, double > nominalCipPosition = nominalCipPositionFunction_( terrestrialTime );

    // Retrieve measured corrections to model.
    Eigen::Vector2d iersCorrections = dailyCorrectionInterpolator_->interpolate( utc );

    // Add nominal values and corrections and return.
    return std::pair< Eigen::Vector2d, double >(
                nominalCipPosition.first + iersCorrections, nominalCipPosition.second );
}

}

}


#include <koinos/crypto/elliptic.hpp>
#include <koinos/crypto/multihash.hpp>
#include <koinos/crypto/openssl.hpp>
#include <koinos/util/base58.hpp>
#include <koinos/util/conversion.hpp>

#include <boost/core/ignore_unused.hpp>
#include <boost/multiprecision/cpp_int.hpp>

#include <secp256k1.h>
#include <secp256k1_recovery.h>

namespace koinos { namespace crypto {

using koinos::exception;
using namespace boost::multiprecision::literals;

const secp256k1_context* _get_context()
{
   static secp256k1_context* ctx = secp256k1_context_create( SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN );
   return ctx;
}

void _init_lib()
{
   static const secp256k1_context* ctx = _get_context();
   static int init_o = init_openssl();
   boost::ignore_unused(ctx, init_o);
}

static int extended_nonce_function( unsigned char *nonce32, const unsigned char *msg32,
                                    const unsigned char *key32, const unsigned char* algo16,
                                    void *data, const unsigned int attempt )
{
   unsigned int* extra = (unsigned int*) data;
   (*extra)++;
   return secp256k1_nonce_function_default( nonce32, msg32, key32, algo16, nullptr, *extra );
}

namespace detail {

   using public_key_data = std::array< std::byte, sizeof( secp256k1_pubkey ) >; ///< The full non-compressed ECDSA public key point

   const public_key_data& empty_pub()
   {
      static const public_key_data empty_pub{};
      return empty_pub;
   }

   struct public_key_impl
   {
      public_key_impl();
      public_key_impl( const public_key_impl& k );
      public_key_impl( public_key_impl&& pk );

      ~public_key_impl();

      compressed_public_key serialize()const;

      public_key_impl add( const multihash& offset )const;

      bool valid()const;

      friend bool operator ==( const public_key_impl& a, const public_key_impl& b )
      {
         return std::memcmp( a._key.data(), b._key.data(), sizeof(public_key_data) ) == 0;
      }

      std::string to_address_bytes( std::byte prefix ) const;

      unsigned int fingerprint() const;

      public_key_data _key;
   };

   public_key_impl::public_key_impl() { _init_lib(); }

   public_key_impl::public_key_impl( const public_key_impl& pk ) : _key( pk._key ) { _init_lib(); }

   public_key_impl::public_key_impl( public_key_impl&& pk ) : _key( std::move( pk._key ) ) { _init_lib(); }

   public_key_impl::~public_key_impl() {}

   compressed_public_key public_key_impl::serialize()const
   {
      KOINOS_ASSERT( _key != empty_pub(), key_serialization_error, "Cannot serialize an empty public key" );

      compressed_public_key cpk;
      size_t len = cpk.size();
      KOINOS_ASSERT(
         secp256k1_ec_pubkey_serialize(
            _get_context(),
            (unsigned char *) cpk.data(),
            &len,
            (const secp256k1_pubkey*) _key.data(),
            SECP256K1_EC_COMPRESSED ),
         key_serialization_error, "Unknown error during public key serialization" );
      KOINOS_ASSERT( len == cpk.size(), key_serialization_error,
         "Serialized key does not match expected size of ${n} bytes", ("n", cpk.size()) );

      return cpk;
   }

   public_key_impl public_key_impl::add( const multihash& hash )const
   {
      KOINOS_ASSERT( hash.digest().size() == 32, key_manipulation_error, "Digest must be 32 bytes" );
      KOINOS_ASSERT( _key != empty_pub(), key_manipulation_error, "Cannot add to an empty key" );
      public_key_impl new_key( *this );
      KOINOS_ASSERT(
         secp256k1_ec_pubkey_tweak_add(
            _get_context(),
            (secp256k1_pubkey*) new_key._key.data(),
            (unsigned char*) hash.digest().data() ),
         key_manipulation_error, "Unknown error when adding to public key" );
      return new_key;
   }

   bool public_key_impl::valid()const
   {
      return _key != empty_pub();
   }

   std::string public_key_impl::to_address_bytes( std::byte prefix )const
   {
      auto compressed_key = serialize();
      auto sha256 = hash( multicodec::sha2_256, (char*)compressed_key.data(), compressed_key.size() );
      auto ripemd160 = hash( multicodec::ripemd_160, sha256 );
      std::array< std::byte, 25 > d;
      d[0] = prefix;
      std::memcpy( d.data() + 1, ripemd160.digest().data(), ripemd160.digest().size() );
      sha256 = hash( multicodec::sha2_256, (char*)d.data(), ripemd160.digest().size() + 1 );
      sha256 = hash( multicodec::sha2_256, sha256 );
      std::memcpy( d.data() + ripemd160.digest().size() + 1, sha256.digest().data(), 4 );
      return util::converter::as< std::string >( d );
   }

   unsigned int public_key_impl::fingerprint() const
   {
      multihash sha256 = hash( multicodec::sha2_256, (char*)_key.data(), _key.size() );
      multihash ripemd160 = hash( multicodec::ripemd_160, sha256 );
      unsigned char* fp = (unsigned char*) ripemd160.digest().data();
      return (fp[0] << 24) | (fp[1] << 16) | (fp[2] << 8) | fp[3];
   }
}

const private_key_secret& empty_priv()
{
   static const private_key_secret empty_priv{};
   return empty_priv;
}

public_key::public_key() : _my( std::make_unique< detail::public_key_impl >() ) {}
public_key::public_key( const public_key& k ) : _my( std::make_unique< detail::public_key_impl >( *k._my ) ) {}
public_key::public_key( public_key&& k ) : _my( std::move( k._my ) ) {}

public_key::~public_key() {}

compressed_public_key public_key::serialize() const
{
   return _my->serialize();
}

public_key public_key::deserialize( const compressed_public_key& cpk )
{
   public_key pk;
   KOINOS_ASSERT(
      secp256k1_ec_pubkey_parse(
         _get_context(),
         (secp256k1_pubkey*) pk._my->_key.data(),
         (const unsigned char*) cpk.data(),
         cpk.size() ),
      key_serialization_error, "Unknown serror during public key deserialization" );
   return pk;
}

public_key public_key::recover( const recoverable_signature& sig, const multihash& hash )
{
   KOINOS_ASSERT( hash.digest().size() == 32, key_recovery_error, "Digest must be 32 bytes" );
   KOINOS_ASSERT( is_canonical( sig ), key_recovery_error, "Signature is not canonical" );
   std::array< std::byte, 65 > internal_sig;
   public_key pk;

   int32_t rec_id = std::to_integer< int32_t >( sig[0] );
   KOINOS_ASSERT( 31 <= rec_id && rec_id <= 33, key_recovery_error, "Recovery ID mismatch. Must be in range [31,33]" );

   // The internal representation, as per the secp256k1 documentation, is an implementation
   // detail and not guaranteed across platforms or versions of secp256k1. We need to
   // convert the portable format to the internal format first.
   KOINOS_ASSERT(
      secp256k1_ecdsa_recoverable_signature_parse_compact(
         _get_context(),
         (secp256k1_ecdsa_recoverable_signature*) internal_sig.data(),
         (const unsigned char*) sig.data() + 1,
         rec_id >> 5 ),
      key_recovery_error, "Unknown error when parsing signature" );

   KOINOS_ASSERT(
      secp256k1_ecdsa_recover(
         _get_context(),
         (secp256k1_pubkey*) pk._my->_key.data(),
         (const secp256k1_ecdsa_recoverable_signature*) internal_sig.data(),
         (unsigned char*) hash.digest().data() ),
      key_recovery_error, "Unknown error recovering public key from signature" );

   return pk;
}

public_key public_key::add( const multihash& offset ) const
{
   public_key pk;
   pk._my = std::make_unique< detail::public_key_impl >( std::move( _my->add( offset ) ) );
   return std::move( pk );
}

bool public_key::valid() const
{
   return _my->valid();
}

public_key& public_key::operator=( const public_key& pk )
{
   _my = std::make_unique< detail::public_key_impl >( *pk._my );
   return *this;
}

public_key& public_key::operator=( public_key&& pk )
{
   _my = std::move( pk._my );
   return *this;
}

bool operator ==( const public_key& a, const public_key& b )
{
   return *a._my == *b._my;
}

std::string public_key::to_address_bytes( std::byte prefix ) const
{
   return _my->to_address_bytes( prefix );
}

unsigned int public_key::fingerprint() const
{
   return _my->fingerprint();
}

bool public_key::is_canonical( const recoverable_signature& c )
{
   using boost::multiprecision::uint256_t;
   constexpr uint256_t n_2 =
      0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0_cppui256;

   // BIP-0062 states that sig must be in [1,n/2], however because a sig of value 0 is an invalid
   // signature under all circumstances, the lower bound does not need checking
   return std::memcmp( c.data() + 33, &n_2, 32 ) <= 0;
}

private_key::private_key() { _init_lib(); }

private_key::private_key( private_key&& pk ) : _key( std::move( pk._key ) ) { _init_lib(); }

private_key::private_key( const private_key& pk ) : _key( pk._key ) { _init_lib(); }

private_key::~private_key() {}

private_key& private_key::operator=( private_key&& pk )
{
   _key = std::move( pk._key );
   return *this;
}

private_key& private_key::operator=( const private_key& pk )
{
   _key = pk._key;
   return *this;
}

private_key private_key::regenerate( const multihash& secret )
{
   KOINOS_ASSERT(
      secret.digest().size() == sizeof( private_key_secret ),
      koinos::exception,
      "secret must be ${s} bits", ("s", sizeof( private_key_secret ))
   );
   private_key self;
   std::memcpy( self._key.data(), secret.digest().data(), self._key.size() );
   return self;
}

private_key private_key::generate_from_seed( const multihash& seed, const multihash& offset )
{
   // There is non-determinism in this function, perhaps purposefully.
   ssl_bignum z;
   BN_bin2bn( (unsigned char*)offset.digest().data(), offset.digest().size(), z );

   ec_group group( EC_GROUP_new_by_curve_name( NID_secp256k1 ) );
   bn_ctx ctx( BN_CTX_new() );
   ssl_bignum order;
   EC_GROUP_get_order( group, order, ctx );

   // secexp = (seed + z) % order
   ssl_bignum secexp;
   BN_bin2bn( (unsigned char*)&seed, sizeof( seed ), secexp );
   BN_add(secexp, secexp, z);
   BN_mod(secexp, secexp, order, ctx);

   std::vector< std::byte > digest;
   digest.resize( 32 );
   assert( BN_num_bytes( secexp ) <= int64_t( digest.size() ) );
   auto shift = digest.size() - BN_num_bytes( secexp );
   BN_bn2bin( secexp, ( (unsigned char*)digest.data() ) + shift );
   return regenerate( multihash( multicodec::sha2_256, digest ) );
}

private_key_secret private_key::get_secret()const
{
   return _key;
}

recoverable_signature private_key::sign_compact( const multihash& digest )const
{
   KOINOS_ASSERT( digest.digest().size() == _key.size(), koinos::exception, "digest must be ${s} bits", ("s", _key.size()) );
   KOINOS_ASSERT( _key != empty_priv(), signing_error, "cannot sign with an empty key" );
   std::array< std::byte, 65 > internal_sig;
   recoverable_signature sig;
   int32_t rec_id;
   unsigned int counter = 0;
   do
   {
      KOINOS_ASSERT(
         secp256k1_ecdsa_sign_recoverable(
            _get_context(),
            (secp256k1_ecdsa_recoverable_signature*) internal_sig.data(),
            (unsigned char*) digest.digest().data(),
            (unsigned char*) _key.data(),
            extended_nonce_function,
            &counter ),
         signing_error, "unknown error when signing" );
      KOINOS_ASSERT(
         secp256k1_ecdsa_recoverable_signature_serialize_compact(
            _get_context(),
            (unsigned char*) sig.data() + 1,
            &rec_id,
            (const secp256k1_ecdsa_recoverable_signature*) internal_sig.data() ),
         signing_error, "unknown error when serialzing recoverable signature" );
      sig[0] = static_cast< std::byte >( rec_id + 31 );
   } while( !public_key::is_canonical( sig ) );

   return sig;
}

public_key private_key::get_public_key()const
{
   KOINOS_ASSERT( _key != empty_priv(), key_manipulation_error, "Cannot get private key of an empty public key" );
   public_key pk;
   KOINOS_ASSERT(
      secp256k1_ec_pubkey_create(
         _get_context(),
         (secp256k1_pubkey*) pk._my->_key.data(),
         (unsigned char*) _key.data() ),
      key_manipulation_error, "Unknown error creating public key from a private key" );
   return pk;
}

std::string private_key::to_wif( std::byte prefix )
{
   std::array< std::byte, 37 > d;
   uint32_t check;
   assert( _key.size() + sizeof(check) + 1 == d.size() );
   d[0] = prefix;
   std::memcpy( d.data() + 1, _key.data(), _key.size() );
   auto extended_hash = hash( multicodec::sha2_256, (char*)d.data(), _key.size() + 1 );
   check = *((uint32_t*)hash( multicodec::sha2_256, extended_hash ).digest().data());
   std::memcpy( d.data() + _key.size() + 1, (const char*)&check, sizeof(check)  );
   return util::encode_base58( d );
}

private_key private_key::from_wif( const std::string& b58, std::byte prefix )
{
   std::array< char, 37 > d;
   util::decode_base58( b58, d );
   KOINOS_ASSERT( d[0] == std::to_integer< char >( prefix ), key_serialization_error, "incorrect WIF prefix" );
   private_key key;
   auto extended_hash = hash( multicodec::sha2_256, d.data(), key._key.size() + 1 );
   uint32_t check = *((uint32_t*)hash( multicodec::sha2_256, extended_hash ).digest().data());

   KOINOS_ASSERT(
      std::memcmp( (char*)&check, d.data() + key._key.size() + 1, sizeof(check) ) == 0,
      key_serialization_error,
      "invalid checksum"
   );

   std::memcpy( key._key.data(), d.data() + 1, key._key.size() );
   return key;
}

} // crypto

template<>
void to_binary< crypto::public_key >( std::ostream& s, const crypto::public_key& k )
{
   auto cpk = k.serialize();
   s.write( reinterpret_cast< const char* >( cpk.data() ), cpk.size() );
}

template<>
void from_binary< crypto::public_key >( std::istream& s, crypto::public_key& k )
{
   crypto::compressed_public_key cpk;
   s.readsome( reinterpret_cast< char* >( cpk.data() ), cpk.size() );
   k.deserialize( cpk );
}

} // koinos


#include <boost/hana.hpp>
#include <string>
#include <type_traits>

int main()
{
    constexpr auto t1 = boost::hana::make_tuple(boost::hana::type_c<int*>, boost::hana::type_c<double>, boost::hana::type_c<std::string>);
    constexpr auto r1{ boost::hana::find(t1, boost::hana::type_c<std::string>) };
    static_assert(boost::hana::is_just(r1));

    boost::hana::tuple<int*, double, std::string> t2;
    auto r2 { boost::hana::find_if(t2, [](auto t){ return boost::hana::bool_c<std::is_same<decltype(t), std::string>::value>; }) };
    static_assert(boost::hana::is_just(r2));
}


//  (C) Copyright Jeremy Siek 1999. Permission to copy, use, modify,
//  sell and distribute this software is granted provided this
//  copyright notice appears in all copies. This software is provided
//  "as is" without express or implied warranty, and with no claim as
//  to its suitability for any purpose.

//  Revision History
//  08 Mar 2001   Jeremy Siek
//       Moved test of transform iterator into its own file. It to
//       to be in iterator_adaptor_test.cpp.

#include <boost/config.hpp>
#include <iostream>
#include <algorithm>
#include <boost/iterator_adaptors.hpp>
#include <boost/pending/iterator_tests.hpp>

struct mult_functor {
  typedef int result_type;
  typedef int argument_type;
  // Functors used with transform_iterator must be
  // DefaultConstructible, as the transform_iterator must be
  // DefaultConstructible to satisfy the requirements for
  // TrivialIterator.
  mult_functor() { }
  mult_functor(int aa) : a(aa) { }
  int operator()(int b) const { return a * b; }
  int a;
};

int
main()
{
  const int N = 10;

  // Borland is getting confused about typedef's and constructors here

  // Test transform_iterator
  {
    int x[N], y[N];
    for (int k = 0; k < N; ++k)
      x[k] = k;
    std::copy(x, x + N, y);

    for (int k2 = 0; k2 < N; ++k2)
      x[k2] = x[k2] * 2;

    boost::transform_iterator_generator<mult_functor, int*>::type i(y, mult_functor(2));
    boost::input_iterator_test(i, x[0], x[1]);
    boost::input_iterator_test(boost::make_transform_iterator(&y[0], mult_functor(2)), x[0], x[1]);
  }
  std::cout << "test successful " << std::endl;
  return 0;
}


//  Copyright (c) 2007-2017 Hartmut Kaiser, Richard D Guidry Jr.
//  Copyright (c) 2011 Vinay C Amatya
//  Copyright (c) 2011 Bryce Lelbach
//
//  Distributed under 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)
//
//  Parts of this nqueen_client.cpp has been taken from the accumulator example
//  by Hartmut Kaiser.

//#include <boost/lexical_cast.hpp>

#include <cstddef>
#include <iostream>
#include <string>
#include <vector>
#include <chrono>

#include "nqueen.hpp"

using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::nanoseconds;

int task_create( nqueen::board sub_board, int size, int col)
{
    //return sub_board.solve_board( sub_board.access_board(), size, 0, i);

    int *sub_count = new int[size];
    nqueen::board b1(sub_board.access_board(), size, 0);
    b1.update_board(0, col);

    if(b1.check_board(b1.access_board(), 0)) {
        int level = 1;

        nqueen::board b2(b1.access_board(), size, level);
        for(int i=0; i < size; i++) {
            b2.update_board(level, i);
            if (b2.check_board(b2.access_board(), level)) {
#pragma omp task
                {
                    sub_count[i] = b2.solve_board(b2.access_board(), size, level+1, col);
                }
            } else {
                sub_count[i] = 0;
            }
        }
    }
#pragma omp taskwait
    int soln_count_total = 0;
    for(int i=0; i < size; i++) {
        soln_count_total += sub_count[i];
    }

    delete[] sub_count;

    return soln_count_total;
}

int main(int argc, char* argv[])
{
    int soln_count_total = 0;
    int sz = 8;
    int task_level = 1;
    if(argc > 1) {
        sz = atoi(argv[1]);
    }
    if(argc > 2) {
        task_level = atoi(argv[2]);
    }

    std::cout << "board size: " << sz << std::endl;

#pragma omp parallel
    {
#pragma omp single
        {
            nqueen::board *sub_boards = new nqueen::board[sz];
            int *sub_count = new int[sz];
            auto t1 = high_resolution_clock::now();
            for(int i=0; i < sz; i++) {
                sub_boards[i] = nqueen::board();
                sub_boards[i].init_board(sz);
                if(task_level == 0) {
#pragma omp task
                    {
                        sub_count[i] = sub_boards[i].solve_board(sub_boards[i].access_board(), sz, 0, i);
                    }
                } else {
#pragma omp task
                    {
                        sub_count[i] = task_create( sub_boards[i], sz, i);
                    }
                }
            }
#pragma omp taskwait
            for(int i=0; i < sz; i++) {
                soln_count_total += sub_count[i];
            }
            auto t2 = high_resolution_clock::now();

            auto total = duration_cast<nanoseconds> (t2-t1).count();
            std::cout << "time: " << total << " ns" << std::endl;
            std::cout << "soln_count:" << soln_count_total << std::endl;
            delete[] sub_boards;
            delete[] sub_count;

        }}//end parallel
    return 0;
}


#define BOOST_TEST_MODULE TEST_TRAINERS

#include <dynet/dynet.h>
#include <dynet/expr.h>
#include <dynet/training.h>
#include <dynet/grad-check.h>
#include <boost/test/unit_test.hpp>
#include "test.h"
#include <stdexcept>

using namespace dynet;
using namespace std;

struct TrainerTest {
  TrainerTest() {
    // initialize if necessary
    if(default_device == nullptr) {
      for (auto x : {"TrainerTest", "--dynet-mem", "10"}) {
        av.push_back(strdup(x));
      }
      ADD_EXTRA_ARGUMENTS(av)
      char **argv = &av[0];
      int argc = av.size();
      dynet::initialize(argc, argv);
    }
    ones_vals = {1.f,1.f,1.f};
    param_vals = {1.1f,-2.2f,3.3f};
    param2_vals = {1.1f,-2.2f,3.3f};
  }
  ~TrainerTest() {
    for (auto x : av) free(x);
  }

  template <class T>
  std::string print_vec(const std::vector<T> vec) {
    ostringstream oss;
    if(vec.size()) oss << vec[0];
    for(size_t i = 1; i < vec.size(); i++)
      oss << ' ' << vec[i];
    return oss.str();
  }

  std::vector<float> ones_vals, param_vals, param2_vals;
  std::vector<char*> av;
};

// define the test suite
BOOST_FIXTURE_TEST_SUITE(trainer_test, TrainerTest);

// Test direction (does updating reduce the objective value)

#define DYNET_TRAINER_DIRECTION_TEST_CASE(name, TRAINER_TYPE)       \
BOOST_AUTO_TEST_CASE( name ) {                                      \
  dynet::ParameterCollection mod;                                   \
  dynet::Parameter param = mod.add_parameters({3});                 \
  TensorTools::set_elements(param.get_storage().values, param_vals);\
  TRAINER_TYPE trainer(mod);                                        \
  dynet::ComputationGraph cg;                                       \
  Expression x = parameter(cg, param);                              \
  Expression y = input(cg, {1, 3}, ones_vals);                      \
  Expression z = y * x;                                             \
  float before = as_scalar(cg.forward(z));                          \
  cg.backward(z);                                                   \
  trainer.update();                                                 \
  float after = as_scalar(cg.forward(z));                           \
  BOOST_CHECK_LT(after, before);                                    \
}                                                                   \

DYNET_TRAINER_DIRECTION_TEST_CASE(simple_sgd_direction, dynet::SimpleSGDTrainer)

DYNET_TRAINER_DIRECTION_TEST_CASE(cyclical_sgd_direction, dynet::CyclicalSGDTrainer)

DYNET_TRAINER_DIRECTION_TEST_CASE(momentum_sgd_direction, dynet::MomentumSGDTrainer)

DYNET_TRAINER_DIRECTION_TEST_CASE(adagrad_direction, dynet::AdagradTrainer)

DYNET_TRAINER_DIRECTION_TEST_CASE(adadelta_direction, dynet::AdadeltaTrainer)

DYNET_TRAINER_DIRECTION_TEST_CASE(rmsprop_direction, dynet::RMSPropTrainer)

DYNET_TRAINER_DIRECTION_TEST_CASE(adam_direction, dynet::AdamTrainer)

BOOST_AUTO_TEST_CASE( eg_direction ) {
  param_vals = {0.5f,0.1f,0.4f};// EGTrainer requires values belonging to simplex [0,1]
  dynet::ParameterCollection mod;
  dynet::Parameter param = mod.add_parameters({3});
  TensorTools::set_elements(param.get_storage().values,param_vals);
  EGTrainer trainer(mod);
  dynet::ComputationGraph cg;
  Expression x = parameter(cg, param);
  Expression y = input(cg, {1,3}, ones_vals);
  Expression z = y*x;
  float before = as_scalar(cg.forward(z));
  cg.backward(z);
  trainer.update();
  float after = as_scalar(cg.forward(z));
  BOOST_CHECK_EQUAL(after, before);
  param_vals = {1.1f,-2.2f,3.3f};// revert back to original values
}

// Test the restart method (TODO: for now only checks for errors)

#define DYNET_TRAINER_RESTART_TEST_CASE(name, TRAINER_TYPE)         \
BOOST_AUTO_TEST_CASE( name ) {                                      \
  dynet::ParameterCollection mod;                                   \
  dynet::Parameter param = mod.add_parameters({3});                 \
  TensorTools::set_elements(param.get_storage().values, param_vals);\
  TRAINER_TYPE trainer(mod);                                        \
  dynet::ComputationGraph cg;                                       \
  Expression x = parameter(cg, param);                              \
  Expression y = input(cg, {1, 3}, ones_vals);                      \
  Expression z = y * x;                                             \
  cg.forward(z);                                                    \
  cg.backward(z);                                                   \
  trainer.update();                                                 \
  trainer.restart(0.1);                                             \
}                                                                   \

DYNET_TRAINER_RESTART_TEST_CASE(simple_sgd_restart, dynet::SimpleSGDTrainer)

DYNET_TRAINER_RESTART_TEST_CASE(cyclical_sgd_restart, dynet::CyclicalSGDTrainer)

DYNET_TRAINER_RESTART_TEST_CASE(momentum_sgd_restart, dynet::MomentumSGDTrainer)

DYNET_TRAINER_RESTART_TEST_CASE(adagrad_restart, dynet::AdagradTrainer)

DYNET_TRAINER_RESTART_TEST_CASE(adadelta_restart, dynet::AdadeltaTrainer)

DYNET_TRAINER_RESTART_TEST_CASE(rmsprop_restart, dynet::RMSPropTrainer)

DYNET_TRAINER_RESTART_TEST_CASE(adam_restart, dynet::AdamTrainer)

DYNET_TRAINER_RESTART_TEST_CASE(eg_restart, dynet::EGTrainer)

BOOST_AUTO_TEST_CASE( momentum_restart_correctness ) {
  dynet::ParameterCollection pc;
  dynet::Parameter param = pc.add_parameters({3});
  MomentumSGDTrainer trainer(pc, 1.0);
  dynet::ComputationGraph cg;
  Expression x1 = parameter(cg, param);
  Expression y = input(cg, {1,3}, ones_vals);
  Expression z = y * x1;
  cg.backward(z);
  trainer.update();
  vector<float> vp_val = as_vector(trainer.vp[0].h);
  // Test that the velocity has been updated
  for(size_t i = 0; i < vp_val.size(); ++i)
    BOOST_CHECK_EQUAL(vp_val[i], -1.0);
  trainer.restart(0.5);
  vp_val = as_vector(trainer.vp[0].h);
  // Test that the velocity has been reset to 0
  for(size_t i = 0; i < vp_val.size(); ++i)
    BOOST_CHECK_EQUAL(vp_val[i], 0);
  // Test that the learning rate has been set to 0.5
  BOOST_CHECK_EQUAL(trainer.learning_rate, 0.5);
}

// Test subset update

BOOST_AUTO_TEST_CASE( simple_sgd_update_subset ) {
  dynet::ParameterCollection mod;
  dynet::Parameter param = mod.add_parameters({3});
  dynet::Parameter param2 = mod.add_parameters({3});
  TensorTools::set_elements(param.get_storage().values,param_vals);
  TensorTools::set_elements(param2.get_storage().values,param2_vals);
  param2.set_updated(false);
  SimpleSGDTrainer trainer(mod);
  dynet::ComputationGraph cg;
  Expression x1 = parameter(cg, param);
  Expression x2 = parameter(cg, param2);
  Expression y = input(cg, {1,3}, ones_vals);
  Expression z = y*(x1+x2);
  cg.backward(z);
  trainer.update();
  vector<float> param_after = as_vector(param.get_storage().values);
  vector<float> param2_after = as_vector(param2.get_storage().values);
  for(size_t i = 0; i < param_after.size(); ++i)
    BOOST_CHECK_NE(param_vals[i], param_after[i]);
  for(size_t i = 0; i < param2_after.size(); ++i)
    BOOST_CHECK_EQUAL(param2_vals[i], param2_after[i]);
}

BOOST_AUTO_TEST_CASE( simple_edge_case_test_dynet_867 ) {
  ParameterCollection model;
  LookupParameter emb_weight = model.add_lookup_parameters(1, {1});
  model.add_parameters({1, 1});
  SimpleSGDTrainer trainer(model);
  ComputationGraph cg;
  dynet::real zero = 0.0;
  Expression loss2 = input(cg, zero);
  for (int i = 0; i < 1000; ++i)
    dynet::lookup(cg, emb_weight, 0u);
  cg.backward(loss2);
  trainer.update();
}

BOOST_AUTO_TEST_SUITE_END()