//////////////////////////////////////////////////////////////////////////////////////////////
/// \file GaussNewtonSolverBase.cpp
///
/// \author Sean Anderson, ASRL
//////////////////////////////////////////////////////////////////////////////////////////////

#include <steam/solver/GaussNewtonSolverBase.hpp>

#include <iostream>
#include <Eigen/Cholesky>

#include <steam/common/Timer.hpp>

namespace steam {

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Constructor
//////////////////////////////////////////////////////////////////////////////////////////////
GaussNewtonSolverBase::GaussNewtonSolverBase(OptimizationProblem* problem) :
  SolverBase(problem), patternInitialized_(false), factorizedInformationSuccesfully_(false) {
}

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Query the covariance related to a single state variable
//////////////////////////////////////////////////////////////////////////////////////////////
Eigen::MatrixXd GaussNewtonSolverBase::queryCovariance(const steam::StateKey& key) {

  return queryCovariance(key, key);
}

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Query the covariance relating two state variables
//////////////////////////////////////////////////////////////////////////////////////////////
Eigen::MatrixXd GaussNewtonSolverBase::queryCovariance(const steam::StateKey& rowKey,
                                                       const steam::StateKey& colKey) {

  std::vector<steam::StateKey> rkeys; rkeys.push_back(rowKey);
  std::vector<steam::StateKey> ckeys; ckeys.push_back(colKey);
  BlockMatrix m = queryCovarianceBlock(rkeys, ckeys);
  return m.at(0,0);
}

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Query a block of covariances
//////////////////////////////////////////////////////////////////////////////////////////////
BlockMatrix GaussNewtonSolverBase::queryCovarianceBlock(const std::vector<steam::StateKey>& keys) {

  return queryCovarianceBlock(keys, keys);
}

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Query a block of covariances
//////////////////////////////////////////////////////////////////////////////////////////////
BlockMatrix GaussNewtonSolverBase::queryCovarianceBlock(const std::vector<steam::StateKey>& rowKeys,
                                                        const std::vector<steam::StateKey>& colKeys) {

  // Check if the Hessian has been factorized (without augmentation, i.e. the Information matrix)
  if (!factorizedInformationSuccesfully_) {
    throw std::runtime_error("Cannot query covariance, as the plain approximate Hessian "
                             "was not factorized properly. If using LevMarq, you may "
                             "have to call solveCovariances().");
  }

  // Creating indexing
  BlockMatrixIndexing indexing(this->getStateVector().getStateBlockSizes());
  const BlockDimIndexing& blkRowIndexing = indexing.rowIndexing();
  const BlockDimIndexing& blkColIndexing = indexing.colIndexing();

  // Fixed sizes
  unsigned int numRowKeys = rowKeys.size();
  unsigned int numColKeys = colKeys.size();

  // Look up block indexes
  std::vector<unsigned int> blkRowIndices; blkRowIndices.resize(numRowKeys);
  for (unsigned int i = 0; i < numRowKeys; i++) {
    blkRowIndices[i] = this->getStateVector().getStateBlockIndex(rowKeys[i]);
  }
  std::vector<unsigned int> blkColIndices; blkColIndices.resize(numColKeys);
  for (unsigned int i = 0; i < numColKeys; i++) {
    blkColIndices[i] = this->getStateVector().getStateBlockIndex(colKeys[i]);
  }

  // Look up block size of state variables
  std::vector<unsigned int> blkRowSizes; blkRowSizes.resize(numRowKeys);
  for (unsigned int i = 0; i < numRowKeys; i++) {
    blkRowSizes[i] = blkRowIndexing.blkSizeAt(blkRowIndices[i]);
  }
  std::vector<unsigned int> blkColSizes; blkColSizes.resize(numColKeys);
  for (unsigned int i = 0; i < numColKeys; i++) {
    blkColSizes[i] = blkColIndexing.blkSizeAt(blkColIndices[i]);
  }

  // Create result container
  BlockMatrix result(blkRowSizes, blkColSizes);

  // For each column key
  for (unsigned int c = 0; c < numColKeys; c++) {

    // For each scalar column
    Eigen::VectorXd projection(blkRowIndexing.scalarSize()); projection.setZero();
    for (unsigned int j = 0; j < blkColSizes[c]; j++) {

      // Get scalar index
      unsigned int scalarColIndex = blkColIndexing.cumSumAt(blkColIndices[c]) + j;

      // Solve for scalar column of covariance matrix
      projection(scalarColIndex) = 1.0;
      Eigen::VectorXd x = hessianSolver_.solve(projection);
      projection(scalarColIndex) = 0.0;

      // For each block row
      for (unsigned int r = 0; r < numRowKeys; r++) {

        // Get scalar index into solution vector
        unsigned int scalarRowIndex = blkRowIndexing.cumSumAt(blkRowIndices[r]);

        // Do the backward pass, using the Cholesky factorization (fast)
        result.at(r,c).block(0, j, blkRowSizes[r], 1) = x.block(scalarRowIndex, 0, blkRowSizes[r], 1);
      }
    }
  }

  return result;
}

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Build the left-hand and right-hand sides of the Gauss-Newton system of equations
//////////////////////////////////////////////////////////////////////////////////////////////
void GaussNewtonSolverBase::buildGaussNewtonTerms(Eigen::SparseMatrix<double>* approximateHessian,
                                                  Eigen::VectorXd* gradientVector) {
  this->getProblem().buildGaussNewtonTerms(this->getStateVector(), approximateHessian, gradientVector);
}

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Perform the LLT decomposition on the approx. Hessian matrix
//////////////////////////////////////////////////////////////////////////////////////////////
void GaussNewtonSolverBase::factorizeHessian(const Eigen::SparseMatrix<double>& approximateHessian,
                                             bool augmentedHessian) {

  // Check if the pattern has been initialized
  if (!patternInitialized_) {

    // The first time we are solving the problem we need to analyze the sparsity pattern
    // ** Note we use approximate-minimal-degree (AMD) reordering.
    //    Also, this step does not actually use the numerical values in gaussNewtonLHS
    hessianSolver_.analyzePattern(approximateHessian);
    patternInitialized_ = true;
  }

  // Perform a Cholesky factorization of the approximate Hessian matrix
  factorizedInformationSuccesfully_ = false;
  hessianSolver_.factorize(approximateHessian);

  // Check if the factorization succeeded
  if (hessianSolver_.info() != Eigen::Success) {

    std::cout << "Approximate Hessian is: " << std::endl;
    std::cout << approximateHessian << std::endl;

    throw decomp_failure("During steam solve, Eigen LLT decomposition failed. "
                         "It is possible that the matrix was ill-conditioned, in which case "
                         "adding a prior may help. On the other hand, it is also possible that "
                         "the problem you've constructed is not positive semi-definite.");
  } else {

    // Information matrix was solved successfully, if the hessian was not augmented
    factorizedInformationSuccesfully_ = !augmentedHessian;
  }

  // todo - it would be nice to check the condition number (not just the determinant) of the
  // solved system... need to find a fast way to do this
}

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Solve the Gauss-Newton system of equations: A*x = b
//////////////////////////////////////////////////////////////////////////////////////////////
Eigen::VectorXd GaussNewtonSolverBase::solveGaussNewton(const Eigen::SparseMatrix<double>& approximateHessian,
                                                        const Eigen::VectorXd& gradientVector,
                                                        bool augmentedHessian) {

  // Perform a Cholesky factorization of the approximate Hessian matrix
  this->factorizeHessian(approximateHessian, augmentedHessian);

  // Do the backward pass, using the Cholesky factorization (fast)
  return hessianSolver_.solve(gradientVector);
}

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Find the Cauchy point (used for the Dogleg method).
///        The cauchy point is the optimal step length in the gradient descent direction.
//////////////////////////////////////////////////////////////////////////////////////////////
Eigen::VectorXd GaussNewtonSolverBase::getCauchyPoint(const Eigen::SparseMatrix<double>& approximateHessian,
                                                      const Eigen::VectorXd& gradientVector) {
  double num = gradientVector.squaredNorm();
  double den = gradientVector.transpose() *
               (approximateHessian.selfadjointView<Eigen::Upper>() * gradientVector);
  return (num/den)*gradientVector;
}

//////////////////////////////////////////////////////////////////////////////////////////////
/// \brief Get the predicted cost reduction based on the proposed step
//////////////////////////////////////////////////////////////////////////////////////////////
double GaussNewtonSolverBase::predictedReduction(const Eigen::SparseMatrix<double>& approximateHessian,
                                                 const Eigen::VectorXd& gradientVector,
                                                 const Eigen::VectorXd& step) {
  // grad^T * step - 0.5 * step^T * Hessian * step
  double gradTransStep = gradientVector.transpose() * step;
  double stepTransHessianStep = step.transpose()
                                * (approximateHessian.selfadjointView<Eigen::Upper>() * step);
  return gradTransStep - 0.5 * stepTransHessianStep;
}

} // steam


