// (C) Copyright 2007-2009 Andrew Sutton
//
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0 (See accompanying file
// LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GRAPH_CLOSENESS_CENTRALITY_HPP
#define BOOST_GRAPH_CLOSENESS_CENTRALITY_HPP

#include <boost/graph/detail/geodesic.hpp>
#include <boost/graph/exterior_property.hpp>
#include <boost/concept/assert.hpp>

namespace boost
{
template < typename Graph, typename DistanceType, typename ResultType,
    typename Reciprocal = detail::reciprocal< ResultType > >
struct closeness_measure
: public geodesic_measure< Graph, DistanceType, ResultType >
{
    typedef geodesic_measure< Graph, DistanceType, ResultType > base_type;
    typedef typename base_type::distance_type distance_type;
    typedef typename base_type::result_type result_type;

    result_type operator()(distance_type d, const Graph&)
    {
        BOOST_CONCEPT_ASSERT((NumericValueConcept< DistanceType >));
        BOOST_CONCEPT_ASSERT((NumericValueConcept< ResultType >));
        BOOST_CONCEPT_ASSERT((AdaptableUnaryFunctionConcept< Reciprocal,
            ResultType, ResultType >));
        return (d == base_type::infinite_distance()) ? base_type::zero_result()
                                                     : rec(result_type(d));
    }
    Reciprocal rec;
};

template < typename Graph, typename DistanceMap >
inline closeness_measure< Graph,
    typename property_traits< DistanceMap >::value_type, double,
    detail::reciprocal< double > >
measure_closeness(const Graph&, DistanceMap)
{
    typedef typename property_traits< DistanceMap >::value_type Distance;
    return closeness_measure< Graph, Distance, double,
        detail::reciprocal< double > >();
}

template < typename T, typename Graph, typename DistanceMap >
inline closeness_measure< Graph,
    typename property_traits< DistanceMap >::value_type, T,
    detail::reciprocal< T > >
measure_closeness(const Graph&, DistanceMap)
{
    typedef typename property_traits< DistanceMap >::value_type Distance;
    return closeness_measure< Graph, Distance, T, detail::reciprocal< T > >();
}

template < typename T, typename Graph, typename DistanceMap,
    typename Reciprocal >
inline closeness_measure< Graph,
    typename property_traits< DistanceMap >::value_type, T, Reciprocal >
measure_closeness(const Graph&, DistanceMap)
{
    typedef typename property_traits< DistanceMap >::value_type Distance;
    return closeness_measure< Graph, Distance, T, Reciprocal >();
}

template < typename Graph, typename DistanceMap, typename Measure,
    typename Combinator >
inline typename Measure::result_type closeness_centrality(
    const Graph& g, DistanceMap dist, Measure measure, Combinator combine)
{
    BOOST_CONCEPT_ASSERT((VertexListGraphConcept< Graph >));
    typedef typename graph_traits< Graph >::vertex_descriptor Vertex;
    BOOST_CONCEPT_ASSERT((ReadablePropertyMapConcept< DistanceMap, Vertex >));
    typedef typename property_traits< DistanceMap >::value_type Distance;
    BOOST_CONCEPT_ASSERT((NumericValueConcept< Distance >));
    BOOST_CONCEPT_ASSERT((DistanceMeasureConcept< Measure, Graph >));

    Distance n = detail::combine_distances(g, dist, combine, Distance(0));
    return measure(n, g);
}

template < typename Graph, typename DistanceMap, typename Measure >
inline typename Measure::result_type closeness_centrality(
    const Graph& g, DistanceMap dist, Measure measure)
{
    BOOST_CONCEPT_ASSERT((GraphConcept< Graph >));
    typedef typename graph_traits< Graph >::vertex_descriptor Vertex;
    BOOST_CONCEPT_ASSERT((ReadablePropertyMapConcept< DistanceMap, Vertex >));
    typedef typename property_traits< DistanceMap >::value_type Distance;

    return closeness_centrality(g, dist, measure, std::plus< Distance >());
}

template < typename Graph, typename DistanceMap >
inline double closeness_centrality(const Graph& g, DistanceMap dist)
{
    return closeness_centrality(g, dist, measure_closeness(g, dist));
}

template < typename T, typename Graph, typename DistanceMap >
inline T closeness_centrality(const Graph& g, DistanceMap dist)
{
    return closeness_centrality(g, dist, measure_closeness< T >(g, dist));
}

template < typename Graph, typename DistanceMatrixMap, typename CentralityMap,
    typename Measure >
inline void all_closeness_centralities(
    const Graph& g, DistanceMatrixMap dist, CentralityMap cent, Measure measure)
{
    BOOST_CONCEPT_ASSERT((VertexListGraphConcept< Graph >));
    typedef typename graph_traits< Graph >::vertex_descriptor Vertex;
    BOOST_CONCEPT_ASSERT(
        (ReadablePropertyMapConcept< DistanceMatrixMap, Vertex >));
    typedef
        typename property_traits< DistanceMatrixMap >::value_type DistanceMap;
    BOOST_CONCEPT_ASSERT((ReadablePropertyMapConcept< DistanceMap, Vertex >));
    BOOST_CONCEPT_ASSERT((WritablePropertyMapConcept< CentralityMap, Vertex >));
    typedef typename property_traits< CentralityMap >::value_type Centrality;

    typename graph_traits< Graph >::vertex_iterator i, end;
    for (boost::tie(i, end) = vertices(g); i != end; ++i)
    {
        DistanceMap dm = get(dist, *i);
        Centrality c = closeness_centrality(g, dm, measure);
        put(cent, *i, c);
    }
}

template < typename Graph, typename DistanceMatrixMap, typename CentralityMap >
inline void all_closeness_centralities(
    const Graph& g, DistanceMatrixMap dist, CentralityMap cent)
{
    BOOST_CONCEPT_ASSERT((GraphConcept< Graph >));
    typedef typename graph_traits< Graph >::vertex_descriptor Vertex;
    BOOST_CONCEPT_ASSERT(
        (ReadablePropertyMapConcept< DistanceMatrixMap, Vertex >));
    typedef
        typename property_traits< DistanceMatrixMap >::value_type DistanceMap;
    BOOST_CONCEPT_ASSERT((ReadablePropertyMapConcept< DistanceMap, Vertex >));
    typedef typename property_traits< CentralityMap >::value_type Result;

    all_closeness_centralities(
        g, dist, cent, measure_closeness< Result >(g, DistanceMap()));
}

} /* namespace boost */

#endif


#include <opencv2/core/core.hpp>
#include <string>
#include <boost/shared_ptr.hpp>

#include "visual_utility/VisualUtilityEstimator.h"
#include "visual_utility/TransformEstimator.h"

using namespace cv;
using namespace boost;
using namespace std;

namespace visual_utility {

VisualUtilityFilterNodelet::~VisualUtilityFilterNodelet() {}

void VisualUtilityFilterNodelet::onInit() {
  InitializeVisualUtilityFilter();

  cameraInfo_.reset(new CameraInfo());

  // Setup the connections for the the node
  ros::NodeHandle handle;

  // Setup a listener for Image messages
  imgSubscriber_ = handle.subscribe(
    "image",
    10,
    &VisualUtilityFilterNodelet::HandleImage,
    this);
  cameraInfoSubscriber_ = handle.subscribe(
    "camera_info",
    10,
    &VisualUtilityFilterNodelet::HandleCameraInfo,
    this);

  // Register the publishers
  imagePub_ = handle.advertise<sensor_msgs::Image>("filtered/image",
                                                   10, false);
  infoPub_ = handle.advertise<sensor_msgs::CameraInfo>("filtered/camera_info",
                                                       10, false);
  timePub_ = handle.advertise<std_msgs::Duration>("processing_time",
                                                  10, false);

  // Advertise the service
  service_ = handle.advertiseService(
    "filter_image_service",
    &VisualUtilityFilterNodelet::HandleImageService, this);
}

boost::shared_ptr<VisualUtilityEstimator> CreateVisualUtilityEstimator() {
  // Node handle for the parameters for filtering
  ros::NodeHandle localHandle("~");

  // Build up the transform estimator to figure out the transform
  // between two frames.
  int maxIterations;
  double minPrecision;
  double imageScaling;
  localHandle.param<int>("affine_max_iterations", maxIterations, 100);
  localHandle.param<double>("min_affine_precision", minPrecision, 1e-7);
  localHandle.param<double>("affine_scaling_factor", imageScaling, 4.0);
  transformEstimator_.reset(new AffineTransformEstimator(maxIterations,
                                                         minPrecision,
                                                         imageScaling));

  // Build up the visual utility estimator
  double paretoThreshold;
  double distDecay;
  string vuEstimatorClass;
  localHandle.param<double>("pareto_thresh", paretoThreshold, 0.03);
  localHandle.param<double>("dist_decay", distDecay, 2.0);
  localHandle.param<string>("vu_estimator", vuEstimatorClass,
                            "LABMotionVUEstimator");
  if (vuEstimatorClass == "LABMotionVUEstimator") {
    vuEstimator_.reset(new LABMotionVUEstimator(*transformEstimator_,
                                                paretoThreshold,
                                                distDecay));
  } else if (vuEstimatorClass == "SpectralSaliency") {
    vuEstimator_.reset(new SpectralSaliency());
  }

  // Build up the visual utiliy mosaic
  int morphCloseSize;
  double gaussSigma;
  localHandle.param<int>("morph_close_size", morphCloseSize, 0);
  localHandle.param<double>("gauss_sigma", gaussSigma, 0.0);
  vuMosaic_.reset(new NullVUMosaic(morphCloseSize, gaussSigma));

  // Build up the frame estimator
  int xFramesize;
  int yFramesize;
  double frameExpansion;
  string frameEstimatorClass;
  localHandle.param<int>("xframesize", xFramesize, 192);
  localHandle.param<int>("yframesize", yFramesize, 108);
  localHandle.param<double>("frame_expansion", frameExpansion, 1.0);
  localHandle.param<string>("frame_estimator", frameEstimatorClass,
                            "MaxPointConstantFramesize");
  if (frameEstimatorClass == "MaxPointConstantFramesize") {
    frameEstimator_.reset(new MaxPointConstantFramesize(
      Size_<int>(xFramesize, yFramesize)));
  } else if (frameEstimatorClass == "RandomPointConstantFramesize") {
    frameEstimator_.reset(new RandomPointConstantFramesize(
      Size_<int>(xFramesize, yFramesize)));
  } else if (frameEstimatorClass == "HighRelativeEntropy") {
    int minFrameArea;
    double minEntropy;
    localHandle.param<int>("min_frame_area", minFrameArea, 200);
    localHandle.param<double>("min_entropy", minEntropy, 0.2);
    frameEstimator_.reset(new HighRelativeEntropy(frameExpansion,
                                                  minFrameArea,
                                                  minEntropy));
  }

  filter_.reset(new VisualUtilityFilter(vuEstimator_.get(),
                                        vuMosaic_.get(),
                                        frameEstimator_.get()));

}

bool VisualUtilityFilterNodelet::HandleImageService(
  visual_utility::FilterImage::Request& request,
  visual_utility::FilterImage::Response& response) {

  bool retval = HandleImageImpl(request.image,
                                response.images,
                                response.camera_infos);

  return retval;
}

void VisualUtilityFilterNodelet::HandleImage(
  const sensor_msgs::ImagePtr& image) {
  vector<CameraInfo> cameraInfos;
  vector<sensor_msgs::Image> filteredImageMsgs;
  HandleImageImpl(*image, filteredImageMsgs, cameraInfos);

  for (unsigned int i = 0; i < cameraInfos.size(); ++i) {
    imagePub_.publish(filteredImageMsgs[i]);
    infoPub_.publish(cameraInfos[i]);
  }
}

bool VisualUtilityFilterNodelet::HandleImageImpl(
  const sensor_msgs::Image& image,
  vector<sensor_msgs::Image>& filteredImageMsgs,
  vector<CameraInfo>& cameraInfos) {
  ROS_ASSERT(filter_.get() != NULL);

  // First convert the image to OpenCV
#ifdef CTURTLE
  IplImage* iplImage = NULL;
  CvBridge bridge;
  try {
    iplImage = bridge.imgMsgToCv(
      image,
      "bgr8");
  } catch (sensor_msgs::CvBridgeException error) {
    NODELET_ERROR("Could not convert the image to OpenCV");
    return false;
  }
  Mat cvImage(iplImage);
#else
  cv_bridge::CvImageConstPtr cvImagePtr;
  // Makes sure that the converted image stays around as long as this object
  boost::shared_ptr<void const> dummy_object;
  try {
    cvImagePtr = cv_bridge::toCvShare(image,
                                      dummy_object,
                                      sensor_msgs::image_encodings::BGR8);
  } catch (cv_bridge::Exception& e) {
    NODELET_ERROR("Could not convert image to OpenCV: %s", e.what());
    return false;
  }
  const Mat& cvImage(cvImagePtr->image);
#endif // CTURTLE

  std_msgs::Duration timeMsg;
  ros::Time startTime = ros::Time::now();

  // Do the filtering
  vector<Mat> filteredImages;
  filter_->AddImage(cvImage);
  timeMsg.data = ros::Time::now() - startTime;
  filter_->GetFilteredImages(&filteredImages);

  timePub_.publish(timeMsg);

  if (filteredImages.empty()) {
    NODELET_WARN("After filtering the image did not find a valid region");
    return true;
  }

  const vector<Rect_<int> >& rois = filter_->GetROI();

  ROS_ASSERT(rois.size() == filteredImages.size());

  // Now create the output messages
  for (unsigned int i = 0; i < rois.size(); ++i) {
#ifdef CTURTLE
    IplImage iplFilteredImage(filteredImages[i]);
    sensor_msgs::Image::Ptr curMsg = CvBridge::cvToImgMsg(&iplFilteredImage,
                                                          "bgr8");
    curMsg->header.stamp = image.header.stamp;
    curMsg->header.frame_id = image.header.frame_id;
    curMsg->header.seq = image.header.seq;
    filteredImageMsgs.push_back(*curMsg);
#else
    cv_bridge::CvImage bridgeMsg;
    bridgeMsg.header.stamp = image.header.stamp;
    bridgeMsg.header.frame_id = image.header.frame_id;
    bridgeMsg.header.seq = image.header.seq;
    bridgeMsg.encoding = "bgr8";
    bridgeMsg.image = filteredImages[i];
    filteredImageMsgs.push_back(sensor_msgs::Image());
    bridgeMsg.toImageMsg(*(filteredImageMsgs.end()-1));
#endif // CTURTLE

    cameraInfos.push_back(*cameraInfo_);
    vector<CameraInfo>::iterator cameraInfo = cameraInfos.end()-1;
    cameraInfo->header.stamp = image.header.stamp;
    cameraInfo->header.frame_id = image.header.frame_id;
    cameraInfo->header.seq = image.header.seq;
    cameraInfo->roi.x_offset += rois[i].x;
    cameraInfo->roi.y_offset += rois[i].y;
    cameraInfo->roi.width = rois[i].width;
    cameraInfo->roi.height = rois[i].height;
  }

  ROS_ASSERT(filteredImageMsgs.size() == cameraInfos.size());

  return true;

}

} // namespace

int main(int argc, char** argv) {
  ros::init(argc, argv, "CreateVisualUtilityVideo");

  if (argc < 3) {
    ROS_FATAL("Usage: CreateVisualUtilityVideo [options] <outputVideo> <listOfFrames.txt>");
    return -1;
  }

  string outputVideo(argv[1]);
  string frameList(argv[2]);

  // Create the visual utility estimator

}


#include "NMFEngine.hpp"

#include <string>
#include <glog/logging.h>
#include <algorithm>
#include <fstream>
#include <cmath>
#include <mutex>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <petuum_ps_common/include/petuum_ps.hpp>

#include "util/Eigen/Dense"
#include "util/context.hpp"

namespace NMF {

    // Constructor
    NMFEngine::NMFEngine(): thread_counter_(0) {
        // timer
        initT_ = boost::posix_time::microsec_clock::local_time();

        /* context */
        lda::Context & context = lda::Context::get_instance();
        // input and output
        data_file_ = context.get_string("data_file");
        input_data_format_ = context.get_string("input_data_format");
        is_partitioned_ = context.get_bool("is_partitioned");
        output_path_ = context.get_string("output_path");
        output_data_format_ = context.get_string("output_data_format");
        maximum_running_time_ = context.get_double("maximum_running_time");
        load_cache_ = context.get_bool("load_cache");
        cache_path_ = context.get_string("cache_path");

        // objective function parameters
        int m = context.get_int32("m");
        int n = context.get_int32("n");
        rank_ = context.get_int32("rank");

        // petuum parameters
        client_id_ = context.get_int32("client_id");
        num_clients_ = context.get_int32("num_clients");
        num_worker_threads_ = context.get_int32("num_worker_threads");

        // optimization parameters
        num_epochs_ = context.get_int32("num_epochs");
        minibatch_size_ = context.get_int32("minibatch_size");
        num_eval_minibatch_ = context.get_int32("num_eval_minibatch");
        num_eval_samples_ = context.get_int32("num_eval_samples");
        num_iter_L_per_minibatch_ =
            context.get_int32("num_iter_L_per_minibatch");
        init_step_size_L_ = context.get_double("init_step_size_L");
        step_size_offset_L_ = context.get_double("step_size_offset_L");
        step_size_pow_L_ = context.get_double("step_size_pow_L");
        init_step_size_R_ = context.get_double("init_step_size_R");
        step_size_offset_R_ = context.get_double("step_size_offset_R");
        step_size_pow_R_ = context.get_double("step_size_pow_R");
        // default step size
        if (init_step_size_L_ < FLT_MIN) {
            init_step_size_L_ = context.get_double("init_step_size") / m;
            step_size_offset_L_ = context.get_double("step_size_offset");
            step_size_pow_L_ = context.get_double("step_size_pow");
        }
        if (init_step_size_R_ < FLT_MIN) {
            init_step_size_R_ = context.get_double("init_step_size")
                / num_clients_ / num_worker_threads_;
            step_size_offset_R_ = context.get_double("step_size_offset");
            step_size_pow_R_ = context.get_double("step_size_pow");
        }
        init_L_low_ = context.get_double("init_L_low");
        init_L_high_ = context.get_double("init_L_high");
        init_R_low_ = context.get_double("init_R_low");
        init_R_high_ = context.get_double("init_R_high");

        /* Init matrices */
        // Partition by column id mod num_clients_
        int client_n = (n - (n / num_clients_) * num_clients_ > client_id_)?
            n / num_clients_ + 1: n / num_clients_;
        // Init matrix loader of data matrix X
        if (is_partitioned_) {
            X_matrix_loader_.Init(data_file_, input_data_format_, m, client_n);
        } else {
            X_matrix_loader_.Init(data_file_, input_data_format_, m, n,
                    client_id_, num_clients_);
        }

        // Init matrix loader of L matrix
        if (rank_ == 0)
            rank_ = n;
        L_matrix_loader_.Init(rank_, client_n, init_L_low_, init_L_high_);

        int max_client_n = ceil(float(n) / num_clients_);
        int iter_minibatch =
            ceil(float(max_client_n / num_worker_threads_) / minibatch_size_);
        num_eval_per_client_ =
            (num_epochs_ * iter_minibatch - 1)
              / num_eval_minibatch_ + 1;

        // Ouput parameters
        LOG(INFO) << "NMF running on "
            << num_clients_
            << ((num_clients_ > 1)? " clients, each with ": " client, with ")
            << num_worker_threads_ << " threads";
        LOG(INFO) << "Input file: " << data_file_;
        LOG(INFO) << "Matrix size: " << n << " by " << m;
        LOG(INFO) << "Factorization rank: " << rank_;
        LOG(INFO) << "Minibatch size: " << minibatch_size_;
        LOG(INFO) << "Epochs: " << num_epochs_
            << " (Max iter: " << iter_minibatch  * num_epochs_ << ")";
        LOG(INFO) << "Evaluate loss per " << num_eval_minibatch_ << " iter "
            << "by evaluating " << num_eval_samples_ << " samples per thread"
            << " (*" << num_clients_ * num_worker_threads_<< " = "
            << num_eval_samples_ * num_clients_ * num_worker_threads_ << ")";
        LOG(INFO) << "Iter of L in each L loop: " << num_iter_L_per_minibatch_;
        LOG(INFO) << "step size of L at ith iteration: " << init_step_size_L_
            << " * (i + " << step_size_offset_L_ << ")^(-" << step_size_pow_L_
            << ")";
        LOG(INFO) << "step size of R at ith iteration: " << init_step_size_R_
            << " * (i + " << step_size_offset_R_ << ")^(-" << step_size_pow_R_
            << ")";
    }

    // Helper function non-negativise a vector vec
    inline void RegVec(std::vector<float> & vec,
            std::vector<float> & vec_result) {
        int len = vec.size();
        for (int i = 0; i < len; ++i) {
            vec_result[i] = (vec[i] > 0)? vec[i]: 0;
        }
        for (int i = 0; i < len; ++i) {
            vec_result[i] = (vec[i] < MAXELEVAL)? vec[i]: MAXELEVAL;
        }
        for (int i = 0; i < len; ++i) {
            vec_result[i] = (vec[i] > MINELEVAL)? vec[i]: MINELEVAL;
        }
    }

    // Save results: L, R, loss evaluated on different
    // machines, time between evaluations to disk.
    // Shall be called after calling petuum::PSTableGroup::GlobalBarrier()
    void NMFEngine::SaveResults(int thread_id, petuum::Table<float> & R_table,
            petuum::Table<float> & loss_table) {
        if (thread_id == 0) {
            LOG(INFO) << "Saving results to " << output_path_ << ".";
            LOG(INFO) << "Matrix element whose absolute value is smaller than "
                << INFINITESIMAL << "(set by INFINITESIMAL in Makefile)"
                " is set to zero during output.";
        }
        // size of matrices
        int m = X_matrix_loader_.GetM();
        int client_n = X_matrix_loader_.GetClientN();

        // Caches
        std::vector<float> R_row_cache(m), L_cache(rank_);

        // Output files
        std::ofstream fout_loss, fout_R, fout_L, fout_time;

        // Only thread 0 of client 0 write R, loss and time to disk
        if (client_id_ == 0 && thread_id == 0) {
            // Write loss to disk
            std::string loss_filename = output_path_ + "/loss.txt";
            fout_loss.open(loss_filename.c_str());
            petuum::RowAccessor row_acc;
            std::vector<float> petuum_row_cache(m);
            for (int iter = 0; iter < num_eval_per_client_; ++iter) {
                for (int client = 0; client < num_clients_; ++client) {
                    int row_id = client * num_eval_per_client_ + iter;
                    const auto & row = loss_table.
                        Get<petuum::DenseRow<float> >(row_id, &row_acc);
                    row.CopyToVector(&petuum_row_cache);
                    if (std::abs(petuum_row_cache[0]) > FLT_MIN) {
                        fout_loss << petuum_row_cache[0] << "\t";
                    } else {
                        fout_loss << "N/A" << "\t";
                    }
                }
                fout_loss << "\n";
            }
            fout_loss.close();
            // Write time to disk
            std::string time_filename = output_path_ + "/time.txt";
            fout_time.open(time_filename.c_str());
            for (int iter = 0; iter < num_eval_per_client_; ++iter) {
                for (int client = 0; client < num_clients_; ++client) {
                    int row_id =
                        (client + num_clients_) * num_eval_per_client_ + iter;
                    const auto & row = loss_table.
                        Get<petuum::DenseRow<float> >(row_id, &row_acc);
                    row.CopyToVector(&petuum_row_cache);
                    if (std::abs(petuum_row_cache[0]) > FLT_MIN) {
                        fout_time << petuum_row_cache[0] << "\t";
                    } else {
                        fout_time << "N/A" << "\t";
                    }
                }
                fout_time << "\n";
            }
            fout_time.close();
            // Write R to disk
            // with filename output_path_/R.[txt|bin]
            if (output_data_format_ == "text") {
                std::string R_filename = output_path_ + "/R.txt";
                fout_R.open(R_filename.c_str());
            } else if (output_data_format_ == "binary") {
                std::string R_filename = output_path_ + "/R.bin";
                fout_R.open(R_filename.c_str(), std::ios::binary);
            } else {
                LOG(FATAL) << "Unrecognized data format: " << output_data_format_;
            }
            for (int row_id = 0; row_id < rank_; ++row_id) {
                const auto & row = R_table.
                    Get<petuum::DenseRow<float> >(row_id, &row_acc);
                row.CopyToVector(&petuum_row_cache);
                // Non-negativise
                RegVec(petuum_row_cache, R_row_cache);
                for (int col_id = 0; col_id < m; ++col_id) {
                    if (std::abs(R_row_cache[col_id]) < INFINITESIMAL) {
                        R_row_cache[col_id] = 0.0;
                    }
                    if (output_data_format_ == "text") {
                        fout_R << R_row_cache[col_id] << "\t";
                    } else if (output_data_format_ == "binary") {
                        fout_R.write(reinterpret_cast<char*> (
                                &(R_row_cache[col_id])), 4);
                    }
                }
                if (output_data_format_ == "text") {
                    fout_R << "\n";
                }
            }
            fout_R.close();
        }
        // Thread 0 of each client save that client's part of L
        // to output_path_/L.[txt|bin].client_id_
        if (thread_id == 0) {
            if (output_data_format_ == "text") {
                std::string L_filename = output_path_ + "/L.txt."
                    + std::to_string(client_id_);
                fout_L.open(L_filename.c_str());
            } else if (output_data_format_ == "binary") {
                std::string L_filename = output_path_ + "/L.bin."
                    + std::to_string(client_id_);
                fout_L.open(L_filename.c_str(), std::ios::binary);
            } else {
                LOG(FATAL) << "Unrecognized data format: " << output_data_format_;
            }
            for (int col_id_client = 0; col_id_client < client_n;
                    ++col_id_client) {
                if (L_matrix_loader_.GetCol(col_id_client, L_cache)) {
                    for (int row_id = 0; row_id < rank_; ++row_id) {
                        if (std::abs(L_cache[row_id]) < INFINITESIMAL) {
                            L_cache[row_id] = 0.0;
                        }
                        if (output_data_format_ == "text") {
                            fout_L << L_cache[row_id] << "\t";
                        } else if (output_data_format_ == "binary") {
                            fout_L.write(reinterpret_cast<char*> (
                                    &(L_cache[row_id])), 4);
                        }
                    }
                        if (output_data_format_ == "text") {
                            fout_L << "\n";
                        }
                }
            }
            fout_L.close();
        }
        if (thread_id == 0) {
            LOG(INFO) << "Results saved.";
        }
    }

    // Init L and R from cache file
    void NMFEngine::LoadCache(int thread_id, petuum::Table<float> & R_table) {
        // size of matrices
        int m = X_matrix_loader_.GetM();
        int client_n = X_matrix_loader_.GetClientN();
        if (client_id_ == 0 && thread_id == 0) {
            // Load R
            std::ifstream fout_R;
            std::vector<float> R_row_cache(m);

            std::string R_filename;
            if (input_data_format_ == "text") {
                R_filename = cache_path_ + "/R.txt";
                fout_R.open(R_filename.c_str());
            } else if (input_data_format_ == "binary") {
                R_filename = cache_path_ + "/R.bin";
                fout_R.open(R_filename.c_str(), std::ios::binary);
            } else {
                LOG(FATAL) << "Unrecognized data format: " << output_data_format_;
            }

            CHECK(fout_R.good())
                << "Cache file " << R_filename << " does not exist!";
            for (int row_id = 0; row_id < rank_; ++row_id) {
                petuum::UpdateBatch<float> R_update;
                for (int col_id = 0; col_id < m; ++col_id) {
                    if (input_data_format_ == "text") {
                        fout_R >> R_row_cache[col_id];
                    } else if (input_data_format_ == "binary") {
                        fout_R.read(reinterpret_cast<char*> (
                                &(R_row_cache[col_id])), 4);
                    }
                    R_update.Update(col_id, R_row_cache[col_id]);
                }
                R_table.BatchInc(row_id, R_update);
            }
            fout_R.close();
        }
        // Load L
        if (thread_id == 0) {
            std::ifstream fout_L;
            std::vector<float> L_cache(rank_),
                L_inc_cache(rank_);

            std::string L_filename;
            if (input_data_format_ == "text") {
                L_filename = cache_path_ + "/L.txt." +
                    std::to_string(client_id_);
                   fout_L.open(L_filename.c_str());
            } else if (input_data_format_ == "binary") {
                L_filename = cache_path_ + "/L.bin." +
                    std::to_string(client_id_);
                   fout_L.open(L_filename.c_str(), std::ios::binary);
            } else {
                LOG(FATAL) << "Unrecognized data format: " << output_data_format_;
            }

            CHECK(fout_L.good())
                << "Cache file " << L_filename << " does not exist!";
               for (int col_id_client = 0; col_id_client < client_n;
                    ++col_id_client) {
                   if (L_matrix_loader_.GetCol(col_id_client, L_cache)) {
                       for (int row_id = 0; row_id < rank_;
                            ++row_id) {
                        if (input_data_format_ == "text") {
                               fout_L >> L_inc_cache[row_id];
                        } else if (input_data_format_ == "binary") {
                            fout_L.read(reinterpret_cast<char*> (
                                &(L_inc_cache[row_id])), 4);
                        }
                        L_inc_cache[row_id] =
                            L_inc_cache[row_id] - L_cache[row_id];
                       }
                    L_matrix_loader_.IncCol(col_id_client, L_inc_cache, 0.0);
                   }
                L_matrix_loader_.GetCol(col_id_client, L_cache);
               }
               fout_L.close();
        }
    }

    // Init R table
    void NMFEngine::InitRand(int thread_id, petuum::Table<float> & R_table) {
        if (thread_id != 0 || client_id_ != 0)
            return;
        // size of matrices
        int m = X_matrix_loader_.GetM();
        srand((unsigned)time(NULL));
        std::vector<float> R_row_cache(m);
        // petuum row accessor
        petuum::RowAccessor row_acc;
        for (int row_id = 0; row_id < rank_; ++row_id) {
            petuum::UpdateBatch<float> R_update;
            for (int col_id = 0; col_id < m; ++col_id) {
                R_row_cache[col_id] = double(rand()) / RAND_MAX *
                    (init_R_high_ - init_R_low_) + init_R_low_;
            }
            for (int col_id = 0; col_id < m; ++col_id) {
                R_update.Update(col_id, R_row_cache[col_id]);
            }
            R_table.BatchInc(row_id, R_update);
        }
    }

    // Stochastic Gradient Descent Optimization
    void NMFEngine::Start() {
        // thread id on a client
        int thread_id = thread_counter_++;
        petuum::PSTableGroup::RegisterThread();
        LOG(INFO) << "client " << client_id_ << ", thread "
            << thread_id << " registers!";

        // Get R table and loss table
        petuum::Table<float> R_table =
            petuum::PSTableGroup::GetTableOrDie<float>(0);
        petuum::Table<float> loss_table =
            petuum::PSTableGroup::GetTableOrDie<float>(1);

        // size of matrices
        int m = X_matrix_loader_.GetM();
        int client_n = X_matrix_loader_.GetClientN();

        // Cache R table
        Eigen::MatrixXf petuum_table_cache(m, rank_);
        // Accumulate update of R table in minibatch
        Eigen::MatrixXf petuum_update_cache(m, rank_);
        // Cache a column of matrix L
        Eigen::VectorXf Lj(rank_);
        // Cache a column of update of L_j
        Eigen::VectorXf Lj_inc(rank_);
        // Cache a column of data X
        Eigen::VectorXf Xj(m);
        Eigen::VectorXf Xj_inc(m);
        // Cache a row of R table
        std::vector<float> petuum_row_cache(m);

        // Register rows
        if (thread_id == 0) {
            for (int row_id = 0; row_id < rank_; ++row_id) {
                R_table.GetAsyncForced(row_id);
            }
            int loss_table_size = num_eval_per_client_ * num_clients_ * 2;
            for (int row_id = 0; row_id < loss_table_size; ++row_id) {
                loss_table.GetAsyncForced(row_id);
            }
        }
        petuum::PSTableGroup::GlobalBarrier();

        // initialize R
        STATS_APP_INIT_BEGIN();
        if (client_id_ == 0 && thread_id == 0) {
            LOG(INFO) << "starting to initialize R";
        }
        if (load_cache_) {// load L and R from cache
            LoadCache(thread_id, R_table);
        } else { // randomly init R
            InitRand(thread_id, R_table);
        }
        if (thread_id == 0 && client_id_ == 0) {
            LOG(INFO) << "matrix R initialization finished!";
        }
        petuum::PSTableGroup::GlobalBarrier();
        STATS_APP_INIT_END();

        // Optimization Loop
        // Timer
        boost::posix_time::ptime beginT =
            boost::posix_time::microsec_clock::local_time();
        // Step size for optimization
        float step_size_R = init_step_size_R_, step_size_L = init_step_size_L_;

        int num_minibatch = 0;
        for (int iter = 0; iter < num_epochs_; ++iter) {
            // how many minibatches per epoch
            int minibatch_per_epoch = (client_n / num_worker_threads_ > 0)?
                client_n / num_worker_threads_: 1;
            for (int iter_per_epoch = 0; iter_per_epoch * minibatch_size_
                    < minibatch_per_epoch; ++iter_per_epoch) {
                boost::posix_time::time_duration runTime =
                    boost::posix_time::microsec_clock::local_time() - initT_;
                // Terminate and save states to disk if running time exceeds
                // limit
                if (maximum_running_time_ > 0.0 &&
                        (float) runTime.total_milliseconds() >
                        maximum_running_time_*3600*1000) {
                    LOG(INFO) << "Maximum runtime limit activates, "
                        "terminating now!";
                    petuum::PSTableGroup::GlobalBarrier();
                    SaveResults(thread_id, R_table, loss_table);
                    petuum::PSTableGroup::DeregisterThread();
                    return;
                }
                // Update petuum table cache
                petuum::RowAccessor row_acc;
                for (int row_id = 0; row_id < rank_; ++row_id) {
                    const auto & row =
                        R_table.Get<petuum::DenseRow<float> >(row_id, &row_acc);
                    row.CopyToVector(&petuum_row_cache);
                    for (int col_id = 0; col_id < m; ++col_id) {
                        petuum_table_cache(col_id, row_id) =
                            petuum_row_cache[col_id];
                    }
                }
                // evaluate obj
                if (num_minibatch % num_eval_minibatch_ == 0) {
                    boost::posix_time::time_duration elapTime =
                        boost::posix_time::microsec_clock::local_time() - beginT;
                    // evaluate partial obj
                    double obj = 0.0;
                    int num_samples = num_eval_samples_;
                    petuum_table_cache = ( (petuum_table_cache.array() > 0).
                        cast<float>() * petuum_table_cache.array() ).matrix();
                    for (int i = 0; i < num_samples; ++i) {
                        int col_id_client = 0;
                        if (L_matrix_loader_.GetRandCol(col_id_client, Lj)
                                && X_matrix_loader_.GetCol(col_id_client, Xj)) {
                            Xj_inc = Xj - petuum_table_cache * Lj;
                            obj += Xj_inc.squaredNorm();
                        }
                    }
                    obj = obj / num_samples;
                    LOG(INFO) << "iter: " << num_minibatch << ", client "
                        << client_id_ << ", thread " << thread_id <<
                        " average loss: " << obj;
                    // update loss table
                    loss_table.Inc(client_id_ * num_eval_per_client_ +
                            num_minibatch / num_eval_minibatch_,  0,
                            obj/num_worker_threads_);
                    loss_table.Inc((num_clients_+client_id_) * num_eval_per_client_
                            + num_minibatch / num_eval_minibatch_, 0,
                            ((float) elapTime.total_milliseconds()) / 1000
                            / num_worker_threads_);
                    beginT = boost::posix_time::microsec_clock::local_time();
                }
                step_size_R = init_step_size_R_ *
                    pow(step_size_offset_R_ + num_minibatch,
                            -1.0*step_size_pow_R_);
                step_size_L = init_step_size_L_ *
                    pow(step_size_offset_L_ + num_minibatch,
                            -1.0*step_size_pow_L_);
                num_minibatch++;
                // clear update table
                petuum_update_cache.fill(0.0);
                // minibatch
                for (int k = 0; k < minibatch_size_; ++k) {
                    int col_id_client = 0;
                    if (L_matrix_loader_.GetRandCol(col_id_client, Lj)
                            && X_matrix_loader_.GetCol(col_id_client, Xj)) {
                        // update L_j
                        for (int iter_L = 0;
                                iter_L < num_iter_L_per_minibatch_; ++iter_L) {
                            // compute gradient of Lj
                            Lj_inc = step_size_L * 2.0 *
                                ( petuum_table_cache.transpose()
                                 * (Xj - petuum_table_cache * Lj) );
                            L_matrix_loader_.IncCol(col_id_client, Lj_inc, 0.0);

                            // get updated L_j
                            L_matrix_loader_.GetCol(col_id_client, Lj);
                        }
                        // update R
                        Xj_inc = Xj - petuum_table_cache * Lj;
                        petuum_update_cache.noalias() +=
                            step_size_R * 2.0 * Xj_inc * Lj.transpose();
                    }
                }
                // Update R_table
                for (int row_id = 0; row_id < rank_; ++row_id) {
                    petuum::UpdateBatch<float> R_update;
                    for (int col_id = 0; col_id < m; ++col_id) {
                        R_update.Update(col_id,
                                petuum_update_cache(col_id, row_id)
                                / minibatch_size_);
                    }
                    R_table.BatchInc(row_id, R_update);
                }
                petuum::PSTableGroup::Clock();
                // Update R_table to non-negativise
                std::vector<float> R_row_cache(m);
                for (int row_id = 0; row_id < rank_; ++row_id) {
                    const auto & row =
                        R_table.Get<petuum::DenseRow<float> >(row_id, &row_acc);
                    row.CopyToVector(&petuum_row_cache);
                    RegVec(petuum_row_cache, R_row_cache);
                    petuum::UpdateBatch<float> R_update;
                    for (int col_id = 0; col_id < m; ++col_id) {
                        R_update.Update(col_id,
                                (-1.0 * petuum_row_cache[col_id] +
                                R_row_cache[col_id]) / num_clients_ /
                                num_worker_threads_);
                    }
                    R_table.BatchInc(row_id, R_update);
                }
                petuum::PSTableGroup::Clock();
            }
        }
        // Save results to disk
        petuum::PSTableGroup::GlobalBarrier();
        SaveResults(thread_id, R_table, loss_table);
        petuum::PSTableGroup::DeregisterThread();
    }

    NMFEngine::~NMFEngine() {
    }
} // namespace NMF


#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <math.h>
#include <boost/lexical_cast.hpp>
using namespace std;

#include "directory_cache.h"
#include "simulator.h"
#include "config.h"
#include "log.h"
#include "utils.h"

#define DETAILED_TRACKING_ENABLED    1

DirectoryCache::DirectoryCache(Tile* tile,
                               string directory_type_str,
                               UInt32 total_entries,
                               UInt32 associativity,
                               UInt32 cache_block_size,
                               UInt32 max_hw_sharers,
                               UInt32 max_num_sharers,
                               UInt32 num_directory_caches,
                               UInt64 dram_directory_cache_access_delay_in_clock_cycles,
                               ShmemPerfModel* shmem_perf_model):
   m_tile(tile),
   m_total_entries(total_entries),
   m_associativity(associativity),
   m_cache_block_size(cache_block_size),
   m_dram_directory_cache_access_delay_in_clock_cycles(dram_directory_cache_access_delay_in_clock_cycles),
   m_shmem_perf_model(shmem_perf_model),
   m_cache_power_model(NULL),
   m_cache_area_model(NULL),
   m_total_directory_cache_accesses(0),
   m_enabled(false)
{
   LOG_PRINT("Directory Cache ctor enter");
   m_num_sets = m_total_entries / m_associativity;

   // Instantiate the directory
   m_directory = new Directory(directory_type_str, total_entries, max_hw_sharers, max_num_sharers);

   initializeParameters(num_directory_caches);

   volatile float core_frequency = Config::getSingleton()->getCoreFrequency(m_tile->getMainCoreId());
   LOG_PRINT("Got Core Frequency");

   // Size of each directory entry (in bits)
   UInt32 directory_entry_size = m_directory->getDirectoryEntrySize();
   LOG_PRINT("Got Directory Entry Size");

   if (Config::getSingleton()->getEnablePowerModeling())
   {
      m_cache_power_model = new CachePowerModel("directory", ceil(m_total_entries * directory_entry_size / 8),
            directory_entry_size, m_associativity, m_dram_directory_cache_access_delay_in_clock_cycles, core_frequency);
   }
   if (Config::getSingleton()->getEnableAreaModeling())
   {
      m_cache_area_model = new CacheAreaModel("directory", ceil(m_total_entries * directory_entry_size / 8),
            directory_entry_size, m_associativity, m_dram_directory_cache_access_delay_in_clock_cycles, core_frequency);
   }
   LOG_PRINT("Directory Cache ctor exit");
}

DirectoryCache::~DirectoryCache()
{
   delete m_directory;
}

void
DirectoryCache::initializeParameters(UInt32 num_directory_caches)
{
   LOG_PRINT("initializeParameters() enter");
   UInt32 num_tiles = Config::getSingleton()->getTotalTiles();

   m_log_num_sets = floorLog2(m_num_sets);
   m_log_cache_block_size = floorLog2(m_cache_block_size);

   m_log_num_tiles = floorLog2(num_tiles);
   m_log_num_directory_caches = ceilLog2(num_directory_caches);

   IntPtr stack_size = boost::lexical_cast<IntPtr> (Sim()->getCfg()->get("stack/stack_size_per_core"));
   LOG_ASSERT_ERROR(isPower2(stack_size), "stack_size(%#llx) should be a power of 2", stack_size);
   m_log_stack_size = floorLog2(stack_size);

#ifdef DETAILED_TRACKING_ENABLED
   m_set_specific_address_map.resize(m_num_sets);
   m_set_replacement_histogram.resize(m_num_sets);
#endif
   LOG_PRINT("initializeParameters() exit");
}

DirectoryEntry*
DirectoryCache::getDirectoryEntry(IntPtr address)
{
   // Update Performance Model
   if (getShmemPerfModel())
      getShmemPerfModel()->incrCycleCount(m_dram_directory_cache_access_delay_in_clock_cycles);

   if (m_enabled)
   {
      // Update Event & Dynamic Energy Counters
      if (Config::getSingleton()->getEnablePowerModeling())
         m_cache_power_model->updateDynamicEnergy();
      m_total_directory_cache_accesses ++;
   }

   IntPtr tag;
   UInt32 set_index;

   // Assume that it always hit in the Dram Directory Cache for now
   splitAddress(address, tag, set_index);

   // Find the relevant directory entry
   for (UInt32 i = 0; i < m_associativity; i++)
   {
      DirectoryEntry* directory_entry = m_directory->getDirectoryEntry(set_index * m_associativity + i);

      if (directory_entry->getAddress() == address)
      {
         if (getShmemPerfModel())
            getShmemPerfModel()->incrCycleCount(directory_entry->getLatency());
         // Simple check for now. Make sophisticated later
         return directory_entry;
      }
   }

   // Find a free directory entry if one does not currently exist
   for (UInt32 i = 0; i < m_associativity; i++)
   {
      DirectoryEntry* directory_entry = m_directory->getDirectoryEntry(set_index * m_associativity + i);
      if (directory_entry->getAddress() == INVALID_ADDRESS)
      {
         // Simple check for now. Make sophisticated later
         directory_entry->setAddress(address);
         return directory_entry;
      }
   }

   // Check in the m_replaced_directory_entry_list
   vector<DirectoryEntry*>::iterator it;
   for (it = m_replaced_directory_entry_list.begin(); it != m_replaced_directory_entry_list.end(); it++)
   {
      if ((*it)->getAddress() == address)
      {
         return (*it);
      }
   }

   return (DirectoryEntry*) NULL;
}

void
DirectoryCache::getReplacementCandidates(IntPtr address, vector<DirectoryEntry*>& replacement_candidate_list)
{
   assert(getDirectoryEntry(address) == NULL);

   IntPtr tag;
   UInt32 set_index;
   splitAddress(address, tag, set_index);

   for (UInt32 i = 0; i < m_associativity; i++)
   {
      replacement_candidate_list.push_back(m_directory->getDirectoryEntry(set_index * m_associativity + i));
   }
}

DirectoryEntry*
DirectoryCache::replaceDirectoryEntry(IntPtr replaced_address, IntPtr address)
{
   // Update Performance Model
   if (getShmemPerfModel())
      getShmemPerfModel()->incrCycleCount(m_dram_directory_cache_access_delay_in_clock_cycles);

   if (m_enabled)
   {
      // Update Event & Dynamic Energy Counters
      if (Config::getSingleton()->getEnablePowerModeling())
         m_cache_power_model->updateDynamicEnergy();
      m_total_directory_cache_accesses ++;
   }

   IntPtr tag;
   UInt32 set_index;
   splitAddress(replaced_address, tag, set_index);

   for (UInt32 i = 0; i < m_associativity; i++)
   {
      DirectoryEntry* replaced_directory_entry = m_directory->getDirectoryEntry(set_index * m_associativity + i);
      if (replaced_directory_entry->getAddress() == replaced_address)
      {
         m_replaced_directory_entry_list.push_back(replaced_directory_entry);

         DirectoryEntry* directory_entry = m_directory->createDirectoryEntry();
         directory_entry->setAddress(address);
         m_directory->setDirectoryEntry(set_index * m_associativity + i, directory_entry);

#ifdef DETAILED_TRACKING_ENABLED
         m_replaced_address_map[replaced_address] ++;
         m_set_replacement_histogram[set_index] ++;
#endif

         return directory_entry;
      }
   }

   // Should not reach here
   assert(false);
}

void
DirectoryCache::invalidateDirectoryEntry(IntPtr address)
{
   vector<DirectoryEntry*>::iterator it;
   for (it = m_replaced_directory_entry_list.begin(); it != m_replaced_directory_entry_list.end(); it++)
   {
      if ((*it)->getAddress() == address)
      {
         delete (*it);
         m_replaced_directory_entry_list.erase(it);

         return;
      }
   }

   // Should not reach here
   assert(false);
}

void
DirectoryCache::splitAddress(IntPtr address, IntPtr& tag, UInt32& set_index)
{
   IntPtr cache_block_address = address >> (m_log_cache_block_size + m_log_num_directory_caches);

   tag = cache_block_address;
   set_index = computeSetIndex(address);

#ifdef DETAILED_TRACKING_ENABLED
   m_address_map[address] ++;
   m_set_specific_address_map[set_index][address] ++;
#endif
}

IntPtr
DirectoryCache::computeSetIndex(IntPtr address)
{
   UInt32 num_tile_id_bits = (m_log_num_tiles <= m_log_num_sets) ? m_log_num_tiles : m_log_num_sets;
   IntPtr tile_id_bits = (address >> m_log_stack_size) & ((1 << num_tile_id_bits) - 1);

   UInt32 log_num_sub_block_bits = m_log_num_sets - num_tile_id_bits;

   IntPtr sub_block_id = (address >> (m_log_cache_block_size + m_log_num_directory_caches)) \
                         & ((1 << log_num_sub_block_bits) - 1);

   IntPtr super_block_id = (address >> (m_log_cache_block_size + m_log_num_directory_caches + log_num_sub_block_bits)) \
                           & ((1 << num_tile_id_bits) - 1);

   return ((tile_id_bits ^ super_block_id) << log_num_sub_block_bits) + sub_block_id;
}

void
DirectoryCache::outputSummary(ostream& out)
{
   if (m_tile->getId() == 0)
   {
      SInt32 num_tiles = Config::getSingleton()->getTotalTiles();
      SInt32 num_directory_caches = 0;
      UInt64 l2_cache_size = 0;
      try
      {
         num_directory_caches = (UInt32) Sim()->getCfg()->getInt("perf_model/dram/num_controllers");
         l2_cache_size = (UInt64) Sim()->getCfg()->getInt("perf_model/l2_cache/" + \
               Config::getSingleton()->getL2CacheType(m_tile->getId()) + "/cache_size");
      }
      catch (...)
      {
         LOG_PRINT_ERROR("Could not read parameters from cfg file");
      }
      if (num_directory_caches == -1)
         num_directory_caches = num_tiles;

      UInt64 expected_entries_per_directory_cache = (num_tiles * l2_cache_size * 1024 / m_cache_block_size) / num_directory_caches;
      // Convert to a power of 2
      expected_entries_per_directory_cache = UInt64(1) << floorLog2(expected_entries_per_directory_cache);

      if (m_total_entries < (expected_entries_per_directory_cache / 2))
      {
         LOG_PRINT_WARNING("Dram Directory under-provisioned, use \"perf_model/dram_directory/total_entries\" = %u",
               (expected_entries_per_directory_cache / 2));
      }
      else if (m_total_entries > (expected_entries_per_directory_cache * 2))
      {
         LOG_PRINT_WARNING("Dram Directory over-provisioned, use \"perf_model/dram_directory/total_entries\" = %u",
               (expected_entries_per_directory_cache * 2));
      }
   }

   out << "    Total Directory Cache Accesses: " << m_total_directory_cache_accesses << endl;
   // The power and area model summary
   if (Config::getSingleton()->getEnablePowerModeling())
      m_cache_power_model->outputSummary(out);
   if (Config::getSingleton()->getEnableAreaModeling())
      m_cache_area_model->outputSummary(out);

#ifdef DETAILED_TRACKING_ENABLED

   UInt32 max_set_size = 0;
   UInt32 min_set_size = 100000000;
   SInt32 set_index_with_max_size = -1;
   SInt32 set_index_with_min_size = -1;

   UInt64 total_evictions = 0;
   UInt64 max_set_evictions = 0;
   SInt32 set_index_with_max_evictions = -1;

   UInt64 max_address_evictions = 0;
   IntPtr address_with_max_evictions = INVALID_ADDRESS;

   for (UInt32 i = 0; i < m_num_sets; i++)
   {
      // max, min, average set size, set evictions
      if (m_set_specific_address_map[i].size() > max_set_size)
      {
         max_set_size = m_set_specific_address_map[i].size();
         set_index_with_max_size = i;
      }
      if (m_set_specific_address_map[i].size() < min_set_size)
      {
         min_set_size = m_set_specific_address_map[i].size();
         set_index_with_min_size = i;
      }
      if (m_set_replacement_histogram[i] > max_set_evictions)
      {
         max_set_evictions = m_set_replacement_histogram[i];
         set_index_with_max_evictions = i;
      }
      total_evictions += m_set_replacement_histogram[i];
   }

   for (map<IntPtr,UInt64>::iterator it = m_replaced_address_map.begin(); \
         it != m_replaced_address_map.end(); it++)
   {
      if ((*it).second > max_address_evictions)
      {
         max_address_evictions = (*it).second;
         address_with_max_evictions = (*it).first;
      }
   }

   // Total Number of Addresses
   // Max Set Size, Average Set Size, Min Set Size
   // Evictions: Average per set, Max, Address with max evictions
   out << "    Total Addresses: " << m_address_map.size() << endl;

   out << "    Average set size: " << float(m_address_map.size()) / m_num_sets << endl;
   out << "    Set index with max size: " << set_index_with_max_size << endl;
   out << "    Max set size: " << max_set_size << endl;
   out << "    Set index with min size: " << set_index_with_min_size << endl;
   out << "    Min set size: " << min_set_size << endl;

   out << "    Average evictions per set: " << float(total_evictions) / m_num_sets << endl;
   out << "    Set index with max evictions: " << set_index_with_max_evictions << endl;
   out << "    Max set evictions: " << max_set_evictions << endl;

   out << "    Address with max evictions: 0x" << hex << address_with_max_evictions << dec << endl;
   out << "    Max address evictions: " << max_address_evictions << endl;
#endif
}

void
DirectoryCache::dummyOutputSummary(ostream& out)
{
   // The power and area model summary
   if (Config::getSingleton()->getEnablePowerModeling())
      CachePowerModel::dummyOutputSummary(out);
   if (Config::getSingleton()->getEnableAreaModeling())
      CacheAreaModel::dummyOutputSummary(out);

#ifdef DETAILED_TRACKING_ENABLED
   out << "Dram Directory Cache: " << endl;
   out << "    Total Addresses: NA" << endl;

   out << "    Average set size: NA" << endl;
   out << "    Set index with max size: NA" << endl;
   out << "    Max set size: NA" << endl;
   out << "    Set index with min size: NA" << endl;
   out << "    Min set size: NA" << endl;

   out << "    Average evictions per set: NA" << endl;
   out << "    Set index with max evictions: NA" << endl;
   out << "    Max set evictions: NA" << endl;

   out << "    Address with max evictions: NA" << endl;
   out << "    Max address evictions: NA" << endl;
#endif
}


#ifndef VIENNACL_LINALG_DETAIL_AMG_AMG_INTERPOL_HPP
#define VIENNACL_LINALG_DETAIL_AMG_AMG_INTERPOL_HPP

/* =========================================================================
   Copyright (c) 2010-2014, Institute for Microelectronics,
                            Institute for Analysis and Scientific Computing,
                            TU Wien.
   Portions of this software are copyright by UChicago Argonne, LLC.

                            -----------------
                  ViennaCL - The Vienna Computing Library
                            -----------------

   Project Head:    Karl Rupp                   rupp@iue.tuwien.ac.at

   (A list of authors and contributors can be found in the PDF manual)

   License:         MIT (X11), see file LICENSE in the base directory
============================================================================= */

/** @file amg_interpol.hpp
    @brief Implementations of several variants of the AMG interpolation operators (setup phase). Experimental.
*/

#include <boost/numeric/ublas/vector.hpp>
#include <cmath>
#include "viennacl/linalg/detail/amg/amg_base.hpp"

#include <map>
#ifdef VIENNACL_WITH_OPENMP
#include <omp.h>
#endif

#include "viennacl/linalg/detail/amg/amg_debug.hpp"

namespace viennacl
{
namespace linalg
{
namespace detail
{
namespace amg
{

/** @brief Calls the right function to build interpolation matrix
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
 * @param tag          AMG preconditioner tag
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag & tag)
{
  switch (tag.get_interpol())
  {
  case VIENNACL_AMG_INTERPOL_DIRECT:  amg_interpol_direct (level, A, P, pointvector, tag); break;
  case VIENNACL_AMG_INTERPOL_CLASSIC: amg_interpol_classic(level, A, P, pointvector, tag); break;
  case VIENNACL_AMG_INTERPOL_AG:      amg_interpol_ag     (level, A, P, pointvector, tag); break;
  case VIENNACL_AMG_INTERPOL_SA:      amg_interpol_sa     (level, A, P, pointvector, tag); break;
  }
}

/** @brief Direct interpolation. Multi-threaded! (VIENNACL_AMG_INTERPOL_DIRECT)
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
 * @param tag          AMG preconditioner tag
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol_direct(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag & tag)
{
  typedef typename InternalT1::value_type         SparseMatrixType;
  //typedef typename InternalType2::value_type    PointVectorType;
  typedef typename SparseMatrixType::value_type   ScalarType;
  typedef typename SparseMatrixType::iterator1    InternalRowIterator;
  typedef typename SparseMatrixType::iterator2    InternalColIterator;

  unsigned int c_points = pointvector[level].get_cpoints();

  // Setup Prolongation/Interpolation matrix
  P[level] = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), c_points);
  P[level].clear();

  // Assign indices to C points
  pointvector[level].build_index();

  // Direct Interpolation (Yang, p.14)
#ifdef VIENNACL_WITH_OPENMP
  #pragma omp parallel for
#endif
  for (long x=0; x < static_cast<long>(pointvector[level].size()); ++x)
  {
    amg_point *pointx = pointvector[level][static_cast<unsigned int>(x)];
    /*if (A[level](x,x) > 0)
      diag_sign = 1;
    else
      diag_sign = -1;*/

    // When the current line corresponds to a C point then the diagonal coefficient is 1 and the rest 0
    if (pointx->is_cpoint())
      P[level](static_cast<unsigned int>(x),pointx->get_coarse_index()) = 1;

    // When the current line corresponds to a F point then the diagonal is 0 and the rest has to be computed (Yang, p.14)
    if (pointx->is_fpoint())
    {
      // Jump to row x
      InternalRowIterator row_iter = A[level].begin1();
      row_iter += vcl_size_t(x);

      // Row sum of coefficients (without diagonal) and sum of influencing C point coefficients has to be computed
      ScalarType row_sum = 0;
      ScalarType c_sum   = 0;
      ScalarType diag    = 0;
      for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
      {
        long y = static_cast<long>(col_iter.index2());
        if (x == y)// || *col_iter * diag_sign > 0)
        {
          diag += *col_iter;
          continue;
        }

        // Sum all other coefficients in line x
        row_sum += *col_iter;

        amg_point *pointy = pointvector[level][static_cast<unsigned int>(y)];
        // Sum all coefficients that correspond to a strongly influencing C point
        if (pointy->is_cpoint())
          if (pointx->is_influencing(pointy))
            c_sum += *col_iter;
      }
      ScalarType temp_res = -row_sum/(c_sum*diag);

      // Iterate over all strongly influencing points of point x
      for (amg_point::iterator iter = pointx->begin_influencing(); iter != pointx->end_influencing(); ++iter)
      {
        amg_point *pointy = *iter;
        // The value is only non-zero for columns that correspond to a C point
        if (pointy->is_cpoint())
        {
          if (temp_res > 0 || temp_res < 0)
            P[level](static_cast<unsigned int>(x), pointy->get_coarse_index()) = temp_res * A[level](static_cast<unsigned int>(x),pointy->get_index());
        }
      }

      //Truncate interpolation if chosen
      if (tag.get_interpolweight() > 0)
        amg_truncate_row(P[level], static_cast<unsigned int>(x), tag);
    }
  }

  // P test
  //test_interpolation(A[level], P[level], Pointvector[level]);

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Prolongation Matrix:" << std::endl;
  printmatrix (P[level]);
  #endif
}

/** @brief Classical interpolation. Don't use with onepass classical coarsening or RS0 (Yang, p.14)! Multi-threaded! (VIENNACL_AMG_INTERPOL_CLASSIC)
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
 * @param tag          AMG preconditioner tag
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol_classic(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag & tag)
{
  typedef typename InternalT1::value_type           SparseMatrixType;
  //typedef typename InternalType2::value_type      PointVectorType;
  typedef typename SparseMatrixType::value_type     ScalarType;
  typedef typename SparseMatrixType::iterator1      InternalRowIterator;
  typedef typename SparseMatrixType::iterator2      InternalColIterator;

  unsigned int c_points = pointvector[level].get_cpoints();

  // Setup Prolongation/Interpolation matrix
  P[level] = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), c_points);
  P[level].clear();

  // Assign indices to C points
  pointvector[level].build_index();

  // Classical Interpolation (Yang, p.13-14)
#ifdef VIENNACL_WITH_OPENMP
  #pragma omp parallel for
#endif
  for (long x=0; x < static_cast<long>(pointvector[level].size()); ++x)
  {
    amg_point *pointx = pointvector[level][static_cast<unsigned int>(x)];
    int diag_sign = (A[level](static_cast<unsigned int>(x),static_cast<unsigned int>(x)) > 0) ? 1 : -1;

    // When the current line corresponds to a C point then the diagonal coefficient is 1 and the rest 0
    if (pointx->is_cpoint())
      P[level](static_cast<unsigned int>(x),pointx->get_coarse_index()) = 1;

    // When the current line corresponds to a F point then the diagonal is 0 and the rest has to be computed (Yang, p.14)
    if (pointx->is_fpoint())
    {
      // Jump to row x
      InternalRowIterator row_iter = A[level].begin1();
      row_iter += vcl_size_t(x);

      ScalarType weak_sum = 0;
      amg_sparsevector<ScalarType> c_sum_row(static_cast<unsigned int>(A[level].size1()));
      c_sum_row.clear();
      for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
      {
        long k = static_cast<unsigned int>(col_iter.index2());
        amg_point *pointk = pointvector[level][static_cast<unsigned int>(k)];

        // Sum of weakly influencing neighbors + diagonal coefficient
        if (x == k || !pointx->is_influencing(pointk))// || *col_iter * diag_sign > 0)
        {
          weak_sum += *col_iter;
          continue;
        }

        // Sums of coefficients in row k (strongly influening F neighbors) of C point neighbors of x are calculated
        if (pointk->is_fpoint() && pointx->is_influencing(pointk))
        {
          for (amg_point::iterator iter = pointx->begin_influencing(); iter != pointx->end_influencing(); ++iter)
          {
            amg_point *pointm = *iter;
            long m = pointm->get_index();

            if (pointm->is_cpoint())
              // Only use coefficients that have opposite sign of diagonal.
              if (A[level](static_cast<unsigned int>(k),static_cast<unsigned int>(m)) * ScalarType(diag_sign) < 0)
                c_sum_row[static_cast<unsigned int>(k)] += A[level](static_cast<unsigned int>(k), static_cast<unsigned int>(m));
          }
          continue;
        }
      }

      // Iterate over all strongly influencing points of point x
      for (amg_point::iterator iter = pointx->begin_influencing(); iter != pointx->end_influencing(); ++iter)
      {
        amg_point *pointy = *iter;
        long y = pointy->get_index();

        // The value is only non-zero for columns that correspond to a C point
        if (pointy->is_cpoint())
        {
          ScalarType strong_sum = 0;
          // Calculate term for strongly influencing F neighbors
          for (typename amg_sparsevector<ScalarType>::iterator iter2 = c_sum_row.begin(); iter2 != c_sum_row.end(); ++iter2)
          {
            long k = iter2.index();
            // Only use coefficients that have opposite sign of diagonal.
            if (A[level](static_cast<unsigned int>(k),static_cast<unsigned int>(y)) * ScalarType(diag_sign) < 0)
              strong_sum += (A[level](static_cast<unsigned int>(x),static_cast<unsigned int>(k)) * A[level](static_cast<unsigned int>(k),static_cast<unsigned int>(y))) / (*iter2);
          }

          // Calculate coefficient
          ScalarType temp_res = - (A[level](static_cast<unsigned int>(x),static_cast<unsigned int>(y)) + strong_sum) / (weak_sum);
          if (temp_res < 0 || temp_res > 0)
            P[level](static_cast<unsigned int>(x),pointy->get_coarse_index()) = temp_res;
        }
      }

      //Truncate iteration if chosen
      if (tag.get_interpolweight() > 0)
        amg_truncate_row(P[level], static_cast<unsigned int>(x), tag);
    }
  }

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Prolongation Matrix:" << std::endl;
  printmatrix (P[level]);
  #endif
}

/** @brief Interpolation truncation (for VIENNACL_AMG_INTERPOL_DIRECT and VIENNACL_AMG_INTERPOL_CLASSIC)
*
* @param P    Interpolation matrix
* @param row  Row which has to be truncated
* @param tag  AMG preconditioner tag
*/
template<typename SparseMatrixT>
void amg_truncate_row(SparseMatrixT & P, unsigned int row, amg_tag & tag)
{
  typedef typename SparseMatrixT::value_type   ScalarType;
  typedef typename SparseMatrixT::iterator1    InternalRowIterator;
  typedef typename SparseMatrixT::iterator2    InternalColIterator;

  ScalarType row_max, row_min, row_sum_pos, row_sum_neg, row_sum_pos_scale, row_sum_neg_scale;

  InternalRowIterator row_iter = P.begin1();
  row_iter += row;

  row_max = 0;
  row_min = 0;
  row_sum_pos = 0;
  row_sum_neg = 0;

  // Truncate interpolation by making values to zero that are a lot smaller than the biggest value in a row
  // Determine max entry and sum of row (seperately for negative and positive entries)
  for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
  {
    if (*col_iter > row_max)
      row_max = *col_iter;
    if (*col_iter < row_min)
      row_min = *col_iter;
    if (*col_iter > 0)
      row_sum_pos += *col_iter;
    if (*col_iter < 0)
      row_sum_neg += *col_iter;
  }

  row_sum_pos_scale = row_sum_pos;
  row_sum_neg_scale = row_sum_neg;

  // Make certain values to zero (seperately for negative and positive entries)
  for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
  {
    if (*col_iter > 0 && *col_iter < tag.get_interpolweight() * row_max)
    {
      row_sum_pos_scale -= *col_iter;
      *col_iter = 0;
    }
    if (*col_iter < 0 && *col_iter > tag.get_interpolweight() * row_min)
    {
      row_sum_pos_scale -= *col_iter;
      *col_iter = 0;
    }
  }

  // Scale remaining values such that row sum is unchanged
  for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
  {
    if (*col_iter > 0)
      *col_iter = *col_iter *(row_sum_pos/row_sum_pos_scale);
    if (*col_iter < 0)
      *col_iter = *col_iter *(row_sum_neg/row_sum_neg_scale);
  }
}

/** @brief AG (aggregation based) interpolation. Multi-Threaded! (VIENNACL_INTERPOL_SA)
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol_ag(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag)
{
  typedef typename InternalT1::value_type SparseMatrixType;
  //typedef typename InternalType2::value_type PointVectorType;
  //typedef typename SparseMatrixType::value_type ScalarType;
  //typedef typename SparseMatrixType::iterator1 InternalRowIterator;
  //typedef typename SparseMatrixType::iterator2 InternalColIterator;

  unsigned int c_points = pointvector[level].get_cpoints();

  P[level] = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), c_points);
  P[level].clear();

  // Assign indices to C points
  pointvector[level].build_index();

  // Set prolongation such that F point is interpolated (weight=1) by the aggregate it belongs to (Vanek et al p.6)
#ifdef VIENNACL_WITH_OPENMP
  #pragma omp parallel for
#endif
  for (long x=0; x<static_cast<long>(pointvector[level].size()); ++x)
  {
    amg_point *pointx = pointvector[level][static_cast<unsigned int>(x)];
    amg_point *pointy = pointvector[level][pointx->get_aggregate()];
    // Point x belongs to aggregate y.
    P[level](static_cast<unsigned int>(x), pointy->get_coarse_index()) = 1;
  }

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Aggregation based Prolongation:" << std::endl;
  printmatrix(P[level]);
  #endif
}

/** @brief SA (smoothed aggregate) interpolation. Multi-Threaded! (VIENNACL_INTERPOL_SA)
 * @param level        Coarse level identifier
 * @param A            Operator matrix on all levels
 * @param P            Prolongation matrices. P[level] is constructed
 * @param pointvector  Vector of points on all levels
 * @param tag          AMG preconditioner tag
*/
template<typename InternalT1, typename InternalT2>
void amg_interpol_sa(unsigned int level, InternalT1 & A, InternalT1 & P, InternalT2 & pointvector, amg_tag & tag)
{
  typedef typename InternalT1::value_type         SparseMatrixType;
  //typedef typename InternalType2::value_type    PointVectorType;
  typedef typename SparseMatrixType::value_type   ScalarType;
  typedef typename SparseMatrixType::iterator1    InternalRowIterator;
  typedef typename SparseMatrixType::iterator2    InternalColIterator;

  unsigned int c_points = pointvector[level].get_cpoints();

  InternalT1 P_tentative = InternalT1(P.size());
  SparseMatrixType Jacobi = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), static_cast<unsigned int>(A[level].size2()));
  Jacobi.clear();
  P[level] = SparseMatrixType(static_cast<unsigned int>(A[level].size1()), c_points);
  P[level].clear();

  // Build Jacobi Matrix via filtered A matrix (Vanek et al. p.6)
#ifdef VIENNACL_WITH_OPENMP
  #pragma omp parallel for
#endif
  for (long x=0; x<static_cast<long>(A[level].size1()); ++x)
  {
    ScalarType diag = 0;
    InternalRowIterator row_iter = A[level].begin1();
    row_iter += vcl_size_t(x);
    for (InternalColIterator col_iter = row_iter.begin(); col_iter != row_iter.end(); ++col_iter)
    {
      long y = static_cast<long>(col_iter.index2());
      // Determine the structure of the Jacobi matrix by using a filtered matrix of A:
      // The diagonal consists of the diagonal coefficient minus all coefficients of points not in the neighborhood of x.
      // All other coefficients are the same as in A.
      // Already use Jacobi matrix to save filtered A matrix to speed up computation.
      if (x == y)
        diag += *col_iter;
      else if (!pointvector[level][static_cast<unsigned int>(x)]->is_influencing(pointvector[level][static_cast<unsigned int>(y)]))
        diag += -*col_iter;
      else
        Jacobi (static_cast<unsigned int>(x), static_cast<unsigned int>(y)) = *col_iter;
    }
    InternalRowIterator row_iter2 = Jacobi.begin1();
    row_iter2 += vcl_size_t(x);
    // Traverse through filtered A matrix and compute the Jacobi filtering
    for (InternalColIterator col_iter2 = row_iter2.begin(); col_iter2 != row_iter2.end(); ++col_iter2)
    {
        *col_iter2 = - static_cast<ScalarType>(tag.get_interpolweight())/diag * *col_iter2;
    }
    // Diagonal can be computed seperately.
    Jacobi (static_cast<unsigned int>(x), static_cast<unsigned int>(x)) = 1 - static_cast<ScalarType>(tag.get_interpolweight());
  }

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Jacobi Matrix:" << std::endl;
  printmatrix(Jacobi);
  #endif

  // Use AG interpolation as tentative prolongation
  amg_interpol_ag(level, A, P_tentative, pointvector, tag);

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Tentative Prolongation:" << std::endl;
  printmatrix(P_tentative[level]);
  #endif

  // Multiply Jacobi matrix with tentative prolongation to get actual prolongation
  amg_mat_prod(Jacobi,P_tentative[level],P[level]);

  #ifdef VIENNACL_AMG_DEBUG
  std::cout << "Prolongation Matrix:" << std::endl;
  printmatrix(P[level]);
  #endif
}

} //namespace amg
} //namespace detail
} //namespace linalg
} //namespace viennacl

