#include <bits/stdc++.h>
#include <boost/algorithm/string.hpp>

using namespace std;
using namespace boost;

#define RESERVE_SIZE 10000
// #define DEBUG

enum fields
{
  no_field,
  birth_year,
  issue_year,
  expiration_year,
  height,
  hair_color,
  eye_color,
  passport_id,
  country_id
};

unordered_set<fields> optional_fields = {country_id};

unordered_set<fields> required_fields = {birth_year, issue_year, expiration_year, height, hair_color, eye_color, passport_id};

unordered_map<fields, string> field_key_map({
    {birth_year, "byr"},
    {issue_year, "iyr"},
    {expiration_year, "eyr"},
    {height, "hgt"},
    {hair_color, "hcl"},
    {eye_color, "ecl"},
    {passport_id, "pid"},
    {country_id, "cid"},
});

enum units
{
  no_units,
  cm,
  in
};

unordered_map<units, string> units_key_map({
    {cm, "cm"},
    {in, "in"},
});

enum eye_colors
{
  no_eye_color,
  amber,
  blue,
  brown,
  gray,
  green,
  hazel,
  other
};

unordered_map<eye_colors, string> eye_colors_key_map({
    {amber, "amb"},
    {blue, "blu"},
    {brown, "brn"},
    {gray, "gry"},
    {green, "grn"},
    {hazel, "hzl"},
    {other, "oth"},
});

unordered_set<fields> numeric_fields({
    birth_year,
    issue_year,
    expiration_year,
});

unordered_map<fields, pair<size_t, size_t>> numeric_min_max({{birth_year, pair<size_t, size_t>(1920, 2002)},
                                                             {issue_year, pair<size_t, size_t>(2010, 2020)},
                                                             {expiration_year, pair<size_t, size_t>(2020, 2030)}});

unordered_map<units, pair<size_t, size_t>> height_min_max({
    {cm, pair<size_t, size_t>(150, 193)},
    {in, pair<size_t, size_t>(59, 76)},
});

unordered_set<fields> string_fields({
    hair_color,
    passport_id,
    country_id,
});

unordered_map<fields, size_t> string_length_map({
    {passport_id, 9},
});

const regex hex_code_regex = regex("^#[0-9a-f]{6}$");

struct passport_data
{
  fields invalid_field = no_field;
  size_t birth_year, issue_year, expiration_year, height;
  string hair_color, passport_id, country_id;
  eye_colors eye_color = no_eye_color;
  units height_units = no_units;
  unordered_set<fields> found_fields;
  string to_string() const;
};

string passport_data::to_string() const
{
  stringstream str_stream;
  str_stream << "invalid field: " << ((*this).invalid_field == no_field ? "none" : field_key_map[(*this).invalid_field]) << endl;
  str_stream << "birth year: " << (*this).birth_year << endl;
  str_stream << "issue year: " << (*this).issue_year << endl;
  str_stream << "expiration year: " << (*this).expiration_year << endl;
  str_stream << "height: " << (*this).height << endl;
  str_stream << "height units: " << units_key_map[(*this).height_units] << endl;
  str_stream << "hair color: " << (*this).hair_color << endl;
  str_stream << "eye color: " << ((*this).eye_color == no_eye_color ? "invalid" : eye_colors_key_map[(*this).eye_color]) << endl;
  str_stream << "passport id: " << (*this).passport_id << endl;
  str_stream << "country id: " << (*this).country_id << endl;
  return str_stream.str();
}

bool is_present(passport_data &passport)
{
  size_t num_required_fields = count_if(required_fields.begin(), required_fields.end(), [&](fields required_field) -> bool {
    bool found_required_field = passport.found_fields.find(required_field) != passport.found_fields.end();
    if (!found_required_field)
    {
      passport.invalid_field = required_field;
    }
    return found_required_field;
  });
  return num_required_fields == required_fields.size();
}

size_t solution_1(vector<passport_data> &passports)
{
  return count_if(passports.begin(), passports.end(), is_present);
}

bool is_valid(passport_data &passport)
{
  // numeric fields
  for (const fields &current_field : numeric_fields)
  {
    if (required_fields.find(current_field) != required_fields.end())
    {
      unordered_map<fields, pair<size_t, size_t>>::iterator find_min_max = numeric_min_max.find(current_field);
      if (find_min_max == numeric_min_max.end())
      {
        continue;
      }
      size_t current_value;
      switch (current_field)
      {
      case height:
        current_value = passport.height;
        break;
      case birth_year:
        current_value = passport.birth_year;
        break;
      case issue_year:
        current_value = passport.issue_year;
        break;
      case expiration_year:
        current_value = passport.expiration_year;
        break;
      default:
        cout << "unhandled field for numeric retrieval: " << field_key_map[current_field] << endl;
        exit(-1);
        return false;
      }
      pair<size_t, size_t> min_max = find_min_max->second;
      if (current_value < min_max.first || current_value > min_max.second)
      {
        passport.invalid_field = current_field;
        return false;
      }
    }
  }
  // height
  if (required_fields.find(height) != required_fields.end())
  {
    if (passport.height_units == no_units)
    {
      passport.invalid_field = height;
      return false;
    }
    unordered_map<units, pair<size_t, size_t>>::iterator find_min_max = height_min_max.find(passport.height_units);
    if (find_min_max != height_min_max.end())
    {
      pair<size_t, size_t> min_max = find_min_max->second;
      if (passport.height < min_max.first || passport.height > min_max.second)
      {
        passport.invalid_field = height;
        return false;
      }
    }
  }
  // eye color
  if (required_fields.find(eye_color) != required_fields.end())
  {
    if (passport.eye_color == no_eye_color)
    {
      passport.invalid_field = eye_color;
      return false;
    }
  }
  // string fields
  for (const fields &current_field : string_fields)
  {
    if (required_fields.find(current_field) != required_fields.end())
    {
      if (current_field == hair_color)
      {
        if (!regex_match(passport.hair_color, hex_code_regex))
        {
          passport.invalid_field = hair_color;
          return false;
        }
      }
      else if (current_field == passport_id || current_field == country_id)
      {
        string current_val;
        switch (current_field)
        {
        case passport_id:
          current_val = passport.passport_id;
          break;
        case country_id:
          current_val = passport.country_id;
          break;
        default:
          cout << "unhandled field for passport / country retrieval: " << field_key_map[current_field] << endl;
          exit(-1);
          return false;
        }
        unordered_map<fields, size_t>::iterator find_string_length = string_length_map.find(current_field);
        if (find_string_length != string_length_map.end())
        {
          if (current_val.length() != find_string_length->second)
          {
            passport.invalid_field = current_field;
            return false;
          }
          bool is_numeric = !current_val.empty() && find_if(current_val.begin(),
                                                            current_val.end(), [](unsigned char c) { return !isdigit(c); }) == current_val.end();
          if (!is_numeric)
          {
            passport.invalid_field = current_field;
            return false;
          }
        }
      }
      else
      {
        cout << "unhandled field for string retrieval: " << field_key_map[current_field] << endl;
        exit(-1);
        return false;
      }
    }
  }
  return true;
}

size_t solution_2(vector<passport_data> &passports)
{
  return count_if(passports.begin(), passports.end(), [](passport_data passport) -> bool {
    if (!is_present(passport))
    {
      return false;
    }
    bool valid = is_valid(passport);
#ifdef DEBUG
    if (!valid)
    {
      cout << (valid ? "valid" : "invalid") << "\n\n"
           << endl;
      cout << passport.to_string() << endl;
    }
#endif
    return valid;
  });
}

int main()
{
  string line;
  ifstream thefile("input.txt");
  stringstream str_stream, split_stream, int_stream;
  vector<passport_data> data;
  data.reserve(RESERVE_SIZE);
  passport_data current_passport;
  while (getline(thefile, line))
  {
    trim(line);
    if (line.length() == 0 && current_passport.found_fields.size() > 0)
    {
      data.push_back(current_passport);
      current_passport = passport_data();
    }
    str_stream.str(line);
    string split_space, split_elem;
    while (getline(str_stream, split_space, ' '))
    {
      trim(split_space);
      split_stream.str(split_space);
      int i = 0;
      fields current_field;
      while (getline(split_stream, split_elem, ':'))
      {
        if (i == 0)
        {
          // get field
          unordered_map<fields, string>::iterator find_field = find_if(field_key_map.begin(), field_key_map.end(), [&](pair<fields, string> key_val) -> bool {
            return key_val.second == split_elem;
          });
          if (find_field == field_key_map.end())
          {
            cout << "cannot find field " << split_elem << endl;
            return -1;
          }
          current_field = find_field->first;
        }
        else if (i == 1)
        {
          // get value
          if (numeric_fields.find(current_field) != numeric_fields.end() || current_field == height)
          {
            if (current_field == height)
            {
              unordered_map<units, string>::iterator find_unit = find_if(units_key_map.begin(), units_key_map.end(), [&](pair<units, string> key_val) -> bool {
                return algorithm::ends_with(split_elem, key_val.second);
              });
              if (find_unit == units_key_map.end())
              {
                cout << "cannot find units for " << split_elem << endl;
              }
              else
              {
                current_passport.height_units = find_unit->first;
                split_elem.substr(0, split_elem.length() - units_key_map[current_passport.height_units].length());
              }
            }
            int_stream.str(split_elem);
            long curr_cast;
            if (!(int_stream >> curr_cast))
            {
              cout << "cannot cast " << split_elem << " to int" << endl;
            }
            else
            {
              switch (current_field)
              {
              case height:
                if (current_passport.height_units != no_units)
                {
                  current_passport.height = curr_cast;
                }
                break;
              case birth_year:
                current_passport.birth_year = curr_cast;
                break;
              case issue_year:
                current_passport.issue_year = curr_cast;
                break;
              case expiration_year:
                current_passport.expiration_year = curr_cast;
                break;
              default:
                cout << "unhandled field for numeric cast: " << field_key_map[current_field] << endl;
                return -1;
              }
            }
            int_stream.clear();
          }
          else if (current_field == eye_color)
          {
            unordered_map<eye_colors, string>::iterator find_eye_color = find_if(eye_colors_key_map.begin(), eye_colors_key_map.end(), [&](pair<eye_colors, string> key_val) -> bool {
              return key_val.second == split_elem;
            });
            if (find_eye_color == eye_colors_key_map.end())
            {
              cout << "cannot find eye color " << split_elem << endl;
            }
            else
            {
              current_passport.eye_color = find_eye_color->first;
            }
          }
          else if (string_fields.find(current_field) != string_fields.end())
          {
            switch (current_field)
            {
            case hair_color:
              current_passport.hair_color = split_elem;
              break;
            case passport_id:
              current_passport.passport_id = split_elem;
              break;
            case country_id:
              current_passport.country_id = split_elem;
              break;
            default:
              cout << "unhandled field for string input: " << field_key_map[current_field] << endl;
              return -1;
            }
            current_passport.hair_color = split_elem;
          }
          current_passport.found_fields.insert(current_field);
        }
        else
        {
          cout << "too many key values in element" << endl;
          return -1;
        }
        i++;
      }
      split_stream.clear();
    }
    str_stream.clear();
  }
  if (current_passport.found_fields.size() > 0)
  {
    data.push_back(current_passport);
  }
  size_t res_1 = solution_1(data);
  cout << "part 1 output: " << res_1 << endl;
  size_t res_2 = solution_2(data);
  cout << "part 2 output: 1" << res_2 << endl;
}


#include <boost/preprocessor/arithmetic/mod.hpp>


#include <boost/program_options.hpp>
#include <chrono>
#include <thread>

#include "EncryptedClient.hpp"
#include "SecretSharingNTT.hpp"
#include "ZkArgument.hpp"

#include "Ligero.hpp"

#include "Common.hpp"
#include "Hash.hpp"

namespace po = boost::program_options;

using namespace ligero;
using namespace ligero::lattice;
using namespace std::chrono_literals;

/** Parameters RSA Ceremony */
constexpr auto degree = 1 << 16;
constexpr auto p = 9;
constexpr auto q = 21;
constexpr auto sigma = 8;
constexpr auto lambda = 128;
constexpr auto tau_limit_bit = 175;
constexpr auto tau = 1000;
constexpr auto numCandidates = 2048;