/*  $Id: triangle.cpp 745 2011-05-16 10:32:25Z anders.e.e.wallin $
 *
 *  Copyright 2010 Anders Wallin (anders.e.e.wallin "at" gmail.com)
 *
 *  This file is part of OpenCAMlib.
 *
 *  OpenCAMlib is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  OpenCAMlib is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenCAMlib.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <cassert>

#include <boost/foreach.hpp>

#include "triangle.h"
#include "point.h"
#include "numeric.h"

namespace ocl
{

Triangle::Triangle() {
    p[0]=Point(1,0,0);
    p[1]=Point(0,1,0);
    p[2]=Point(0,0,1);
    calcNormal();
    calcBB();
}

Triangle::Triangle(Point p1, Point p2, Point p3) {
    p[0]=p1;
    p[1]=p2;
    p[2]=p3;
    calcNormal();
    calcBB();
}

Triangle::Triangle(const Triangle &t) {
    p[0]=t.p[0];
    p[1]=t.p[1];
    p[2]=t.p[2];
    calcNormal();
    calcBB();
}

/// calculate bounding box values
void Triangle::calcBB() {
    bb.clear();
    bb.addTriangle( *this );
}

/// calculate, normalize, and set the Triangle normal
void Triangle::calcNormal() {
    Point v1=p[0]-p[1];
    Point v2=p[0]-p[2];
    Point ntemp = v1.cross(v2);  // the normal is in the direction of the cross product between the edge vectors
    ntemp.normalize(); // FIXME this might fail if norm()==0
    n = Point(ntemp.x,ntemp.y,ntemp.z);
}

Point Triangle::upNormal() const {
    return (n.z < 0) ? -1.0* n : n;
}

bool Triangle::zslice_verts(Point& p1, Point& p2, const double zcut) const {
    if ( (zcut <= this->bb.minpt.z) || ((zcut >= this->bb.maxpt.z)) )
        return false; // no zslice
    // find out how many vertices are below zcut
    std::vector<Point> below;
    std::vector<Point> above;
    for (int m=0;m<3;++m) {
        if ( p[m].z <= zcut )
            below.push_back(p[m]);
        else
            above.push_back(p[m]);
    }
    if ( !(below.size() == 1) && !(below.size() == 2) ) {
        std::cout << "triangle.cpp: zslice_verts() error while trying to z-slice\n";
        std::cout << " triangle=" << *this << "\n";
        std::cout << " zcut=" << zcut << "\n";
        std::cout << above.size() << " above points:\n";
        BOOST_FOREACH(Point p, above) {
            std::cout << "   " << p << "\n";
        }
        std::cout << below.size() << " below points:\n";
        BOOST_FOREACH(Point p, below) {
            std::cout << "   " << p << "\n";
        }
    }
    assert( (below.size() == 1) || (below.size() == 2) );

    if ( below.size() == 2 ) {
        assert( above.size() == 1 );
        // find two new intersection points
        // edge is p1 + t*(p2-p1) = zcut
        // so t = zcut-p1 / (p2-p1)
        double t1 = (zcut - above[0].z) / (below[0].z - above[0].z); // div by zero?!
        double t2 = (zcut - above[0].z) / (below[1].z - above[0].z);
        p1 = above[0] + t1*(below[0] - above[0]);
        p2 = above[0] + t2*(below[1] - above[0]);
        return true;
    } else if ( below.size() == 1 ) {
        assert( above.size() == 2 );
        // find intersection points and add two new triangles
        // t = (zcut -p1) / (p2-p1)
        double t1 = (zcut - above[0].z) / (below[0].z - above[0].z);
        double t2 = (zcut - above[1].z) / (below[0].z - above[1].z);
        p1 = above[0] + t1*(below[0]-above[0]);
        p2 = above[1] + t2*(below[0]-above[1]);
        return true;
    } else {
        assert(0);
        return false;
    }

}

void Triangle::rotate(double xr, double yr, double zr) {
    for (int n=0;n<3;++n) {
        p[n].xRotate(xr);
        p[n].yRotate(yr);
        p[n].zRotate(zr);
    }
    calcNormal();
    calcBB();
}

std::ostream &operator<<(std::ostream &stream, const Triangle t) {
  stream <<  "T: " << t.p[0] << " " << t.p[1] << " " << t.p[2] <<  "n=" << t.n ;
  return stream;
}

}  // end namespace
// end file triangle.cpp


// Copyright 2018 The Simons Foundation, Inc. - All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef NETKET_LOCAL_OPERATOR_HPP
#define NETKET_LOCAL_OPERATOR_HPP

#include <mpi.h>
#include <Eigen/Dense>
#include <algorithm>
#include <cassert>
#include <complex>
#include <iostream>
#include <limits>
#include <map>
#include <vector>

namespace netket {

/**
    Class for local operators acting on a list of sites and for generic local
    Hilbert spaces.
*/