#endif


#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
#include <iomanip>
#include <optional>
#include <cctype>
#include <map>
#include <vector>
#include <range/v3/view.hpp>
#include <range/v3/algorithm.hpp>
#include <boost/dynamic_bitset.hpp>
#include "knothash.h"
#include "../aoc2020/matrix.h"
#include "../aoc2020/mypair.h"
#include "../aoc2020/combinations_algo.h"
namespace views = ranges::views;
using mybitset = boost::dynamic_bitset<std::uint8_t>;
using uint8 = std::uint8_t;
std::string to_str(std::array<std::uint8_t, 16> &r)
{
  //auto r = (*this)(s);
  auto hexstring = r | views::transform([](auto t) {
    std::string result;
    char hex[] = "0123456789abcdef";
    result += hex[(t / 16)];
    result += hex[(t % 16)];
    return result;
  });
  std::string sres;
  return ranges::accumulate(hexstring, sres);
}
struct Game
{
  static inline constexpr int NX = 130;
  using Network = FixedMatrix<bool, NX, NX>;

private:
  Network networks_;

public:
  friend std::istream &operator>>(std::istream &is, Game &g)
  {
    std::string str;
    is >> str;

    Knothash hash_;
    auto range = views::ints | views::take(128) | views::transform([str, hash_](int i) {
      auto vec = hash_(str + std::string("-") + std::to_string(i));
      mybitset bitset(vec.begin(), vec.end());

      return bitset;
    }) | views::common;

    int i = 1;
    for (auto row : range) {
      for (int j = 1; j < NX - 1; ++j) {
        auto blocknum = (j - 1) / 8;
        auto inblockpos = (j - 1) % 8;
        auto index = blocknum * 8 + (8 - 1 - inblockpos);
        bool t = row[index];
        g.networks_(i, j) = t;
      }
      ++i;
    }
    return is;
  }
  std::array<Mypair, 4> step_ = {
    Mypair{ 0, -1 },
    Mypair{ 0, 1 },
    Mypair{ -1, 0 },
    Mypair{ 1, 0 }
  };
  void setbrick(Network &n, Mypair pos)
  {
    n(pos.x, pos.y) = 0;
  }
  bool isroad(Mypair p) const
  {
    return networks_(p.x, p.y);
  }
  bool isroad(int i, int j) const
  {
    return isroad(Mypair(i, j));
  }
  int CountGroup()
  {
    int n = 0;
    for (int i = 1; i < 129; ++i) {
      for (int j = 1; j < 129; ++j) {
        if (isroad(i, j)) {
          shortestpath(Mypair(i, j));
          ++n;
        }
      }
    }
    return n;
  }

