/******************************************************************************
 * Copyright 2017 The Apollo Authors. All Rights Reserved.
 *
 * 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.
 *****************************************************************************/

#ifndef MODULES_LOCALIZATION_MSF_LOCAL_GNSS_GNSS_UTILITY_HPP_
#define MODULES_LOCALIZATION_MSF_LOCAL_GNSS_GNSS_UTILITY_HPP_

#include <math.h>
#include <stdarg.h>
#include <Eigen/Eigen>
#include <string>
#include <vector>
#include "modules/localization/msf/local_gnss/gnss_constants.hpp"

#include "modules/localization/proto/gnss_pnt_result.pb.h"
#include "modules/drivers/gnss/proto/gnss_raw_observation.pb.h"

namespace apollo {
namespace localization {
namespace local_gnss {

typedef apollo::drivers::gnss::GnssType GnssType;
typedef apollo::drivers::gnss::GnssBandID GnssBandID;
typedef apollo::drivers::gnss::EpochObservation EpochObservation;
typedef apollo::drivers::gnss::SatelliteObservation SatelliteObservation;
typedef apollo::drivers::gnss::BandObservation BandObservation;
typedef apollo::drivers::gnss::GlonassOrbit GlonassOrbit;
typedef apollo::drivers::gnss::GnssEphemeris GnssEphemeris;
typedef apollo::drivers::gnss::KepplerOrbit KepplerOrbit;

struct PointThreeDim {
  double x;
  double y;
  double z;
  PointThreeDim() {
    x = 0.0;
    y = 0.0;
    z = 0.0;
  }

  PointThreeDim(double t_x, double t_y, double t_z) {
    x = t_x;
    y = t_y;
    z = t_z;
  }

  PointThreeDim& operator=(const PointThreeDim& p2) {
    x = p2.x;
    y = p2.y;
    z = p2.z;
    return *this;
  }

  PointThreeDim& operator+=(const PointThreeDim& p2) {
    x = x + p2.x;
    y = y + p2.y;
    z = z + p2.z;
    return *this;
  }

  PointThreeDim operator+(const PointThreeDim& p2) {
    PointThreeDim t;
    t.x = this->x + p2.x;
    t.y = this->y + p2.y;
    t.z = this->z + p2.z;
    return t;
  }

  PointThreeDim operator-(const PointThreeDim& p2) {
    PointThreeDim t;
    t.x = this->x - p2.x;
    t.y = this->y - p2.y;
    t.z = this->z - p2.z;
    return t;
  }

  PointThreeDim SquareRoot() {
    PointThreeDim t;
    t.x = sqrt(x);
    t.y = sqrt(y);
    t.z = sqrt(z);
    return t;
  }

  PointThreeDim operator/(const double scale) {
    PointThreeDim t;
    t.x = x / scale;
    t.y = y / scale;
    t.z = z / scale;
    return t;
  }

  PointThreeDim operator*(const double scale) {
    PointThreeDim t;
    t.x = x * scale;
    t.y = y * scale;
    t.z = z * scale;
    return t;
  }

  double Norm3D() { return sqrt(x * x + y * y + z * z); }