class LocalOperator {
 public:
  using MatType = std::vector<std::vector<std::complex<double>>>;

 private:
  const Hilbert &hilbert_;
  MatType mat_;

  std::vector<int> sites_;

  std::map<std::vector<double>, int> invstate_;
  int localsize_;

  std::vector<std::vector<double>> states_;
  std::vector<std::vector<int>> connected_;

 public:
  LocalOperator(const Hilbert &hilbert, const MatType &mat,
                const std::vector<int> &sites)
      : hilbert_(hilbert), mat_(mat), sites_(sites) {
    Init();
  }

  void Init() {
    if (!hilbert_.IsDiscrete()) {
      std::cerr << "Cannot construct operators on infinite local hilbert spaces"
                << std::endl;
      std::abort();
    }

    if (*std::max_element(sites_.begin(), sites_.end()) >= hilbert_.Size() ||
        *std::min_element(sites_.begin(), sites_.end()) < 0) {
      std::cerr << "Operator acts on an invalid set of sites" << std::endl;
      std::abort();
    }

    auto localstates = hilbert_.LocalStates();
    localsize_ = localstates.size();

    // Finding the non-zero matrix elements
    const double epsilon = 1.0e-6;

    connected_.resize(mat_.size());

    if (mat_.size() != std::pow(localsize_, sites_.size())) {
      std::cerr << "Matrix size in operator is inconsistent with Hilbert space"
                << std::endl;
      std::abort();
    }

    for (std::size_t i = 0; i < mat_.size(); i++) {
      for (std::size_t j = 0; j < mat_[i].size(); j++) {
        if (mat_.size() != mat_[i].size()) {
          std::cerr
              << "Matrix size in operator is inconsistent with Hilbert space"
              << std::endl;
          std::abort();
        }

        if (i != j && std::abs(mat_[i][j]) > epsilon) {
          connected_[i].push_back(j);
        }
      }
    }

    // Construct the mapping
    // Internal index -> State
    std::vector<double> st(sites_.size(), 0);

    do {
      states_.push_back(st);
    } while (netket::next_variation(st.begin(), st.end(), localsize_ - 1));

    for (std::size_t i = 0; i < states_.size(); i++) {
      for (std::size_t k = 0; k < states_[i].size(); k++) {
        states_[i][k] = localstates[states_[i][k]];
      }
    }

    // Now construct the inverse mapping
    // State -> Internal index
    std::size_t k = 0;
    for (auto state : states_) {
      invstate_[state] = k;
      k++;
    }

    assert(k == mat_.size());
  }