/** throughput parties */
constexpr auto wait_timeout = 1200 * 1000; /** wait 20 min when start */
constexpr auto timeout = 25 * 1000;        /** a little longer than test */
constexpr auto data_size = 32 * 1024;      /** 32 KB */
constexpr auto nb_max_send = 1024;

/** general timeout */
constexpr auto receive_timeout = 60min;

/** Parameters Zero-Knowledge */

/** Settings Zero-Knowledge */
RegularInteger p_zk(10514644122014433281ULL);
typedef Fpp_Fixed<p_zk> FieldT;

zksnark::InterleavedRSCode irs;        /** Reed-Solomon Interleaved Code */
ligero::MTParameters<FieldT> mtParams; /** Merkle Tree Parameters */
constexpr size_t l = 8192;             // 4096;             /** Bloc size */
constexpr size_t k = 16384;            // 8192;             /** Degree */
constexpr size_t n = 65536;            // 16384;            /** # of Shares */

Statements statement = Statements::RSACeremony;

/** Columns to Open Up (By default half the columns) */
constexpr size_t t = 189;
constexpr auto ptNumberEvaluation = 3;

/** Stitching Proofs */
constexpr int nbIterations = 3;
constexpr int nbVarsStitched = 4;
constexpr int nbBlocksPerVar = degree / l;

void compromise(uint64_t& u) {
  int r = rand() % 10;

  if (r == 3) {
    u += ligero::math::generateRandomValue(mpz_class(std::random_device()()))
             .get_ui();
  }
};

void compromise(std::vector<uint64_t>& u) {
  int r = rand() % 10;

  if (r == 3) {
    if (u.size() != 0) {
      size_t pos =
          ligero::math::generateRandomValue(mpz_class(std::random_device()()))
              .get_ui() %
          u.size();

      u[pos] +=
          ligero::math::generateRandomValue(mpz_class(std::random_device()()))
              .get_ui();
    }
  }
};

void compromise(mpz_class& u) {
  int r = rand() % 10;

  if (r == 3) {
    u += ligero::math::generateRandomValue(mpz_class(std::random_device()()));
  }
};

/** This function handles the ceremony itself, whether it is performed with
 * passive or active security.
 * @param transport transport object to communicate with coordinator
 * @param socketId unique identifier for this party
 * @param localIpAddr local ip address for this party
 * @param passive boolean flag indicating whether to generate a ZK proof or
 * not
 * @return Unit if successful
 */
expected<Unit> participateHelper(ZeroMQClientTransport& transport,
                                 SocketId socketId, std::string localIpAddr,
                                 bool passive) {
  auto client = new EncryptedClient<FieldT, uint64_t, degree, p, q>(socketId);
  expected<Unit> result = Unit{};

  if ((!passive) && (statement == Statements::RSACeremony)) {
    Statements localStatement = statement;

    /** Zero-Knowledge Argument */

    /** Setting Protocol Parameters */
    /** =========================== */

    /** Reed-Solomon Interleaved Code */
    zksnark::InterleavedRSCode irs;

    irs.k = k;  // Degree of the Polynomial
    irs.n = n;  // Number of Shares

    /** Merkle Tree Parameters */
    ligero::MTParameters<FieldT> mtParams;

    mtParams.digestLength = 32;
    mtParams.zkByteSize = 8;
    mtParams.hashInnerNodeF = hash::blake2bTwoToOneHash;
    mtParams.hashLeafContentF = hash::blake2bFieldElementHash<FieldT>;
    mtParams.hashZKRandomnessF = hash::blake2bZKElementHash;
    mtParams.leavesNumber = irs.n;

    /** Fiat-Shamir Seed Generators */
    const hash::HashIntegerSeedGeneratorF<FieldT>& fieldGenerator =
        hash::blake2bIntegerSeedGenerator<FieldT>;
    const hash::HashStringSeedGeneratorF& stringGenerator =
        hash::blake2bStringHash;

    PublicData pdata;
    SigmaProtocolPublicData spdata;
    SecretData sdata;

    /** No Fiat-Shamir in Early Witness */
    std::string hashedPublicData("");

    /** Core */
    pdata.degree = degree;
    pdata.p = p;
    pdata.q = q;
    pdata.sigma = sigma;
    pdata.lambda = lambda;
    pdata.tau_limit_bit = tau_limit_bit;
    pdata.tau = tau;

    zksnark::Prover<FieldT> prover(
        localStatement, irs, t, l, fieldGenerator, stringGenerator, mtParams,
        pdata, spdata, sdata, permut<k>::compute, permut<n>::compute, transport,
        hashedPublicData);

    result = client->start(transport, sigma, lambda, localIpAddr, &prover);
  } else {
    result = client->start(transport, sigma, lambda, localIpAddr);
  }

  if (hasError(result)) {
    LOG(INFO) << "Party failed with error message: "
              << showError(getError(result));
    exit(EXIT_FAILURE);
  }

  if (passive) {
    delete client;
    return Unit{};
  } else {
    LOG(INFO) << "Generating Zero-Knowledge Argument.";
    transport.myTimers.begin(1, "Active Security", 1);

    /** Zero-Knowledge Argument */

    /** Setting Protocol Parameters */
    /** =========================== */

    /** Reed-Solomon Interleaved Code */
    zksnark::InterleavedRSCode irs;

    irs.k = k; /** Degree of the Polynomial */
    irs.n = n; /** Number of Shares */

    /** Merkle Tree Parameters */
    ligero::MTParameters<FieldT> mtParams;

    mtParams.digestLength = 32;
    mtParams.zkByteSize = 8;
    mtParams.hashInnerNodeF = hash::blake2bTwoToOneHash;
    mtParams.hashLeafContentF = hash::blake2bFieldElementHash<FieldT>;
    mtParams.hashZKRandomnessF = hash::blake2bZKElementHash;
    mtParams.leavesNumber = irs.n;

    /** Fiat-Shamir Seed Generators */
    const hash::HashIntegerSeedGeneratorF<FieldT>& fieldGenerator =
        hash::blake2bIntegerSeedGenerator<FieldT>;
    const hash::HashStringSeedGeneratorF& stringGenerator =
        hash::blake2bStringHash;

    PublicData pdata;
    SigmaProtocolPublicData spdata;
    SecretData sdata;

    client->gatherData(pdata, spdata, sdata);
    transport.send(MessageType::GATHER_SIGMA_DATA, spdata);

    /** Set all parameters for active and passive protocols */

    delete client;

    /** core */
    pdata.degree = degree;
    pdata.p = p;
    pdata.q = q;
    pdata.sigma = sigma;
    pdata.lambda = lambda;
    pdata.tau_limit_bit = tau_limit_bit;
    pdata.tau = tau;

    /** Hashing Public Data */
    pdata.modulusIdx = 0;
    pdata.roots.clear();
    pdata.ptNumberEvaluation = ptNumberEvaluation;

    /** Compromise some of the public data **/
    compromise(pdata.ps);
    compromise(pdata.A);
    compromise(pdata.bi);
    compromise(pdata.b);
    compromise(pdata.ci_1);
    compromise(pdata.ci_2);
    compromise(pdata.ci_1_prime);
    compromise(pdata.ci_2_prime);
    compromise(pdata.c_1);
    compromise(pdata.c_2);
    compromise(pdata.di);
    compromise(pdata.c_1_prime);
    compromise(pdata.c_2_prime);
    compromise(pdata.ax_shares);
    compromise(pdata.by_shares);
    compromise(pdata.fixedCoefs);
    compromise(pdata.coefsCAN);
    compromise(pdata.cans);
    compromise(pdata.prodcans);
    compromise(pdata.ax);
    compromise(pdata.by);
    compromise(pdata.axby);
    compromise(pdata.ax_shares_GCD);
    compromise(pdata.by_shares_GCD);
    compromise(pdata.coefsGCD);
    compromise(pdata.gcds);
    compromise(pdata.prodgcds);
    compromise(pdata.axGCD);
    compromise(pdata.byGCD);
    compromise(pdata.axbyGCD);
    compromise(pdata.finalModuli_GCD);
    compromise(pdata.roots);
    compromise(pdata.sigmaeGCD);
    compromise(pdata.sigmazGCD);
    compromise(pdata.gammaSeed);
    compromise(pdata.log2Ds);
    compromise(pdata.Cs);
    compromise(pdata.Ds);

    /** Compromise some of the sigma public data **/
    compromise(spdata.sigmaeGCD);
    compromise(spdata.sigmazGCD);
    compromise(spdata.sigmaaGCD);
    compromise(spdata.sigmagGCD);

    /** Compromise some of the secret data **/
    compromise(sdata.siP);
    compromise(sdata.eiP);
    compromise(sdata.q_r3);
    compromise(sdata.xi);
    compromise(sdata.ux);
    compromise(sdata.vx);
    compromise(sdata.wx);
    compromise(sdata.vxP);
    compromise(sdata.wxP);
    compromise(sdata.q_r4_1);
    compromise(sdata.q_r4_2);
    compromise(sdata.zp);
    compromise(sdata.yi);
    compromise(sdata.zi);
    compromise(sdata.uz);
    compromise(sdata.vz);
    compromise(sdata.vzP);
    compromise(sdata.wz);
    compromise(sdata.wzP);
    compromise(sdata.q_r5_1);
    compromise(sdata.q_r5_2);
    compromise(sdata.rNoise);
    compromise(sdata.q_r6);
    compromise(sdata.x_sharesCAN);
    compromise(sdata.x_sharesPS);
    compromise(sdata.y_sharesCAN);
    compromise(sdata.y_sharesPS);
    compromise(sdata.z_sharesCAN);
    compromise(sdata.q_p_prod_r7);
    compromise(sdata.q_p_r7);
    compromise(sdata.q_q_prod_r7);
    compromise(sdata.q_q_r7);
    compromise(sdata.q_r8);
    compromise(sdata.x_sharesGCD);
    compromise(sdata.y_sharesGCD);
    compromise(sdata.z_sharesGCD);
    compromise(sdata.q_p_prod_r11);
    compromise(sdata.q_q_prod_r11);
    compromise(sdata.q_pq_r11);
    compromise(sdata.r_CRTs);
    compromise(sdata.q_r_r11);
    compromise(sdata.ss_GCD);
    compromise(sdata.q_r12);
    compromise(sdata.alpha);
    compromise(sdata.W1);
    compromise(sdata.W2);
    compromise(sdata.XplusCs);
    compromise(sdata.XplusDs);
    compromise(sdata.Xs);
    compromise(sdata.sigmarGCD);
    compromise(sdata.sigmaxGCD);
    compromise(sdata.sigmaqGCD);
    compromise(sdata.exp_q);
    compromise(sdata.expqGCD);

    std::string hashedPublicData =
        hash::hashPublicData(stringGenerator, pdata, spdata);

    /** Primitive Roots of Unity for All Moduli */
    nfl::poly_p<uint64_t, degree, q> roots({0});
    for (size_t idx = 0; idx < q; idx++) {
      roots(idx, 1) = 1ull;
    }
    roots.ntt_pow_phi();

    LOG(INFO) << "Generating Zero-Knowledge Argument.";

    /** Compute Stitching */
    /** Variables we store across proofs */
    mpz_class alpha =
        math::generateRandomValue(mpz_class(std::random_device()()), 210);
    uint64_t W1 = ligero::math::scaleToPrime(
        math::generateRandomValue(mpz_class(std::random_device()()), 62)
            .get_ui(),
        0);
    uint64_t W2 = ligero::math::scaleToPrime(
        math::generateRandomValue(mpz_class(std::random_device()()), 62)
            .get_ui(),
        0);
    std::vector<uint64_t> rs(4 * degree);

    /** Preliminary checks */
    {
      /** Compute Stitching */
      std::vector<mpz_class> mods, alpha_CRT;
      for (size_t i = 1; i < q; i++) {
        mods.emplace_back(params<uint64_t>::P[i]);
      }

      /** CRT deconstruction */
      alpha_CRT = math::crt_deconstruct(
          alpha * mpz_class(params<uint64_t>::P[0]), mods);
      LOG(INFO) << "CRT 1:" << alpha_CRT[0];
      LOG(INFO) << "CRT 2:" << alpha_CRT[1];

      /** CRT reconstruction */
      std::vector<mpz_class> coefs;
      mpz_class reconstructed_alpha =
          math::crt_reconstruct(alpha_CRT, coefs, mods);

      assert(math::mod(reconstructed_alpha -
                           alpha * mpz_class(params<uint64_t>::P[0]),
                       mpz_class(params<uint64_t>::P[0])) == mpz_class(0));
      LOG(INFO) << math::mod(
          reconstructed_alpha - alpha * mpz_class(params<uint64_t>::P[0]),
          mpz_class(params<uint64_t>::P[0]));
      LOG(INFO) << "CRT Check Successful";
    }

    /** Dimensioning ai scalars */
    sdata.ai.resize(nbIterations);
    for (size_t iter = 0; iter < nbIterations; iter++) {
      sdata.ai[iter].resize(nbVarsStitched);
      for (size_t var = 0; var < nbVarsStitched; var++) {
        sdata.ai[iter][var].resize(nbBlocksPerVar);
        for (size_t block = 0; block < nbBlocksPerVar; block++) {
          sdata.ai[iter][var][block].resize(l);
        }
      }
    }

    for (pdata.modulusIdx = 0; pdata.modulusIdx < q; pdata.modulusIdx++) {
      Statements localStatement = statement;
      LOG(INFO) << "Generating proof for modulus idx:" << pdata.modulusIdx;

      /** Roots of Unity */
      pdata.roots.assign(
          roots.poly_obj().data() + pdata.modulusIdx * (size_t)roots.degree,
          roots.poly_obj().data() +
              (pdata.modulusIdx + 1) * (size_t)roots.degree);

      /** Zero-Knowledge */
      p_zk = nfl::params<uint64_t>::P[pdata.modulusIdx];
      FieldT::modulusIdx_ = pdata.modulusIdx;
      FieldT::preprocessing(log2(n) + 1);

      /** Cross-Moduli Integrity */
      sdata.alpha = math::mod(alpha, mpz_class(p_zk)).get_ui();
      sdata.W1 = math::mod(W1, mpz_class(p_zk)).get_ui();
      sdata.W2 = math::mod(W2, mpz_class(p_zk)).get_ui();

      /** For portions 7 to 12, only run the first 9 moduli */
      if ((statement == Statements::RSACeremony) && (pdata.modulusIdx > 8))
        localStatement = Statements::RSACeremony_Rounds3to6;

      zksnark::Prover<FieldT> prover(
          localStatement, irs, t, l, fieldGenerator, stringGenerator, mtParams,
          pdata, spdata, sdata, permut<k>::compute, permut<n>::compute,
          transport, hashedPublicData);

      prover.produceArgumentOfKnowledge(1, false);
    }

    LOG(INFO) << "Waiting for possible restart";
    auto command = transport.awaitReply();

    if (!hasError(command) && (getResult(command) == MessageType::END)) {
      transport.myTimers.end(2, "1.b. Overall speed", 1, true);
      transport.myTimers.end(1, "Active Security", 1, true);
      return Unit{};
    } else if (hasError(command) && (getError(command) == Error::RESTART)) {
      LOG(INFO) << "Restart protocol";
      transport.myTimers.reset();
      return participateHelper(transport, socketId, localIpAddr, passive);
    } else {
      LOG(INFO) << "Error happen, quit";
      exit(EXIT_FAILURE);
    }
  }

  return Unit{};
}