  void shortestpath(Mypair pos)
  {
    std::vector<Mypair> curr;
    curr.push_back(pos);
    setbrick(networks_, pos);
    for (int j = 0;; ++j) {
      std::vector<Mypair> adjs;
      for (auto cur : curr) {
        for (int i = 0; i < 4; ++i) {
          if (isroad(cur + step_[i])) {
            setbrick(networks_, cur + step_[i]);
            adjs.push_back(cur + step_[i]);
          }
        }
      }
      curr = adjs;
      if (adjs.empty()) {
        break;
      }
    }
  }
};
int main(int argc, char **argv)
{
  if (argc > 1) {
    Game g;
    std::string s(argv[1]);
    std::istringstream iss(s);
    iss >> g;
    std::cout << g.CountGroup() << std::endl;
  }
}


#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> test_double_D_D(const Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & M)
{
	return M;
}
void export_double_D_D()
{
	boost::python::def("test_double_D_D",test_double_D_D);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, Eigen::Dynamic, 1> test_double_D_1(const Eigen::Matrix<double, Eigen::Dynamic, 1> & M)
{
	return M;
}
void export_double_D_1()
{
	boost::python::def("test_double_D_1",test_double_D_1);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, Eigen::Dynamic, 2> test_double_D_2(const Eigen::Matrix<double, Eigen::Dynamic, 2> & M)
{
	return M;
}
void export_double_D_2()
{
	boost::python::def("test_double_D_2",test_double_D_2);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, Eigen::Dynamic, 3> test_double_D_3(const Eigen::Matrix<double, Eigen::Dynamic, 3> & M)
{
	return M;
}
void export_double_D_3()
{
	boost::python::def("test_double_D_3",test_double_D_3);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, Eigen::Dynamic, 4> test_double_D_4(const Eigen::Matrix<double, Eigen::Dynamic, 4> & M)
{
	return M;
}
void export_double_D_4()
{
	boost::python::def("test_double_D_4",test_double_D_4);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, Eigen::Dynamic, 5> test_double_D_5(const Eigen::Matrix<double, Eigen::Dynamic, 5> & M)
{
	return M;
}
void export_double_D_5()
{
	boost::python::def("test_double_D_5",test_double_D_5);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, Eigen::Dynamic, 6> test_double_D_6(const Eigen::Matrix<double, Eigen::Dynamic, 6> & M)
{
	return M;
}
void export_double_D_6()
{
	boost::python::def("test_double_D_6",test_double_D_6);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 1, Eigen::Dynamic> test_double_1_D(const Eigen::Matrix<double, 1, Eigen::Dynamic> & M)
{
	return M;
}
void export_double_1_D()
{
	boost::python::def("test_double_1_D",test_double_1_D);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 1, 1> test_double_1_1(const Eigen::Matrix<double, 1, 1> & M)
{
	return M;
}
void export_double_1_1()
{
	boost::python::def("test_double_1_1",test_double_1_1);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 1, 2> test_double_1_2(const Eigen::Matrix<double, 1, 2> & M)
{
	return M;
}
void export_double_1_2()
{
	boost::python::def("test_double_1_2",test_double_1_2);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 1, 3> test_double_1_3(const Eigen::Matrix<double, 1, 3> & M)
{
	return M;
}
void export_double_1_3()
{
	boost::python::def("test_double_1_3",test_double_1_3);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 1, 4> test_double_1_4(const Eigen::Matrix<double, 1, 4> & M)
{
	return M;
}
void export_double_1_4()
{
	boost::python::def("test_double_1_4",test_double_1_4);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 1, 5> test_double_1_5(const Eigen::Matrix<double, 1, 5> & M)
{
	return M;
}
void export_double_1_5()
{
	boost::python::def("test_double_1_5",test_double_1_5);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 1, 6> test_double_1_6(const Eigen::Matrix<double, 1, 6> & M)
{
	return M;
}
void export_double_1_6()
{
	boost::python::def("test_double_1_6",test_double_1_6);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 2, Eigen::Dynamic> test_double_2_D(const Eigen::Matrix<double, 2, Eigen::Dynamic> & M)
{
	return M;
}
void export_double_2_D()
{
	boost::python::def("test_double_2_D",test_double_2_D);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 2, 1> test_double_2_1(const Eigen::Matrix<double, 2, 1> & M)
{
	return M;
}
void export_double_2_1()
{
	boost::python::def("test_double_2_1",test_double_2_1);
}

#include <Eigen/Core>

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

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 2, 3> test_double_2_3(const Eigen::Matrix<double, 2, 3> & M)
{
	return M;
}
void export_double_2_3()
{
	boost::python::def("test_double_2_3",test_double_2_3);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 2, 4> test_double_2_4(const Eigen::Matrix<double, 2, 4> & M)
{
	return M;
}
void export_double_2_4()
{
	boost::python::def("test_double_2_4",test_double_2_4);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 2, 5> test_double_2_5(const Eigen::Matrix<double, 2, 5> & M)
{
	return M;
}
void export_double_2_5()
{
	boost::python::def("test_double_2_5",test_double_2_5);
}

#include <Eigen/Core>

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

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 3, Eigen::Dynamic> test_double_3_D(const Eigen::Matrix<double, 3, Eigen::Dynamic> & M)
{
	return M;
}
void export_double_3_D()
{
	boost::python::def("test_double_3_D",test_double_3_D);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 3, 1> test_double_3_1(const Eigen::Matrix<double, 3, 1> & M)
{
	return M;
}
void export_double_3_1()
{
	boost::python::def("test_double_3_1",test_double_3_1);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 3, 2> test_double_3_2(const Eigen::Matrix<double, 3, 2> & M)
{
	return M;
}
void export_double_3_2()
{
	boost::python::def("test_double_3_2",test_double_3_2);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 3, 3> test_double_3_3(const Eigen::Matrix<double, 3, 3> & M)
{
	return M;
}
void export_double_3_3()
{
	boost::python::def("test_double_3_3",test_double_3_3);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 3, 4> test_double_3_4(const Eigen::Matrix<double, 3, 4> & M)
{
	return M;
}
void export_double_3_4()
{
	boost::python::def("test_double_3_4",test_double_3_4);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 3, 5> test_double_3_5(const Eigen::Matrix<double, 3, 5> & M)
{
	return M;
}
void export_double_3_5()
{
	boost::python::def("test_double_3_5",test_double_3_5);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 3, 6> test_double_3_6(const Eigen::Matrix<double, 3, 6> & M)
{
	return M;
}
void export_double_3_6()
{
	boost::python::def("test_double_3_6",test_double_3_6);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 4, Eigen::Dynamic> test_double_4_D(const Eigen::Matrix<double, 4, Eigen::Dynamic> & M)
{
	return M;
}
void export_double_4_D()
{
	boost::python::def("test_double_4_D",test_double_4_D);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 4, 1> test_double_4_1(const Eigen::Matrix<double, 4, 1> & M)
{
	return M;
}
void export_double_4_1()
{
	boost::python::def("test_double_4_1",test_double_4_1);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 4, 2> test_double_4_2(const Eigen::Matrix<double, 4, 2> & M)
{
	return M;
}
void export_double_4_2()
{
	boost::python::def("test_double_4_2",test_double_4_2);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 4, 3> test_double_4_3(const Eigen::Matrix<double, 4, 3> & M)
{
	return M;
}
void export_double_4_3()
{
	boost::python::def("test_double_4_3",test_double_4_3);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 4, 4> test_double_4_4(const Eigen::Matrix<double, 4, 4> & M)
{
	return M;
}
void export_double_4_4()
{
	boost::python::def("test_double_4_4",test_double_4_4);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 4, 5> test_double_4_5(const Eigen::Matrix<double, 4, 5> & M)
{
	return M;
}
void export_double_4_5()
{
	boost::python::def("test_double_4_5",test_double_4_5);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 4, 6> test_double_4_6(const Eigen::Matrix<double, 4, 6> & M)
{
	return M;
}
void export_double_4_6()
{
	boost::python::def("test_double_4_6",test_double_4_6);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 5, Eigen::Dynamic> test_double_5_D(const Eigen::Matrix<double, 5, Eigen::Dynamic> & M)
{
	return M;
}
void export_double_5_D()
{
	boost::python::def("test_double_5_D",test_double_5_D);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 5, 1> test_double_5_1(const Eigen::Matrix<double, 5, 1> & M)
{
	return M;
}
void export_double_5_1()
{
	boost::python::def("test_double_5_1",test_double_5_1);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 5, 2> test_double_5_2(const Eigen::Matrix<double, 5, 2> & M)
{
	return M;
}
void export_double_5_2()
{
	boost::python::def("test_double_5_2",test_double_5_2);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 5, 3> test_double_5_3(const Eigen::Matrix<double, 5, 3> & M)
{
	return M;
}
void export_double_5_3()
{
	boost::python::def("test_double_5_3",test_double_5_3);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 5, 4> test_double_5_4(const Eigen::Matrix<double, 5, 4> & M)
{
	return M;
}
void export_double_5_4()
{
	boost::python::def("test_double_5_4",test_double_5_4);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 5, 5> test_double_5_5(const Eigen::Matrix<double, 5, 5> & M)
{
	return M;
}
void export_double_5_5()
{
	boost::python::def("test_double_5_5",test_double_5_5);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 5, 6> test_double_5_6(const Eigen::Matrix<double, 5, 6> & M)
{
	return M;
}
void export_double_5_6()
{
	boost::python::def("test_double_5_6",test_double_5_6);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 6, Eigen::Dynamic> test_double_6_D(const Eigen::Matrix<double, 6, Eigen::Dynamic> & M)
{
	return M;
}
void export_double_6_D()
{
	boost::python::def("test_double_6_D",test_double_6_D);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 6, 1> test_double_6_1(const Eigen::Matrix<double, 6, 1> & M)
{
	return M;
}
void export_double_6_1()
{
	boost::python::def("test_double_6_1",test_double_6_1);
}

#include <Eigen/Core>

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

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 6, 3> test_double_6_3(const Eigen::Matrix<double, 6, 3> & M)
{
	return M;
}
void export_double_6_3()
{
	boost::python::def("test_double_6_3",test_double_6_3);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 6, 4> test_double_6_4(const Eigen::Matrix<double, 6, 4> & M)
{
	return M;
}
void export_double_6_4()
{
	boost::python::def("test_double_6_4",test_double_6_4);
}

#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<double, 6, 5> test_double_6_5(const Eigen::Matrix<double, 6, 5> & M)
{
	return M;
}
void export_double_6_5()
{
	boost::python::def("test_double_6_5",test_double_6_5);
}

#include <Eigen/Core>

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


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE MultiNll0InplaceTest

#include <../random_util.hpp>
#include <boost/filesystem.hpp>
#include <boost/test/unit_test.hpp>
#include <chrono>
#include <complex>
#include <filereader.hpp>
#include <iostream>
#include <popart/builder.hpp>
#include <popart/dataflow.hpp>
#include <popart/devicemanager.hpp>
#include <popart/inputshapeinfo.hpp>
#include <popart/ir.hpp>
#include <popart/names.hpp>
#include <popart/ndarraywrapper.hpp>
#include <popart/op/add.hpp>
#include <popart/op/identity.hpp>
#include <popart/op/nll.hpp>
#include <popart/popx/devicex.hpp>
#include <popart/session.hpp>
#include <popart/sgd.hpp>
#include <popart/stepio.hpp>
#include <popart/tensordata.hpp>
#include <popart/tensornames.hpp>
#include <popart/testdevice.hpp>

using namespace popart;

BOOST_AUTO_TEST_CASE(test) {

  // (see T11824, T12152, D16243)
  //
  //
  // Input ---|          |--slice--softmax--nll
  //          |--matmul--|
  // Weight --|          |--slice--softmax--nll
  //
  //  backwards :
  //
  //   ... sliceGrad---|
  //                   |--Add-- (the gradient of the output of matmul).
  //   ... sliceGrad---|
  //
  // We disable PadSum pattern to retain the slice grads.

  auto getFinalWeights = [](int batchesPerStep,
                            const std::array<float, 6 * 2>
                                &vWeight, // initial weights
                            bool doInplace) {
    auto builder     = Builder::create();
    auto aiOnnx      = builder->aiOnnxOpset9();
    auto aiGraphcore = builder->aiGraphcoreOpset1();

    int batchSize = 1;

    // data
    TensorInfo dataSampleInfo{"FLOAT", std::vector<int64_t>{4, 6}};
    TensorInfo dataBatchInfo{"FLOAT", std::vector<int64_t>{batchSize, 4, 6}};

    TensorInfo dataStepInfo{
        "FLOAT", std::vector<int64_t>{batchesPerStep, batchSize, 4, 6}};
    auto input = builder->addInputTensor(dataBatchInfo);

    // weights
    TensorInfo weightInfo{"FLOAT", std::vector<int64_t>{6, 2}};
    ConstVoidData cvWeight = {vWeight.data(), weightInfo};
    auto weight            = builder->addInitializedInputTensor(cvWeight);

    // labels 0 and 1
    TensorInfo labelSampleInfo{"INT32", std::vector<int64_t>{1}};
    TensorInfo labelBatchInfo{"INT32", std::vector<int64_t>{batchSize, 1}};
    TensorInfo labelStepInfo{
        "INT32", std::vector<int64_t>{batchesPerStep, batchSize, 1}};

    // matmul (1, 4, 2) (why not (4, 2)?)
    auto mmOut = aiOnnx.matmul({input, weight});

    auto slice0 =
        aiOnnx.slice({mmOut}, {1, 4, 1}, {0, 0, 0}, {0, 1, 2}, "slc0");
    auto reshape0 = builder->reshape_const(aiOnnx, {slice0}, {1, 4}, "rsh0");
    auto sm0      = aiOnnx.softmax({reshape0}, 1, "sm0");
    auto label0   = builder->addInputTensor(labelSampleInfo);
    auto nll0     = aiGraphcore.nllloss({sm0, label0}, ReductionType::Mean);

    auto slice1 =
        aiOnnx.slice({mmOut}, {1, 4, 2}, {0, 0, 1}, {0, 1, 2}, "slc1");
    auto reshape1 = builder->reshape_const(aiOnnx, {slice1}, {1, 4}, "rsh1");
    auto sm1      = aiOnnx.softmax({reshape1}, 1, "sm1");
    auto label1   = builder->addInputTensor(labelSampleInfo);
    auto nll1     = aiGraphcore.nllloss({sm1, label1}, ReductionType::Mean);

    auto finalLoss = aiOnnx.sum({nll0, nll1});

    auto device = createTestDevice(TEST_TARGET, 1, 20);

    auto session = popart::TrainingSession::createFromOnnxModel(
        builder->getModelProto(),
        DataFlow(batchesPerStep),
        finalLoss,
        ConstSGD(1.0),
        device,
        InputShapeInfo(),
        SessionOptions(),
        popart::Patterns(PatternsLevel::Default)
            .enableInPlace(doInplace)
            .enablePattern("PadSum", false));

    auto sched =
        session->getIr().getOpSchedule({}, RequireOptimalSchedule::Yes);
    std::cout << "The op schedule with inplace=" << doInplace << " is :\n";
    int nAdds = 0;
    for (const auto *op : sched) {
      std::cout << op->str() << std::endl;
      if (dynamic_cast<const AddOp *>(op) ||
          dynamic_cast<const AddLhsInplaceOp *>(op) ||
          dynamic_cast<const AddRhsInplaceOp *>(op)) {
        ++nAdds;
      }
    }

    // We expect a single add in the backwards pass, which should be inplaced
    // IFF inplacing is enabled. Note that in the future we might have an
    // optimization which removes this Add, in which case this check will fail
    // (T12632)
    BOOST_CHECK(nAdds == 1);

    int expectedAddInplace = doInplace ? 1 : 0;
    BOOST_CHECK(session->getIr()
                        .opsOfType(Onnx::CustomOperators::AddLhsInplace)
                        .size() +
                    session->getIr()
                        .opsOfType(Onnx::CustomOperators::AddRhsInplace)
                        .size() ==
                expectedAddInplace);

    session->prepareDevice();

    auto seed = 1011;
    DefaultRandomEngine eng(seed);
    UniformRealDistribution<float> fdis(0.f, 0.5f);
    UniformIntDistribution<int> idis(0, 3);

    WeightsIO weightsRead;
    std::vector<float> readBackWeights(weightInfo.nelms(), -777.0f);
    weightsRead.insert(weight, {readBackWeights.data(), weightInfo});

    std::vector<float> vInput(dataStepInfo.nelms(), 0.1);
    popart::NDArrayWrapper<float> inputWrapper(vInput.data(), dataStepInfo);
    for (auto &v : vInput) {
      v = fdis(eng);
    }

    std::vector<int> vLabel0(labelStepInfo.nelms(), 2);
    popart::NDArrayWrapper<int> label0Wrapper(vLabel0.data(), labelStepInfo);
    for (auto &v : vLabel0) {
      v = idis(eng);
    }

    std::vector<int> vLabel1(labelStepInfo.nelms(), 3);
    popart::NDArrayWrapper<int> label1Wrapper(vLabel1.data(), labelStepInfo);
    for (auto &v : vLabel1) {
      v = idis(eng);
    }

    std::map<popart::TensorId, popart::IArray &> inputs = {
        {input, inputWrapper},
        {label0, label0Wrapper},
        {label1, label1Wrapper}};

    popart::StepIO stepio(inputs, {});

    session->weightsFromHost();

    session->run(stepio);
    session->weightsToHost();
    session->readWeights(weightsRead);

    return readBackWeights;
  };

  // generate random input data
  auto seed = 1011;
  DefaultRandomEngine eng(seed);
  UniformRealDistribution<float> fdis(0.f, 0.5f);
  std::array<float, 6 * 2> vWeight;
  for (auto &val : vWeight) {
    val = fdis(eng);
  }

  auto weights0 = getFinalWeights(2, vWeight, false);
  auto weights1 = getFinalWeights(2, vWeight, true);

  float absErr = 0.0f;
  for (int i = 0; i < weights1.size(); ++i) {
    absErr += std::abs(weights0[i] - weights1[i]);
  }
  std::cout << "Absolute error is " << absErr << std::endl;
  BOOST_CHECK(absErr < 1e-6);
}