  void FindConn(const Eigen::VectorXd &v,
                std::vector<std::complex<double>> &mel,
                std::vector<std::vector<int>> &connectors,
                std::vector<std::vector<double>> &newconfs) const {
    assert(v.size() == hilbert_.Size());

    connectors.clear();
    newconfs.clear();
    mel.resize(0);

    AddConn(v, mel, connectors, newconfs);
  }

  void AddConn(const Eigen::VectorXd &v, std::vector<std::complex<double>> &mel,
               std::vector<std::vector<int>> &connectors,
               std::vector<std::vector<double>> &newconfs) const {
    if (mel.size() == 0) {
      connectors.resize(1);
      newconfs.resize(1);
      mel.resize(1);

      mel[0] = 0;
      connectors[0].resize(0);
      newconfs[0].resize(0);
    }

    int st1 = StateNumber(v);
    assert(st1 < int(mat_.size()));
    assert(st1 < int(connected_.size()));

    mel[0] += (mat_[st1][st1]);

    // off-diagonal part
    for (auto st2 : connected_[st1]) {
      connectors.push_back(sites_);
      assert(st2 < int(states_.size()));
      newconfs.push_back(states_[st2]);
      mel.push_back(mat_[st1][st2]);
    }
  }

  inline int StateNumber(const Eigen::VectorXd &v) const {
    std::vector<double> state(sites_.size());
    for (std::size_t i = 0; i < sites_.size(); i++) {
      state[i] = v(sites_[i]);
    }
    return invstate_.at(state);
  }
};

}  // namespace netket
#endif