  double GetDistance(const PointThreeDim& dest) {
    double dist = 0.0;
    dist += (x - dest.x) * (x - dest.x);
    dist += (y - dest.y) * (y - dest.y);
    dist += (z - dest.z) * (z - dest.z);
    return sqrt(dist);
  }
};

struct SatelliteInfor {
  short sat_prn; // NOLINT
  GnssType sat_sys;
  int index_in_obs;
  double clock_bias;
  double clock_drift;
  PointThreeDim position;
  PointThreeDim velocity;
  PointThreeDim direction;
  double distance;
  double elevation;
  double azimuth;
  bool multi_path;
  // week_num * SECOND_PER_WEEK + week_second_s
  double toe;
  unsigned int gnss_week;
  double time_signal_transmitted;
  double time_travles;
  SatelliteInfor() {
    sat_prn = 0;
    sat_sys = apollo::drivers::gnss::SYS_UNKNOWN;
    index_in_obs = -1;
    clock_bias = 0;
    clock_drift = 0;
    distance = 0;
    elevation = 0;
    azimuth = 0;
    multi_path = false;
    toe = -0.1;
    gnss_week = 0;
    time_signal_transmitted = 0.0;
    time_travles = 0.0;
  }
  bool IsSameSatellite(const SatelliteInfor& other) {
    return (sat_sys == other.sat_sys && sat_prn == other.sat_prn);
  }
  bool operator==(const SatelliteInfor& other) const {
    return (sat_sys == other.sat_sys && sat_prn == other.sat_prn);
  }
  void PrintSat(const bool& b_print = false) {
    if (!b_print) {
      return;
    }
    printf("%3d%3d%14.9f%14.3f%14.3f%14.3f%11.3f%11.3f%11.3f\n", sat_prn,
          static_cast<int>(sat_sys), clock_bias, position.x, position.y,
          position.z, velocity.x, velocity.y, velocity.z);
  }
};

struct ObsKey {
  apollo::drivers::gnss::GnssBandID band_id;
  unsigned int sat_prn;
  ObsKey(const apollo::drivers::gnss::GnssBandID& id, const unsigned int prn) {
    band_id = id;
    sat_prn = prn;
  }
  ObsKey() {
    band_id = apollo::drivers::gnss::GPS_L1;
    sat_prn = 0;
  }
  bool operator<(const ObsKey& key2) const {
    if (band_id < key2.band_id) {
      return true;
    }
    if (band_id == key2.band_id) {
      return sat_prn < key2.sat_prn;
    }
    return false;
  }
  bool operator==(const ObsKey& key2) const {
    return (band_id == key2.band_id) && (sat_prn == key2.sat_prn);
  }
  ObsKey& operator=(const ObsKey& key2) {
    band_id = key2.band_id;
    sat_prn = key2.sat_prn;
    return *this;
  }
};

typedef ObsKey AmbKey;

struct EphKey {
  GnssType gnss_type;
  unsigned int sat_prn;
  // toe = eph.toe + eph.week_num * sec_per_week
  double eph_toe;
  EphKey(const GnssType type, const unsigned int prn, double toe) {
    gnss_type = type;
    sat_prn = prn;
    eph_toe = toe;
  }
  EphKey(const GnssType type, const unsigned int prn,
         const unsigned int week_num, double toe) {
    gnss_type = type;
    sat_prn = prn;
    eph_toe = toe + week_num * SECOND_PER_WEEK;
  }
  EphKey() {
    gnss_type = apollo::drivers::gnss::SYS_UNKNOWN;
    sat_prn = 0;
    eph_toe = -0.1;
  }
  bool operator<(const EphKey& key2) const {
    if (gnss_type < key2.gnss_type) {
      return true;
    }
    if (gnss_type == key2.gnss_type) {
      if (sat_prn < key2.sat_prn) {
        return true;
      }
      if (sat_prn == key2.sat_prn) {
        return eph_toe < key2.eph_toe;
      }
      return false;
    }
    return false;
  }
  bool operator==(const EphKey& key2) const {
    return (gnss_type == key2.gnss_type) && (sat_prn == key2.sat_prn) &&
           (eph_toe == key2.eph_toe);
  }
  EphKey& operator=(const EphKey& key2) {
    gnss_type = key2.gnss_type;
    sat_prn = key2.sat_prn;
    eph_toe = key2.eph_toe;
    return *this;
  }
};

namespace gnss_utility {

inline int RoundDoubleToInt(double val) { return static_cast<int>(val); }

inline double sign(const double val) {
  if (val >= 0) {
    return 1.0;
  } else {
    return -1.0;
  }
}

inline double round(double x) {
  return static_cast<double>(std::floor(x + 0.5));
}

template <typename T>
int GetIndexInVector(const T& type, const std::vector<T>& group) {
  unsigned int size = group.size();
  for (unsigned int m = 0; m < size; m++) {
    if (type == group[m]) {
      return m;
    }
  }
  return -1;
}

template <typename T>
int AppendNewToVector(const T& type, const std::vector<T>& group) {
  if (GetIndexInVector<T>(type, group) == -1) {
    group.push_back(type);
    return 0;
  }
  return -1;
}

inline std::string FormatString(const char* format, ...) {
  const int buf_size = 2048;
  char temp[buf_size] = {'\0'};
  va_list args;
  va_start(args, format);
  vsprintf(temp, format, args);
  va_end(args);
  return std::string(temp);
}

inline void PrintPvtResult(const GnssPntResult& rover_pnt, double ratio) {
  std::string part1 = FormatString(
      "%6d%12.3f%4d%16.3f%16.3f%16.3f%4d%4.1f%6.1f%8.3f%8.3f%8.3f",
      rover_pnt.gnss_week(), rover_pnt.gnss_second_s(),
      static_cast<int>(rover_pnt.pnt_type()), rover_pnt.pos_x_m(),
      rover_pnt.pos_y_m(), rover_pnt.pos_z_m(), rover_pnt.sovled_sat_num(),
      rover_pnt.pdop(), ratio, rover_pnt.vel_x_m(), rover_pnt.vel_y_m(),
      rover_pnt.vel_z_m());
  std::string part2 =
      FormatString("%8.3f%8.3f%8.3f", rover_pnt.std_pos_x_m(),
                    rover_pnt.std_pos_y_m(), rover_pnt.std_pos_z_m());
  printf("%s%s\n", part1.c_str(), part2.c_str());
}

inline void PrintEigenMatrix(const Eigen::MatrixXd& t, const char* t_name,
                               const bool print_long = false) {
  if (t_name == NULL) {
    return;
  }
  printf("debug matrix %s row=%d col=%d\n", t_name, static_cast<int>(t.rows()),
         static_cast<int>(t.cols()));
  for (unsigned int r = 0; r < t.rows(); ++r) {
    for (unsigned int c = 0; c < t.cols(); ++c) {
      if (print_long) {
        printf("%40.20f", t(r, c));
      } else {
        printf("%16.8f", t(r, c));
      }
    }
    printf("\n");
  }
}

// time conversion
inline bool IsLeapYear(const unsigned int year) {
  if (year % 4 != 0) {
    return false;
  }
  if (year % 400 == 0) {
    return true;
  }
  if (year % 100 == 0) {
    return false;
  }
  /*if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
    return true;
  }*/
  return true;
}

inline bool CheckDateForGnss(const int& year, const int& month,
                               const int& day) {
  if (year < 1981 || month < 1 || month > 12 || day < 1 || day > 31) {
    return false;
  }
  return true;
}

inline unsigned int GetDayOfYear(const int& year, const int& month,
                                const int& day) {
  const int dinmth[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  int dayofy = 0;
  if (!CheckDateForGnss(year, month, day)) {
    return 0;
  }
  if (month == 1) {
    return day;
  }
  dayofy = 0;
  for (int m = 1; m <= (month - 1); ++m) {
    dayofy += dinmth[m];
    if (m == 2 && IsLeapYear(year)) {
      dayofy += 1;
    }
  }
  dayofy += day;
  return dayofy;
}

inline void GpsTime2DayTime(int weekno, double gpstime, int* year,
                                 int* month, int* day, int* hour, int* minute,
                                 double* second) {
  int dayofweek = RoundDoubleToInt(gpstime / 86400);
  double secofday = gpstime - dayofweek * 86400;
  *hour = RoundDoubleToInt(secofday / 3600);
  double sminute = secofday - *hour * 3600;
  *minute = RoundDoubleToInt(sminute / 60);
  *second = sminute - *minute * 60;

  int daysum = weekno * 7 + dayofweek;
  // 44244 is gps-time origin corrected julian-day
  double cor_julian_day = 44244 + daysum;

  double abs_julian_day = 1 + RoundDoubleToInt(cor_julian_day) + 2400000;
  double part_cor_jul = cor_julian_day - RoundDoubleToInt(cor_julian_day);
  int ih = RoundDoubleToInt((abs_julian_day - 1867216.25) / 36524.25);
  double tt2 = abs_julian_day + 1 + ih - RoundDoubleToInt(ih / 4);
  double tt3 = tt2 - 1720995;
  int th1 = RoundDoubleToInt((tt3 - 122.1) / 365.25);
  abs_julian_day = 365.25 * th1 -
                  (365.25 * th1 - RoundDoubleToInt(365.25 * th1));
  int ih2 = RoundDoubleToInt((tt3 - abs_julian_day) / 30.6001);
  *day = RoundDoubleToInt((tt3 - abs_julian_day -
        RoundDoubleToInt(30.6001 * ih2)) + part_cor_jul);
  *month = ih2 - 1;
  if (ih2 > 13) {
    *month = ih2 - 13;
  }
  *year = th1;
  if (*month <= 2) {
    *year = *year + 1;
  }
}

inline int DayTime2GpsTime(int year, int month, int day, int hour,
                                int minute, double second, double* gpstime) {
  int dayofw = 0;
  int dayofy = 0;
  int weekno = 0;
  *gpstime = -0.1;
  if (!CheckDateForGnss(year, month, day)) {
    return weekno;
  }
  // Convert day, month and year to day of year
  dayofy = GetDayOfYear(year, month, day);
  // Convert day of year and year into week numearth_ber and day of week
  int ttlday = 360;
  for (int yr = 1981; yr <= (year - 1); yr++) {
    ttlday += 365;
    if (IsLeapYear(yr)) {
      ttlday += 1;
    }
  }
  ttlday += dayofy;
  weekno = ttlday / 7;
  dayofw = ttlday - 7 * weekno;
  *gpstime = (hour * 3600 + minute * 60 + second + dayofw * 86400);
  return weekno;
}

// coordinate conversion
inline void llh2xyz(const double longitude, const double latitude,
                    const double height, double r_xyz[3]) {
  // earth parameters of WGS-84
  double a = 6378137;
  double f = 0.00335281066474;
  double b = a - a * f;
  double easquare = (a + b) * (a - b) / (a * a);
  double n = a / sqrt(1 - easquare * sin(latitude) * sin(latitude));

  r_xyz[0] = (n + height) * cos(latitude) * cos(longitude);
  r_xyz[1] = (n + height) * cos(latitude) * sin(longitude);
  r_xyz[2] = (n * (1 - easquare) + height) * sin(latitude);
}

inline void llh2xyz(const double longitude, const double latitude,
                    const double height, PointThreeDim* user) {
  double r_xyz[3] = {0.0};
  llh2xyz(longitude, latitude, height, r_xyz);
  user->x = r_xyz[0];
  user->y = r_xyz[1];
  user->z = r_xyz[2];
}

inline void xyz2llh(double* rr, double* lat, double* lon, double* rh) {
  // PNT solving done under WGS-84 with listed below
  const double earth_ae = 6378137;
  // const double earth_be = 6356752.3142;
  const double earth_fe = 1.0 / 298.257223563;
  double earth_e2 = 2 * earth_fe - earth_fe * earth_fe;

  double x = rr[0];
  double y = rr[1];
  double z = rr[2];

  if (fabs(y) < 1.0E-12) {
    if (x > 0.0) {
      *lon = 0.0;
    } else {
      *lon = PI;
    }
  } else {
    *lon = atan2(y, x);
  }
  double s = sqrt(x * x + y * y) + 1e-10;
  double zs = z / s;
  *rh = sqrt(x * x + y * y + z * z) - earth_ae;
  *lat = atan(zs / (1 - earth_e2 * earth_ae / (earth_ae + *rh + 1e-12)));

  int cnum = 0;
  int b_true = 1;
  do {
    cnum++;
    double nn1 = 0;
    nn1 = earth_ae / sqrt(1 - earth_e2 * pow(sin(*lat), 2));
    double lat1 = *lat;
    *rh = s / cos(*lat) - nn1;
    *lat = atan(zs / (1 - earth_e2 * nn1 / (nn1 + *rh + 1e-12)));
    if ((fabs(*lat - lat1) < 1e-12) || (cnum > 5)) {
      break;
    }
  } while (b_true);

  if (fabs(*lon) < 1e-12) {
    *lon = *lon + 2 * PI;
  }
}

inline void xyz2blh(const PointThreeDim& user, double* lat, double* lon,
                    double* alt) {
  double r[3] = {user.x, user.y, user.z};
  xyz2llh(r, lat, lon, alt);
}

inline void dxyz2enu(const double dxyz[3], const double lat, const double lon,
                     double denu[3]) {
  denu[0] = -sin(lon) * dxyz[0] + cos(lon) * dxyz[1];
  denu[1] = -sin(lat) * cos(lon) * dxyz[0] - sin(lat) * sin(lon) * dxyz[1] +
            cos(lat) * dxyz[2];
  denu[2] = cos(lat) * cos(lon) * dxyz[0] + cos(lat) * sin(lon) * dxyz[1] +
            sin(lat) * dxyz[2];
  return;
}

inline void enu2xyz(const double denu[3], const double lat, const double lon,
                    double dxyz[3]) {
  double matrix_r[3][3] = {-sin(lon),
                           cos(lon),
                           0,
                           -sin(lat) * cos(lon),
                           -sin(lat) * sin(lon),
                           cos(lat),
                           cos(lat) * cos(lon),
                           cos(lat) * sin(lon),
                           sin(lat)};
  // xyz = m.transpose() * denu
  for (unsigned int i = 0; i < 3; ++i) {
    dxyz[i] = 0;
    for (unsigned int j = 0; j < 3; ++j) {
      dxyz[i] += matrix_r[j][i] * denu[j];
    }
  }
  return;
}

inline void enu2xyz(const double denu[3], const double lat, const double lon,
                    PointThreeDim* xyz) {
  double dxyz[3] = {0.0};
  //
  enu2xyz(denu, lat, lon, dxyz);
  xyz->x = dxyz[0];
  xyz->y = dxyz[1];
  xyz->z = dxyz[2];
  return;
}

inline void dxyz2enu(const PointThreeDim& user, const PointThreeDim& dxyz,
                     PointThreeDim* denu) {
  double dxyz0[3] = {dxyz.x, dxyz.y, dxyz.z};
  double lat = 0;
  double lon = 0;
  double rh = 0;
  xyz2blh(user, &lat, &lon, &rh);
  double denu0[3] = {0.0};
  dxyz2enu(dxyz0, lat, lon, denu0);
  denu->x = denu0[0];
  denu->y = denu0[1];
  denu->z = denu0[2];
}

inline double GetDistance(const PointThreeDim& src, const PointThreeDim& dest) {
  double dist = 0.0;
  dist += (src.x - dest.x) * (src.x - dest.x);
  dist += (src.y - dest.y) * (src.y - dest.y);
  dist += (src.z - dest.z) * (src.z - dest.z);
  return sqrt(dist);
}

inline double GetCosineDirection(const PointThreeDim& src,
                               const PointThreeDim& dest,
                               PointThreeDim* cosine) {
  double dist = GetDistance(src, dest);
  cosine->x = (src.x - dest.x) / dist;
  cosine->y = (src.y - dest.y) / dist;
  cosine->z = (src.z - dest.z) / dist;
  return dist;
}

inline void GetEleAzm(const PointThreeDim& user, const PointThreeDim& sat,
                    double* elv_deg, double* azm_deg) {
  double lat = 0;
  double lon = 0;
  double alt = 0;

  xyz2blh(user, &lat, &lon, &alt);
  double dx = sat.x - user.x;
  double dy = sat.y - user.y;
  double dz = sat.z - user.z;

  double xp =
      -sin(lat) * cos(lon) * dx - sin(lat) * sin(lon) * dy + cos(lat) * dz;
  double yp = -sin(lon) * dx + cos(lon) * dy;
  double zp =
      cos(lat) * cos(lon) * dx + cos(lat) * sin(lon) * dy + sin(lat) * dz;
  *elv_deg = atan(zp / sqrt(xp * xp + yp * yp)) / PI * 180;
  if (fabs(yp) < 1.0E-12) {
    if (xp > 0.0) {
      *azm_deg = 0.0;
    } else {
      *azm_deg = PI;
    }
  } else {
    *azm_deg = atan2(yp, xp);
  }
  *azm_deg = *azm_deg / PI * 180;
  return;
}

inline Eigen::MatrixXd DcmEcef2Navi(const PointThreeDim& pos) {
  // dcm from ecef XYZ to local navigation
  double lat = 0;
  double lon = 0;
  double rh = 0;
  gnss_utility::xyz2blh(pos, &lat, &lon, &rh);
  Eigen::Matrix3d matrix_r;
  matrix_r(0, 0) = -sin(lon);
  matrix_r(0, 1) = cos(lon);
  matrix_r(0, 2) = 0;
  matrix_r(1, 0) = -sin(lat) * cos(lon);
  matrix_r(1, 1) = -sin(lat) * sin(lon);
  matrix_r(1, 2) = cos(lat);
  matrix_r(2, 0) = cos(lat) * cos(lon);
  matrix_r(2, 1) = cos(lat) * sin(lon);
  matrix_r(2, 2) = sin(lat);
  return matrix_r;
}

inline Eigen::Matrix3d DcmBody2Navi(const double pitch, const double roll,
                               const double yaw) {
  // keep following codes for further non-Eigen environment
  // NOTICE: positive yaw being North by West (= anti-clockwise).
  double matrix_body2navi[3][3] = {0.0};
  matrix_body2navi[0][0] =
      cos(roll) * cos(yaw) - sin(pitch) * sin(yaw) * sin(roll);
  matrix_body2navi[0][1] = -cos(pitch) * sin(yaw);
  matrix_body2navi[0][2] =
      sin(roll) * cos(yaw) + cos(roll) * sin(pitch) * sin(yaw);
  matrix_body2navi[1][0] =
      cos(roll) * sin(yaw) + sin(roll) * sin(pitch) * cos(yaw);
  matrix_body2navi[1][1] = cos(pitch) * cos(yaw);
  matrix_body2navi[1][2] =
      sin(roll) * sin(yaw) - cos(roll) * sin(pitch) * cos(yaw);
  matrix_body2navi[2][0] = -sin(roll) * cos(pitch);
  matrix_body2navi[2][1] = sin(pitch);
  matrix_body2navi[2][2] = cos(roll) * cos(pitch);

  Eigen::Matrix3d temp;
  for (unsigned int i = 0; i < 3; ++i) {
    for (unsigned int j = 0; j < 3; ++j) {
      temp(i, j) = matrix_body2navi[i][j];
    }
  }
  return temp;
}

inline Eigen::MatrixXd RoundMatrix(const Eigen::MatrixXd& m1) {
  Eigen::MatrixXd m2 = m1;
  for (unsigned int r = 0; r < m2.rows(); ++r) {
    for (unsigned int c = 0; c < m2.cols(); ++c) {
      m2(r, c) = static_cast<int>(m2(r, c) + 0.5 * sign(m2(r, c)));
    }
  }
  return m2;
}

}  // namespace gnss_utility

}  // namespace local_gnss
}  // namespace localization
}  // namespace apollo

#endif


//==================================================================================================
/*!
    @file

    @Copyright 2016 Numscale SAS
    @copyright 2016 J.T.Lapreste

    Distributed under the Boost Software License, Version 1.0.
    (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_X86_SSE2_SIMD_FUNCTION_SUBS_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_SSE2_SIMD_FUNCTION_SUBS_HPP_INCLUDED
#include <boost/simd/detail/overload.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd =  boost::dispatch;
  BOOST_DISPATCH_OVERLOAD ( subs_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::uint8_<A0>, bs::sse_>
                          , bs::pack_<bd::uint8_<A0>, bs::sse_>
                         )
  {
    BOOST_FORCEINLINE A0 operator() ( const A0 & a0
                                    , const A0 & a1 ) const BOOST_NOEXCEPT
    {
      return _mm_subs_epu8(a0, a1);
    }
  };

  BOOST_DISPATCH_OVERLOAD ( subs_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::int8_<A0>, bs::sse_>
                          , bs::pack_<bd::int8_<A0>, bs::sse_>
                         )
  {
    BOOST_FORCEINLINE A0 operator() ( const A0 & a0
                                    , const A0 & a1 ) const BOOST_NOEXCEPT
    {
      return _mm_subs_epi8(a0, a1);
    }
  };
  BOOST_DISPATCH_OVERLOAD ( subs_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::uint16_<A0>, bs::sse_>
                          , bs::pack_<bd::uint16_<A0>, bs::sse_>
                         )
  {
    BOOST_FORCEINLINE A0 operator() ( const A0 & a0
                                    , const A0 & a1 ) const BOOST_NOEXCEPT
    {
      return _mm_subs_epu16(a0, a1);
    }
  };
  BOOST_DISPATCH_OVERLOAD ( subs_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::int16_<A0>, bs::sse_>
                          , bs::pack_<bd::int16_<A0>, bs::sse_>
                         )
  {
    BOOST_FORCEINLINE A0 operator() ( const A0 & a0
                                    , const A0 & a1 ) const BOOST_NOEXCEPT
    {
      return _mm_subs_epi16(a0, a1);
    }
  };

} } }

#endif


#include <stdio.h>
#include <filesystem>

#include <deal.II/fe/fe_q.h>

#include "framework/builder/framework_builder.hpp"
#include "framework/framework_parameters.hpp"

// Instantiated concerete classes
#include "calculator/drift_diffusion/drift_diffusion_vector_calculator.hpp"
#include "convergence/parameters/single_parameter_checker.hpp"
#include "convergence/iteration_completion_checker.hpp"
#include "data/cross_sections/material_cross_sections.hpp"
#include "domain/finite_element/finite_element_gaussian.hpp"
#include "domain/domain.hpp"
#include "eigenvalue/k_eigenvalue/calculator_via_fission_source.hpp"
#include "eigenvalue/k_eigenvalue/calculator_via_rayleigh_quotient.hpp"
#include "formulation/scalar/diffusion.hpp"
#include "formulation/scalar/drift_diffusion.hpp"
#include "formulation/angular/self_adjoint_angular_flux.h"
#include "formulation/updater/saaf_updater.h"
#include "formulation/updater/diffusion_updater.hpp"
#include "formulation/updater/drift_diffusion_updater.hpp"
#include "formulation/stamper.hpp"
#include "instrumentation/instrument.h"
#include "instrumentation/basic_instrument.h"
#include "iteration/outer/outer_power_iteration.hpp"
#include "iteration/outer/outer_fixed_source_iteration.hpp"
#include "quadrature/calculators/scalar_moment.h"
#include "quadrature/calculators/angular_flux_integrator.hpp"
#include "quadrature/calculators/spherical_harmonic_zeroth_moment.h"
#include "quadrature/quadrature_set.hpp"
#include "solver/linear/gmres.h"
#include "solver/group/single_group_solver.h"
#include "system/solution/mpi_group_angular_solution.h"
#include "iteration/initializer/initialize_fixed_terms_once.h"
#include "iteration/initializer/initialize_fixed_terms_reset_moments.hpp"
#include "iteration/group/group_source_iteration.hpp"
#include "iteration/subroutine/get_scalar_flux_from_framework.hpp"
#include "system/system_types.h"
#include "system/solution/solution_types.h"
#include "system/system_helper.hpp"

// Mock objects
#include "convergence/tests/convergence_checker_mock.hpp"
#include "convergence/tests/iteration_completion_checker_mock.hpp"
#include "domain/tests/domain_mock.hpp"
#include "domain/finite_element/tests/finite_element_mock.hpp"
#include "eigenvalue/k_eigenvalue/tests/k_eigenvalue_calculator_mock.hpp"
#include "formulation/angular/tests/self_adjoint_angular_flux_mock.h"
#include "formulation/scalar/tests/diffusion_mock.hpp"
#include "formulation/scalar/tests/drift_diffusion_mock.hpp"
#include "formulation/tests/stamper_mock.hpp"
#include "formulation/updater/tests/boundary_conditions_updater_mock.h"
#include "formulation/updater/tests/scattering_source_updater_mock.h"
#include "formulation/updater/tests/fission_source_updater_mock.h"
#include "formulation/updater/tests/fixed_updater_mock.h"
#include "framework/builder/tests/framework_validator_mock.hpp"
#include "framework/tests/framework_mock.hpp"
#include "iteration/group/tests/group_solve_iteration_mock.hpp"
#include "data/material/tests/material_mock.hpp"
#include "problem/tests/parameters_mock.hpp"
#include "formulation/updater/tests/fixed_updater_mock.h"
#include "quadrature/calculators/tests/angular_flux_integrator_mock.hpp"
#include "quadrature/tests/quadrature_set_mock.hpp"
#include "quadrature/calculators/tests/spherical_harmonic_moments_mock.h"
#include "solver/group/tests/single_group_solver_mock.h"
#include "system/solution/tests/mpi_group_angular_solution_mock.h"
#include "system/moments/tests/spherical_harmonic_mock.h"

#include "test_helpers/gmock_wrapper.h"
#include "test_helpers/test_helper_functions.h"

namespace {

using namespace bart;

using ::testing::Return, ::testing::NiceMock, ::testing::DoDefault;
using ::testing::WhenDynamicCastTo, ::testing::NotNull;
using ::testing::HasSubstr, ::testing::_;
using ::testing::ReturnRef, ::testing::A;

using ::testing::AtLeast;

using Part = framework::builder::FrameworkPart;

template <typename DimensionWrapper>
class FrameworkBuilderIntegrationTest : public ::testing::Test {
 public:
  static constexpr int dim = DimensionWrapper::value;

  using FrameworkBuilder = framework::builder::FrameworkBuilder<dim>;
  using ProblemParameters = NiceMock<problem::ParametersMock>;
  using Material = NiceMock<data::material::MaterialMock>;

  // Mock object types
  using AngularFluxIntegrator = quadrature::calculators::AngularFluxIntegratorMock;
  using BoundaryConditionsUpdaterType = formulation::updater::BoundaryConditionsUpdaterMock;
  using DiffusionFormulationType = formulation::scalar::DiffusionMock<dim>;
  using DriftDiffusionFormulation = formulation::scalar::DriftDiffusionMock<dim>;
  using DomainType = domain::DomainMock<dim>;
  using FiniteElementType = domain::finite_element::FiniteElementMock<dim>;
  using FissionSourceUpdaterType = formulation::updater::FissionSourceUpdaterMock;
  using FrameworkMock = framework::FrameworkMock;
  using GroupSolutionType = system::solution::MPIGroupAngularSolutionMock;
  using GroupSolveIterationType = iteration::group::GroupSolveIterationMock;
  using KEffectiveUpdaterType = eigenvalue::k_eigenvalue::K_EigenvalueCalculatorMock;
  using MomentCalculatorType = quadrature::calculators::SphericalHarmonicMomentsMock;
  using MomentConvergenceCheckerType = convergence::IterationCompletionCheckerMock<bart::system::moments::MomentVector>;
  using ParameterConvergenceCheckerType = convergence::IterationCompletionCheckerMock<double>;
  using QuadratureSetType = quadrature::QuadratureSetMock<dim>;
  using SAAFFormulationType = formulation::angular::SelfAdjointAngularFluxMock<dim>;
  using ScatteringSourceUpdaterType = formulation::updater::ScatteringSourceUpdaterMock;
  using SphericalHarmonicMoments = system::moments::SphericalHarmonicMock;
  using SingleGroupSolverType = solver::group::SingleGroupSolverMock;
  using StamperType = formulation::StamperMock<dim>;
  using Validator = NiceMock<framework::builder::FrameworkValidatorMock>;

  FrameworkBuilderIntegrationTest()
      : mock_material() {}

  std::unique_ptr<FrameworkBuilder> test_builder_ptr_;
  ProblemParameters parameters;
  Material mock_material;
  system::SystemHelper<dim> system_helper_;

  // Various mock objects to be used
  std::shared_ptr<AngularFluxIntegrator> angular_flux_integrator_sptr_ { nullptr };
  std::shared_ptr<BoundaryConditionsUpdaterType> boundary_conditions_updater_sptr_;
  std::shared_ptr<data::cross_sections::MaterialCrossSections> cross_sections_sptr_;
  std::unique_ptr<DiffusionFormulationType> diffusion_formulation_uptr_;
  std::unique_ptr<DriftDiffusionFormulation> drift_diffusion_formulation_uptr_;
  std::shared_ptr<DomainType> domain_sptr_;
  std::shared_ptr<FiniteElementType> finite_element_sptr_;
  std::shared_ptr<FissionSourceUpdaterType> fission_source_updater_sptr_;
  std::unique_ptr<FrameworkMock> framework_uptr_{ nullptr };
  std::shared_ptr<GroupSolutionType> group_solution_sptr_;
  std::unique_ptr<GroupSolveIterationType> group_solve_iteration_uptr_;
  std::unique_ptr<KEffectiveUpdaterType> k_effective_updater_uptr_;
  std::unique_ptr<MomentCalculatorType> moment_calculator_uptr_;
  std::unique_ptr<MomentConvergenceCheckerType> moment_convergence_checker_uptr_;
  std::unique_ptr<ParameterConvergenceCheckerType> parameter_convergence_checker_uptr_;
  std::shared_ptr<QuadratureSetType> quadrature_set_sptr_;
  std::unique_ptr<SAAFFormulationType> saaf_formulation_uptr_;
  std::shared_ptr<ScatteringSourceUpdaterType> scattering_source_updater_sptr_;
  std::shared_ptr<SphericalHarmonicMoments> spherical_harmonics_sptr_;
  std::unique_ptr<SingleGroupSolverType> single_group_solver_uptr_;
  std::unique_ptr<StamperType> stamper_uptr_;

  Validator* validator_obs_ptr_{ nullptr };

  // Test Parameters
  const int polynomial_degree = 2;
  std::vector<double> spatial_max;
  std::vector<int> n_cells;
  const int n_energy_groups = 3;
  const int n_angles = 2;
  std::array<int, 4> dofs_per_cell_by_dim_{1, 3, 9, 27};
  std::map<problem::Boundary, bool> reflective_bcs_;
  static int files_in_working_directory_;

  static void SetUpTestSuite() {
    for (const auto & entry : std::filesystem::directory_iterator(".")) {
      std::ignore = entry;
      ++files_in_working_directory_;
    }
  }
  static void TearDownTestSuite() {
    files_in_working_directory_ = 0;
  }
  void SetUp() override;
  void TearDown() override;
};

template <typename DimensionWrapper>
int FrameworkBuilderIntegrationTest<DimensionWrapper>::files_in_working_directory_ = 0;

template <typename DimensionWrapper>
void FrameworkBuilderIntegrationTest<DimensionWrapper>::SetUp() {
  angular_flux_integrator_sptr_ = std::make_shared<AngularFluxIntegrator>();
  boundary_conditions_updater_sptr_ = std::make_shared<BoundaryConditionsUpdaterType>();
  cross_sections_sptr_ = std::make_shared<data::cross_sections::MaterialCrossSections>(mock_material);
  diffusion_formulation_uptr_ = std::move(std::make_unique<DiffusionFormulationType>());
  drift_diffusion_formulation_uptr_ = std::move(std::make_unique<DriftDiffusionFormulation>());
  domain_sptr_ = std::make_shared<DomainType>();
  finite_element_sptr_ = std::make_shared<FiniteElementType>();
  fission_source_updater_sptr_ = std::make_shared<FissionSourceUpdaterType>();
  framework_uptr_ = std::make_unique<FrameworkMock>();
  group_solution_sptr_ = std::make_shared<GroupSolutionType>();
  group_solve_iteration_uptr_  = std::move(std::make_unique<GroupSolveIterationType>());
  k_effective_updater_uptr_ = std::move(std::make_unique<KEffectiveUpdaterType>());
  moment_calculator_uptr_ = std::move(std::make_unique<MomentCalculatorType>());
  moment_convergence_checker_uptr_ = std::move(std::make_unique<MomentConvergenceCheckerType>());
  parameter_convergence_checker_uptr_ = std::move(std::make_unique<ParameterConvergenceCheckerType>());
  quadrature_set_sptr_ = std::make_shared<QuadratureSetType>();
  saaf_formulation_uptr_ = std::move(std::make_unique<SAAFFormulationType>());
  scattering_source_updater_sptr_ = std::make_shared<ScatteringSourceUpdaterType>();
  spherical_harmonics_sptr_ = std::make_shared<SphericalHarmonicMoments>();
  stamper_uptr_ = std::move(std::make_unique<StamperType>());
  single_group_solver_uptr_ = std::move(std::make_unique<SingleGroupSolverType>());
  auto validator_ptr = std::make_unique<Validator>();
  validator_obs_ptr_ = validator_ptr.get();

  ON_CALL(*validator_ptr, AddPart(_)).WillByDefault(ReturnRef(*validator_ptr));

  test_builder_ptr_ = std::move(std::make_unique<FrameworkBuilder>(std::move(validator_ptr)));

  for (int i = 0; i < this->dim; ++i) {
    spatial_max.push_back(10);
    n_cells.push_back(2);
  }

  reflective_bcs_ = {
      {problem::Boundary::kXMin, true},
      {problem::Boundary::kXMax, true},
      {problem::Boundary::kYMin, false},
      {problem::Boundary::kYMax, false},
      {problem::Boundary::kZMin, false},
      {problem::Boundary::kZMax, false},
  };

  ON_CALL(parameters, NEnergyGroups())
      .WillByDefault(Return(n_energy_groups));
  ON_CALL(parameters, NCells())
      .WillByDefault(Return(n_cells));
  ON_CALL(parameters, SpatialMax())
      .WillByDefault(Return(spatial_max));
  ON_CALL(parameters, FEPolynomialDegree())
      .WillByDefault(Return(polynomial_degree));
  ON_CALL(parameters, TransportModel())
      .WillByDefault(Return(problem::EquationType::kDiffusion));
  ON_CALL(parameters, ReflectiveBoundary())
      .WillByDefault(Return(reflective_bcs_));
}

template <typename DimensionWrapper>
void FrameworkBuilderIntegrationTest<DimensionWrapper>::TearDown() {
  int files_in_working_directory_after{0};
  for (const auto & entry : std::filesystem::directory_iterator(".")) {
    std::ignore = entry;
    ++files_in_working_directory_after;
  }
  EXPECT_EQ(files_in_working_directory_after,
            files_in_working_directory_)
            << "Test changed number of files in working directory from "
            << files_in_working_directory_ << " to "
            << files_in_working_directory_after << std::endl;
}

TYPED_TEST_CASE(FrameworkBuilderIntegrationTest, bart::testing::AllDimensions);

// =====================================================================================================================

TYPED_TEST(FrameworkBuilderIntegrationTest, Constructor) {
  ASSERT_NE(this->test_builder_ptr_->validator_ptr(), nullptr);
}

// =============================================================================

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildAngularFluxIntegratorTest) {
  constexpr int dim = this->dim;
  auto angular_flux_integrator_ptr = this->test_builder_ptr_->BuildAngularFluxIntegrator(this->quadrature_set_sptr_);

  using ExpectedType = typename quadrature::calculators::AngularFluxIntegrator<dim>;
  auto dynamic_ptr = dynamic_cast<ExpectedType*>(angular_flux_integrator_ptr.get());
  ASSERT_NE(nullptr, dynamic_ptr);
  EXPECT_EQ(dynamic_ptr->quadrature_set_ptr(), this->quadrature_set_sptr_.get());
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildDiffusionFormulationTest) {
  constexpr int dim = this->dim;

  auto finite_element_ptr =
      std::make_shared<domain::finite_element::FiniteElementMock<dim>>();
  auto cross_sections_ptr =
      std::make_shared<data::cross_sections::MaterialCrossSections>(this->mock_material);

  EXPECT_CALL(*finite_element_ptr, dofs_per_cell());
  EXPECT_CALL(*finite_element_ptr, n_cell_quad_pts());
  EXPECT_CALL(*finite_element_ptr, n_face_quad_pts());

  auto diffusion_formulation_ptr = this->test_builder_ptr_->BuildDiffusionFormulation(
      finite_element_ptr, cross_sections_ptr);

  using ExpectedType = formulation::scalar::Diffusion<dim>;
  EXPECT_THAT(diffusion_formulation_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildDriftDiffusionFormulationTest) {
  constexpr int dim = this->dim;

  EXPECT_CALL(*this->finite_element_sptr_, dofs_per_cell());
  EXPECT_CALL(*this->finite_element_sptr_, n_cell_quad_pts());
  EXPECT_CALL(*this->finite_element_sptr_, n_face_quad_pts());

  auto drift_diffusion_formulation_ptr = this->test_builder_ptr_->BuildDriftDiffusionFormulation(
      this->angular_flux_integrator_sptr_,
      this->finite_element_sptr_,
      this->cross_sections_sptr_);

  using Formulation = formulation::scalar::DriftDiffusion<dim>;
  using DriftDiffusionCalculator = calculator::drift_diffusion::DriftDiffusionVectorCalculator<dim>;

  auto dynamic_formulation_ptr = dynamic_cast<Formulation*>(drift_diffusion_formulation_ptr.get());
  ASSERT_NE(dynamic_formulation_ptr, nullptr);
  EXPECT_EQ(dynamic_formulation_ptr->angular_flux_integrator_ptr(), this->angular_flux_integrator_sptr_.get());
  EXPECT_THAT(dynamic_formulation_ptr->drift_diffusion_calculator_ptr(),
              WhenDynamicCastTo<DriftDiffusionCalculator*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildDiffusionUpdaterPointers) {
  constexpr int dim = this->dim;
  using ExpectedType = formulation::updater::DiffusionUpdater<dim>;
  std::map<problem::Boundary, bool> reflective_bcs{
      {problem::Boundary::kXMin, false},
      {problem::Boundary::kXMax, false},
      {problem::Boundary::kYMin, false},
      {problem::Boundary::kYMax, false},
      {problem::Boundary::kZMin, false},
      {problem::Boundary::kZMax, false},
  };
  auto updater_struct = this->test_builder_ptr_->BuildUpdaterPointers(
      std::move(this->diffusion_formulation_uptr_),
      std::move(this->stamper_uptr_),
      reflective_bcs);
  EXPECT_THAT(updater_struct.fixed_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_THAT(updater_struct.scattering_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_THAT(updater_struct.fission_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildDriftDiffusionUpdaterPointers) {
  constexpr int dim = this->dim;
  using ExpectedType = typename formulation::updater::DriftDiffusionUpdater<dim>;
  system::solution::EnergyGroupToAngularSolutionPtrMap angular_flux_storage;
  this->system_helper_.SetUpEnergyGroupToAngularSolutionPtrMap(angular_flux_storage,
                                                               this->n_energy_groups,
                                                               this->n_angles);

  auto updater_struct = this->test_builder_ptr_->BuildUpdaterPointers(
      std::move(this->diffusion_formulation_uptr_),
      std::move(this->drift_diffusion_formulation_uptr_),
      std::move(this->stamper_uptr_),
      this->angular_flux_integrator_sptr_,
      this->spherical_harmonics_sptr_,
      angular_flux_storage,
      this->reflective_bcs_);
  ASSERT_THAT(updater_struct.fixed_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  ASSERT_THAT(updater_struct.scattering_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  ASSERT_THAT(updater_struct.fission_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  auto dynamic_ptr = dynamic_cast<ExpectedType*>(updater_struct.fixed_updater_ptr.get());
  ASSERT_NE(dynamic_ptr, nullptr);
  EXPECT_NE(dynamic_ptr->drift_diffusion_formulation_ptr(), nullptr);
  EXPECT_NE(dynamic_ptr->formulation_ptr(), nullptr);
  EXPECT_EQ(dynamic_ptr->integrated_flux_calculator_ptr(), this->angular_flux_integrator_sptr_.get());
  EXPECT_EQ(dynamic_ptr->high_order_moments(), this->spherical_harmonics_sptr_.get());
  EXPECT_EQ(angular_flux_storage.size(), dynamic_ptr->angular_flux_storage_map().size());
  for (auto& [boundary, is_reflective] : this->reflective_bcs_ ) {
    if (is_reflective) {
      EXPECT_EQ(dynamic_ptr->reflective_boundaries().count(boundary), 1);
    }
  }
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildDiffusionUpdaterPointersRefl) {
  constexpr int dim = this->dim;
  using ExpectedType = formulation::updater::DiffusionUpdater<dim>;

  auto updater_struct = this->test_builder_ptr_->BuildUpdaterPointers(
      std::move(this->diffusion_formulation_uptr_),
      std::move(this->stamper_uptr_),
      this->reflective_bcs_);
  ASSERT_THAT(updater_struct.fixed_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_THAT(updater_struct.scattering_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_THAT(updater_struct.fission_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));

  auto dynamic_ptr =
      dynamic_cast<ExpectedType*>(updater_struct.fixed_updater_ptr.get());
  for (auto& [boundary, is_reflective] : this->reflective_bcs_ ) {
    if (is_reflective) {
      EXPECT_EQ(dynamic_ptr->reflective_boundaries().count(boundary), 1);
    }
  }
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildSAAFUpdaterPointers) {
  constexpr int dim = this->dim;
  using ExpectedType = formulation::updater::SAAFUpdater<dim>;
  auto updater_struct = this->test_builder_ptr_->BuildUpdaterPointers(
      std::move(this->saaf_formulation_uptr_),
      std::move(this->stamper_uptr_),
      this->quadrature_set_sptr_);
  EXPECT_THAT(updater_struct.fixed_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_THAT(updater_struct.scattering_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_THAT(updater_struct.fission_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest,
    BuildSAAFUpdaterPointersWithReflectiveBCs) {
  using ExpectedType = formulation::updater::SAAFUpdater<this->dim>;
  system::solution::EnergyGroupToAngularSolutionPtrMap angular_flux_storage;

  this->system_helper_.SetUpEnergyGroupToAngularSolutionPtrMap(
      angular_flux_storage, this->n_energy_groups, this->n_angles);

  auto updater_struct = this->test_builder_ptr_->BuildUpdaterPointers(
      std::move(this->saaf_formulation_uptr_),
      std::move(this->stamper_uptr_),
      this->quadrature_set_sptr_,
      this->reflective_bcs_,
      angular_flux_storage);
  EXPECT_THAT(updater_struct.fixed_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_THAT(updater_struct.scattering_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_THAT(updater_struct.fission_source_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_THAT(updater_struct.boundary_conditions_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildDomainParametersTest) {
  constexpr int dim = this->dim;
  using Parameters = framework::FrameworkParameters;
  auto finite_element_ptr = std::make_shared<NiceMock<domain::finite_element::FiniteElementMock<dim>>>();

  auto test_domain_ptr = this->test_builder_ptr_->BuildDomain(Parameters::DomainSize(this->spatial_max),
                                                              Parameters::NumberOfCells(this->n_cells),
                                                              finite_element_ptr,
                                                              "1 1 2 2");

  using ExpectedType = domain::Domain<this->dim>;

  ASSERT_THAT(test_domain_ptr.get(), WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildDomainNullFiniteElementPtr) {
  using Parameters = framework::FrameworkParameters;
  EXPECT_ANY_THROW({
  auto test_domain_ptr = this->test_builder_ptr_->BuildDomain(Parameters::DomainSize(this->spatial_max),
                                                              Parameters::NumberOfCells(this->n_cells),
                                                              nullptr,
                                                              "1 1 2 2");
                   });
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildDomainBadDomainSize) {
  constexpr int dim = this->dim;
  using Parameters = framework::FrameworkParameters;
  auto finite_element_ptr = std::make_shared<NiceMock<domain::finite_element::FiniteElementMock<dim>>>();

  const auto bad_index{ bart::test_helpers::RandomInt(0, this->dim) };
  auto bad_spatial_max { this->spatial_max };
  bad_spatial_max.at(bad_index) = 0;

  EXPECT_ANY_THROW({
    auto test_domain_ptr = this->test_builder_ptr_->BuildDomain(Parameters::DomainSize(bad_spatial_max),
                                                                Parameters::NumberOfCells(this->n_cells),
                                                                finite_element_ptr,
                                                                "1 1 2 2");
                   });
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildDomainBadNCells) {
  constexpr int dim = this->dim;
  using Parameters = framework::FrameworkParameters;
  auto finite_element_ptr = std::make_shared<NiceMock<domain::finite_element::FiniteElementMock<dim>>>();

  const auto bad_index{ bart::test_helpers::RandomInt(0, this->dim) };
  auto bad_n_cells { this->n_cells };
  bad_n_cells.at(bad_index) = 0;

  EXPECT_ANY_THROW({
                     auto test_domain_ptr = this->test_builder_ptr_->BuildDomain(Parameters::DomainSize(this->spatial_max),
                                                                                Parameters::NumberOfCells(bad_n_cells),
                                                                                finite_element_ptr,
                                                                                "1 1 2 2");
                   });
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildGroupSourceIterationTest) {
  using ExpectedType = iteration::group::GroupSourceIteration<this->dim>;
  using UpdaterPointersStruct = typename framework::builder::FrameworkBuilder<this->dim>::UpdaterPointers;

  UpdaterPointersStruct updater_ptrs;
  updater_ptrs.scattering_source_updater_ptr = this->scattering_source_updater_sptr_;

  EXPECT_CALL(*this->validator_obs_ptr_, AddPart(Part::ScatteringSourceUpdate)).WillOnce(DoDefault());

  auto source_iteration_ptr = this->test_builder_ptr_->BuildGroupSolveIteration(
      std::move(this->single_group_solver_uptr_),
      std::move(this->moment_convergence_checker_uptr_),
      std::move(this->moment_calculator_uptr_),
      this->group_solution_sptr_,
      updater_ptrs,
      nullptr);
  EXPECT_THAT(source_iteration_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildGroupSourceIterationWithBCUpdateTest) {
  using ExpectedType = iteration::group::GroupSourceIteration<this->dim>;
  using UpdaterPointersStruct = typename framework::builder::FrameworkBuilder<this->dim>::UpdaterPointers;

  UpdaterPointersStruct updater_ptrs;
  updater_ptrs.scattering_source_updater_ptr = this->scattering_source_updater_sptr_;
  updater_ptrs.boundary_conditions_updater_ptr = this->boundary_conditions_updater_sptr_;

  EXPECT_CALL(*this->validator_obs_ptr_, AddPart(Part::ScatteringSourceUpdate)).WillOnce(DoDefault());

  auto source_iteration_ptr = this->test_builder_ptr_->BuildGroupSolveIteration(
      std::move(this->single_group_solver_uptr_),
      std::move(this->moment_convergence_checker_uptr_),
      std::move(this->moment_calculator_uptr_),
      this->group_solution_sptr_,
      updater_ptrs,
      nullptr);
  EXPECT_THAT(source_iteration_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildGroupSolution) {
  using ExpectedType = system::solution::MPIGroupAngularSolution;
  const int n_angles = bart::test_helpers::RandomDouble(1, 10);
  auto group_solution_ptr = this->test_builder_ptr_->BuildGroupSolution(n_angles);

  ASSERT_THAT(group_solution_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_EQ(n_angles, group_solution_ptr->total_angles());
}

// BuildFiniteElement should return correct object when given good parameters
TYPED_TEST(FrameworkBuilderIntegrationTest, BuildFiniteElementFrameworkParameters) {
  constexpr int dim = this->dim;
  using PolynomialDegree = framework::FrameworkParameters::PolynomialDegree;
  using ExpectedType = domain::finite_element::FiniteElementGaussian<dim>;

  auto finite_element_ptr = this->test_builder_ptr_->BuildFiniteElement(problem::CellFiniteElementType::kGaussian,
                                                                        problem::DiscretizationType::kContinuousFEM,
                                                                        PolynomialDegree(this->polynomial_degree));
  ASSERT_NE(finite_element_ptr, nullptr);
  auto gaussian_ptr = dynamic_cast<ExpectedType*>(finite_element_ptr.get());
  ASSERT_NE(gaussian_ptr, nullptr);
  EXPECT_EQ(finite_element_ptr->polynomial_degree(), this->polynomial_degree);
  auto dealii_finite_element_ptr = dynamic_cast<dealii::FE_Q<dim>*>(finite_element_ptr->finite_element());
  ASSERT_NE(dealii_finite_element_ptr, nullptr);
}

// BuildFiniteElement should throw if bad parameters are passed
TYPED_TEST(FrameworkBuilderIntegrationTest, BuildFiniteElementFrameworkParametersBadPolynomialDegree) {
  constexpr int dim = this->dim;
  using PolynomialDegree = framework::FrameworkParameters::PolynomialDegree;
  using ExpectedType = domain::finite_element::FiniteElementGaussian<dim>;
  auto bad_polynomial_degrees = bart::test_helpers::RandomVector(5, -10, -1);
  bad_polynomial_degrees.push_back(0);
  for (int bad_polynomial_degree : bad_polynomial_degrees) {
    EXPECT_ANY_THROW({
      auto finite_element_ptr = this->test_builder_ptr_->BuildFiniteElement(problem::CellFiniteElementType::kGaussian,
                                                                            problem::DiscretizationType::kContinuousFEM,
                                                                            PolynomialDegree(bad_polynomial_degree));
    });
  }
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildKeffectiveUpdater) {
  using ExpectedType = eigenvalue::k_eigenvalue::CalculatorViaFissionSource;
  EXPECT_CALL(*this->finite_element_sptr_, n_cell_quad_pts())
      .WillOnce(Return(10));
  auto k_effective_updater_ptr = this->test_builder_ptr_->BuildKEffectiveUpdater(
      this->finite_element_sptr_,
      this->cross_sections_sptr_,
      this->domain_sptr_);
  EXPECT_THAT(k_effective_updater_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildKeffectiveUpdaterRayleighQuotient) {
  using ExpectedType = eigenvalue::k_eigenvalue::CalculatorViaRayleighQuotient;

  auto k_effective_updater_ptr = this->test_builder_ptr_->BuildKEffectiveUpdater();
  EXPECT_THAT(k_effective_updater_ptr.get(), WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildMomentCalculatorScalar) {
  using ExpectedType = quadrature::calculators::ScalarMoment;

  auto moment_calculator_ptr = this->test_builder_ptr_->BuildMomentCalculator();
  ASSERT_THAT(moment_calculator_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildMomentCalculatorScalarQuadSet) {
  using ExpectedType = quadrature::calculators::ScalarMoment;
  using Implementation = quadrature::MomentCalculatorImpl;
  auto moment_calculator_ptr = this->test_builder_ptr_->BuildMomentCalculator(
      this->quadrature_set_sptr_, Implementation::kScalarMoment);
  ASSERT_THAT(moment_calculator_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BulidMomentCalculatorAngular) {
  constexpr int dim = this->dim;
  using ExpectedType = quadrature::calculators::SphericalHarmonicZerothMoment<dim>;

  auto moment_calculator_ptr = this->test_builder_ptr_->BuildMomentCalculator(
      this->quadrature_set_sptr_);
  ASSERT_THAT(moment_calculator_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildPowerIterationTest) {
  EXPECT_CALL(*this->validator_obs_ptr_, AddPart(Part::FissionSourceUpdate)).WillOnce(DoDefault());

  auto power_iteration_ptr = this->test_builder_ptr_->BuildOuterIteration(
      std::move(this->group_solve_iteration_uptr_),
      std::move(this->parameter_convergence_checker_uptr_),
      std::move(this->k_effective_updater_uptr_),
      this->fission_source_updater_sptr_,
      "test");
  using ExpectedType = iteration::outer::OuterPowerIteration;
  ASSERT_THAT(power_iteration_ptr.get(),
                  WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_EQ(remove("test_iteration_error.csv"), 0);
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildFixedSourceIterationTest) {
  auto power_iteration_ptr = this->test_builder_ptr_->BuildOuterIteration(
      std::move(this->group_solve_iteration_uptr_),
      std::move(this->parameter_convergence_checker_uptr_), "");
  using ExpectedType = iteration::outer::OuterFixedSourceIteration;
  ASSERT_THAT(power_iteration_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildQuadratureSetBadOrder) {
  using Order = framework::FrameworkParameters::AngularQuadratureOrder;
  auto bad_orders{ test_helpers::RandomVector(5, -10, -1) };
  bad_orders.push_back(0);
  for (const auto bad_order : bad_orders) {
    for (const auto quadrature_type : {problem::AngularQuadType::kLevelSymmetricGaussian,
                                       problem::AngularQuadType::kGaussLegendre}) {
      EXPECT_ANY_THROW({
        auto quadrature_set = this->test_builder_ptr_->BuildQuadratureSet(quadrature_type, Order(bad_order));
      });
    }
  }
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildGaussLegendreQuadratureSetWithParameters) {
  constexpr int dim = this->dim;
  const framework::FrameworkParameters::AngularQuadratureOrder order{ 4 };
  const auto framework_type { problem::AngularQuadType::kGaussLegendre };

  if (dim == 1) {
    using ExpectedType = quadrature::QuadratureSet<dim>;
    auto quadrature_set = this->test_builder_ptr_->BuildQuadratureSet(framework_type, order);
    ASSERT_NE(nullptr, quadrature_set);
    ASSERT_NE(nullptr, dynamic_cast<ExpectedType*>(quadrature_set.get()));
    EXPECT_EQ(quadrature_set->size(), 2*order.get());
  } else {
    EXPECT_ANY_THROW({
      auto quadrature_set = this->test_builder_ptr_->BuildQuadratureSet(framework_type, order);
                     });
  }
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildLSAngularQuadratureSetWithParameters) {
  constexpr int dim = this->dim;
  const framework::FrameworkParameters::AngularQuadratureOrder order{ 4 };
  const auto framework_type { problem::AngularQuadType::kLevelSymmetricGaussian };

  if (dim == 3) {
    using ExpectedType = quadrature::QuadratureSet<dim>;
    auto quadrature_set = this->test_builder_ptr_->BuildQuadratureSet(framework_type, order);
    ASSERT_NE(nullptr, quadrature_set);
    ASSERT_NE(nullptr, dynamic_cast<ExpectedType*>(quadrature_set.get()));
    EXPECT_EQ(quadrature_set->size(), order.get() * (order.get() + 2));
  } else {
    EXPECT_ANY_THROW({
      auto quadrature_set = this->test_builder_ptr_->BuildQuadratureSet(framework_type, order);
                     });
  }
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildkNoneTypeQuadratureSetWithParameters) {
  const framework::FrameworkParameters::AngularQuadratureOrder order{ 4 };
  const auto framework_type { problem::AngularQuadType::kNone };

  EXPECT_ANY_THROW({
    auto quadrature_set = this->test_builder_ptr_->BuildQuadratureSet(framework_type, order);
                   });
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildSingleGroupSolver) {
  using ExpectedType = solver::group::SingleGroupSolver;

  auto solver_ptr = this->test_builder_ptr_->BuildSingleGroupSolver(100, 1e-12);

  ASSERT_NE(nullptr, solver_ptr);

  auto dynamic_ptr = dynamic_cast<ExpectedType*>(solver_ptr.get());
  ASSERT_NE(nullptr, dynamic_ptr);

  using ExpectedLinearSolverType = solver::linear::GMRES;

  auto linear_solver_ptr = dynamic_cast<ExpectedLinearSolverType*>(
      dynamic_ptr->linear_solver_ptr());

  ASSERT_NE(nullptr, linear_solver_ptr);
  EXPECT_EQ(linear_solver_ptr->convergence_tolerance(), 1e-12);
  EXPECT_EQ(linear_solver_ptr->max_iterations(), 100);
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildConvergenceChecker) {
  const double max_delta = 1e-4;
  const int max_iterations = 100;

  auto convergence_ptr =
      this->test_builder_ptr_->BuildParameterConvergenceChecker(
          max_delta,
          max_iterations);

  using ParameterConvergenceChecker = convergence::IterationCompletionChecker<double>;

  ASSERT_NE(convergence_ptr, nullptr);
  EXPECT_NE(nullptr, dynamic_cast<ParameterConvergenceChecker*>(convergence_ptr.get()));
  EXPECT_EQ(convergence_ptr->max_iterations(), max_iterations);

}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildMomentConvergenceChecker) {
  const double max_delta = 1e-4;
  const int max_iterations = 73;

  auto convergence_ptr =
      this->test_builder_ptr_->BuildMomentConvergenceChecker(
          max_delta,
          max_iterations);

  using ExpectedType = convergence::IterationCompletionChecker<system::moments::MomentVector>;

  EXPECT_THAT(convergence_ptr.get(), WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_EQ(convergence_ptr->max_iterations(), max_iterations);

}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildMomentMapConvergenceChecker) {
  const double max_delta = 1e-4;
  const int max_iterations = 73;

  auto convergence_ptr = this->test_builder_ptr_->BuildMomentMapConvergenceChecker(max_delta, max_iterations);
  using ExpectedType = convergence::IterationCompletionChecker<system::moments::MomentsMap>;
  ASSERT_THAT(convergence_ptr.get(), WhenDynamicCastTo<ExpectedType*>(NotNull()));
  EXPECT_EQ(convergence_ptr->max_iterations(), max_iterations);
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildSAAFFormulationTest) {
  constexpr int dim = this->dim;

  auto finite_element_ptr =
      std::make_shared<domain::finite_element::FiniteElementMock<dim>>();
  auto cross_sections_ptr =
      std::make_shared<data::cross_sections::MaterialCrossSections>(this->mock_material);
  auto quadrature_set_ptr =
      std::make_shared<quadrature::QuadratureSetMock<dim>>();

  EXPECT_CALL(*finite_element_ptr, dofs_per_cell());
  EXPECT_CALL(*finite_element_ptr, n_cell_quad_pts());
  EXPECT_CALL(*finite_element_ptr, n_face_quad_pts());

  auto saaf_formulation_ptr = this->test_builder_ptr_->BuildSAAFFormulation(
      finite_element_ptr, cross_sections_ptr, quadrature_set_ptr);

  using ExpectedType = formulation::angular::SelfAdjointAngularFlux<dim>;

  EXPECT_THAT(saaf_formulation_ptr.get(),
              WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildStamper) {
  constexpr int dim = this->dim;

  auto domain_ptr = std::make_shared<domain::DomainMock<dim>>();

  using ExpectedType = formulation::Stamper<dim>;
  auto stamper_ptr = this->test_builder_ptr_->BuildStamper(domain_ptr);

  EXPECT_THAT(stamper_ptr.get(), WhenDynamicCastTo<ExpectedType*>(NotNull()));
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildSubroutine) {
  using ExpectedType = iteration::subroutine::GetScalarFluxFromFramework;
  auto subroutine_ptr = this->test_builder_ptr_->BuildSubroutine(
      std::move(this->framework_uptr_),
      iteration::subroutine::SubroutineName::kGetScalarFluxFromFramework);
  auto dynamic_ptr{ dynamic_cast<ExpectedType*>(subroutine_ptr.get()) };
  ASSERT_NE(dynamic_ptr, nullptr);
  EXPECT_NE(dynamic_ptr->framework_ptr(), nullptr);
}

TYPED_TEST(FrameworkBuilderIntegrationTest, BuildSystem) {
  constexpr int dim = this->dim;
  using VariableLinearTerms = system::terms::VariableLinearTerms;

  domain::DomainMock<dim> mock_domain;
  const int total_groups = 2, total_angles = 3;
  const std::size_t solution_size = 10;
  const bool is_eigenvalue_problem = true, need_rhs_boundary_condition = false;

  EXPECT_CALL(mock_domain, MakeSystemMatrix())
      .Times(total_angles * total_groups)
      .WillRepeatedly(Return(std::make_shared<system::MPISparseMatrix>()));
  EXPECT_CALL(mock_domain, MakeSystemVector())
      .Times(3*total_angles * total_groups)
      .WillRepeatedly(Return(std::make_shared<system::MPIVector>()));

  auto system_ptr = this->test_builder_ptr_->BuildSystem(
      total_groups, total_angles, mock_domain, solution_size,
      is_eigenvalue_problem, need_rhs_boundary_condition);

  auto& system = *system_ptr;

  EXPECT_EQ(system.total_groups, total_groups);
  EXPECT_EQ(system.total_angles, total_angles);
  EXPECT_EQ(system.k_effective.value(), 1.0);
  ASSERT_NE(nullptr, system.right_hand_side_ptr_);
  EXPECT_THAT(system.right_hand_side_ptr_->GetVariableTerms(),
            ::testing::UnorderedElementsAre(VariableLinearTerms::kFissionSource,
                                            VariableLinearTerms::kScatteringSource));
  ASSERT_NE(nullptr, system.left_hand_side_ptr_);

  for (const auto& moments : {system.current_moments.get(),
                              system.previous_moments.get()}) {
    ASSERT_NE(nullptr, moments);
    EXPECT_EQ(moments->total_groups(), total_groups);
    EXPECT_EQ(moments->max_harmonic_l(), 0);
    for (const auto& moment : *moments)
      EXPECT_EQ(moment.second.size(), solution_size);
  }
}

/* ===== Non-dimensional tests =================================================
 * These tests instantiate classes and use depdent classes that do not have a
 * dimension template varaible and therefore only need to be run in a single
 * dimension.
*/

class FrameworkBuilderIntegrationNonDimTest
 : public FrameworkBuilderIntegrationTest<bart::testing::OneD> {};

TEST_F(FrameworkBuilderIntegrationNonDimTest, BuildDefaultInitializer) {
  using InitializerName = iteration::initializer::InitializerName;
  auto fixed_updater_ptr = std::make_shared<formulation::updater::FixedUpdaterMock>();

  using ExpectedType = iteration::initializer::InitializeFixedTermsOnce;
  const int total_groups = bart::test_helpers::RandomDouble(1, 10);
  const int total_angles = total_groups + 1;

  auto initializer_ptr = this->test_builder_ptr_->BuildInitializer(fixed_updater_ptr,
                                                                  total_groups,
                                                                  total_angles);
  auto dynamic_ptr = dynamic_cast<ExpectedType*>(initializer_ptr.get());
  ASSERT_NE(initializer_ptr, nullptr);
  ASSERT_NE(dynamic_ptr, nullptr);
  EXPECT_EQ(dynamic_ptr->total_angles(), total_angles);
  EXPECT_EQ(dynamic_ptr->total_groups(), total_groups);
}

TEST_F(FrameworkBuilderIntegrationNonDimTest, BuildInitializerFixedOnceSpecified) {
  using InitializerName = iteration::initializer::InitializerName;
  auto fixed_updater_ptr = std::make_shared<formulation::updater::FixedUpdaterMock>();

  using ExpectedType = iteration::initializer::InitializeFixedTermsOnce;
  const int total_groups = bart::test_helpers::RandomDouble(1, 10);
  const int total_angles = total_groups + 1;

  auto initializer_ptr = this->test_builder_ptr_->BuildInitializer(fixed_updater_ptr,
                                                                   total_groups,
                                                                   total_angles,
                                                                   InitializerName::kInitializeFixedTermsOnce);
  auto dynamic_ptr = dynamic_cast<ExpectedType*>(initializer_ptr.get());
  ASSERT_NE(initializer_ptr, nullptr);
  ASSERT_NE(dynamic_ptr, nullptr);
  EXPECT_EQ(dynamic_ptr->total_angles(), total_angles);
  EXPECT_EQ(dynamic_ptr->total_groups(), total_groups);
}

TEST_F(FrameworkBuilderIntegrationNonDimTest, BuildInitializerResetMoments) {
  using InitializerName = iteration::initializer::InitializerName;
  auto fixed_updater_ptr = std::make_shared<formulation::updater::FixedUpdaterMock>();

  using ExpectedType = iteration::initializer::InitializeFixedTermsResetMoments;
  const int total_groups = bart::test_helpers::RandomDouble(1, 10);
  const int total_angles = total_groups + 1;

  auto initializer_ptr = this->test_builder_ptr_->BuildInitializer(fixed_updater_ptr,
                                                                   total_groups,
                                                                   total_angles,
                                                                   InitializerName::kInitializeFixedTermsAndResetMoments);
  auto dynamic_ptr = dynamic_cast<ExpectedType*>(initializer_ptr.get());
  ASSERT_NE(initializer_ptr, nullptr);
  ASSERT_NE(dynamic_ptr, nullptr);
  EXPECT_EQ(dynamic_ptr->total_angles(), total_angles);
  EXPECT_EQ(dynamic_ptr->total_groups(), total_groups);
}

} // namespace