/** A small helper function for setting up the ceremony, handling optional
 * preliminary steps such as throughput test. This is primarily to ensure the
 * socket construction happens on the same thread as the socket usage.
 *
 * @param ipAddr ip address of the coordinator for the ceremony
 * @param locaIpAddr ip address of the party joining the ceremony, for
 * logging/references only
 * @param passive instructs whether or not to generate a ZKP
 * @param speedtest instructs whether or not to run a throughput test ahead of
 * the ceremony
 */
expected<Unit> participate(std::string ipAddr, std::string localIpAddr,
                           bool passive, bool speedtest) {
  try {
    // RSA Ceremony
    SocketId socketId = boost::uuids::random_generator()();
    ZeroMQClientTransport transport(
        std::string("tcp://") + ipAddr + std::string(":5555"), socketId,
        receive_timeout);

    if (speedtest) {
      auto proceed = transport.joinThroughputTest(wait_timeout, timeout,
                                                  data_size, nb_max_send);

      if (hasError(proceed)) {
        LOG(INFO) << "Kicked out due to poor network connection";
        LOG(INFO) << "Error message is: " << showError(getError(proceed));
        return Error::KILLED_BY_COORDINATOR;
      }
    }

    participateHelper(transport, socketId, localIpAddr, passive);
  } catch (zmq::error_t& e) {
    LOG(ERROR) << "Caught an error trying to participate... " << e.what();
  }
  return Unit{};
}

/**
 * =================================================================================
 */
int main(int argc, char** argv) {
  std::string ipAddr("127.0.0.1");
  std::string localIpAddr("");

  bool passive = false;
  bool speedtest = false;

  po::options_description desc("Allowed options");
  desc.add_options()("help", "produce help message")("version", "show version")(
      "ip", po::value<std::string>(), "set the ip address for the coordinator")(
      "localIPaddress", po::value<std::string>(), "sets the local IP address")(
      "passive", "do not include a zero-knowledge argument")(
      "speedtest", "perform throughput test before kick off the ceremony");

  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);

  if (vm.count("help")) {
    LOG(INFO) << desc << "\n";
    return 0;
  }

  if (vm.count("passive")) {
    passive = true;
  }

  if (vm.count("speedtest")) {
    speedtest = true;
    LOG(TRACE) << "Running with throughput test, make sure running the "
                  "coordinator with `--speedtest` also";
  }

  if (vm.count("version")) {
    std::cout << "party version " << std::hex << PROTOCOL_VERSION_NUMBER
              << std::endl;
    return 0;
  }

  if (vm.count("ip")) {
    ipAddr = vm["ip"].as<std::string>();
    LOG(INFO) << "Coordinator resides at " << ipAddr;
  }

  if (vm.count("localIPaddress")) {
    localIpAddr = vm["localIPaddress"].as<std::string>();
    LOG(INFO) << "Local IP Address" << localIpAddr;
  }

  /** Configure easylogging */
  configureEasyLogging("malicious_party");

  auto result = participate(ipAddr, localIpAddr, passive, speedtest);
  if (hasError(result)) {
    return 1;
  }
  return 0;
}


#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<boost::int64_t, 2, 6> test_long_2_6(const Eigen::Matrix<boost::int64_t, 2, 6> & M)
{
	return M;
}
void export_long_2_6()
{
	boost::python::def("test_long_2_6",test_long_2_6);
}


/*!
 * @file libfacwt.cpp
 * @brief C++ file for Python wrapper of CWT
 * @date 1 Dec. 2015.
 * @author Tomohiko Nakamura
 */

#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <boost/range/value_type.hpp>
#include <common.hpp>
#include <cwt.hpp>
#include <chrono>

namespace py = boost::python;
namespace np = boost::python::numpy;

template <typename T>
np::ndarray convert_vector_to_ndarray(std::vector<T> &vec)
{
  const unsigned int N = vec.size();
  py::tuple shape = py::make_tuple(N);
  np::ndarray ret = np::zeros(shape, np::dtype::get_builtin<T>());
  for (int i = 0; i < N; i++)
  {
    ret[i] = vec[i];
  }
  return ret;
}

template <typename T>
np::ndarray convert_matrix_to_ndarray(std::vector<std::vector<T>> &vec)
{
  const unsigned int N = vec.size(), M = vec[0].size();
  py::tuple shape = py::make_tuple(N, M);
  np::ndarray ret = np::zeros(shape, np::dtype::get_builtin<T>());
  for (int i = 0; i < N; i++)
  {
    for (int j = 0; j < M; j++)
    {
      ret[i][j] = vec[i][j];
    }
  }
  return ret;
}

template <typename T>
std::vector<T> convert_ndarray_to_vector(np::ndarray &nd)
{
  const unsigned int N = nd.shape(0);
  std::vector<T> vec(N);
  T *data = reinterpret_cast<T *>(nd.get_data());
  for (int i = 0; i < N; i++)
  {
    vec[i] = data[i];
  }
  return vec;
}

template <typename T>
std::vector<std::vector<T>> convert_ndarray_to_matrix(np::ndarray &nd)
{
  const unsigned int N = nd.shape(0);
  const unsigned int M = nd.shape(1);
  std::vector<std::vector<T>> vec(N);
  T *data = reinterpret_cast<T *>(nd.get_data());
  for (int i = 0; i < N; i++)
  {
    vec[i].resize(M);
    for (int j = 0; j < M; j++)
    {
      vec[i][j] = data[i * M + j];
    }
  }
  return vec;
}

/*! @brief initialization of cwt instance
 *
 * @tparam D_TYPE data type
 *
 * @param facwt cwt instance
 * @param _sigLen signal length
 * @param lowFreq lowest frequency
 * @param highFreq highest frequency
 * @param fs sampling rate
 * @param resol resolution of an octave
 * @param width Ratio of standard deviation for approximating frequency responses of the filterbank. The frequency responses are assumed to be zero outside the range [-width\sigma,width\sigma] around each center frequency.
 * @param sd Standard deviation of Gaussian shape in the log-frequency domain.
 * @param alpha If alpha=1 (2), magnitudes (power, resp.) of frequency responses have Gaussian shape.
 * @param multirate If true, setup for multirate spectrogram; otherwise setup for spectrogram with common sampling rate between all channels.
 */
template <typename D_TYPE>
void init_cwt(cwt::CWT<D_TYPE> &facwt,
              const int _sigLen,
              const D_TYPE lowFreq,
              const D_TYPE highFreq,
              const long fs,
              const int resol,
              const D_TYPE width,
              const D_TYPE sd,
              const D_TYPE alpha,
              const bool multirate,
              const int minWidth,
              const std::string waveletType)
{
  facwt.init(_sigLen, lowFreq, highFreq, fs, resol);
  if(waveletType == std::string("log_normal")){
    std::tie(facwt.startFreqIndexes, facwt.freqResps, facwt.sumOfFilterOutputs) =
        cwt::set_lognormal_wavelet<D_TYPE>(facwt.centerAngFreqs, facwt.T, width, sd, alpha, multirate);
  }else if(waveletType == std::string("gabor")){
    std::tie(facwt.startFreqIndexes, facwt.freqResps, facwt.sumOfFilterOutputs) =
        cwt::set_gabor_wavelet<D_TYPE>(facwt.centerAngFreqs, facwt.T, width, sd, alpha, multirate);
  }else{
    throw std::runtime_error("Unknown wavelet type");
  }
  for(auto &v: facwt.freqResps){
    if(v.size()<minWidth){
      while(v.size()!=minWidth){
        v.push_back(0.0);
      }
    }
  }
  facwt.init_ffts();
}

template <typename D_TYPE>
void cwt_forward(py::list &pySpectrogram, const np::ndarray &waveform, cwt::CWT<D_TYPE> &facwt, const int verbose)
{
  // check
  if (waveform.get_nd() != 1)
  {
    throw std::runtime_error("Waveform must be monaural");
  }
  if (waveform.get_dtype() != np::dtype::get_builtin<D_TYPE>())
  {
    throw std::runtime_error("Waveform must be float32");
  }
  // prepare
  const int T = waveform.shape(0);
  std::vector<D_TYPE> data(T);
  std::vector<std::complex<D_TYPE>> spectrum;
  std::vector<std::vector<std::complex<D_TYPE>>> spectrogram;
  // copy data
  std::chrono::system_clock::time_point start, end;
  if (verbose > 0)
  {
    std::cerr << "Copying data: ";
    start = std::chrono::system_clock::now();
  }
  D_TYPE *waveformPtr = reinterpret_cast<D_TYPE *>(waveform.get_data());
  #ifdef _OPNEMP
  #pragma omp parallel for
  #endif
  for (int t = 0; t < T; t++)
  {
    data[t] = waveformPtr[t];
  }
  if (verbose > 0)
  {
    end = std::chrono::system_clock::now();
    std::cerr << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / 100.0 << " s elapsed." << std::endl;
  }
  // forward
  if (verbose > 0)
  {
    std::cerr << "Performing foward: ";
    start = std::chrono::system_clock::now();
  }
  facwt.FFT_signal(spectrum, data, true);
  facwt.forward(spectrogram, spectrum, true, false);
  if (verbose > 0)
  {
    end = std::chrono::system_clock::now();
    std::cerr << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / 100.0 << " s elapsed." << std::endl;
  }
  //
  if (verbose > 0)
  {
    std::cerr << "Copying results: ";
    start = std::chrono::system_clock::now();
  }
  for (int l = 0; l < spectrogram.size(); l++)
  {
    np::ndarray filtered = np::zeros(py::make_tuple(spectrogram[l].size()), np::dtype::get_builtin<std::complex<D_TYPE>>());
    std::complex<D_TYPE> *filteredPtr = reinterpret_cast<std::complex<D_TYPE> *>(filtered.get_data());
#ifdef _OPNEMP
#pragma omp parallel for
#endif
    for (int t = 0; t < spectrogram[l].size(); t++)
    {
      filteredPtr[t] = spectrogram[l][t];
    }
    pySpectrogram.append(filtered);
  }
  if (verbose > 0)
  {
    end = std::chrono::system_clock::now();
    std::cerr << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / 100.0 << " s elapsed." << std::endl;
  }
}