#include <LRModel.h>
#include <Utils.h>
#include <MlUtils.h>
#include <Eigen/Dense>
#include <Checksum.h>
#include <algorithm>

namespace cirrus {

LRModel::LRModel(uint64_t d) {
    weights_.resize(d);
}

LRModel::LRModel(const FEATURE_TYPE* w, uint64_t d) {
    weights_.resize(d);
    std::copy(w, w + d, weights_.begin());
}

uint64_t LRModel::size() const {
  return weights_.size();
}

/**
  * Serialization / deserialization routines
  */

/** FORMAT
  * weights
  */
std::unique_ptr<CirrusModel> LRModel::deserialize(void* data, uint64_t size) const {
    uint64_t d = size / sizeof(FEATURE_TYPE);
    std::unique_ptr<LRModel> model = std::make_unique<LRModel>(
            reinterpret_cast<FEATURE_TYPE*>(data), d);
    return model;
}

std::pair<std::unique_ptr<char[]>, uint64_t>
LRModel::serialize() const {
    std::pair<std::unique_ptr<char[]>, uint64_t> res;
    uint64_t size = getSerializedSize();
    res.first.reset(new char[size]);

    res.second = size;
    std::memcpy(res.first.get(), weights_.data(), getSerializedSize());

    return res;
}

void LRModel::serializeTo(void* mem) const {
    std::memcpy(mem, weights_.data(), getSerializedSize());
}

uint64_t LRModel::getSerializedSize() const {
    return size() * sizeof(FEATURE_TYPE);
}

void LRModel::loadSerialized(const void* data) {
    const FEATURE_TYPE* v = reinterpret_cast<const FEATURE_TYPE*>(data);
    std::copy(v, v + size(), weights_.begin());
}

/***
   *
   */

void LRModel::randomize() {
    for (auto& w : weights_) {
        w = 0.001 + get_rand_between_0_1();
    }
}

std::unique_ptr<CirrusModel> LRModel::copy() const {
    std::unique_ptr<LRModel> new_model =
        std::make_unique<LRModel>(weights_.data(), size());
    return new_model;
}

void LRModel::sgd_update(double learning_rate,
        const ModelGradient* gradient) {
    const LRGradient* grad = dynamic_cast<const LRGradient*>(gradient);

    if (grad == nullptr) {
        throw std::runtime_error("Error in dynamic cast");
    }

    for (uint64_t i = 0; i < size(); ++i) {
       weights_[i] += learning_rate * grad->weights[i];
    }
}

std::unique_ptr<ModelGradient> LRModel::minibatch_grad(
        const Matrix& dataset,
        FEATURE_TYPE* labels,
        uint64_t labels_size,
        double epsilon) const {
    auto w = weights_;
#ifdef DEBUG
    dataset.check();
#endif

    if (dataset.cols != size() || labels_size != dataset.rows) {
      throw std::runtime_error("Sizes don't match");
    }

    const FEATURE_TYPE* dataset_data = dataset.data.get();
    // create Matrix for dataset
    Eigen::Map<Eigen::Matrix<FEATURE_TYPE, Eigen::Dynamic,
        Eigen::Dynamic, Eigen::RowMajor>>
          ds(const_cast<FEATURE_TYPE*>(dataset_data), dataset.rows, dataset.cols);

    // create weight vector
    Eigen::Map<Eigen::Matrix<FEATURE_TYPE, -1, 1>> tmp_weights(w.data(), size());

    // create vector with labels
    Eigen::Map<Eigen::Matrix<FEATURE_TYPE, -1, 1>> lab(labels, labels_size);

    // apply logistic function to matrix multiplication
    // between dataset and weights
    auto part1_1 = (ds * tmp_weights);
    auto part1 = part1_1.unaryExpr(std::ptr_fun(s_1_float)); // XXX fix this

    Eigen::Map<Eigen::Matrix<FEATURE_TYPE, -1, 1>> lbs(labels, labels_size);

    // compute difference between labels and logistic probability
    auto part2 = lbs - part1;
    auto part3 = ds.transpose() * part2;
    auto part4 = tmp_weights * 2 * epsilon;
    auto res = part4 + part3;

    std::vector<FEATURE_TYPE> vec_res;
    vec_res.resize(res.size());
    Eigen::Matrix<FEATURE_TYPE, -1, 1>::Map(vec_res.data(), res.size()) = res;

    std::unique_ptr<LRGradient> ret = std::make_unique<LRGradient>(vec_res);

#ifdef DEBUG
    ret->check_values();
#endif

    return ret;
}

std::pair<double, double> LRModel::calc_loss(Dataset& dataset) const {
  double total_loss = 0;
  auto w = weights_;

#ifdef DEBUG
  dataset.check();
#endif

  const FEATURE_TYPE* ds_data =
    reinterpret_cast<const FEATURE_TYPE*>(dataset.samples_.data.get());

  Eigen::Map<Eigen::Matrix<FEATURE_TYPE, Eigen::Dynamic,
    Eigen::Dynamic, Eigen::RowMajor>>
      ds(const_cast<FEATURE_TYPE*>(ds_data),
          dataset.samples_.rows, dataset.samples_.cols);

  Eigen::Map<Eigen::Matrix<FEATURE_TYPE, -1, 1>> weights_eig(w.data(), size());

  // count how many samples are wrongly classified
  uint64_t wrong_count = 0;
  for (uint64_t i = 0; i < dataset.num_samples(); ++i) {
    // get labeled class for the ith sample
    FEATURE_TYPE class_i =
      reinterpret_cast<const FEATURE_TYPE*>(dataset.labels_.get())[i];

    assert(is_integer(class_i));

    int predicted_class = 0;

    auto r1 = ds.row(i) *  weights_eig;
    if (s_1((FEATURE_TYPE)r1) > 0.5) {
      predicted_class = 1;
    }
    if (predicted_class != class_i) {
      wrong_count++;
    }

    FEATURE_TYPE v1 = log_aux(1 - s_1((FEATURE_TYPE)(ds.row(i) * weights_eig)));
    FEATURE_TYPE v2 = log_aux(s_1((FEATURE_TYPE)(ds.row(i) *  weights_eig)));

    FEATURE_TYPE value = class_i * v2 + (1 - class_i) * v1;

    // XXX not sure this check is necessary
    if (value > 0 && value < 1e-6)
      value = 0;

    if (value > 0) {
      std::cout << "ds row: " << std::endl << ds.row(i) << std::endl;
      std::cout << "weights: " << std::endl << weights_eig << std::endl;
      std::cout << "Class: " << class_i << " " << v1 << " " << v2
        << std::endl;
      throw std::runtime_error("Error: logistic loss is > 0");
    }

    total_loss -= value;
  }

  if (total_loss < 0) {
    throw std::runtime_error("total_loss < 0");
  }

  FEATURE_TYPE accuracy = (1.0 - (1.0 * wrong_count / dataset.num_samples()));
  if (std::isnan(total_loss) || std::isinf(total_loss))
    throw std::runtime_error("calc_log_loss generated nan/inf");

  return std::make_pair(total_loss, accuracy);
}

uint64_t LRModel::getSerializedGradientSize() const {
    return size() * sizeof(FEATURE_TYPE);
}

std::unique_ptr<ModelGradient> LRModel::loadGradient(void* mem) const {
    auto grad = std::make_unique<LRGradient>(size());

    for (uint64_t i = 0; i < size(); ++i) {
        grad->weights[i] = reinterpret_cast<FEATURE_TYPE*>(mem)[i];
    }

    return grad;
}

bool LRModel::is_integer(FEATURE_TYPE n) const {
    return floor(n) == n;
}

double LRModel::checksum() const {
    return crc32(weights_.data(), weights_.size() * sizeof(FEATURE_TYPE));
}

void LRModel::print() const {
    std::cout << "MODEL: ";
    for (const auto& w : weights_) {
        std::cout << " " << w;
    }
    std::cout << std::endl;
}

} // namespace cirrus