/* boost random/detail/generator_bits.hpp header file
 *
 * Copyright Steven Watanabe 2011
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *
 * See http://www.boost.org for most recent version including documentation.
 *
 * $Id: generator_bits.hpp 72951 2011-07-07 04:57:37Z steven_watanabe $
 *
 */

#ifndef BOOST_RANDOM_DETAIL_GENERATOR_BITS_HPP
#define BOOST_RANDOM_DETAIL_GENERATOR_BITS_HPP

#include <boost/limits.hpp>

namespace boost {
namespace random {
namespace detail {

// This is a temporary measure that retains backwards
// compatibility.
template<class URNG>
struct generator_bits {
    static std::size_t value() {
        return std::numeric_limits<typename URNG::result_type>::digits;
    }
};

} // namespace detail
} // namespace random
} // namespace boost

#endif // BOOST_RANDOM_DETAIL_GENERATOR_BITS_HPP


#include <boost/python.hpp>
#include <boost/numpy.hpp>

#include "game/rigid_body_motion_estimation.h"

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

BOOST_PYTHON_MODULE(librigid_body_motion_estimation) {
  np::initialize();

  bp::class_<game::RotorTranslationVectorEstimation>("RotorTranslationVectorEstimation", bp::init<const bp::dict&>())
      .def("run", &game::RotorTranslationVectorEstimation::Run)
      .def("summary", &game::RotorTranslationVectorEstimation::Summary);

  bp::class_<game::RotorTranslationVectorBivectorGeneratorEstimation>("RotorTranslationVectorBivectorGeneratorEstimation")
      .def("run", &game::RotorTranslationVectorBivectorGeneratorEstimation::Run)
      .def("summary", &game::RotorTranslationVectorBivectorGeneratorEstimation::Summary);

  bp::class_<game::GeneralRotorEstimation>("GeneralRotorEstimation")
      .def("run", &game::GeneralRotorEstimation::Run)
      .def("summary", &game::GeneralRotorEstimation::Summary);

}