template <typename D_TYPE>
np::ndarray cwt_backward(const py::list &pySpectrogram, cwt::CWT<D_TYPE> &facwt, const int verbose)
{
  // prepare
  const int L = py::len(pySpectrogram);
  std::vector<std::vector<std::complex<D_TYPE>>> spectrogram(L);
  std::vector<D_TYPE> data;
  std::vector<std::complex<D_TYPE>> spectrum;
  // copy spectrogram
  std::chrono::system_clock::time_point start, end;
  if (verbose > 0)
  {
    std::cerr << "Copying data: ";
    start = std::chrono::system_clock::now();
  }
  for (int l = 0; l < L; l++)
  {
    const np::ndarray pySpectrogramSlice = py::extract<np::ndarray>(pySpectrogram[l]);
    //
    if(pySpectrogramSlice.get_dtype() != np::dtype::get_builtin<std::complex<D_TYPE>>()){
      throw std::runtime_error("Spectrogram slice must be complex float");
    }
    //
    std::complex<D_TYPE> *pySpectrogramSlicePtr = reinterpret_cast<std::complex<D_TYPE> *>(pySpectrogramSlice.get_data());
    const int M_l = pySpectrogramSlice.shape(0);
    spectrogram[l].resize(M_l);
    for (int m_l = 0; m_l < M_l; m_l++)
    {
      spectrogram[l][m_l] = pySpectrogramSlicePtr[m_l];
    }
  }
  if (verbose > 0)
  {
    end = std::chrono::system_clock::now();
    std::cerr << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / 100.0 << " s elapsed." << std::endl;
  }
  // backward
  if (verbose > 0)
  {
    std::cerr << "Performing backward: ";
    start = std::chrono::system_clock::now();
  }
  // for (verbose > 0)
  // {
  //   std::cerr << "Performing backward: ";
  //   start = std::chrono::system_clock::now();
  // }
  facwt.backward(spectrum, spectrogram, true, false);
  facwt.IFFT_spectrum(data, spectrum, true);
  //
  if (verbose > 0)
  {
    end = std::chrono::system_clock::now();
    std::cerr << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / 100.0 << " s elapsed." << std::endl;
  }
  // copy data
  if (verbose > 0)
  {
    std::cerr << "Copying results: ";
    start = std::chrono::system_clock::now();
  }
  np::ndarray waveform = np::zeros(py::make_tuple(data.size()), np::dtype::get_builtin<D_TYPE>());
  D_TYPE *waveformPtr = reinterpret_cast<D_TYPE *>(waveform.get_data());
  for (int t = 0; t < data.size(); t++)
  {
    waveformPtr[t] = data[t];
  }
  if (verbose > 0)
  {
    end = std::chrono::system_clock::now();
    std::cerr << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() / 100.0 << " s elapsed." << std::endl;
  }
  return waveform;
}

BOOST_PYTHON_MODULE(libfacwt)
{
  // Py_Initialize();
  np::initialize();
  /// vector indexing
  py::class_<std::vector<int>>("IVec")
      .def(py::vector_indexing_suite<std::vector<int>>());
  py::class_<std::vector<float>>("FVec")
      .def(py::vector_indexing_suite<std::vector<float>>());
  py::class_<std::vector<std::vector<float>>>("FVec2d")
      .def(py::vector_indexing_suite<std::vector<std::vector<float>>>());
  py::class_<std::vector<std::complex<float>>>("CFVec")
      .def(py::vector_indexing_suite<std::vector<std::complex<float>>>());
  py::class_<std::vector<std::vector<std::complex<float>>>>("CFVec2d")
      .def(py::vector_indexing_suite<std::vector<std::vector<std::complex<float>>>>());
  /// function definition
  py::class_<cwt::CWT<float>>("cppCWT")
      .def("init", &cwt::CWT<float>::init)
      .def("init_ffts", &cwt::CWT<float>::init_ffts)
      .def("FFT_signal", &cwt::CWT<float>::FFT_signal)
      .def("IFFT_spectrum", &cwt::CWT<float>::IFFT_spectrum)
      .def("forward", &cwt::CWT<float>::forward)
      .def("backward", &cwt::CWT<float>::backward)
      .def("reconstruct_phase", &cwt::CWT<float>::reconstruct_phase)
      .def("save", &cwt::CWT<float>::save)
      .def("load", &cwt::CWT<float>::load)
      .def_readwrite("sigLen", &cwt::CWT<float>::sigLen)
      .def_readwrite("T", &cwt::CWT<float>::T)
      .def_readwrite("start_freq_indexes", &cwt::CWT<float>::startFreqIndexes)
      .def_readwrite("freq_resps", &cwt::CWT<float>::freqResps)
      .def_readwrite("sum_filout", &cwt::CWT<float>::sumOfFilterOutputs)
      .def_readwrite("center_angfreqs", &cwt::CWT<float>::centerAngFreqs);
  py::def("set_lognormal_wavelet", &cwt::set_lognormal_wavelet<float>);
  py::def("set_gabor_wavelet", &cwt::set_gabor_wavelet<float>);
  py::def("init_cwt", &init_cwt<float>);
  py::def("cwt_forward", &cwt_forward<float>);
  py::def("cwt_backward", &cwt_backward<float>);
  // utility functions
  py::def("read_spectrogram", &utils::read_spectrogram<float>);
  py::def("write_spectrogram", &utils::write_spectrogram<float>);
  py::def("vecf2nd", &convert_vector_to_ndarray<float>);
  py::def("matf2nd", &convert_matrix_to_ndarray<float>);
  py::def("nd2vecf", &convert_ndarray_to_vector<float>);
  py::def("nd2matf", &convert_ndarray_to_matrix<float>);
  py::def("veci2nd", &convert_vector_to_ndarray<int>);
  py::def("mati2nd", &convert_matrix_to_ndarray<int>);
  py::def("nd2veci", &convert_ndarray_to_vector<int>);
  py::def("nd2mati", &convert_ndarray_to_matrix<int>);
}


#include "pipeline.hpp"
#include <boost/program_options.hpp>
namespace po = boost::program_options;
using namespace std;
using namespace CGAL::parameters;

int prog_opts(int ac, char* av[], string * in_dir, string * out_dir, float * edge_size)
{
    string input_directory, output_directory;
    boost::tuple<int, int, int, int, int, int> m_criteria;
    try {
        po::options_description desc("Allowed options");
        desc.add_options()
            ("help", "produce help message")
            ("input_dir,i", po::value<string>(), "pathname for input directory")
            ("output_dir,o", po::value<string>(), "pathname for output directory")
            ("_edge_size,a", po::value<float>(), "edge_size")

            ("_facet_angle,b", po::value<float>(), "facet_angle")
            ("_facet_size,c", po::value<float>(), "facet_size")
            ("_facet_distance,d", po::value<float>(), "facet_distance")
            ("_cell_radius_edge_ratio,e", po::value<float>(), "cell_radius_edge_ratio")
            ("_cell_size,f", po::value<float>(), "cell_size")
        ;
        float _a = 4;       // edge_size
        float _b = 25;      // facet_angle
        float _c = 4;       // facet_size
        float _d = 0.4;     // facet_distance
        float _e = 3;       // cell_radius_edge_ratio
        float _f = 4;       // cell_size

        int integer_val;
        integer_val =8;

        po::variables_map vm;
        po::store(po::parse_command_line(ac, av, desc), vm);
        po::notify(vm);
        if (vm.count("help")) {
            cout << desc << "\n";
            return 0;
        }

        if (vm.count("input_dir")) {
            cout << "Input directory was set to "
                 << vm["input_dir"].as<string>() << ".\n";
            input_directory = vm["input_dir"].as<string>();
            *in_dir = input_directory;                          // copy to pointed location
        } else {
            cout << "Input directory was not set.\n";
        }

        if (vm.count("output_dir")) {
            cout << "Output directory was set to "
                 << vm["output_dir"].as<string>() << ".\n";
            output_directory = vm["output_dir"].as<string>();
            *out_dir = output_directory;                        // copy to pointed location
        } else {
            cout << "Output directory was not set.\n";
        }
        if (vm.count("_edge_size")) {
            cout << " was set to " << vm["_edge_size"].as<float>() << ".\n";
            *edge_size = vm["_edge_size"].as<float>();
        }
        if (vm.count("_facet_angle")) {
            cout << " was set to " << vm["_facet_angle"].as<float>() << ".\n";
            _b = vm["_facet_angle"].as<float>();
        }
        if (vm.count("_facet_size")) {
            cout << " was set to " << vm["_facet_size"].as<float>() << ".\n";
            _c = vm["_facet_size"].as<float>();
        }
        if (vm.count("_facet_distance")) {
            cout << " was set to " << vm["_facet_distance"].as<float>() << ".\n";
            _d = vm["_facet_distance"].as<float>();
        }
        if (vm.count("_cell_radius_edge_ratio")) {
            cout << " was set to " << vm["_cell_radius_edge_ratio"].as<float>() << ".\n";
            _e = vm["_cell_radius_edge_ratio"].as<float>();
        }
        if (vm.count("_cell_size")) {
            cout << " was set to " << vm["_cell_size"].as<float>() << ".\n";
            _f = vm["_cell_size"].as<float>();
        }

//         Mesh_criteria tmp_criteria(edge_size = _a,
//                          facet_angle = _b, facet_size = _c, facet_distance = _d,
//                          cell_radius_edge_ratio = _e, cell_size = _f);
//             /*
//                             edge_size,
//                             facet_angle,
//                             facet_size,
//                             facet_distance,
//                             cell_radius_edge_ratio,
//                             cell_size
//                             );*/
//         *mesh_criteria = tmp_criteria;
    }
    catch(exception& e) {
        cerr << "error: " << e.what() << "\n";
        return 1;
    }
    catch(...) {
        cerr << "Exception of unknown type!\n";
    }
    return 0;
}


/* Copyright (C) 2012-2017 IBM Corp.
 * This program is Licensed under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. See accompanying LICENSE file.
 */
#include <iostream>
#include <cassert>
#include <fstream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <NTL/BasicThreadPool.h>
NTL_CLIENT

#include "EncryptedArray.h"
#include "FHE.h"

#include "intraSlot.h"
#include "binaryArith.h"
#include "binaryCompare.h"

#ifdef DEBUG_PRINTOUT
#include "debugging.h"
#endif
// define flags FLAG_PRINT_ZZX, FLAG_PRINT_POLY, FLAG_PRINT_VEC, functions
//        decryptAndPrint(ostream, ctxt, sk, ea, flags)
//        decryptAndCompare(ctxt, sk, ea, pa);

static std::vector<zzX> unpackSlotEncoding; // a global variable
static bool verbose=false;

static long mValues[][15] = {
// { p, phi(m),   m,   d, m1, m2, m3,    g1,   g2,   g3, ord1,ord2,ord3, B,c}
  {  2,    48,   105, 12,  3, 35,  0,    71,    76,    0,   2,  2,   0, 25, 2},
  {  2 ,  600,  1023, 10, 11, 93,  0,   838,   584,    0,  10,  6,   0, 25, 2},
  {  2,  2304,  4641, 24,  7,  3,221,  3979,  3095, 3760,   6,  2,  -8, 25, 3},
  {  2, 15004, 15709, 22, 23,683,  0,  4099, 13663,    0,  22, 31,   0, 25, 3},
  {  2, 27000, 32767, 15, 31,  7, 151, 11628, 28087,25824, 30,  6, -10, 28, 4}
};