#ifndef HOPS_DIKINELLIPSOIDCALCULATOR_HPP
#define HOPS_DIKINELLIPSOIDCALCULATOR_HPP

#include <Eigen/Cholesky>
#include <Eigen/Core>
#include <Eigen/Dense>
#include "../../FileWriter/CsvWriter.hpp"

namespace hops {
    template<typename MatrixType, typename VectorType>
    class DikinEllipsoidCalculator {
    public:
        DikinEllipsoidCalculator(MatrixType A, VectorType b);

        std::pair<bool, Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic>>
        computeCholeskyFactorOfDikinEllipsoid(const VectorType &x);

        Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic>
        computeDikinEllipsoid(const VectorType &x);

    private:
        MatrixType A;
        VectorType b;
    };

    template<typename MatrixType, typename VectorType>
    DikinEllipsoidCalculator<MatrixType, VectorType>::DikinEllipsoidCalculator(MatrixType A, VectorType b) :
            A(std::move(A)), b(std::move(b)) {}

    template<typename MatrixType, typename VectorType>
    std::pair<bool, Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic>>
    DikinEllipsoidCalculator<MatrixType, VectorType>::computeCholeskyFactorOfDikinEllipsoid(const VectorType &x) {
        Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic> dikinEllipsoid =
                computeDikinEllipsoid(x);

        Eigen::LLT<Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic>> solver(dikinEllipsoid);
        bool successful = solver.info() == Eigen::Success;
        return std::make_pair(successful, solver.matrixL());
    }