///////////////////////////////////////////////////////////////////////////////
// BSD 3-Clause License
//
// Copyright (c) 2018-2020, The Regents of the University of California
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
//   contributors may be used to endorse or promote products derived from
//   this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///////////////////////////////////////////////////////////////////////////////

#include "initialPlace.h"
#include "placerBase.h"
#include <iostream>

#include <Eigen/IterativeLinearSolvers>

#include "plot.h"
#include "graphics.h"

#include "utility/Logger.h"

namespace gpl {
using namespace std;

using Eigen::BiCGSTAB;
using Eigen::IdentityPreconditioner;
using utl::GPL;

typedef Eigen::Triplet< float > T;

InitialPlaceVars::InitialPlaceVars()
{
  reset();
}

void InitialPlaceVars::reset() {
  maxIter = 20;
  minDiffLength = 1500;
  maxSolverIter = 100;
  maxFanout = 200;
  netWeightScale = 800.0;
  incrementalPlaceMode = false;
  debug = false;
}

InitialPlace::InitialPlace()
: ipVars_(), pb_(nullptr), log_(nullptr) {}

InitialPlace::InitialPlace(InitialPlaceVars ipVars,
    std::shared_ptr<PlacerBase> pb,
    utl::Logger* log)
: ipVars_(ipVars), pb_(pb), log_(log)
{
}

InitialPlace::~InitialPlace() {
  reset();
}

void InitialPlace::reset() {
  pb_ = nullptr;
  ipVars_.reset();
}

#ifdef ENABLE_CIMG_LIB
static PlotEnv pe;
#endif

void InitialPlace::doBicgstabPlace() {
  float errorX = 0.0f, errorY = 0.0f;

#ifdef ENABLE_CIMG_LIB
  pe.setPlacerBase(pb_);
  pe.Init();
#endif

  std::unique_ptr<Graphics> graphics;
  if (ipVars_.debug && Graphics::guiActive()) {
    graphics = make_unique<Graphics>(log_, pb_, this);
  }

  // normally, initial place will place all cells in the centers.
  if( !ipVars_.incrementalPlaceMode ) {
    placeInstsCenter();
  }

  // set ExtId for idx reference // easy recovery
  setPlaceInstExtId();
  for(int i=1; i<=ipVars_.maxIter; i++) {
    updatePinInfo();
    createSparseMatrix();

    // BiCGSTAB solver for initial place
    BiCGSTAB< SMatrix, IdentityPreconditioner > solver;
    solver.setMaxIterations(ipVars_.maxSolverIter);
    solver.compute(placeInstForceMatrixX_);
    instLocVecX_ = solver.solveWithGuess(fixedInstForceVecX_, instLocVecX_);
    errorX = solver.error();

    solver.compute(placeInstForceMatrixY_);
    instLocVecY_ = solver.solveWithGuess(fixedInstForceVecY_, instLocVecY_);
    errorY = solver.error();

    log_->report("[InitialPlace]  Iter: {} CG Error: {:0.8f} HPWL: {}",
       i, max(errorX, errorY), pb_->hpwl());
    updateCoordi();

#ifdef ENABLE_CIMG_LIB
    pe.SaveCellPlotAsJPEG(
        string("InitPlace ") + to_string(i), false,
        string("./plot/cell/ip_") + to_string(i));
#endif

    if (graphics) {
        graphics->cellPlot(true);
    }

    if( max(errorX, errorY) <= 1e-5 && i >= 5 ) {
      break;
    }
  }
}

// starting point of initial place is center.
void InitialPlace::placeInstsCenter() {
  const int centerX = pb_->die().coreCx();
  const int centerY = pb_->die().coreCy();

  for(auto& inst: pb_->placeInsts()) {
    inst->setCenterLocation(centerX, centerY);
  }
}

void InitialPlace::setPlaceInstExtId() {
  // reset ExtId for all instances
  for(auto& inst : pb_->insts()) {
    inst->setExtId(INT_MAX);
  }
  // set index only with place-able instances
  for(auto& inst : pb_->placeInsts()) {
    inst->setExtId(&inst - &(pb_->placeInsts()[0]));
  }
}

void InitialPlace::updatePinInfo() {
  // reset all MinMax attributes
  for(auto& pin : pb_->pins()) {
    pin->unsetMinPinX();
    pin->unsetMinPinY();
    pin->unsetMaxPinX();
    pin->unsetMaxPinY();
  }

  for(auto& net : pb_->nets()) {
    Pin* pinMinX = nullptr, *pinMinY = nullptr;
    Pin* pinMaxX = nullptr, *pinMaxY = nullptr;
    int lx = INT_MAX, ly = INT_MAX;
    int ux = INT_MIN, uy = INT_MIN;

    // Mark B2B info on Pin structures
    for(auto& pin : net->pins()) {
      if( lx > pin->cx() ) {
        if( pinMinX ) {
          pinMinX->unsetMinPinX();
        }
        lx = pin->cx();
        pinMinX = pin;
        pinMinX->setMinPinX();
      }

      if( ux < pin->cx() ) {
        if( pinMaxX ) {
          pinMaxX->unsetMaxPinX();
        }
        ux = pin->cx();
        pinMaxX = pin;
        pinMaxX->setMaxPinX();
      }

      if( ly > pin->cy() ) {
        if( pinMinY ) {
          pinMinY->unsetMinPinY();
        }
        ly = pin->cy();
        pinMinY = pin;
        pinMinY->setMinPinY();
      }

      if( uy < pin->cy() ) {
        if( pinMaxY ) {
          pinMaxY->unsetMaxPinY();
        }
        uy = pin->cy();
        pinMaxY = pin;
        pinMaxY->setMaxPinY();
      }
    }
  }
}

// solve placeInstForceMatrixX_ * xcg_x_ = xcg_b_ and placeInstForceMatrixY_ * ycg_x_ = ycg_b_ eq.
void InitialPlace::createSparseMatrix() {
  const int placeCnt = pb_->placeInsts().size();
  instLocVecX_.resize( placeCnt );
  fixedInstForceVecX_.resize( placeCnt );
  instLocVecY_.resize( placeCnt );
  fixedInstForceVecY_.resize( placeCnt );

  placeInstForceMatrixX_.resize( placeCnt, placeCnt );
  placeInstForceMatrixY_.resize( placeCnt, placeCnt );

  //
  // listX and listY is a temporary vector that have tuples, (idx1, idx2, val)
  //
  // listX finally becomes placeInstForceMatrixX_
  // listY finally becomes placeInstForceMatrixY_
  //
  // The triplet vector is recommended usages
  // to fill in SparseMatrix from Eigen docs.
  //

  vector< T > listX, listY;
  listX.reserve(1000000);
  listY.reserve(1000000);

  // initialize vector
  for(auto& inst : pb_->placeInsts()) {
    int idx = inst->extId();

    instLocVecX_(idx) = inst->cx();
    instLocVecY_(idx) = inst->cy();

    fixedInstForceVecX_(idx) = fixedInstForceVecY_(idx) = 0;
  }

  // for each net
  for(auto& net : pb_->nets()) {

    // skip for small nets.
    if( net->pins().size() <= 1 ) {
      continue;
    }

    // escape long time cals on huge fanout.
    //
    if( net->pins().size() >= ipVars_.maxFanout) {
      continue;
    }

    float netWeight = ipVars_.netWeightScale
      / (net->pins().size() - 1);
    //cout << "net: " << net.net()->getConstName() << endl;

    // foreach two pins in single nets.
    auto& pins = net->pins();
    for(int pinIdx1 = 1; pinIdx1 < pins.size(); ++pinIdx1) {
      Pin* pin1 = pins[pinIdx1];
      for(int pinIdx2 = 0; pinIdx2 < pinIdx1; ++pinIdx2) {
        Pin* pin2 = pins[pinIdx2];

        // no need to fill in when instance is same
        if( pin1->instance() == pin2->instance() ) {
          continue;
        }

        // B2B modeling on min/maxX pins.
        if( pin1->isMinPinX() || pin1->isMaxPinX() ||
            pin2->isMinPinX() || pin2->isMaxPinX() ) {
          int diffX = abs(pin1->cx() - pin2->cx());
          float weightX = 0;
          if( diffX > ipVars_.minDiffLength ) {
            weightX = netWeight / diffX;
          }
          else {
            weightX = netWeight
              / ipVars_.minDiffLength;
          }
          //cout << weightX << endl;

          // both pin cames from instance
          if( pin1->isPlaceInstConnected()
              && pin2->isPlaceInstConnected() ) {
            const int inst1 = pin1->instance()->extId();
            const int inst2 = pin2->instance()->extId();
            //cout << "inst: " << inst1 << " " << inst2 << endl;

            listX.push_back( T(inst1, inst1, weightX) );
            listX.push_back( T(inst2, inst2, weightX) );

            listX.push_back( T(inst1, inst2, -weightX) );
            listX.push_back( T(inst2, inst1, -weightX) );

            //cout << pin1->cx() << " "
            //  << pin1->instance()->cx() << endl;
            fixedInstForceVecX_(inst1) +=
              -weightX * (
              (pin1->cx() - pin1->instance()->cx()) -
              (pin2->cx() - pin2->instance()->cx()));

            fixedInstForceVecX_(inst2) +=
              -weightX * (
              (pin2->cx() - pin2->instance()->cx()) -
              (pin1->cx() - pin1->instance()->cx()));
          }
          // pin1 from IO port / pin2 from Instance
          else if( !pin1->isPlaceInstConnected()
              && pin2->isPlaceInstConnected() ) {
            const int inst2 = pin2->instance()->extId();
            //cout << "inst2: " << inst2 << endl;
            listX.push_back( T(inst2, inst2, weightX) );
            fixedInstForceVecX_(inst2) += weightX *
              ( pin1->cx() -
                ( pin2->cx() - pin2->instance()->cx()) );
          }
          // pin1 from Instance / pin2 from IO port
          else if( pin1->isPlaceInstConnected()
              && !pin2->isPlaceInstConnected() ) {
            const int inst1 = pin1->instance()->extId();
            //cout << "inst1: " << inst1 << endl;
            listX.push_back( T(inst1, inst1, weightX) );
            fixedInstForceVecX_(inst1) += weightX *
              ( pin2->cx() -
                ( pin1->cx() - pin1->instance()->cx()) );
          }
        }

        // B2B modeling on min/maxY pins.
        if( pin1->isMinPinY() || pin1->isMaxPinY() ||
            pin2->isMinPinY() || pin2->isMaxPinY() ) {

          int diffY = abs(pin1->cy() - pin2->cy());
          float weightY = 0;
          if( diffY > ipVars_.minDiffLength ) {
            weightY = netWeight / diffY;
          }
          else {
            weightY = netWeight
              / ipVars_.minDiffLength;
          }

          // both pin cames from instance
          if( pin1->isPlaceInstConnected()
              && pin2->isPlaceInstConnected() ) {
            const int inst1 = pin1->instance()->extId();
            const int inst2 = pin2->instance()->extId();

            listY.push_back( T(inst1, inst1, weightY) );
            listY.push_back( T(inst2, inst2, weightY) );

            listY.push_back( T(inst1, inst2, -weightY) );
            listY.push_back( T(inst2, inst1, -weightY) );

            fixedInstForceVecY_(inst1) +=
              -weightY * (
              (pin1->cy() - pin1->instance()->cy()) -
              (pin2->cy() - pin2->instance()->cy()));

            fixedInstForceVecY_(inst2) +=
              -weightY * (
              (pin2->cy() - pin2->instance()->cy()) -
              (pin1->cy() - pin1->instance()->cy()));
          }
          // pin1 from IO port / pin2 from Instance
          else if( !pin1->isPlaceInstConnected()
              && pin2->isPlaceInstConnected() ) {
            const int inst2 = pin2->instance()->extId();
            listY.push_back( T(inst2, inst2, weightY) );
            fixedInstForceVecY_(inst2) += weightY *
              ( pin1->cy() -
                ( pin2->cy() - pin2->instance()->cy()) );
          }
          // pin1 from Instance / pin2 from IO port
          else if( pin1->isPlaceInstConnected()
              && !pin2->isPlaceInstConnected() ) {
            const int inst1 = pin1->instance()->extId();
            listY.push_back( T(inst1, inst1, weightY) );
            fixedInstForceVecY_(inst1) += weightY *
              ( pin2->cy() -
                ( pin1->cy() - pin1->instance()->cy()) );
          }
        }
      }
    }
  }

  placeInstForceMatrixX_.setFromTriplets(listX.begin(), listX.end());
  placeInstForceMatrixY_.setFromTriplets(listY.begin(), listY.end());
}

void InitialPlace::updateCoordi() {
  for(auto& inst : pb_->placeInsts()) {
    int idx = inst->extId();
    inst->dbSetCenterLocation( instLocVecX_(idx), instLocVecY_(idx) );
  }
}

}