void testCompare(FHESecKey& secKey, long bitSize, bool bootstrap=false);

int main(int argc, char *argv[])
{
  ArgMapping amap;
  long prm=1;
  amap.arg("prm", prm, "parameter size (0-tiny,...,4-huge)");
  long bitSize = 5;
  amap.arg("bitSize", bitSize, "bitSize of input integers (<=32)");
  long nTests = 3;
  amap.arg("nTests", nTests, "number of tests to run");
  bool bootstrap = false;
  amap.arg("bootstrap", bootstrap, "test comparison with bootstrapping");
  long seed=0;
  amap.arg("seed", seed, "PRG seed");
  long nthreads=1;
  amap.arg("nthreads", nthreads, "number of threads");
  amap.arg("verbose", verbose, "print more information");

  amap.parse(argc, argv);
  assert(prm >= 0 && prm < 5);
  if (seed) NTL::SetSeed(ZZ(seed));
  if (nthreads>1) NTL::SetNumThreads(nthreads);

  if (bitSize<=0) bitSize=5;
  else if (bitSize>32) bitSize=32;

  long* vals = mValues[prm];
  long p = vals[0];
  //  long phim = vals[1];
  long m = vals[2];

  NTL::Vec<long> mvec;
  append(mvec, vals[4]);
  if (vals[5]>1) append(mvec, vals[5]);
  if (vals[6]>1) append(mvec, vals[6]);

  std::vector<long> gens;
  gens.push_back(vals[7]);
  if (vals[8]>1) gens.push_back(vals[8]);
  if (vals[9]>1) gens.push_back(vals[9]);

  std::vector<long> ords;
  ords.push_back(vals[10]);
  if (abs(vals[11])>1) ords.push_back(vals[11]);
  if (abs(vals[12])>1) ords.push_back(vals[12]);

  long B = vals[13];
  long c = vals[14];

  // Compute the number of levels
  long L;
  if (bootstrap) L = 900; // that should be enough
  else           L = 30*(7+ NTL::NumBits(bitSize+2));

  if (verbose) {
    cout <<"input bitSize="<<bitSize
         <<", running "<<nTests<<" tests for each function\n";
    if (nthreads>1) cout << "  using "<<NTL::AvailableThreads()<<" threads\n";
    cout << "computing key-independent tables..." << std::flush;
  }
  FHEcontext context(m, p, /*r=*/1, gens, ords);
  buildModChain(context, L, c,/*willBeBootstrappable=*/bootstrap);
  if (bootstrap) {
    context.makeBootstrappable(mvec, /*t=*/0);
  }
  buildUnpackSlotEncoding(unpackSlotEncoding, *context.ea);
  if (verbose) {
    cout << " done.\n";
    context.zMStar.printout();
    cout << " L="<<L<<", B="<<B<<endl;
    cout << "\ncomputing key-dependent tables..." << std::flush;
  }
  FHESecKey secKey(context);
  secKey.GenSecKey();
  addSome1DMatrices(secKey); // compute key-switching matrices
  addFrbMatrices(secKey);
  if (bootstrap) secKey.genRecryptData();
  if (verbose) cout << " done\n";

  activeContext = &context; // make things a little easier sometimes
#ifdef DEBUG_PRINTOUT
  dbgEa = (EncryptedArray*) context.ea;
  dbgKey = &secKey;
#endif

  for (long i=0; i<nTests; i++)
    testCompare(secKey, bitSize, bootstrap);
  cout << "GOOD\n";

  if (verbose) printAllTimers(cout);
  return 0;
}

void testCompare(FHESecKey& secKey, long bitSize, bool bootstrap)
{
  const FHEcontext& context = secKey.getContext();
  const EncryptedArray& ea = *(context.ea);

  // Choose two random n-bit integers
  long pa = RandomBits_long(bitSize);
  long pb = RandomBits_long(bitSize+1);
  long pMax = std::max(pa,pb);
  long pMin = std::min(pa,pb);
  bool pMu = pa>pb;
  bool pNi = pa<pb;

  // Encrypt the individual bits
  NTL::Vec<Ctxt> eMax, eMin, enca, encb;

  Ctxt mu(secKey), ni(secKey);
  resize(enca, bitSize, mu);
  resize(encb, bitSize+1, ni);
  for (long i=0; i<=bitSize; i++) {
    if (i<bitSize) secKey.Encrypt(enca[i], ZZX((pa>>i)&1));
    secKey.Encrypt(encb[i], ZZX((pb>>i)&1));
    if (bootstrap) { // put them at a lower level
      if (i<bitSize) enca[i].bringToSet(context.getCtxtPrimes(5));
      encb[i].bringToSet(context.getCtxtPrimes(5));
    }
  }
#ifdef DEBUG_PRINTOUT
  decryptAndPrint((cout<<" before comparison: "), encb[0], secKey, ea,0);
#endif

  vector<long> slotsMin, slotsMax, slotsMu, slotsNi;

  //cmp only
  compareTwoNumbers(mu, ni, CtPtrs_VecCt(enca), CtPtrs_VecCt(encb),
                      &unpackSlotEncoding);
  ea.decrypt(mu, secKey, slotsMu);
  ea.decrypt(ni, secKey, slotsNi);
  if (slotsMu[0]!=pMu || slotsNi[0]!=pNi) {
    cout << "BAD\n";
    if (verbose)
      cout << "Comparison (without min max) error: a="<<pa<<", b="<<pb
           << ", mu="<<slotsMu[0]<<", ni="<<slotsNi[0]<<endl;
    exit(0);
  }
  else if (verbose) {
    cout << "Comparison (without min max) succeeded: ";
    cout << '('<<pa<<','<<pb<<")=> mu="<<slotsMu[0]<<", ni="<<slotsNi[0]<<endl;
  }

  {CtPtrs_VecCt wMin(eMin), wMax(eMax); // A wrappers around output vectors

  //cmp with max and min
  compareTwoNumbers(wMax, wMin, mu, ni,
                    CtPtrs_VecCt(enca), CtPtrs_VecCt(encb),
                    &unpackSlotEncoding);
  decryptBinaryNums(slotsMax, wMax, secKey, ea);
  decryptBinaryNums(slotsMin, wMin, secKey, ea);
  } // get rid of the wrapper
  ea.decrypt(mu, secKey, slotsMu);
  ea.decrypt(ni, secKey, slotsNi);

  if (slotsMax[0]!=pMax || slotsMin[0]!=pMin
      || slotsMu[0]!=pMu || slotsNi[0]!=pNi) {
    cout << "BAD\n";
    if (verbose)
      cout << "Comparison (with min max) error: a="<<pa<<", b="<<pb
           << ", but min="<<slotsMin[0]<<", max="<<slotsMax[0]
           << ", mu="<<slotsMu[0]<<", ni="<<slotsNi[0]<<endl;
    exit(0);
  }
  else if (verbose) {
    cout << "Comparison (with min max) succeeded: ";
    cout << '('<<pa<<','<<pb<<")=>("<<slotsMin[0]<<','<<slotsMax[0]
         <<"), mu="<<slotsMu[0]<<", ni="<<slotsNi[0]<<endl;
  }

#ifdef DEBUG_PRINTOUT
  const Ctxt* minLvlCtxt = nullptr;
  long minLvl=1000;
  for (const Ctxt& c: eMax) {
    long lvl = c.findBaseLevel();
    if (lvl < minLvl) {
      minLvlCtxt = &c;
      minLvl = lvl;
    }
  }
  decryptAndPrint((cout<<" after comparison: "), *minLvlCtxt, secKey, ea,0);
  cout << endl;
#endif
}

#if 0
e2=a2+b2+1
e1=a1+b1+1
e0=a0+b0+1, ne0 = a0+b0

e*2 = e2
e*1 = e2 e1
e*0 = e*1 e0, ne*0 = e*1 ne0

a*2 = a2    , b*2 = b2
a*1 = e2 a1 , b*1 = e*1 -e2 - a*1
a*0 = e*1 a0, b*0 = ne*0 -a*0

c2 = a2 b2
c1 = a1 b1
c0 = a0 b0

c*2 = c2
c*1 = e2 c1
c*0 = e*1 c0

A2 = a2,      B2 = b2,      C2 = c2
A1 = a2 +a*1, B1 = b2 +b*1, C1 = c2 + c*1
A0 = A1 +a*0, B0 = B1 +b*0, C0 = C1 + c*0

(a>b) = A0+C0
(a<b) = B0+C0

mx2 = a2+b2+c2
mx1 = a*1+b*1+c1 + a1(a2+c2) + b1(b2+c)

X ab01 = b1 a0
X b10 = b1 b0

mx0 = a0+b0+c*0
     + a0(A1+C1) = a0 A1 + a0(c*2 +a*1 b1) = a0(A1+c*2) + a*1 ab01
     + b0(B1+C1) = b0 B1 + b0(c*2 +a*1 b1) = b0(B1+c*2) + a*1 b10
    = a0(1+A1+c*2) + b0(1+B1+c*2) + a*1(b10+ab01) + c*0

mn0 = c0
     + a0(B1+C1) = a0 B1 + a0(c*2 +a*1 b1) = a0(B1+c*2) + a*1 ab01
     + b0(A1+C1) = b0 A1 + b0(c*2 +a*1 b1) = b0(A1+c*2) + a*1 b10
    = a0(B1+c*2) + b0(A1+C*2) + a*1(b10 + ab01) + c*0

e20 = e2 ne0

      a*1(b10+ab01)=(a2+b2+1)a1 b1(a0+b0) = e20 c1

mx0 = a0(1+A1+c*2) + b0(1+B1+c*2) + e20 c1 + c*0
mn0 = a0(B1+c*2) + b0(A1+c*2) + e20 c1 + c*0

#endif


#pragma once
#include <boost/fusion/include/std_tuple.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <cstdint>
#include <string>
#include <tuple>

namespace opal {
namespace core {
namespace parser {
enum op_list {
  add,
  sub,
  mul,
  div,
};
struct op_symbols : public boost::spirit::qi::symbols<char, op_list> {
  op_symbols() {
      add
      ("+", op_list::add)
      ("-", op_list::sub)
      ("*", op_list::mul)
      ("/", op_list::div);
  }
};
struct arithmetic_t {
  int32_t left;
  op_list op;
  int32_t right;
};
template <class Iterator>
class test : public boost::spirit::qi::grammar<Iterator, arithmetic_t()> {
 public:
  test() : test::base_type(root) {
    namespace qi = boost::spirit::qi;
    namespace ph = boost::phoenix;
    root = (
      qi::omit[*qi::space] >>
      (qi::int_[(&qi::_val)->*&arithmetic_t::left = qi::_1]) >>
      qi::omit[*qi::space] >>
      op_[(&qi::_val)->*&arithmetic_t::op = qi::_1] >>
      qi::omit[*qi::space] >>
      (qi::int_[(&qi::_val)->*&arithmetic_t::right = qi::_1]));
  };

 private:
  boost::spirit::qi::rule<Iterator, arithmetic_t()> root;
  op_symbols op_;
};
}  // namespace parser
}  // namespace core
}  // namespace opal


#include "driver.hpp"
#include <algorithm>
#include <boost/math/constants/constants.hpp>
#include <fstream>

namespace ccalc
{
    namespace mc = boost::math::constants;

    void Driver::parse(std::string filename) {
        std::ifstream file(filename);
        if (!file) {
            std::cerr << "Failed to open file \"" << filename << "\"\n";
            exit(EXIT_FAILURE);
        }
        loc.initialize(&filename);
        parse(file);
        loc.initialize();
    }

    void Driver::endParse() {
        shouldEnd = true;
    }

    void Driver::setMode(Mode m) {
        driverMode = m;
    }

    Mode Driver::getMode() const {
        return driverMode;
    }

    ccalc::location& Driver::getLocation() {
        return loc;
    }

    void ccalc::Driver::setLastResult(const Float& f) {
        lastResult = f;
    }

    Float Driver::getLastResult() const {
        return lastResult;
    }

    void Driver::addVariable(const std::string& name, Float value) {
        variables[name] = value;
    }

    Float Driver::getVariable(const std::string& name) const {
        try {
            const auto p = predefinedVariables.find(name);
            return p != predefinedVariables.end() ? p->second : variables.at(name);
        } catch (const std::out_of_range&) {
            throw UndefinedIdentifier("Variable \"" + name + "\" does not exist.");
        }
    }