    template<typename MatrixType, typename VectorType>
    Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic>
    DikinEllipsoidCalculator<MatrixType, VectorType>::computeDikinEllipsoid(const VectorType &x) {
        Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, 1> inv_slack = (this->b -
                                                                                this->A * x).cwiseInverse();

        Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic> halfDikin =
                inv_slack.asDiagonal() * this->A;
        Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, Eigen::Dynamic> dikin =
                halfDikin.transpose() * halfDikin;
        return dikin;
    }
}

#endif //HOPS_DIKINELLIPSOIDCALCULATOR_HPP


/**
 * Copyright Soramitsu Co., Ltd. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "utils/amount_utils.hpp"
#include <boost/format.hpp>

namespace shared_model {
  namespace detail {
    const boost::multiprecision::uint256_t ten = 10;

    boost::multiprecision::uint256_t increaseValuePrecision(
        const boost::multiprecision::uint256_t &value, int degree) {
      return value * pow(ten, degree);
    }

    /**
     * Sums up two amounts.
     * Result is returned
     * @param a left term
     * @param b right term
     */
    iroha::expected::PolymorphicResult<shared_model::interface::Amount,
                                       std::string>
    operator+(const shared_model::interface::Amount &a,
              const shared_model::interface::Amount &b) {
      auto max_precision = std::max(a.precision(), b.precision());
      auto val_a =
          increaseValuePrecision(a.intValue(), max_precision - a.precision());
      auto val_b =
          increaseValuePrecision(b.intValue(), max_precision - b.precision());
      if (val_a < a.intValue() || val_b < b.intValue() || val_a + val_b < val_a
          || val_a + val_b < val_b) {
        return iroha::expected::makeError(std::make_shared<std::string>(
            (boost::format("addition overflows (%s + %s)") % a.intValue().str()
             % b.intValue().str())
                .str()));
      }
      std::string val = (val_a + val_b).str();
      if (max_precision != 0) {
        val.insert((val.rbegin() + max_precision).base(), '.');
      }
      return iroha::expected::makeValue(
          std::make_shared<shared_model::interface::Amount>(std::move(val)));
    }

    /**
     * Subtracts two amounts.
     * Result is returned
     * @param a left term
     * @param b right term
     */
    iroha::expected::PolymorphicResult<shared_model::interface::Amount,
                                       std::string>
    operator-(const shared_model::interface::Amount &a,
              const shared_model::interface::Amount &b) {
      auto max_precision = std::max(a.precision(), b.precision());
      auto val_a =
          increaseValuePrecision(a.intValue(), max_precision - a.precision());
      auto val_b =
          increaseValuePrecision(b.intValue(), max_precision - b.precision());
      if (val_a < a.intValue() || val_b < b.intValue()) {
        return iroha::expected::makeError(
            std::make_shared<std::string>("new precision overflows number"));
      }
      auto diff = val_a - val_b;
      std::string val = diff.str();
      if (max_precision != 0 && val.size() > max_precision + 1) {
        auto ptr = val.rbegin() + max_precision;
        val.insert(ptr.base(), '.');
      }
      return iroha::expected::makeValue(
          std::make_shared<shared_model::interface::Amount>(std::move(val)));
    }

    /**
     * Make amount with bigger precision
     * Result is returned
     * @param a amount
     * @param b right term
     */
    iroha::expected::PolymorphicResult<shared_model::interface::Amount,
                                       std::string>
    makeAmountWithPrecision(const shared_model::interface::Amount &amount,
                            const int new_precision) {
      if (amount.precision() > new_precision) {
        return iroha::expected::makeError(std::make_shared<std::string>(
            (boost::format("new precision is smaller than current (%d < %d)")
             % new_precision % amount.precision())
                .str()));
      }
      auto val_amount = increaseValuePrecision(
          amount.intValue(), new_precision - amount.precision());
      if (val_amount < amount.intValue()) {
        return iroha::expected::makeError(
            std::make_shared<std::string>("operation overflows number"));
      }
      std::string val = val_amount.str();
      if (new_precision != 0) {
        val.insert((val.rbegin() + new_precision).base(), '.');
      }
      return iroha::expected::makeValue(
          std::make_shared<shared_model::interface::Amount>(std::move(val)));
    }

    int compareAmount(const shared_model::interface::Amount &a,
                      const shared_model::interface::Amount &b) {
      if (a.precision() == b.precision()) {
        return (a.intValue() < b.intValue())
            ? -1
            : (a.intValue() > b.intValue()) ? 1 : 0;
      }
      // when different precisions transform to have the same scale
      auto max_precision = std::max(a.precision(), b.precision());

      auto val1 =
          increaseValuePrecision(a.intValue(), max_precision - a.precision());
      auto val2 =
          increaseValuePrecision(b.intValue(), max_precision - b.precision());
      return (val1 < val2) ? -1 : (val1 > val2) ? 1 : 0;
    }
  }  // namespace detail
}  // namespace shared_model