#include "biggles/observation.hpp"
#include "biggles/detail/random.hpp"
#include "biggles/mh_moves/mh_moves.hpp"
#include "biggles/mh_moves/utility.hpp"
#include "biggles/tracker.hpp"
#include "biggles/simulate.hpp"
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <fstream>
#include <algorithm>
#include <string>
#include "biggles/tools/sundries.hpp"

// include this last to stop pre-processor macros breaking things
extern "C" {
#include <ccan/tap/tap.h>
}

using namespace biggles;

class select_move_from {
    std::deque<mh_moves::move_type> m_data;
    int m_size;
public:
    select_move_from &operator<<(const mh_moves::move_type &type) {
        m_data.push_back(type);
        m_size = int(m_data.size());
        return *this;
    }
    mh_moves::move_type operator()() const {
        return m_data[biggles::sampling::uniform_int(0, m_size)];
    }
};

mh_moves::move_type simple_metropolis_hastings(partition_ptr_t &partition_ptr, const model::parameters& para,
        const select_move_from &selector)
{
    const capability_recorder_ptr &cap_rec_ptr(partition_ptr->get_capability_recorder());
    mh_moves::move_type this_move(selector());
    biggles_move proposal(mh_moves::move_name(this_move));
    float pdr;
    partition_ptr_t proposed_sample_ptr;
    bool success = proposal(partition_ptr, proposed_sample_ptr, pdr);
    if (not success) {
        return mh_moves::IDENTITY;
    }

    float proposed_density = model::log_partition_given_parameters_density(*proposed_sample_ptr, para);
    float current_density = model::log_partition_given_parameters_density(*partition_ptr, para);
    float mh_ratio = proposed_density - current_density + pdr;
    bool accept = logf(sampling::uniform_real()) < mh_ratio;
    if (accept) {
        partition_ptr = proposed_sample_ptr;
        cap_rec_ptr->commit_changes(partition_ptr->tracks());
    }
    else {
        this_move = mh_moves::NONE;
    }
    return this_move;
}