    Float Driver::call(const std::string& name, const std::vector<Float>& args) const {
        try {
            return functions.at(name)(args);
        } catch (const std::out_of_range&) {
            throw UndefinedIdentifier("\"" + name + "\" is not a function.");
        }
    }

    void Driver::callSysFunction(const std::string& name, const std::vector<Float>& args) const {
        try {
            sysFunctions.at(name)(args);
        } catch (const std::out_of_range&) {
            throw UndefinedIdentifier("\"" + name + "\" is not a system function.");
        }
    }

    // clang-format off
    const std::unordered_map<std::string, Float> Driver::predefinedVariables = {
        {"pi", mc::pi<Float>()},
        {"e", mc::e<Float>()},
        {"degree", mc::degree<Float>()},
        {"radian", mc::radian<Float>()}
    };

    const std::unordered_map<std::string, std::function<Float(const std::vector<Float>&)>> Driver::functions = {
        {"abs", builtin::abs},
        {"sqrt", builtin::sqrt},
        {"max", builtin::max},
        {"min", builtin::min},
        {"sin", builtin::sin},
        {"cos", builtin::cos},
        {"tan", builtin::tan},
    };

    const std::unordered_map<std::string, std::function<void(const std::vector<Float>&)>> Driver::sysFunctions = {
        {"setPrecision", sys::setPrecision}
    };
    // clang-format on

    // Definition of built-in functions
    namespace builtin
    {
        Float abs(const std::vector<Float>& args) {
            if (args.size() != 1)
                throw InvalidArgument("Function \"abs\" takes exactly 1 argument.");
            return mp::abs(args[0]);
        }

        Float sqrt(const std::vector<Float>& args) {
            if (args.size() != 1)
                throw InvalidArgument("Function \"sqrt\" takes exactly 1 argument.");
            return mp::sqrt(args[0]);
        }

        Float max(const std::vector<Float>& args) {
            if (args.size() < 2)
                throw InvalidArgument("Function \"max\" takes at least 2 arguments.");
            return *std::max_element(args.begin(), args.end());
        }

        Float min(const std::vector<Float>& args) {
            if (args.size() < 2)
                throw InvalidArgument("Function \"min\" takes at least 2 arguments.");
            return *std::min_element(args.begin(), args.end());
        }

        Float sin(const std::vector<Float>& args) {
            if (args.size() != 1)
                throw InvalidArgument("Function \"sin\" takes exactly 1 argument.");
            const ccalc::Float res = mp::sin(args[0]);
            return mp::abs(res) > std::numeric_limits<ccalc::Float>::epsilon() ? res : ccalc::Float(0);
        }

        Float cos(const std::vector<Float>& args) {
            if (args.size() != 1)
                throw InvalidArgument("Function \"cos\" takes exactly 1 argument.");
            const ccalc::Float res = mp::cos(args[0]);
            return mp::abs(res) > std::numeric_limits<ccalc::Float>::epsilon() ? res : ccalc::Float(0);
        }

        Float tan(const std::vector<Float>& args) {
            if (args.size() != 1)
                throw InvalidArgument("Function \"tan\" takes exactly 1 argument.");
            // return mp::tan(args[0]);
            return sin(args) / cos(args);
        }
    } // namespace builtin

    namespace sys
    {
        void setPrecision(const std::vector<Float>& args) {
            if (args.size() != 1)
                throw InvalidArgument("Function \"setPrecision\" takes exactly 1 argument.");
            const int p = static_cast<int>(mp::round(args[0]));
            if (p < 0)
                throw InvalidArgument("setPrecision expects a positive argument or 0 for the maximum number of significant digits");
            std::cout.precision(p);
        }
    } // namespace sys
} // namespace ccalc


#include "utils.h"
#include "ps/ps.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <iostream>
#include <fstream>
#include <iterator>
#include <vector>
#include <thread>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/timer/timer.hpp>
#include <boost/generator_iterator.hpp>
#include <boost/program_options.hpp>
#include <boost/algorithm/string.hpp>
#include <limits>
#include <sstream>
#include <string>
#include <iostream>
#include <unistd.h>
#include <bitset>
#include <Eigen/Dense>
#include <Eigen/SparseCore>
#include "mf/update.h"
#include "mf/data.h"
#include "mf/io.h"

using namespace ps;
using namespace std;

typedef double ValT;
typedef DefaultColoServerHandle<ValT> HandleT;
typedef ColoKVServer<ValT, HandleT> ServerT;
typedef ColoKVWorker<ValT, HandleT> WorkerT;

typedef Eigen::VectorXd vect;
typedef Eigen::SparseMatrix<ValT,Eigen::RowMajor> SpMatrixRM;
typedef Eigen::SparseMatrix<ValT,Eigen::ColMajor> SpMatrixCM;
typedef Eigen::Matrix<ValT, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> DeMatrixRM;
typedef Eigen::Matrix<ValT, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor> DeMatrixCM;

enum class Alg {dsgd, columnwise, plain_sgd};
std::ostream& operator<<(std::ostream &o, const Alg& rsm) {
  switch(rsm) {
  case Alg::dsgd: return o << "dsgd";
  case Alg::columnwise: return o << "columnwise";
  case Alg::plain_sgd: return o << "plain_sgd";
  default: return o << "unkown";
  }
}
std::istream& operator>>(std::istream& in, Alg& rsm) {
  std::string token; in >> token;
  if (token == "dsgd") rsm = Alg::dsgd;
  else if (token == "columnwise") rsm = Alg::columnwise;
  else if (token == "plain_sgd") rsm = Alg::plain_sgd;
  else { CHECK(false) << "Fatal! Unknown MF algorithm " << token; }
  return in;
}

// Matrix factorization config
string dataset;
uint mf_rank;
int epochs;
double initial_step_size;
double lambda;
double increase_step_factor;
double decrease_step_factor;
bool use_wor_block_schedule;
bool use_wor_point_schedule;
bool compute_loss;
bool bold_driver;

// System config
uint num_workers;
int  num_threads;
int  num_cols;
int  num_rows;
bool shared_memory;
Alg algorithm;
bool disable_localize_blocks;
bool disable_localize_left;
size_t prelocalize_steps;
size_t prelocalize_groupsize;
int init_parameters;
bool enforce_random_keys;
unsigned long early_stop;
bool sync_push;
size_t replicate;
long max_runtime;
bool prevent_full_model_pull;

// set by system
Key first_col_key;
Key num_keys;
std::vector<Key> key_assignment;
unsigned model_seed;

// Shared data
std::vector<Key> full_h_keys;
std::vector<ValT> full_h;

inline Key row_key(size_t i) {
  return enforce_random_keys ? key_assignment[i] : i;
}
inline Key col_key(size_t j) {
  return enforce_random_keys ? key_assignment[first_col_key+j] : first_col_key+j;
}

#include "mf/loss.h"

// Calculate loss on training and test data
double calculate_loss(const int epoch, std::vector<Key>& local_w_keys,
                      mf::DataPart& train_data, mf::DataPart& test_data,
                      WorkerT& kv, const int worker_id, const int num_workers, const int customer_id) {

  // key and vector for aggregation (in PS)
  std::vector<Key> loss_key { num_keys-1 };
  std::vector<ValT> agg_vec (mf_rank);
  assert(mf_rank >= 2); // need two elements to aggregate train and test loss

  // reset aggregation
  if (worker_id == 0) {
    kv.Wait(kv.Pull(loss_key, &agg_vec));
    for(size_t i=0; i!=agg_vec.size(); ++i) {
      agg_vec[i] = -agg_vec[i];
    }
    kv.Wait(kv.Push(loss_key, agg_vec));
  }
  kv.Barrier();

  // wait for sync
  kv.WaitReplicaSync();

  // calculate local loss
  double local_train_loss = 0;
  double local_test_loss = 0;
  if (!prevent_full_model_pull) {
    // pull local part of W and H (once per process)
    if (worker_id == 0) ADLOG("Pull model for loss calc");
    std::vector<ValT> local_w (local_w_keys.size() * mf_rank);
    int t_pullw = kv.Pull(local_w_keys, &local_w); // fetch local part of w
    if (customer_id == 1) { // fetch full h once per process
      full_h.resize(num_cols * mf_rank);
      kv.Wait(kv.Pull(full_h_keys, &full_h));
    }
    kv.Wait(t_pullw);
    kv.Barrier();

    // calculate loss (with a copy of the model)
    local_train_loss = loss_Nzsl_L2(train_data, local_w, full_h, lambda, mf_rank, worker_id);
    local_test_loss =  loss_Nzsl_L2(test_data,  local_w, full_h, 0.0,    mf_rank, worker_id);
  } else {
    // calculate loss (pulling necessary factors on demand)
    if (worker_id == 0) ADLOG("No full model pull for eval. Pull factors on demand.");
    local_train_loss = loss_Nzsl_L2_pull(train_data, kv, local_w_keys, lambda, mf_rank, worker_id);
    local_test_loss =  loss_Nzsl_L2_pull(test_data,  kv, local_w_keys, 0.0,    mf_rank, worker_id);
  }
  // ADLOG("Local training loss (worker " << worker_id << "): " << local_train_loss);
  // ADLOG("Local test loss (worker " << worker_id << "): " << local_test_loss);

  // aggregate local losses to global loss (using the PS)
  agg_vec[0] = local_train_loss;
  agg_vec[1] = local_test_loss;
  kv.Wait(kv.Push(loss_key, agg_vec));
  kv.Barrier();
  kv.Wait(kv.Pull(loss_key, &agg_vec));

  auto train_loss = agg_vec[0] / train_data.total_nnz();
  auto test_loss = agg_vec[1] / test_data.total_nnz();

  if (worker_id == 0) {
    ADLOG("(Ep." << epoch << ") Train loss: " << train_loss);
    ADLOG("(Ep." << epoch << ") Test loss:  " << test_loss);
  }

  return train_loss;
}

// determine which parameters to replicate:
// we replicate the factors of the N most frequent columns
std::vector<Key> determine_hotspot_keys (size_t N_hotspots) {
  std::vector<Key> hotspot_keys {};
  if (N_hotspots == 0) return hotspot_keys;

  hotspot_keys.reserve(N_hotspots);

  // stats
  long freq_replicated = 0;
  long freq_total = 0;

  // read which relations have not been partitioned
  ifstream colfreqs_ifs(dataset+"train.mmc.bin.colfreqs.csv", ios::in);
  if (!colfreqs_ifs.fail()) {
    long freq;
    long j;
    string line;

    // read header line
    getline(colfreqs_ifs, line);

    while (getline(colfreqs_ifs, line)) {
      stringstream ss(line);
      ss >> freq >> j;
      freq_total += freq;
      if (hotspot_keys.size() < N_hotspots) {
        hotspot_keys.push_back(col_key(j));
        freq_replicated += freq;
      }
    }
    colfreqs_ifs.close();
  } else {
    ADLOG("Did not find colfreqs file. Don't know which columns to replicate");
    abort();
  }
  ADLOG("Replicate " << hotspot_keys.size() << " keys. This should cover " << std::setprecision(5) << 100.0*freq_replicated/freq_total << "% of column accesses (and " << 100.0*freq_replicated/(freq_total*2) << "% of all accesses)");
  // ADLOG("Replicate columns " << hotspot_keys);
  return hotspot_keys;
}