std::ostream &get_ostream(const std::string &fname, std::ofstream &fout) {
    if (fname.size() > 0) {
        fout.open(fname.c_str());
        if (fout.good()) {
            return fout;
        }
    }
    return std::cout;
}

void log_part_for_para_sampler(partition &partition_ptr, const general_paras &opts) {
    model::parameters para_sample;
    std::string fname(boost::filesystem::temp_directory_path().string());
    std::ofstream fh((fname + "/cpp_biggles.log_part_for_para.txt").c_str());
    for (int i = 0; i < opts.total; ++i) {
        sample_model_parameters_given_partition(partition_ptr, para_sample);
        fh
            << model::log_partition_given_parameters_density(partition_ptr, para_sample)
            << std::endl
        ;
    }
}

void parameter_sampler(partition &partition_ptr, const general_paras &opts) {
    model::parameters para_sample;
    std::ofstream fh;
    std::ostream &out = get_ostream(opts.rgp.output, fh);
    for (int i = 0; i < opts.total; ++i) {
        sample_model_parameters_given_partition(partition_ptr, para_sample);
        out << model::mean_new_tracks_per_frame(para_sample) << ","
            << model::mean_false_observations_per_frame(para_sample) << ","
            << model::generate_observation_probability(para_sample) << ","
            << model::frame_to_frame_survival_probability(para_sample)
            << std::endl
        ;
    }
}

bool mh_only(partition_ptr_t &partition_ptr, model::parameters& para_sample, const general_paras &opts) {
    int half_time = opts.total/2;
    mh_moves::move_type last_move;
    select_move_from selector;
    std::map<mh_moves::move_type, size_t> move_hist;
    std::map<mh_moves::move_type, size_t>::iterator it;
    selector << mh_moves::BIRTH << mh_moves::DEATH << mh_moves::UPDATE
        << mh_moves::MERGE << mh_moves::SPLIT << mh_moves::EXTEND << mh_moves::REDUCE;
    diag("parameters: %s", parameters_to_string(para_sample).c_str());

    for (int i = 0; i < half_time; ++i) {
        last_move = simple_metropolis_hastings(partition_ptr, para_sample, selector);
        ++move_hist[last_move];
    }
    for (it = move_hist.begin(); it not_eq move_hist.end(); ++it) {
        diag("%8s => %6.2f%%", mh_moves::move_name(it->first).c_str(), 100.f*(it->second)/half_time);
    }
    return true;
}

bool simple_gibbs(partition_ptr_t partition_ptr, model::parameters& para_sample, const general_paras &opts) {
    int half_time = opts.total/2;
    mh_moves::move_type last_move;
    select_move_from selector;
    std::map<mh_moves::move_type, size_t> move_hist;
    std::map<mh_moves::move_type, size_t>::iterator it;
    //selector << mh_moves::BIRTH << mh_moves::DEATH << mh_moves::UPDATE;
    selector << mh_moves::BIRTH << mh_moves::DEATH << mh_moves::UPDATE
        << mh_moves::MERGE << mh_moves::SPLIT << mh_moves::EXTEND << mh_moves::REDUCE;
    diag("parameters: %s", parameters_to_string(para_sample).c_str());
    capability_recorder_ptr cap_rec_ptr(new_cap_recorder(*partition_ptr));

    for (int i = 0; i < half_time; ++i) {
        partition_ptr->set_capability_recorder(cap_rec_ptr);
        last_move = simple_metropolis_hastings(partition_ptr, para_sample, selector);
        ++move_hist[last_move];
        sample_model_parameters_given_partition(*partition_ptr, para_sample);

    }
    for (it = move_hist.begin(); it not_eq move_hist.end(); ++it) {
        diag("%8s => %6.2f%%", mh_moves::move_name(it->first).c_str(), 100.f*(it->second)/half_time);
    }
    move_hist.clear();
    bool write_parameters = opts.rgp.output.size() > 0;
    std::ofstream fh;
    std::ostream &out = get_ostream(opts.rgp.output, fh);
    int generic_ctr = 0;
    for (int i = 0; i < half_time; ++i) {
        partition_ptr->set_capability_recorder(cap_rec_ptr);
        last_move = simple_metropolis_hastings(partition_ptr, para_sample, selector);
        sample_model_parameters_given_partition(*partition_ptr, para_sample);
        ++move_hist[last_move];
        if (partition_ptr->tracks().size() == 0)
            generic_ctr++;
        if (write_parameters) out << model::mean_new_tracks_per_frame(para_sample) << ","
            << model::mean_false_observations_per_frame(para_sample) << ","
            << model::generate_observation_probability(para_sample) << ","
            << model::frame_to_frame_survival_probability(para_sample)
            << std::endl
        ;
    }
    for (it = move_hist.begin(); it not_eq move_hist.end(); ++it) {
        diag("%8s => %6.2f%%", mh_moves::move_name(it->first).c_str(), 100.f*(it->second)/half_time);
    }
    diag("trivial proportion %6.2f%%", 100.0f*float(generic_ctr)/float(half_time));
    return true;
}