void RunWorker(int customer_id, ServerT* server=nullptr) {
  Start(customer_id);
  WorkerT kv(0, customer_id, *server);

  boost::random::mt19937 rng(static_cast<unsigned int>(std::time(0)));
  std::unordered_map<std::string, util::Stopwatch> sw {};
  util::Trace trace {"./measurements/mf/mf_trace.csv"};

  UpdateNsqlL2 update_fun {mf_rank, initial_step_size, lambda};
  int worker_id = ps::MyRank()*num_threads+customer_id-1; // a unique id for this worker thread
  Eigen::MatrixXi block_schedule = mf::initBlockSchedule(num_workers);

  /* Load data, factors, and allocate memory */

  // Load train data
  sw["read_data"].start();
  mf::DataPart data = read_sparse_matrix_part(dataset + string("train.mmc"), true, num_workers, worker_id, num_rows, num_cols, customer_id, algorithm == Alg::dsgd, kv);
  mf::DataPart data_test = read_sparse_matrix_part(dataset + string("test.mmc"), true, num_workers, worker_id, num_rows, num_cols, customer_id, algorithm == Alg::dsgd, kv);
  sw["read_data"].stop();
  ALOG("Finished reading train data in worker " << worker_id << " (" << sw["read_data"] << ")");

  // allocate memory for factors in training loop
  std::vector<Key> factors_keys ( 2 );
  std::vector<ValT> factors ( factors_keys.size() * mf_rank );
  std::vector<ValT> factors_update ( factors.size() );

  // allocate memory for factors in loss computation (once per process)
  if (customer_id == 1) {
    full_h_keys.resize(num_cols);
    for(size_t j=0; j!=full_h_keys.size(); ++j) {
      full_h_keys[j] = col_key(j);
    }
  }

  std::vector<Key> local_h_keys ( data.num_cols_per_block() );
  std::vector<Key> local_w_keys ( data.num_rows_per_block() );
  for(size_t z=0; z!=data.num_rows_per_block(); ++z) {
    local_w_keys[z] = row_key(data.num_rows_per_block() * worker_id + z);
  }

  boost::random::uniform_real_distribution<> factor_generator(0,1);

  // push factors into the parameter servers
  kv.BeginSetup();
  if (worker_id == 0 && init_parameters > 0) {
    // read and send H
    sw["read_factors"].start();
    std::vector<ValT> init_h {};
    if (init_parameters == 1) {
      // read factors from file
      init_h = read_dense_matrix<std::vector<ValT>>(dataset + "H.mma", false);
    } else if (init_parameters == 2) {
      // init model randomly
      ALOG("Init H randomly (seed " << model_seed << ")");
      init_h.resize(num_cols * mf_rank);
      boost::random::mt19937 rng_H (model_seed+133273);
      for (size_t z=0; z!=init_h.size(); ++z) {
        init_h[z] = factor_generator(rng_H);
      }
    }
    kv.Wait(kv.StaggeredPush(full_h_keys, init_h));
    sw["read_factors"].stop();
    ALOG("Initialized H (" << sw["read_factors"] << "): " << init_h[0] << " " << init_h[1] << " " << init_h[2] << " ..");
  }

  if (init_parameters > 0 && static_cast<uint>(worker_id) == num_workers-1) {
    // read and send W
    sw["read_factors"].start();
    ValT first, second;
    if (init_parameters == 1) {
      // read factors from files
      vector<Key> full_w_keys (num_rows);
      for(size_t i = 0; i!=full_w_keys.size(); ++i) full_w_keys[i] = row_key(i);
      std::vector<ValT> full_w;
      full_w = read_dense_matrix<std::vector<ValT>>(dataset + "W.mma", true);
      kv.Wait(kv.StaggeredPush(full_w_keys, full_w));
      first = full_w[0];
      second = full_w[1];
    } else if (init_parameters == 2) {
      // init model randomly
      ALOG("Init W randomly (seed " << model_seed << ")");
      boost::random::mt19937 rng_W (model_seed);

      // use (more complicated) batched implementation so we can run large models on a single machine
      size_t batch_size = 10000;
      vector<Key> partial_w_keys {};
      std::vector<ValT> partial_w {};
      vector<int> tss {};
      size_t gen = 0;
      for (int i=0; i!=num_rows; ++i) {
        partial_w_keys.push_back(row_key(i));

        // generate
        for (size_t z=0; z!=mf_rank; ++z) {
          auto r = factor_generator(rng_W);
          if (gen == 0) first = r;
          if (gen == 1) second = r;
          ++gen;
          partial_w.push_back(r);
        }

        // push
        if (i % batch_size == batch_size-1) {
          tss.push_back(kv.Push(partial_w_keys, partial_w));
          partial_w_keys.resize(0);
          partial_w.resize(0);
        }
      }
      tss.push_back(kv.Push(partial_w_keys, partial_w));
      for (auto ts : tss) kv.Wait(ts);
    }
    sw["read_factors"].stop();
    ALOG("Initialized W (" << sw["read_factors"] << "): " << first << " " << second << " ..");
  }
  kv.EndSetup(); // waits for replica sync and does barrier

  // Localize local part of W (left matrix)
  if (!disable_localize_left) {
    kv.Wait(kv.Localize(local_w_keys));
  }

  // prep for column-wise access
  std::vector<size_t> my_columns {};
  if (algorithm == Alg::columnwise) {
    for (int j=0; j!=num_cols; ++j) {
      if (data.block_has_nnz(j)) {
        my_columns.push_back(j);
      }
    }

    srand(worker_id ^ 13 * 17); // make sure workers have unique column permutations
    if (use_wor_block_schedule) std::random_shuffle(my_columns.begin(), my_columns.end());
  }

  // Compute initial loss
  ValT previous_train_loss=0;
  if (compute_loss) {
    auto train_loss = calculate_loss(0, local_w_keys, data, data_test,
                                     kv, worker_id, num_workers, customer_id);
    previous_train_loss = train_loss;
  }

  kv.Barrier(); // make sure all workers start training at the same time

  /* Training loop */
  for(int epoch = 1; epoch != epochs+1; ++epoch) {
    if (worker_id == 0) ADLOG("(Ep." << epoch << ") Starting epoch " << epoch);
    sw["epoch.worker"].start();
    sw["epoch"].start();
    sw["runtime"].resume();

    // create WOR block schedule for this epoch, if desired (WOR = random, without replacement)
    if (use_wor_block_schedule) mf::newWorSchedule(block_schedule, epoch, rng);

    long updates = 0;

    if (algorithm == Alg::dsgd) { // use DSGD to create locality in parameter accesses
      for(uint subepoch = 0; subepoch != num_workers; ++subepoch) {
        if (worker_id == 0) ADLOG("Subepoch " << epoch << "." << subepoch);

        // get the block this workers works on in this epoch
        sw["comm"].resume();
        int h_block = block_schedule(subepoch, worker_id);
        if (!disable_localize_blocks) {
          for (size_t z = 0; z!=local_h_keys.size(); ++z) {
            local_h_keys[z] = col_key(h_block * data.num_cols_per_block() + z);
          }
          kv.Localize(local_h_keys);
        }
        sw["comm"].stop();

        // permute training points (WOR training schedule)
        sw["perm"].resume();
        if (use_wor_point_schedule) data.permuteBlock(h_block);
        sw["perm"].stop();

        // train on this block

        // iterate over the nonzeros in this block
        sw["comp"].resume();
        for (unsigned long z=data.block_start(h_block); z!=data.block_end(h_block); ++z) {
          const mf::DataPoint& dp = data.data()[data.permutation()[z]];

          // get factors
          factors_keys[0] = row_key(dp.i);
          factors_keys[1] = col_key(dp.j);
          kv.Wait(kv.Pull(factors_keys, &factors));

          // run update
          update_fun(dp.i, dp.j, factors, factors_update, dp.x, data.row_nnz(dp.i), data.col_nnz(dp.j));
          ++updates;

          // send factor updates
          auto ts = kv.Push(factors_keys, factors_update);
          if (sync_push) kv.Wait(ts);
        }

        sw["comp"].stop();

        // wait for all workers to finish the subepoch
        sw["barrier"].resume();
        kv.Barrier();
        sw["barrier"].stop();

        // ADLOG("Subepoch " << epoch << "." << subepoch << " took " << sw["subepoch"]);
      }
    } else if (algorithm == Alg::columnwise) { // columnwise
      // permute columns
      sw["perm"].resume();
      ADLOG("Permute columns");
      if(use_wor_block_schedule) std::random_shuffle(my_columns.begin(), my_columns.end());
      sw["perm"].stop();

      // iterate over the nonzeros that are partitioned to this worker
      sw["comp"].resume();
      long col_future=0;
      long z_total=0;
      long block_start_future=0;
      for (unsigned long col=0; col!=my_columns.size(); ++col) {
        auto j = my_columns[col];

        // permute data points of this column
        if (use_wor_point_schedule) data.permuteBlock(j);

        for (unsigned long z=data.block_start(j); z!=data.block_end(j); ++z, ++z_total) {
          const mf::DataPoint& dp = data.data()[data.permutation()[z]];

          // Pre-localize column parameters
          if (prelocalize_steps != 0 && z_total % prelocalize_groupsize == 0) {
            vector<Key> localize;
            localize.reserve(prelocalize_groupsize);
            long localize_until = z_total + (prelocalize_steps+1)*prelocalize_groupsize; // +1 gets us to the block end
            while (col_future < static_cast<long>(my_columns.size()) && block_start_future < localize_until) {
              localize.push_back(col_key(my_columns[col_future]));
              block_start_future += data.block_size(my_columns[col_future]);
              ++col_future;
            }
            kv.Localize(localize);
          }

          // get factors
          factors_keys[0] = row_key(dp.i);
          factors_keys[1] = col_key(dp.j);
          kv.Wait(kv.Pull(factors_keys, &factors));

          // run update
          update_fun(dp.i, dp.j, factors, factors_update, dp.x, data.row_nnz(dp.i), data.col_nnz(dp.j));
          ++updates;

          // send factor updates
          auto ts = kv.Push(factors_keys, factors_update);
          if (sync_push) kv.Wait(ts);

          // early stopping (for debugging)
          if(early_stop != 0 && z == early_stop) {
            ALOG("Worker " << worker_id << " stops the epoch early after " << early_stop << " of " << data.num_nnz() << " data points");
            goto endepoch;
          }
        }
      }
    endepoch:
      sw["comp"].stop();
      sw["epoch.worker"].stop();
      ALOG("Worker " << worker_id << " is through its " << data.num_nnz() << " data points (" << sw["epoch.worker"] << ")");

      // wait for all workers to finish the epoch
      sw["barrier"].resume();
      kv.Barrier();
      sw["barrier"].stop();

    } else { // plain SGD: access the data points in fully random order (i.e., no blocks and no columnwise access)

      // permute training data points (WOR training schedule)
      sw["perm"].resume();
      if (worker_id == 0) ADLOG("Permute ...");
      if (use_wor_point_schedule) data.permuteData();
      sw["perm"].stop();

      // iterate over the nonzeros that are partitioned to this worker
      sw["comp"].resume();
      if (worker_id == 0) ADLOG("Compute ...");
      unsigned long z_future=0;
      for (unsigned long z=0; z!=data.num_nnz(); ++z) {
        const mf::DataPoint& dp = data.data()[data.permutation()[z]];

        // early stopping (for debugging)
        if(early_stop != 0 && z == early_stop) {
          ALOG("Worker " << worker_id << " stops the epoch early after " << early_stop << " of " << data.num_nnz() << " data points");
          break;
        }

        // Pre-localize parameters
        if (prelocalize_steps != 0 && z % prelocalize_groupsize == 0) {
          vector<Key> localize;
          localize.reserve(prelocalize_groupsize);
          while (z_future < z+static_cast<int>((prelocalize_steps+1)*prelocalize_groupsize) && z_future < data.num_nnz()) {
            const mf::DataPoint& dp_f = data.data()[data.permutation()[z_future]];
            localize.push_back(col_key(dp_f.j));
            ++z_future;
          }
          kv.Localize(localize);
        }

        // get factors
        factors_keys[0] = row_key(dp.i);
        factors_keys[1] = col_key(dp.j);
        kv.Wait(kv.Pull(factors_keys, &factors));

        // run update
        update_fun(dp.i, dp.j, factors, factors_update, dp.x, data.row_nnz(dp.i), data.col_nnz(dp.j));
        ++updates;

        // send factor updates
        auto ts = kv.Push(factors_keys, factors_update);
        if (sync_push) kv.Wait(ts);
      }
      sw["comp"].stop();
      sw["epoch.worker"].stop();
      ALOG("Worker " << worker_id << " is through its " << data.num_nnz() << " data points (" << sw["epoch.worker"] << ")");

      // wait for all workers to finish the epoch
      sw["barrier"].resume();
      kv.Barrier();
      sw["barrier"].stop();

    }
    sw["epoch"].stop();
    sw["runtime"].stop();
    ADLOG("(Ep." << epoch << ") Worker " << worker_id << " finished epoch " << epoch <<" (" << sw["epoch"] << ": " << sw["comm"] << " comm, "  << sw["comp"] << " comp, " << sw["barrier"] << " barrier, updates: " << updates <<")");
    if (worker_id == 0) {
      ALOG("All workers finished epoch " << epoch << " (epoch: " << sw["epoch"] << ", total: " << sw["runtime"] << ").");
    }
    sw["comm"].reset(); sw["comp"].reset(); sw["perm"].reset(); sw["barrier"].reset();

    if (compute_loss) {
      auto train_loss = calculate_loss(epoch, local_w_keys, data, data_test,
                                       kv, worker_id, num_workers, customer_id);

      // bold driver: adapt step size
      if (bold_driver) {
        if (train_loss < previous_train_loss) {
          update_fun.update_step_size(increase_step_factor); // good epoch. increase step size slightly
          if (worker_id == 0) {
            ADLOG("Bold driver: Increase step size to " << update_fun.current_step_size() << " (Train loss: " << previous_train_loss << " -> " << train_loss << ")");
          }
        } else {
          update_fun.update_step_size(decrease_step_factor); // bad epoch. reduce step size
          if (worker_id == 0) {
            ADLOG("Bold driver: Decrease step size to " << update_fun.current_step_size() << " (Train loss: " << previous_train_loss << " -> " << train_loss << ")");
          }
        }
      }

      previous_train_loss = train_loss;
    }

    // maximum time
    if (sw["runtime"].elapsed_s() > max_runtime ||
        sw["runtime"].elapsed_s() + sw["epoch"].elapsed_s() > max_runtime * 1.05) {
      ADLOG("Worker " << worker_id << " stops after epoch " << epoch << " because max. time is reached: " << sw["runtime"].elapsed_s() << "s (+1 epoch) > " << max_runtime << "s (epoch: " << sw["epoch"].elapsed_s() << "s)");
      break;
    }
  }

  // make sure all workers finished
  LLOG("Worker " << worker_id << " done.");
  kv.Finalize();
  Finalize(customer_id, false);
}