int main(int argc, char** argv)
{
    general_paras gen_pars;
    gen_pars.total = 200000;
    gen_pars.seed = 0;
    gen_pars.opts = "gibbs";
    gen_pars.rgp.lambda = 2.5;
    gen_pars.rgp.p_no = 0.2;
    gen_pars.rgp.p_yes = 0.2;
    gen_pars.rgp.p_tr = 0.5;
    gen_pars.rgp.min_tracks = 1;
    gen_pars.rgp.max_tracks = 2;
    if (not parse_args(argc, argv, gen_pars))
        return exit_status();
    biggles::detail::seed_prng(gen_pars.seed);

    plan_no_plan();

    model::parameters para_sample;
    generate_parameters(para_sample);
    diag("paramters: %s", parameters_to_string(para_sample).c_str());
    partition_ptr_t partition_ptr(random_debug_partition(gen_pars.rgp));
    diag("opts = %s, total = %d", gen_pars.opts.c_str(), gen_pars.total);
    if (gen_pars.opts == "part")
        log_part_for_para_sampler(*partition_ptr, gen_pars);
    else if (gen_pars.opts == "part")
        parameter_sampler(*partition_ptr, gen_pars);
    else if (gen_pars.opts == "gibbs")
        simple_gibbs(partition_ptr, para_sample, gen_pars);
    else if (gen_pars.opts == "moves") {
        model::mean_new_tracks_per_frame(para_sample)           = 0.1f;
        model::mean_false_observations_per_frame(para_sample)   = 0.25f;
        model::frame_to_frame_survival_probability(para_sample) = 0.9f;
        model::generate_observation_probability(para_sample)    = 0.9f;
        mh_only(partition_ptr, para_sample, gen_pars);
    }
    else
        std::cout << "--opts is required"
            << std::endl
            << "Possible values: \"para\", \"part\", \"gibbs\", \"moves\""
            << std::endl
            ;
    ok1(true);

    diag("random seed = 0x%x", gen_pars.seed);
    return exit_status();
}