int process_program_options(const int argc, const char *const argv[]) {
  namespace po = boost::program_options;
  po::options_description desc("Allowed options");
  desc.add_options()
    ("help,h", "produce help message")
    ("dataset,d", po::value<std::string>(&dataset), "Dataset to train from")
    ("num_rows", po::value<int>(&num_rows), "number of rows in the dataset")
    ("num_cols", po::value<int>(&num_cols), "number of columns in the dataset")
    ("rank,r", po::value<uint>(&mf_rank), "Rank of matrix factorization")
    ("epochs,e", po::value<int>(&epochs), "Number of epochs to run")
    ("lambda,l", po::value<double>(&lambda)->default_value(0.05), "Regularization parameter lambda")
    ("eps", po::value<double>(&initial_step_size)->default_value(0.001), "Initial step size")
    ("bold_driver", po::value<bool>(&bold_driver)->default_value(true), "Use bold driver for step size selection")
    ("increase_step_factor", po::value<double>(&increase_step_factor)->default_value(1.05), "Factor to increase step size after successful epoch")
    ("decrease_step_factor", po::value<double>(&decrease_step_factor)->default_value(0.5), "Factor to decrease step size after unsuccessful epoch")
    ("shared_memory", po::value<bool>(&shared_memory)->default_value(true), "access local parameters via shared memory")
    ("algorithm", po::value<Alg>(&algorithm)->default_value(Alg::dsgd), "which algorithm to use. options: (1) dsgd = DSGD, (2) columnwise = access data points by column, (3) plain_sgd")
    ("disable_localize_blocks", po::value<bool>(&disable_localize_blocks)->default_value(false), "disable localizes for H blocks (relevant only for DSGD)")
    ("disable_localize_left", po::value<bool>(&disable_localize_left)->default_value(false), "disable localization of left matrix (default: off, i.e., do localize left)")
    ("prelocalize_steps", po::value<size_t>(&prelocalize_steps)->default_value(0), "localize parameters N steps ahead of time")
    ("prelocalize_groupsize", po::value<size_t>(&prelocalize_groupsize)->default_value(1000), "number of data points to pre-localize in one call")
    ("replicate", po::value<size_t>(&replicate)->default_value(0), "number of keys to treat as hotspots (i.e., replicate)")
    ("num_threads,t", po::value<int>(&num_threads)->default_value(1), "number of worker threads to run")
    ("wor_blocks", po::value<bool>(&use_wor_block_schedule)->default_value(true), "use WOR schedule for blocks")
    ("wor_points", po::value<bool>(&use_wor_point_schedule)->default_value(true), "use WOR schedule for data points")
    ("sync_push", po::value<bool>(&sync_push)->default_value(false), "synchronous or asynchronous (default) pushes")
    ("compute_loss", po::value<bool>(&compute_loss)->default_value(true), "compute loss")
    ("init_parameters", po::value<int>(&init_parameters)->default_value(2), "how to initialize parameters. 0: no init, 1: read factors from files, 2: draw random factors")
    ("model_seed", po::value<unsigned>(&model_seed)->default_value(134827), "seed for model generation")
    ("enforce_random_keys", po::value<bool>(&enforce_random_keys)->default_value(false), "enforce that keys are assigned randomly")
    ("early_stop", po::value<unsigned long>(&early_stop)->default_value(0), "stop an epoch early after N data points (for debugging)")
    ("max_runtime", po::value<long>(&max_runtime)->default_value(std::numeric_limits<long>::max()), "set a maximum run tim, after which the job will be terminated (in seconds)")
    ("prevent_full_model_pull", po::value<bool>(&prevent_full_model_pull)->default_value(false), "prevent a full model pull (slower, but makes it possible to run large models in memory constrained settings)")
    ;

  // add system options
  ServerT::AddSystemOptions(desc);

  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);

  if (vm.count("help")) {
    cout << desc << "\n";
    return 1;
  }

  if (!vm.count("dataset") || !vm.count("rank") || !vm.count("epochs")) {
    cout << "Either the dataset, the MF rank, or the number of epochs to run was not specified. Usage:\n\n";
    cout << desc << "\n";
    return 1;
  }

  if (algorithm == Alg::dsgd && prelocalize_steps > 0) {
    cout << "Setting `prelocalize_steps` > 0 is invalid when using DSGD\n";
    cout << desc << "\n";
    return 1;
  }
  if (algorithm != Alg::dsgd && disable_localize_blocks) {
    cout << "`disable_localize_blocks` = true has no effect when not using DSGD\n";
    cout << desc << "\n";
    return 1;
  }
  if (algorithm == Alg::dsgd && replicate > 0) {
    cout << "Both DSGD and replication activated, which does not make sense\n";
    cout << desc << "\n";
    return 1;
  }

  return 0;
}

int main(int argc, char *argv[]) {
  // Read cmd arguments
  int po_error = process_program_options(argc, argv);
  if(po_error) return 1;
  num_keys = num_cols + num_rows + 50000;
  Postoffice::Get()->enable_dynamic_allocation(num_keys, num_threads);
  Postoffice::Get()->set_shared_memory_access(shared_memory);

  std::string role = std::string(getenv("DMLC_ROLE"));
  std::cout << "mf. " << role << ": " << epochs << " epochs on " << dataset << "\n";
  std::cout << "localization: disable blocks " << disable_localize_blocks << ". disable left " << disable_localize_left << "\n";
  std::cout << "replicate " << replicate << "\n";

  // calculate H key offset
  num_workers = atoi(Environment::Get()->find("DMLC_NUM_SERVER")) * num_threads;
  first_col_key = round(ceil(1.0*num_rows/num_workers)) * num_workers;

  // enforce random parameter allocation
  if (enforce_random_keys) {
    key_assignment.resize(num_keys-1); // exclude the loss key
    iota(key_assignment.begin(), key_assignment.end(), 0);
    srand(2); // enforce same seed among different ranks
    random_shuffle(key_assignment.begin(), key_assignment.end());
  }

  // set up replication
  vector<Key> hotspot_keys = determine_hotspot_keys(replicate);

  if (role.compare("scheduler") == 0) {
    Start(0);
    Finalize(0, true);
  } else if (role.compare("server") == 0) { // worker+server

    // Start the server system
    int server_customer_id = 0; // server gets customer_id=0, workers 1..n
    Start(server_customer_id);
    auto server = new ServerT(num_keys, mf_rank, &hotspot_keys);
    RegisterExitCallback([server](){ delete server; });

    // make sure all servers are set up
    server->Barrier();

    // run worker(s)
    std::vector<std::thread> workers {};
    for (int i=0; i!=num_threads; ++i) {
      workers.push_back(std::thread(RunWorker, i+1, server));
      std::string name = std::to_string(ps::MyRank())+"-worker-"+std::to_string(ps::MyRank()*num_threads + i);
      SET_THREAD_NAME((&workers[workers.size()-1]), name.c_str());
    }

    // wait for the workers to finish
    for (auto & w : workers)
      w.join();

    // stop the server
    server->shutdown();
    Finalize(server_customer_id, true);
  }
}


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#define NT2_UNIT_MODULE "boost::simd::algorithm"

#include <boost/config.hpp>

#ifdef BOOST_MSVC
#pragma warning(disable: 4996) // std::transform on pointers may be unsafe
#endif

#include <boost/simd/sdk/simd/algorithm.hpp>
#include <boost/simd/include/functions/plus.hpp>

#include <nt2/sdk/unit/module.hpp>
#include <nt2/sdk/unit/tests/relation.hpp>
#include <vector>
#include <algorithm>
#include <numeric>

struct plus
{
  template<class T>
  T operator()(T const& t0, T const& t1) const
  {
    return t0 + t1;
  }
};

struct plus_one
{
  template<class T>
  T operator()(T const& t0) const
  {
    return t0 + 1;
  }
};

namespace std
{
  template<class T, class Alloc>
  ostream& operator<<(ostream& os, vector<T, Alloc> const& v)
  {
    os << "\n";
    for(typename vector<T, Alloc>::const_iterator it = v.begin(); it != v.end(); ++it)
      os << *it << "  ";
    os << "\n";

    return os;
  }
}

////////////////////////////////////////////////////////////////////////////////
// Test boost::simd::transform behavior with unary operation
////////////////////////////////////////////////////////////////////////////////
NT2_TEST_CASE_TPL( transform_unary, BOOST_SIMD_SIMD_TYPES )
{
  std::vector<T> data_in(113);
  for(size_t i=0; i<113; ++i)
    data_in[i] = T(i);

  std::vector<T> data_out1(113);
  boost::simd::transform(&*data_in.begin(), &*data_in.begin()+data_in.size(), &*data_out1.begin(), plus_one());

  std::vector<T> data_out2(113);
  std::transform(&*data_in.begin(), &*data_in.begin()+data_in.size(), &*data_out2.begin(), plus_one());

  NT2_TEST_EQUAL(data_out1, data_out2);
}

////////////////////////////////////////////////////////////////////////////////
// Test boost::simd::transform behavior with binary operation
////////////////////////////////////////////////////////////////////////////////
NT2_TEST_CASE_TPL( transform_binary, BOOST_SIMD_SIMD_TYPES )
{
  std::vector<T> data_in1(113);
  std::vector<T> data_in2(113);
  for(size_t i=0; i<113; ++i)
  {
    data_in1[i] = T(i);
    data_in2[i] = T(10 + i);
  }

  std::vector<T> data_out1(113);
  boost::simd::transform(&*data_in1.begin(), &*data_in1.begin()+data_in1.size(), &*data_in2.begin(), &*data_out1.begin(), plus());

  std::vector<T> data_out2(113);
  std::transform(&*data_in1.begin(), &*data_in1.begin()+data_in1.size(), &*data_in2.begin(), &*data_out2.begin(), plus());

  NT2_TEST_EQUAL(data_out1, data_out2);
}

////////////////////////////////////////////////////////////////////////////////
// Test boost::simd::accumulate
////////////////////////////////////////////////////////////////////////////////
NT2_TEST_CASE_TPL( accumulate, BOOST_SIMD_SIMD_TYPES )
{
  std::size_t n = 113;

  std::vector<T> data_in(n);
  for(size_t i=0; i<n; ++i)
    data_in[i] = T(i);

  T res1 = boost::simd::accumulate(&*data_in.begin(), &*data_in.begin()+data_in.size(), T(0), plus());
  T res2 = std::accumulate(&*data_in.begin(), &*data_in.begin()+data_in.size(), T(0), plus());

  NT2_TEST_EQUAL(res1, res2);
  NT2_TEST_EQUAL(res1, T((n-1)*n/2));
}