/**
 *  @file
 *  @copyright defined in evt/LICENSE.txt
 */
#include <evt/chain/contracts/evt_link.hpp>

#include <string.h>
#include <algorithm>

#include <boost/multiprecision/cpp_int.hpp>
#include <boost/endian/conversion.hpp>

#include <fc/crypto/hex.hpp>
#include <fc/crypto/elliptic.hpp>
#include <evt/chain/exceptions.hpp>

using namespace boost::multiprecision;

// pay: 2(header) + 5(time) + 5(max_pay) + 7(symbol) + 16(link-id)  = 35
// pass: 2(header) + 5(time) + 22(domain) + 22(token) + 16(link-id) = 67
// sigs: 65 * 3 = 195
using bigint_segs = number<cpp_int_backend<536, 536, unsigned_magnitude, checked, void>>;
using bigint_sigs = number<cpp_int_backend<1560, 1560, unsigned_magnitude, checked, void>>;

namespace evt { namespace chain { namespace contracts {

namespace internal {

const char* ALPHABETS  = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$+-/:*";
const int   MAX_BYTES  = 240;  // 195 / ((42 ^ 2) / 2048)
const char* URI_SCHEMA = "https://evt.li/";

template<typename T>
bytes
decode(const std::string& nums, uint pos, uint end) {
    auto num = T{0};
    auto pz  = nums.find_first_not_of('0', pos);
    EVT_ASSERT(pz != string::npos, evt_link_exception, "Invalid EVT-Link");

    for(auto i = pz; i < end; i++) {
        auto c = strchr(ALPHABETS, nums[i]);
        FC_ASSERT(c != nullptr, "invalid character in evt-link");
        num *= 42;
        num += (c - ALPHABETS);
    }

    auto b = bytes();
    b.reserve(MAX_BYTES);
    for(auto i = 0u; i < (pz - pos); i++) {
        b.emplace_back(0);
    }
    boost::multiprecision::export_bits(num, std::back_inserter(b), 8);

    return b;
}

evt_link::segments_type
parse_segments(const bytes& b, uint16_t& header) {
    FC_ASSERT(b.size() > 2);

    auto h  = *(uint16_t*)&b[0];
    header  = boost::endian::big_to_native(h);

    auto segs = evt_link::segments_type();

    auto i  = 2u;
    auto pk = 0u;
    while(i < b.size()) {
        auto k = (uint8_t)b[i];
        EVT_ASSERT(k > pk, evt_link_exception, "Segments are not ordered by keys");
        pk = k;

        if(k <= 20) {
            FC_ASSERT(b.size() > i + 1); // value is 1 byte
            auto v = (uint8_t)b[i + 1];
            segs.emplace(k, evt_link::segment(k, v));

            i += 2;
        }
        else if(k <= 40) {
            FC_ASSERT(b.size() > i + 2); // value is 2 byte
            auto v = *(uint16_t*)(&b[i] + 1);
            segs.emplace(k, evt_link::segment(k, boost::endian::big_to_native(v)));

            i += 3;
        }
        else if(k <= 90) {
            FC_ASSERT(b.size() > i + 4); // value is 4 byte
            auto v = *(uint32_t*)(&b[i] + 1);
            segs.emplace(k, evt_link::segment(k, boost::endian::big_to_native(v)));

            i += 5;
        }
        else if(k <= 180) {
            auto sz = 0u;
            auto s  = 0u;

            if(k > 155 && k <= 165) {
                sz = 16;  // uuid, sizeof(uint128_t)
            }
            else {
                FC_ASSERT(b.size() > i + 1); // first read length byte
                sz = (uint8_t)b[i + 1];
                s  = 1;
            }

            if(sz > 0) {
                FC_ASSERT(b.size() > i + s + sz);
                segs.emplace(k, evt_link::segment(k, std::string(&b[i] + 1 + s, sz)));
            }
            else {
                segs.emplace(k, evt_link::segment(k, std::string()));
            }

            i += 1 + s + sz;
        }
        else {
            EVT_THROW(evt_link_exception, "Invalid key type: ${k}", ("k",k));
        }
    }
    return segs;
}

evt_link::signatures_type
parse_signatures(const bytes& b) {
    FC_ASSERT(b.size() > 0 && b.size() % 65 == 0);
    auto sigs = evt_link::signatures_type();

    for(auto i = 0u; i < b.size() / 65u; i++) {
        auto shim = fc::ecc::compact_signature();
        static_assert(sizeof(shim) == 65);
        memcpy(shim.data(), &b[0] + i * 65, 65);

        sigs.emplace(fc::ecc::signature_shim(shim));
    }
    return sigs;
}

}  // namespace internal

evt_link
evt_link::parse_from_evtli(const std::string& str) {
    using namespace internal;

    EVT_ASSERT(str.size() < 400, evt_link_exception, "Link is too long, max length allowed: 400");
    EVT_ASSERT(str.size() > 20, evt_link_exception, "Link is too short");

    size_t start = 0;
    if(memcmp(str.data(), URI_SCHEMA, strlen(URI_SCHEMA)) == 0) {
        start = strlen(URI_SCHEMA);
    }

    auto d = str.find_first_of('_', start);

    auto bsegs = bytes();
    auto bsigs = bytes();

    if(d == std::string::npos) {
        bsegs = decode<bigint_segs>(str, start, str.size());
    }
    else {
        bsegs = decode<bigint_segs>(str, start, d);
        bsigs = decode<bigint_sigs>(str, d + 1, str.size());
    }

    auto link = evt_link();

    link.segments_   = parse_segments(bsegs, link.header_);
    link.signatures_ = parse_signatures(bsigs);

    return link;
}

const evt_link::segment&
evt_link::get_segment(uint8_t key) const {
    auto it = segments_.find(key);
    EVT_ASSERT(it != segments_.end(), evt_link_no_key_exception, "Cannot find segment for key: ${k}", ("k",key));

    return it->second;
}

bool
evt_link::has_segment(uint8_t key) const {
    return segments_.find(key) != segments_.end();
}

link_id_type
evt_link::get_link_id() const {
    auto& seg = get_segment(link_id);
    EVT_ASSERT(seg.strv && seg.strv->size() == sizeof(link_id_type), evt_link_id_exception, "Not valid link id in this EVT-Link");

    auto id = link_id_type();
    memcpy(&id, seg.strv->data(), sizeof(id));
    return id;
}

namespace internal {

template<typename Stream>
void
write_segments_bytes(const evt_link& link, Stream& stream) {
    auto h = boost::endian::native_to_big(link.get_header());
    static_assert(sizeof(h) == 2);  // uint16_t
    stream.write((char*)&h, sizeof(h));

    for(auto& seg_ : link.get_segments()) {
        auto  key = seg_.first;
        auto& seg = seg_.second;

        static_assert(sizeof(key) == 1);  // uint8_t
        stream.write((char*)&key, sizeof(key));
        if(key <= 20) {
            auto v = *seg.intv;
            stream.write((char*)&v, 1);
        }
        else if(key <= 40) {
            auto v = boost::endian::native_to_big((uint16_t)*seg.intv);
            stream.write((char*)&v, 2);
        }
        else if(key <= 90) {
            auto v = boost::endian::native_to_big((uint32_t)*seg.intv);
            stream.write((char*)&v, 4);
        }
        else if(key <= 180) {
            if(key > 155 && key <= 165) {
                FC_ASSERT(seg.strv->size() == 16);
                stream.write((char*)seg.strv->data(), 16);
            }
            else {
                auto s = seg.strv->size();
                FC_ASSERT(s < 255); // 1 byte length

                stream.write((char*)&s, 1);
                stream.write(seg.strv->data(), s);
            }
        }
    }
}

template<typename Stream>
struct stream_visitor : public fc::visitor<void> {
public:
    stream_visitor(Stream& stream) : stream_(stream) {}

public:
    template<typename Sig>
    void
    operator()(const Sig& sig) const {
        static_assert(sizeof(sig._data) == 65, "sig size is expected to be 65");
        stream_.write((char*)sig._data.data(), 65);
    }

private:
    Stream& stream_;
};

template<typename Stream>
void
write_signatures_bytes(const evt_link& link, Stream& stream) {
    auto visitor = stream_visitor<Stream>(stream);
    for(auto& sig : link.get_signatures()) {
        sig.view(visitor);
    }
}

template<typename Num>
void
encode(const bytes& b, size_t sz, std::string& str) {
    auto i = 0u;
    for(i = 0u; i < sz; i++) {
        if(b[i] == 0) {
            str.push_back('0');
        }
        else {
            break;
        }
    }

    auto num = Num{0};
    boost::multiprecision::import_bits(num, b.begin() + i, b.begin() + sz, 8);

    while(num >= 42) {
        auto r = num % 42;
        str.push_back(*(ALPHABETS + (int)r));
        num /= 42;
    }
    str.push_back(*(ALPHABETS + (int)num));
    std::reverse(str.begin() + i, str.end());
}

}  // namespace internal

fc::sha256
evt_link::digest() const {
    using namespace internal;

    auto enc = fc::sha256::encoder();
    write_segments_bytes(*this, enc);
    return enc.result();
}

std::string
evt_link::to_string(int prefix) const {
    using namespace internal;

    auto temp = bytes(MAX_BYTES);
    auto ds   = fc::datastream<char*>(temp.data(), temp.size());
    auto str  = string();

    if(prefix) {
        str.append(URI_SCHEMA);
    }

    auto str1 = string();
    write_segments_bytes(*this, ds);
    encode<bigint_segs>(temp, ds.tellp(), str1);
    str.append(str1);

    if(!signatures_.empty()) {
        str.push_back('_');

        ds.seekp(0);
        write_signatures_bytes(*this, ds);

        auto str2 = string();
        encode<bigint_sigs>(temp, ds.tellp(), str2);
        str.append(str2);
    }

    return str;
}

public_keys_set
evt_link::restore_keys() const {
    auto hash = digest();
    auto keys = public_keys_set();

    keys.reserve(signatures_.size());
    for(auto& sig : signatures_) {
        keys.emplace(public_key_type(sig, hash));
    }
    return keys;
}

void
evt_link::add_segment(const segment& seg) {
    auto it = segments_.emplace(seg.key, seg);
    if(!it.second) {
        // existed, replace old one
        it.first->second = seg;
    }
}

void
evt_link::remove_segment(uint8_t key) {
    segments_.erase(key);
}

void
evt_link::add_signature(const signature_type& sig) {
    signatures_.emplace(sig);
}

void
evt_link::sign(const private_key_type& pkey) {
    signatures_.emplace(pkey.sign(digest()));
}

}}}  // namespac evt::chain::contracts

namespace fc {

using evt::chain::contracts::evt_link;

void
from_variant(const fc::variant& v, evt_link& link) {
    link = evt_link::parse_from_evtli(v.get_string());
}

void
to_variant(const evt::chain::contracts::evt_link& link, fc::variant& v) {
    auto vo   = fc::mutable_variant_object();
    auto segs = fc::variants();
    auto sigs = fc::variants();
    auto keys = fc::variants();

    for(auto& it : link.get_segments()) {
        auto  sego = fc::mutable_variant_object();
        auto& seg  = it.second;

        sego["key"] = seg.key;
        if(seg.key <= 90) {
            sego["value"] = *seg.intv;
        }
        else if(seg.key <= 155) {
            sego["value"] = *seg.strv;
        }
        else if(seg.key <= 180) {
            sego["value"] = fc::to_hex(seg.strv->c_str(), seg.strv->size());
        }
        segs.emplace_back(std::move(sego));
    }

    for(auto& sig : link.get_signatures()) {
        sigs.emplace_back((std::string)sig);
    }

    for(auto& key : link.restore_keys()) {
        keys.emplace_back((std::string)key);
    }

    vo["header"]     = link.get_header();
    vo["segments"]   = std::move(segs);
    vo["signatures"] = std::move(sigs);
    vo["keys"]       = std::move(keys);

    v = std::move(vo);
}

}  // namespace fc


/********************************************************************************
 * Copyright 2009 The Robotics Group, The Maersk Mc-Kinney Moller Institute,
 * Faculty of Engineering, University of Southern Denmark
 *
 * 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 RW_MATH_PROJECTIONMATRIX_HPP_
#define RW_MATH_PROJECTIONMATRIX_HPP_

#if !defined(SWIG)
#include <rw/core/Ptr.hpp>

#include <Eigen/Core>
#endif
namespace rw { namespace math {

    /**
     * @brief projection matrix
     */
    class ProjectionMatrix
    {
      private:
        Eigen::Matrix< double, 4, 4 > _matrix;

      public:
        typedef rw::core::Ptr< ProjectionMatrix > Ptr;

        //! @brief constructor
        ProjectionMatrix (){};

        //! @brief get the boost matrix corresponding to this projection
        const Eigen::Matrix< double, 4, 4 > e () const { return _matrix; }

        //! @brief test if this is a perspective projection
        bool isPerspectiveProjection () { return _matrix (2, 3) < -0.5; }

        //! @brief test if this is a ortographic projection
        bool isOrtographicProjection () { return _matrix (3, 3) > 0.5; }

        /**
         * @brief set the projection matrix to an ortographic projection by defining
         * the box with length to all sides (left, right, bottom, top, near and far)
         * @param left [in] length in m to left edge of image
         * @param right [in] length in m to right edge of image
         * @param bottom [in] length in m to bottom edge of image
         * @param top [in] length in m to top edge of image
         * @param zNear [in] length in m to near clipping plane
         * @param zFar [in] length in m to far clipping plane
         */
        void setOrtho (double left, double right, double bottom, double top, double zNear,
                       double zFar);

        //! get ortographic projection. Onli valid if isOrtographicProjection is true
        bool getOrtho (double& left, double& right, double& bottom, double& top, double& zNear,
                       double& zFar) const;

        /**
         * @brief set the projection matrix to the viewing frustum
         * @param left [in] distance in m near cutting plane from center to left edge
         * @param right [in] distance in m near cutting plane from center to right edge
         * @param bottom [in] distance in m near cutting plane from center to bottom edge
         * @param top [in] distance in m near cutting plane from center to top edge
         * @param zNear [in] distance in m along z-axis to near cutting plane
         * @param zFar [in] distance in m along z-axis to far cutting plane
         */
        void setFrustum (double left, double right, double bottom, double top, double zNear,
                         double zFar);

        /**
         * @brief get the projection matrix to the viewing frustum
         * @param left [out] distance in m near cutting plane from center to left edge
         * @param right [out] distance in m near cutting plane from center to right edge
         * @param bottom [out] distance in m near cutting plane from center to bottom edge
         * @param top [out] distance in m near cutting plane from center to top edge
         * @param zNear [out] distance in m along z-axis to near cutting plane
         * @param zFar [out] distance in m along z-axis to far cutting plane
         */
        bool getFrustum (double& left, double& right, double& bottom, double& top, double& zNear,
                         double& zFar) const;

        /**
         * @brief set the projection matrix to perspective projection
         * @param fovy [in] vertical field of view [degrees]
         * @param aspectRatio [in] aspect ratio between width and height of image
         * @param zNear [in] distance to near cutting plane
         * @param zFar [in] distance to far cutting plane
         */
        void setPerspective (double fovy, double aspectRatio, double zNear, double zFar);

        /**
         * @brief set the projection matrix to perspective projection
         * @param fovy [in] vertical field of view [degrees]
         * @param width [in] width of image
         * @param height [in] height of image
         * @param zNear [in] distance to near cutting plane
         * @param zFar [in] distance to far cutting plane
         */
        void setPerspective (double fovy, double width, double height, double zNear, double zFar)
        {
            return setPerspective (fovy, (width * 1.0) / height, zNear, zFar);
        }

        /**
         * @brief get the projection matrix to perspective projection
         * @param fovy [in] vertical field of view [degrees]
         * @param aspectRatio [in] aspect ratio between width and height of image
         * @param zNear [in] distance to near cutting plane
         * @param zFar [in] distance to far cutting plane
         * @return is it succesfull
         */
        bool getPerspective (double& fovy, double& aspectRatio, double& zNear, double& zFar) const;

        /**
         * @brief convert the projection matrix to an OpenGL compatible matrix
         * @param arr [out] array of 16*sizeof(T) with the opengl matrix
         */
        template< class T > void toOpenGLMatrix (T arr[16])
        {
            for (int j = 0; j < 4; j++) {
                for (int k = 0; k < 4; k++)
                    arr[j + 4 * k] = static_cast< T > (_matrix (j, k));
            }
        }

        /**
         * @brief creates a projection matrix with a perspective projection
         * @param fovy
         * @param aspectRatio
         * @param zNear
         * @param zFar
         * @return new ProjectionMatrix.
         */
        static ProjectionMatrix makePerspective (double fovy, double aspectRatio, double zNear,
                                                 double zFar);

        /**
         * @brief creates a projection matrix with a perspective projection
         * @param fovy [in]
         * @param width [in] of image
         * @param height [in] of image
         * @param zNear [in]
         * @param zFar [in]
         * @return new ProjectionMatrix.
         */
        static ProjectionMatrix makePerspective (double fovy, double width, double height,
                                                 double zNear, double zFar);

        /**
         * @brief creates a projection matrix with a orthographic projection
         * @param left
         * @param right
         * @param bottom
         * @param top
         * @param zNear
         * @param zFar
         * @return new ProjectionMatrix.
         */
        static ProjectionMatrix makeOrtho (double left, double right, double bottom, double top,
                                           double zNear, double zFar);

        /**
         * get near and far clipping plane
         * @return
         */
        std::pair< double, double > getClipPlanes () const;
    };

}}    // namespace rw::math

#endif /* PROJECTIONMATRIX_HPP_ */


#include <Eigen/Core>
#include <iostream>
#include <mex.h>
#include <igl/C_STR.h>
#include <igl/matlab/mexErrMsgTxt.h>
#undef assert
#define assert( isOK ) ( (isOK) ? (void)0 : (void) ::mexErrMsgTxt(C_STR(__FILE__<<":"<<__LINE__<<": failed assertion `"<<#isOK<<"'"<<std::endl) ) )

#include <igl/PI.h>
#include <igl/iterative_closest_point.h>
#include <igl/matlab/MexStream.h>
#include <igl/matlab/parse_rhs.h>
#include <igl/matlab/prepare_lhs.h>
#include <igl/matlab/validate_arg.h>

void mexFunction(
  int          nlhs,
  mxArray      *plhs[],
  int          nrhs,
  const mxArray *prhs[]
)
{
  using namespace igl::matlab;
  Eigen::MatrixXd VX,VY;
  Eigen::MatrixXi FX,FY;
  igl::matlab::MexStream mout;
  std::streambuf *outbuf = std::cout.rdbuf(&mout);
  mexPrintf("Compiled at %s on %s\n",__TIME__,__DATE__);
  mexErrMsgTxt(nrhs>=4,"nrhs should be >= 4");
  parse_rhs_double(prhs+0,VX);
  parse_rhs_index(prhs+1,FX);
  parse_rhs_double(prhs+2,VY);
  parse_rhs_index(prhs+3,FY);
  int num_iters = 100;
  int num_samples = 1000;

  {
    int i = 4;
    while(i<nrhs)
    {
      mexErrMsgTxt(mxIsChar(prhs[i]),"Parameter names should be strings");
      // Cast to char
      const char * name = mxArrayToString(prhs[i]);
      if(strcmp("MaxIter",name)==0)
      {
        igl::matlab::validate_arg_scalar(i,nrhs,prhs,name);
        igl::matlab::validate_arg_double(i,nrhs,prhs,name);
        num_iters = (double)*mxGetPr(prhs[++i]);
      }
      else if(strcmp("NumSamples",name)==0)
      {
        igl::matlab::validate_arg_scalar(i,nrhs,prhs,name);
        igl::matlab::validate_arg_double(i,nrhs,prhs,name);
        num_samples = (double)*mxGetPr(prhs[++i]);
      }
      else
      {
        mexErrMsgTxt(false,C_STR("Unrecognized Parameter: "<<name));
      }
      i++;
    }
  }

  Eigen::Matrix3d R;
  Eigen::RowVector3d t;
  igl::iterative_closest_point(VX,FX,VY,FY,num_samples,num_iters,R,t);

  switch(nlhs)
  {
    case 3:
    {
      Eigen::MatrixXd VXRT = (VX*R).rowwise()+t;
      prepare_lhs_double(VXRT,plhs+2);
    }
    case 2:
      prepare_lhs_double(t,plhs+1);
    case 1:
      prepare_lhs_double(R,plhs+0);
    default:break;
  }

  // Restore the std stream buffer Important!
  std::cout.rdbuf(outbuf);
  return;
}


// Copyright Louis Dionne 2013-2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)

#include <boost/hana/assert.hpp>
#include <boost/hana/greater.hpp>
#include <boost/hana/less.hpp>
#include <boost/hana/optional.hpp>
namespace hana = boost::hana;

BOOST_HANA_CONSTANT_CHECK(hana::nothing < hana::just(3));
BOOST_HANA_CONSTANT_CHECK(hana::just(0) > hana::nothing);
static_assert(hana::just(1) < hana::just(3), "");
static_assert(hana::just(3) > hana::just(2), "");

int main() { }


#include "observables/SpectralDimensionObservable.h"
#include "observables/Observable.h"
#include "Utils.h"
#include "Vertex.h"
#include "Triangle.h"
#include <boost/math/special_functions/log1p.hpp>
#include <boost/unordered_map.hpp>
#include "Config.h"

SpectralDimensionObservable::SpectralDimensionObservable(unsigned int writeFrequency) :
Observable(writeFrequency, 0, true),
sigmaMax(READ_CONF("spec.sigmaMax", 1000)),
diffusionConst(READ_CONF("spec.diff", 1.0)),
dualLattice(READ_CONF("spec.dualLattice", false)),
filename(createFilename("specdim")),
file(filename.c_str()),
specDim(sigmaMax),
specDim1(sigmaMax) {
}

SpectralDimensionObservable::~SpectralDimensionObservable() {

}

void SpectralDimensionObservable::process(const std::vector<Vertex*>& state) {
    boost::array<std::vector<double>, 2 > probBuffers;
    std::vector< std::vector<unsigned int> > neighbours;

    if (dualLattice) {
        neighbours = buildDualLatticeConnectivity(state);
    } else {
        neighbours = buildLatticeConnectivity(state);
    }

    probBuffers[0] = std::vector<double>(neighbours.size());
    probBuffers[1] = std::vector<double>(neighbours.size());

    unsigned int cur = 0; // current buffer
    unsigned int start = 0; // TODO: average over multiple starting points?
    probBuffers[cur][start] = 1;
    Spec prob(sigmaMax);

    for (unsigned int sigma = 0; sigma < sigmaMax; sigma++) {
        prob[sigma] = probBuffers[cur][start];

        for (unsigned int i = 0; i < neighbours.size(); i++) {
            if (probBuffers[cur][i] > epsilon) {
                for (unsigned int n = 0; n < neighbours[i].size(); n++) {
                    // if this node could not be reached in half the number of maximum steps,
                    // it will never reach it back to the starting position
                    if (sigma > sigmaMax / 2 && probBuffers[cur][neighbours[i][n]] < epsilon) {
                        continue;
                    }

                    probBuffers[(cur + 1) % 2][neighbours[i][n]] += diffusionConst *
                            probBuffers[cur][i] / static_cast<double> (neighbours[i].size());
                }
            }

            // there is a probability that the diffusion particles remain where
            // they are. This prevents wild oscillations at low sigma.
            probBuffers[(cur + 1) % 2][i] += (1.0 - diffusionConst) * probBuffers[cur][i];
        }

        // switch buffers
        for (unsigned int i = 0; i < neighbours.size(); i++) {
            probBuffers[cur][i] = 0.0;
        }

        cur = (cur + 1) % 2;
    }

    /* Update spectral dimension */
    for (unsigned int sigma = 2; sigma < sigmaMax - 1; sigma++) {
        specDim[sigma] = -2.0 * (double) sigma * (prob[sigma + 1] / prob[sigma] - 1.0);
        specDim1[sigma] = -2.0 * log(prob[sigma + 1] / prob[sigma]) /
                boost::math::log1p(1.0 / (double) sigma);
    }
}

void SpectralDimensionObservable::printToScreen() {
}

void SpectralDimensionObservable::printToFile() {
    for (unsigned int sigma = 0; sigma < sigmaMax - 1; sigma++) {
        file << specDim[sigma] << " ";
    }
    file << std::endl;

    for (unsigned int sigma = 0; sigma < sigmaMax - 1; sigma++) {
        file << specDim1[sigma] << " ";
    }
    file << std::endl;
}


#include <deal.II/base/function.h>
#include <deal.II/base/function_lib.h>
#include <deal.II/base/logstream.h>
#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/symmetric_tensor.h>
#include <deal.II/base/tensor_function.h>
#include <deal.II/base/utilities.h>

#include <deal.II/lac/affine_constraints.h>
#include <deal.II/lac/block_vector.h>
#include <deal.II/lac/block_sparse_matrix.h>
#include <deal.II/lac/full_matrix.h>
#include <deal.II/lac/solver_gmres.h>
#include <deal.II/lac/precondition.h>
#include <deal.II/lac/vector.h>
#include <deal.II/lac/solver_bicgstab.h>

#include <deal.II/grid/tria.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/grid/tria_accessor.h>
#include <deal.II/grid/tria_iterator.h>
#include <deal.II/grid/grid_tools.h>
#include <deal.II/grid/grid_refinement.h>

#include <deal.II/dofs/dof_handler.h>
#include <deal.II/dofs/dof_renumbering.h>
#include <deal.II/dofs/dof_accessor.h>
#include <deal.II/dofs/dof_tools.h>

#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/fe_dgq.h>
#include <deal.II/fe/fe_raviart_thomas.h>
#include <deal.II/fe/fe_system.h>
#include <deal.II/fe/fe_values.h>

#include <deal.II/numerics/vector_tools.h>
#include <deal.II/numerics/matrix_tools.h>
#include <deal.II/numerics/data_out.h>
#include <deal.II/numerics/error_estimator.h>

#include <deal.II/lac/sparse_direct.h>
#include <deal.II/base/timer.h>
#include <deal.II/grid/grid_in.h>

#include <cmath>
#include <fstream>
#include <iostream>

#include "json.hpp"

using namespace dealii;
using Json = nlohmann::json;

template <int dim>
class THMConsolidation
{
public:
  THMConsolidation(const unsigned degree, const nlohmann::json json);
  void run();

private:
  void make_grid();
  void setup_system();
  void assemble_system();
  void solve(unsigned step);
  void output_results(unsigned int step, unsigned int current_time) const;

  Triangulation<dim>        triangulation_;
  FESystem<dim>             fe_;
  DoFHandler<dim>           dof_handler_;
  //AffineConstraints<double> constraints_;

/*
  BlockSparsityPattern      sparsity_pattern_;
  BlockSparseMatrix<double> system_matrix_;
  BlockVector<double> solution_;
  BlockVector<double> system_rhs_;
*/
  SparsityPattern      sparsity_pattern_;
  SparseMatrix<double> system_matrix_;
  SparseMatrix<double> system_matrix_k_;
  SparseMatrix<double> system_matrix_c_;
  Vector<double> solution_;
  Vector<double> old_solution_;
  Vector<double> system_rhs_;
  Vector<double> solution_var_;

  unsigned         degree_;
  // double           model_length_;
  // double           height_over_width_;
  double            inner_radius_;
  double            outer_radius_;
  double            subdivision_;
  double            max_steps_;
  double           time_step_;
  double           current_time_;
  double           k_in_;
  double           k_T_in_;
  double           alpha_in;
  double           lambda_in_;
  double           mu_in_;
  double           pressure_in_;
  double           porosity_in_;
  double           density_in_;
  double           alpha_sw_in_;
  double           h_cap_sw_in_;
  double           step_torr_;
  double           first_step_;
  double           Q_in_;
  const double     gamma_w=1;
};

// For Dirichlet boundary conditions
// Deformation Dirichlet boundary conditions
template <int dim>
class DeformationDirichletBoundary : public Function<dim>
{
public:
  DeformationDirichletBoundary() : Function<dim>(dim + 2) {}
  virtual void vector_value(const Point<dim> &p, Vector<double> &  value) const override;
  void get_geometry(const double inner, const double outer) {inner_radius_ = inner; outer_radius_ = outer;}
private:
  double inner_radius_;
  double outer_radius_;
};

template <int dim>
void DeformationDirichletBoundary<dim>::vector_value(const Point<dim> &p, Vector<double> &  values) const
{
  // if (p[2] == 0) {
  //   for (unsigned int c = 0; c < 3; ++c) {
  //     if (c==2){
  //       values(c) = 0;
  //     }
  //     else {
  //       values(c) = 0;
  //     }

  //   }
  //   //values(0) = 11; values(1) = 21; values(2) = 51;
  // } else if (p[0] == 0 || p[1] == 0 || std::abs(p[0]-model_length_) < 1e-12 || std::abs(p[1]-model_length_) < 1e-12) {
  //   for (unsigned int c = 0; c < 2; ++c) values(c) = 0;
  //   //values(0) = 31; values(1) = 41;
  // if (std::abs(std::pow(p[0],2)+std::pow(p[1],2)+std::pow(p[2],2)-std::pow(inner_radius_,2))<=1e-5)
  // {
  //   values(0)=0; values(1)=0;values(2)=0;
  // }
   values(0)=0; values(1)=0;values(2)=0;
  // if (std::abs(std::pow(p[0],2)+std::pow(p[1],2)+std::pow(p[2],2)-std::pow(outer_radius_,2))==0)
  // {
  //   values(0)=0; values(1)=0;values(2)=0;
  // }

}

// Pressure dirichlet boundary
template <int dim>
class PressureDirichletBoundary : public Function<dim>
{
public:
  PressureDirichletBoundary() : Function<dim>(dim +2) {}
  virtual void vector_value(const Point<dim> &p, Vector<double> &  value) const override;
  void get_geometry(const double inner, const double outer) {inner_radius_ = inner; outer_radius_ = outer;}
private:
  double inner_radius_;
  double outer_radius_;
};

template <int dim>
void PressureDirichletBoundary<dim>::vector_value(const Point<dim> &p, Vector<double> &  values) const
{
  // if (std::abs(p[2] - model_length_*height_over_width_) < 1e-12) values(3) = 0;
  // // if (std::abs(p[2] - 0.0) < 1e-12) values(3) = 0.1;
  // if (std::abs(std::pow(p[0],2)+std::pow(p[1],2)+std::pow(p[2],2)-std::pow(outer_radius_,2))<=1e-5)
  // {
  //   values(3)=0;
  // }
  values(3)=0;
}

// Temperature dirichlet boundary
template <int dim>
class TemperatureDirichletBoundary : public Function<dim>
{
public:
  TemperatureDirichletBoundary() : Function<dim>(dim + 1) {}
  virtual void vector_value(const Point<dim> &p, Vector<double> &  value) const override;
  void get_geometry(const double inner, const double outer) {inner_radius_ = inner; outer_radius_ = outer;}
private:
  double inner_radius_;
  double outer_radius_;
};

template <int dim>
void TemperatureDirichletBoundary<dim>::vector_value(const Point<dim> &p, Vector<double> &  values) const
{

  // if (std::abs(std::pow(p[0],2)+std::pow(p[1],2)+std::pow(p[2],2)-std::pow(outer_radius_,2))<=1e-5)
  // {
  //   values(4)=0;
  // }
   values(4)=0;
  // if (std::abs(std::pow(p[0],2)+std::pow(p[1],2)+std::pow(p[2],2)-std::pow(inner_radius_,2))<=1e-3)
  // {
  //   values(4)=0;
  // }
}

template <int dim>
class DeformationNeumannBoundary : public TensorFunction<1,dim>
{
public:
  DeformationNeumannBoundary() : TensorFunction<1,dim>() {}
  virtual void value_list(const std::vector<Point<dim>> &points,
                          std::vector<Tensor<1,dim>> & values) const override;
  void get_pressure(const double pressure) {pressure_ = pressure; }
private:
  double pressure_;
};

template <int dim>
void DeformationNeumannBoundary<dim>::value_list(const std::vector<Point<dim>> &points,
                                                 std::vector<Tensor<1,dim>> & values) const
{
  (void)points;
  AssertDimension(points.size(), values.size());
  // for(auto &value : values) {
  //   value[0] = 0; value[1] = 0; value[2] = -pressure_;
  // }
}
// Pressure Neumann boundary
template <int dim>
class PressureNeumannBoundary : public TensorFunction<1,dim>
{
public:
  PressureNeumannBoundary() : TensorFunction<1,dim>() {}
  virtual void value_list(const std::vector<Point<dim>> &points,
                          std::vector<Tensor<1,dim>> & values) const override;
  void get_pressure_grad(const Tensor<1,dim> pressure_grad) {pressure_grad_ = pressure_grad; }
private:
  Tensor<1,dim> pressure_grad_;
};

template <int dim>
void PressureNeumannBoundary<dim>::value_list(const std::vector<Point<dim>> &points,
                                                 std::vector<Tensor<1,dim>> & values) const
{
  (void)points;
  AssertDimension(points.size(), values.size());
  for(auto &value : values) {
    value[0] = 0; value[1] = 0; value[2] = 0;
  }
}
// Temperature Neumann boundary
// template <int dim>
// class TemperatureNeumannBoundary : public TensorFunction<1,dim>
// {
// public:
//   TemperatureNeumannBoundary() : TensorFunction<1,dim>() {}
//   virtual void value_list(const std::vector<Point<dim>> &points,
//                           std::vector<Tensor<1,dim>> & values) const override;
//   void get_Q(const double Q) {Q_ = Q; }
// private:
//   double Q_;
// };
// template <int dim>
// void TemperatureNeumannBoundary<dim>::value_list(const std::vector<Point<dim>> &points,
//                                                  std::vector<Tensor<1,dim>> & values) const
// {
//   (void) points;
//   for(auto &value : values) {
//     ;
//   }
// }
template <int dim>
class TemperatureNeumannBoundary : public Function<dim>
{
public:
  TemperatureNeumannBoundary() : Function<dim>(dim + 2) {}
  virtual void vector_value(const Point<dim> &p, Vector<double> &  value) const override;
  void get_Q(const double Q) {Q_ = Q; }
private:
  double Q_;
};

template <int dim>
void TemperatureNeumannBoundary<dim>::vector_value(const Point<dim> &p, Vector<double> &  values) const
{
  (void)p;
  for(auto &value : values) {
     value=Q_;
  }
}

// For initial conditions
template <int dim>
class DeformationInitial : public Function<dim>
{
public:
  DeformationInitial() : Function<dim>(dim + 2) {}
  virtual void vector_value(const Point<dim> &p, Vector<double> &  value) const override;
};

template <int dim>
void DeformationInitial<dim>::vector_value(const Point<dim> &p, Vector<double> &  values) const
{
  (void)p;
  for (unsigned int c = 0; c < this->n_components; ++c)
    values(c) = 0.0;
}

template <int dim>
class PressureInitial : public Function<dim>
{
public:
  PressureInitial() : Function<dim>(dim + 2) {}
  virtual void vector_value(const Point<dim> &p, Vector<double> &  value) const override;
  void get_pressure(const double pressure) {pressure_ = pressure; }
private:
  double pressure_;
};

template <int dim>
void PressureInitial<dim>::vector_value(const Point<dim> &p, Vector<double> &  values) const
{
  (void)p;
  values(3) = 0;
}

// Initial temperature
template <int dim>
class TemperatureInitial : public Function<dim>
{
public:
  TemperatureInitial() : Function<dim>(dim + 2) {}
  void vector_value(const Point<dim> &p, Vector<double> &  value) const override;
  void get_temperature(const double temperature) {temperature_ = temperature; }
private:
  double temperature_;
};

template <int dim>
void TemperatureInitial<dim>::vector_value(const Point<dim> &p, Vector<double> &  values) const
{
  (void)p;
  values(4) = 0;
}

// Initial porosity
template <int dim>
class PorosityInitial : public Function<dim>
{
public:
  PorosityInitial() : Function<dim>(1) {}
  double value(const Point<dim> &p, const unsigned int component=0) const override;
  void get_porosity(const double porosity) {porosity_ = porosity; }
private:
  double porosity_;
};

template <int dim>
double PorosityInitial<dim>::value(const Point<dim> &p, const unsigned int component) const
{
  return porosity_;
}

template <int dim>
THMConsolidation<dim>::THMConsolidation(const unsigned degree, const nlohmann::json json)
  : degree_(degree)
  , fe_(FE_Q<dim>(degree+1), dim, FE_Q<dim>(degree), 1, FE_Q<dim>(degree), 1)
  , dof_handler_(triangulation_)
{
  // model_length_ = json["parameters"]["model_length"].template get<double>();
  // height_over_width_ = json["parameters"]["height_over_width"].template get<double>();
  inner_radius_ = json["parameters"]["inner_radius"].template get<unsigned>();
  outer_radius_ = json["parameters"]["outer_radius"].template get<unsigned>();
  subdivision_ = json["parameters"]["subdivision"].template get<unsigned>();
  max_steps_ = json["parameters"]["max_steps"].template get<unsigned>();
  time_step_ = json["parameters"]["time_step"].template get<double>();
  // pressure_in_ = json["parameters"]["loading_on_top"].template get<double>();
  k_in_ = json["parameters"]["k"].template get<double>();
  k_T_in_ = json["parameters"]["k_T"].template get<double>();
  alpha_in = json["parameters"]["alpha"].template get<double>();
  lambda_in_ = json["parameters"]["lambda"].template get<double>();
  mu_in_ = json["parameters"]["mu"].template get<double>();
  porosity_in_ = json["parameters"]["porosity"].template get<double>();
  density_in_ = json["parameters"]["density"].template get<double>();
  Q_in_=json["parameters"]["Q"].template get<double>();
  alpha_sw_in_=json["parameters"]["alpha_sw"].template get<double>();
  h_cap_sw_in_=json["parameters"]["h_cap_sw"].template get<double>();
  std::cout<<"inner_radius: "<<inner_radius_<<", outer_radius: "<<outer_radius_<<std::endl
           <<"Subdivision: "<<subdivision_<<std::endl
           <<"Total steps: "<<max_steps_<<std::endl<<"Time step: "<<time_step_<<std::endl
           <<"k: "<<k_in_<<std::endl
           <<"lambda: "<<lambda_in_<<", mu: "<<mu_in_<<std::endl
           <<"k_T: "<<k_T_in_<<", Q_in: "<<Q_in_<<std::endl;
}

template <int dim>
void THMConsolidation<dim>::make_grid()
{
  //Tensor<1,dim> TP1;
  //TP1(0)=0;TP1(1)=0;TP1(2)=0;
  //Tensor<1,dim> TP2;
  //TP2(0)=model_length_;TP2(1)=model_length_;TP2(2)=model_length_;
  const Point<dim,double> grid_p1(0.0, 0.0, 0.0);
  // const Point<dim,double> grid_p2(model_length_, model_length_, model_length_ * height_over_width_);
  // const std::vector<unsigned int> repetitions{subdivision_, subdivision_, subdivision_ * height_over_width_};
   GridGenerator::hyper_shell(triangulation_,grid_p1, inner_radius_, outer_radius_,0);
  // GridIn<dim> gridin;
  // gridin.attach_triangulation(triangulation_);
  // std::ifstream f("void_sphere3.msh");
  // gridin.read_msh(f);

  //triangulation_.refine_global(2);
  for (unsigned int i=0; i<=subdivision_; i++)
  {
    std::cout<<(float(i)/float(subdivision_))*inner_radius_+(1-float(i)/float(subdivision_))*outer_radius_<<std::endl;
    int j=0;
    int j0=0;
    for (const auto &cell : triangulation_.active_cell_iterators())
  {
    cell->clear_refine_flag();
       j0=j0+1;
    if (grid_p1.distance(cell->center()) <= (float(i)/float(subdivision_))*inner_radius_+(1-float(i)/float(subdivision_))*outer_radius_)
    {
      // std::cout<<grid_p1.distance(cell->center())<<std::endl;
      j=j+1;
      cell->set_refine_flag();

    }
  }
    triangulation_.execute_coarsening_and_refinement();
    std::cout<<i<<" refine loop "<<j0<<" all "<<j<<"refined"<<std::endl;
  }

  // triangulation_.refine_global(subdivision_);

  // Set boundary indicator for applying boundary conditions
  // -x:1, +x:2, -y:3, +y:4, -z:5, +z:6
  // if (dim == 3) {
  //   for (const auto &cell : triangulation_.active_cell_iterators()) {
  //     for (const auto &face : cell->face_iterators()) {
  //       if (face->center()[0] == 0) face->set_all_boundary_ids(1);
  //       else if (std::abs(face->center()[0] - model_length_) < 1e-12) face->set_all_boundary_ids(2);
  //     }
  //   }
  //   for (const auto &cell : triangulation_.active_cell_iterators()) {
  //     for (const auto &face : cell->face_iterators()) {
  //       if (face->center()[1] == 0) face->set_all_boundary_ids(3);
  //       else if (std::abs(face->center()[1] - model_length_) < 1e-12) face->set_all_boundary_ids(4);
  //     }
  //   }
  //   for (const auto &cell : triangulation_.active_cell_iterators()) {
  //     for (const auto &face : cell->face_iterators()) {
  //       if (face->center()[2] == 0) face->set_all_boundary_ids(5);
  //       else if (std::abs(face->center()[2] - model_length_*height_over_width_) < 1e-12) face->set_all_boundary_ids(6);
  //     }
  //   }
  // }
  // Set boundary indicator for applying boundary conditions
  // inner:1 outer 2
  if (dim==3)
  {
    double k1=0;
    double k2=0;
    int i=0;
    int j=0;
    for (const auto &cell : triangulation_.active_cell_iterators())
    {
      i++;
      j=0;
      for (const auto &face : cell->face_iterators())
      {
        j++;
        if (std::abs(
          std::pow(face->center()[0],2)+
          std::pow(face->center()[1],2)+
          std::pow(face->center()[2],2)
          - std::pow(inner_radius_,2)) < 0.1)
          {
            face->set_all_boundary_ids(1);
            k1++;
            //std::cout<<i<<" id1 "<<j<<std::endl;
          }
        else if (std::abs(
          std::pow(face->center()[0],2)+
          std::pow(face->center()[1],2)+
          std::pow(face->center()[2],2)
          - std::pow(outer_radius_,2)) <10)
          {
            face->set_all_boundary_ids(2);
            k2++;
            // std::cout<<"bc2: "<<face->center()[0]<<","<<face->center()[1]<<","<<face->center()[2]<<std::endl;
            //std::cout<<i<<" id2 "<<j<<std::endl;
          }
          else
          {
            face->set_all_boundary_ids(3);
          }

      }
    }
    std::cout<<"k1 "<<k1<<",k2 "<<k2<<std::endl;
  }

  dof_handler_.distribute_dofs(fe_);

  // Re-numner dof, make velocities and pressures are not intermingled
  DoFRenumbering::component_wise(dof_handler_);

/*
  // Set constraints to apply Dirichlet boundary conditions
  {
    constraints_.clear();

    DeformationDirichletBoundary<dim> deformation_dirichlet;
    deformation_dirichlet.get_model_length_and_ratio(model_length_, height_over_width_);
    PressureDirichletBoundary<dim> pressure_dirichlet;
    pressure_dirichlet.get_model_length_and_ratio(model_length_, height_over_width_);

    FEValuesExtractors::Vector deformations_bottom(0);
    FEValuesExtractors::Scalar pressure(dim);
    std::vector<bool> deformations_side{true, true, false, false};
    DoFTools::make_hanging_node_constraints(dof_handler_, constraints_);
    for (int i = 1; i <= 4; ++i) {
      VectorTools::interpolate_boundary_values(dof_handler_,
                                               i,
                                               deformation_dirichlet,
                                               constraints_,
                                               fe_.component_mask(deformations_side));
    }
    VectorTools::interpolate_boundary_values(dof_handler_,
                                             5,
                                             deformation_dirichlet,
                                             constraints_,
                                             fe_.component_mask(deformations_bottom));
     VectorTools::interpolate_boundary_values(dof_handler_,
                                              6,
                                              pressure_dirichlet,
                                              constraints_,
                                              fe_.component_mask(pressure));
//
          // VectorTools::interpolate_boundary_values(dof_handler_,
          //                                      5,
          //                                      pressure_dirichlet,
          //                                      constraints_,
          //                                      fe_.component_mask(pressure));

  }
  constraints_.close();
*/

  // Count the number of velocity and pressure dofs
  const std::vector<types::global_dof_index> dofs_per_component =
    DoFTools::count_dofs_per_fe_component(dof_handler_);
  unsigned int n_u = 0;
  for(int i = 0; i < dim; ++i) n_u += dofs_per_component[i];
  const unsigned int n_p = dofs_per_component[dim];
  const unsigned int n_T = dofs_per_component[dim+1];

  std::cout << "Number of active cells: " << triangulation_.n_active_cells()<< std::endl
            << "Total number of cells: " << triangulation_.n_cells()<< std::endl
            << "Number of degrees of freedom: " << dof_handler_.n_dofs()
            << " (" << n_u << '+' << n_p << "+"<<n_T<<')' << std::endl;

  // Allocate sparsity patterns
/*
  BlockDynamicSparsityPattern dsp(2, 2);
  dsp.block(0, 0).reinit(n_u, n_u);
  dsp.block(1, 0).reinit(n_p, n_u);
  dsp.block(0, 1).reinit(n_u, n_p);
             +
  DoFTools::make_sparsity_pattern(dof_handler_, dsp, constraints_, false);

  sparsity_pattern_.copy_from(dsp);
  system_matrix_.reinit(sparsity_pattern_);

  // Resize the solution and right hand side vectors
  solution_.reinit(2);
  solution_.block(0).reinit(n_u);
  solution_.block(1).reinit(n_p);
  solution_.collect_sizes();
  system_rhs_.reinit(2);
  system_rhs_.block(0).reinit(n_u);
  system_rhs_.block(1).reinit(n_p);
  system_rhs_.collect_sizes();
*/
  DynamicSparsityPattern dsp(n_u+n_p+n_T, n_u+n_p+n_T);
  DoFTools::make_sparsity_pattern(dof_handler_, dsp/*, constraints_, false*/);
  sparsity_pattern_.copy_from(dsp);
  system_matrix_.reinit(sparsity_pattern_);
  system_matrix_=0;
  system_matrix_k_.reinit(sparsity_pattern_);
  system_matrix_k_=0;
  system_matrix_c_.reinit(sparsity_pattern_);
  system_matrix_c_=0;
  solution_.reinit(n_u+n_p+n_T);
  solution_ = 0;
  system_rhs_.reinit(n_u+n_p+n_T);
  system_rhs_=0;
}

// Assemble matrix system
template <int dim>
void THMConsolidation<dim>::assemble_system()
{
  const std::vector<size_t> deformation_idx{0, 1, 2};
  const std::vector<size_t> pressure_idx{3};
  const std::vector<size_t> temperature_idx{4};

  system_matrix_ = 0;
  system_matrix_k_ = 0;
  system_matrix_c_ = 0;
  system_rhs_    = 0;

  QGauss<dim>     quadrature_formula(degree_+1 );
  QGauss<dim - 1> face_quadrature_formula(degree_+1 );

  FEValues<dim>     fe_values(fe_,
                          quadrature_formula,
                          update_values | update_gradients |
                            update_quadrature_points | update_JxW_values);
  FEFaceValues<dim> fe_face_values(fe_,
                                   face_quadrature_formula,
                                   update_values | update_normal_vectors |
                                     update_quadrature_points |
                                     update_JxW_values);

  const unsigned int dofs_per_cell = fe_.dofs_per_cell;

  const unsigned int n_q_points      = quadrature_formula.size();
  const unsigned int n_face_q_points = face_quadrature_formula.size();

  // Initialize local matrices
  FullMatrix<double> local_matrix(dofs_per_cell, dofs_per_cell);
  FullMatrix<double> local_matrix_k(dofs_per_cell, dofs_per_cell);
  FullMatrix<double> local_matrix_c(dofs_per_cell, dofs_per_cell);
  Vector<double>     local_rhs(dofs_per_cell);

  // Initialize local old solution containers
  std::vector<Vector<double>> old_solution_values(n_q_points,
                                                  Vector<double>(dim+2));
  //std::vector<Vector<double>> old_solution_values_face(n_face_q_points,
                                                       //Vector<double>(dim + 1));

  std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);

  // Initialize defined functions for boundary values
  DeformationNeumannBoundary<dim> deformation_neumann_boundary;
  TemperatureNeumannBoundary<dim> temperature_neumann_boundary;
  temperature_neumann_boundary.get_Q(Q_in_);
  // deformation_neumann_boundary.get_pressure(pressure_in_);
  //PressureNeumannBoundary<dim> pressure_neumann_boundary;
  //GravityValues<dim> gravity_boundary_values;
  // Constant parameters
  std::vector<double> lambda_values(n_q_points);
  std::vector<double> mu_values(n_q_points);
  std::vector<double> k_values(n_q_points);
  std::vector<double> k_T_values(n_q_points);
  std::vector<double> alpha_values(n_q_points);
  std::vector<double> alpha_sw_values(n_q_points);
  std::vector<double> porosity_values(n_q_points);
  std::vector<double> h_cap_w_values(n_q_points);
  std::vector<double> h_cap_sw_values(n_q_points);
  std::vector<double> density_values(n_q_points);
  std::vector<double> pressure_values(n_q_points);

  Functions::ConstantFunction<dim> lambda(lambda_in_), mu(mu_in_), k(k_in_), alpha(alpha_in), k_T(k_T_in_),
   porosity(porosity_in_),density(density_in_),alpha_sw(alpha_sw_in_),h_cap_sw(h_cap_sw_in_);

  // std::vector<Tensor<1, dim>>         deformation_neumann_boundary_values(n_face_q_points);
  std::vector<double>         temperature_neumann_boundary_values(n_face_q_points);

  //std::vector<Tensor<1, dim>> boundary_g_values(n_q_points);
  //std::vector<Tensor<1, dim>> pressure_neumann_boundary_values(n_q_points);
  std::vector<double>         boundary_k_T_values(n_face_q_points);
  const FEValuesExtractors::Vector deformations(0);
  const FEValuesExtractors::Scalar pressure(dim);
  const FEValuesExtractors::Scalar temperature(dim+1);
  // std::cout<<"cell_iterators started"<<std::endl;

  for (const auto &cell : dof_handler_.active_cell_iterators()) {
    fe_values.reinit(cell);
    local_matrix = 0;
    local_matrix_k = 0;
    local_matrix_c = 0;
    local_rhs    = 0;

    lambda.value_list(fe_values.get_quadrature_points(), lambda_values);
    mu.value_list(fe_values.get_quadrature_points(), mu_values);
    k.value_list(fe_values.get_quadrature_points(), k_values);
    k_T.value_list(fe_values.get_quadrature_points(), k_T_values);
    alpha.value_list(fe_values.get_quadrature_points(), alpha_values);
    porosity.value_list(fe_values.get_quadrature_points(), porosity_values);
    density.value_list(fe_values.get_quadrature_points(), density_values);
    alpha_sw.value_list(fe_values.get_quadrature_points(), alpha_sw_values);
    h_cap_sw.value_list(fe_values.get_quadrature_points(), h_cap_sw_values);

    // Get old solution value at Gauss points
    fe_values.get_function_values(solution_, old_solution_values);
    // std::cout<<"intergation"<<std::endl;

    // std::cout<<"intergation"<<std::endl;

    for (unsigned int q = 0; q < n_q_points; ++q) {
      for (unsigned int i = 0; i < dofs_per_cell; ++i) {
        // std::cout<<"i index"<<std::endl;
        const unsigned int component_i = fe_.system_to_component_index(i).first;

        const Tensor<1, dim> phi_i_u = fe_values[deformations].value(i, q);
        const Tensor<2, dim> grad_phi_i_u = fe_values[deformations].gradient(i, q);
        const SymmetricTensor<2,dim> symmgrad_phi_i_u = fe_values[deformations].symmetric_gradient(i, q);
        const double phi_i_p     = fe_values[pressure].value(i, q);
        const double phi_i_T     = fe_values[temperature].value(i, q);
                // std::cout<<"break"<<std::endl;
        const Tensor<1, dim> grad_phi_i_p = fe_values[pressure].gradient(i, q);
        const Tensor<1, dim> grad_phi_i_T = fe_values[temperature].gradient(i, q);
        const double div_phi_i_u=fe_values[deformations].divergence(i,q);

        for (unsigned int j = 0; j < dofs_per_cell; ++j) {
          // std::cout<<"j index"<<std::endl;
          const unsigned int component_j =  fe_.system_to_component_index(j).first;
          const Tensor<1, dim> phi_j_u = fe_values[deformations].value(j, q);
          //const Tensor<2, dim> grad_phi_j_u = fe_values[deformations].gradient(j, q);
          const SymmetricTensor<2,dim> symmgrad_phi_j_u = fe_values[deformations].symmetric_gradient(j, q);
          const double phi_j_p     = fe_values[pressure].value(j, q);
          const double phi_j_T     = fe_values[temperature].value(j, q);
          const Tensor<1, dim> grad_phi_j_p = fe_values[pressure].gradient(j, q);
          const Tensor<1, dim> grad_phi_j_T = fe_values[temperature].gradient(j, q);
          const double div_phi_j_u=fe_values[deformations].divergence(j,q);

          Tensor<1, dim> sub_solution_deformation;
          double sub_solution_pressure;
          for(auto id : deformation_idx) sub_solution_deformation[id] = old_solution_values[q](id);
          sub_solution_pressure = old_solution_values[q](dim);
/*
          local_matrix(i, j) +=
             (
             (lambda_values[q] * div_phi_i_u * div_phi_j_u)
             +
             (2 * mu_values[q] *symmgrad_phi_i_u * symmgrad_phi_j_u)
             +
             (-div_phi_i_u*phi_j_p)
             +
             (grad_phi_i_p * k_values[q] * grad_phi_j_p)/gamma_w
             +
             ( phi_i_p*div_phi_j_u/(time_step_) )
             )*fe_values.JxW(q);
*/

          local_matrix_k(i, j) +=
             (
             (lambda_values[q] * div_phi_i_u * div_phi_j_u)
             +
             (2 * mu_values[q] *symmgrad_phi_i_u * symmgrad_phi_j_u) //K_uu
             +
             (+div_phi_i_u*phi_j_p) //Q_up
             +
             (-grad_phi_i_p * k_values[q] * grad_phi_j_p)/gamma_w //K_pp
             +
             (alpha_values[q]*div_phi_i_u*phi_j_T) //Q_uT
             +
             (k_T_values[q]*grad_phi_i_T*grad_phi_j_T) //K_tt0
             //+
             //(k_T_values[q]*h_cap_w_values[q]/gamma_w*phi_i_T*pressure_values[q]*grad_phi_j_p*grad_phi_j_T) // K_tt1
             )*fe_values.JxW(q);

          local_matrix_c(i, j) +=
             (
             ( phi_i_p*div_phi_j_u ) //R_pu
             +
             //(porosity_values[q]*alpha_values[q]*phi_i_p*phi_j_T)//R_pT Rui's formulation
             (alpha_sw_values[q]*phi_i_p*phi_j_T) //R_pT booker's formulation
             +
             (density_values[q]*h_cap_sw_values[q]*phi_i_T*phi_j_T) //C_TT
             )*fe_values.JxW(q)/(time_step_/* std::pow(10,step-1)*/);
            //  if (local_matrix_c(i, j)!=0)
            //  {
            //    std::cout<<local_matrix_c(i, j)<<" C"<<local_matrix_k(i,j)<<" K  "<<std::endl;

            //  }
/*
          local_rhs(i) +=
           (
             phi_i_p * div_phi_j_u / (time_step_ )
           )
           *
            (
              phi_j_u * sub_solution_deformation +k.value_list(fe_values.get_quadrature_points(), k_values);
              phi_j_p * sub_solution_pressure
            )
            * fe_values.JxW(q);        system_matrix_.add(local_dof_indices[i],
                           local_dof_indices[j],
                           local_matrix_c(i, j));
*/
        } // for j

      } // for i

        //std::cout<<"local rhs "<<local_rhs<<std::endl;

    } // for q

    // Apply Neumann boundary conditions
    for (const auto &face : cell->face_iterators()) {
      if (face->boundary_id() == 1) {

        fe_face_values.reinit(cell, face);

        // deformation_neumann_boundary.value_list(fe_face_values.get_quadrature_points(), deformation_neumann_boundary_values);

        temperature_neumann_boundary.value_list(fe_face_values.get_quadrature_points(), temperature_neumann_boundary_values);

        k_T.value_list(fe_face_values.get_quadrature_points(), boundary_k_T_values);
        //gravity_boundary_values.value_list(fe_face_values.get_quadrature_points(), boundary_g_values);

        for (unsigned int q = 0; q < n_face_q_points; ++q) {
          const Tensor<1, dim> nf = fe_face_values.normal_vector(q);

          for (unsigned int i = 0; i < dofs_per_cell; ++i) {
            const unsigned int component_i = fe_.system_to_component_index(i).first;
            const Tensor<1, dim> phi_i_u = fe_face_values[deformations].value(i, q);
            const double phi_i_p = fe_face_values[pressure].value(i, q);
            const double phi_i_T = fe_face_values[temperature].value(i, q);
            const Tensor<1, dim> grad_phi_i_T = fe_values[temperature].gradient(i, q);

            local_rhs(i) +=
             (
              // (phi_i_u * deformation_neumann_boundary_values[q]) //A_u
              +
              // ((grad_phi_i_T *nf)*temperature_neumann_boundary_values[q]/boundary_k_T_values[q])
              (phi_i_T*Q_in_/boundary_k_T_values[q]) //A_T
             ) * fe_face_values.JxW(q);
            //  std::cout<<temperature_neumann_boundary_values[q]<<" BCvalue"<<std::endl;
            //  if (local_rhs(i)!=0){
            //    std::cout<<local_rhs(i)<<" nonzero RHS"<<std::endl;
            //  }
            //  std::cout<<local_rhs(i)<<" RHS"<<std::endl;

          }
        }
      }
    } // for face

    // Assemble local matrix into global
    cell->get_dof_indices(local_dof_indices);
    for (unsigned int i = 0; i < dofs_per_cell; ++i) {
      for (unsigned int j = 0; j < dofs_per_cell; ++j) {
        system_matrix_.add(local_dof_indices[i],
                           local_dof_indices[j],
                           local_matrix_k(i, j));
        system_matrix_.add(local_dof_indices[i],
                           local_dof_indices[j],
                           local_matrix_c(i, j));
        system_matrix_c_.add(local_dof_indices[i],
                           local_dof_indices[j],
                           local_matrix_c(i, j));
      }
    }
    for (unsigned int i = 0; i < dofs_per_cell; ++i)
      system_rhs_(local_dof_indices[i]) += local_rhs(i);
    //constraints_.distribute_local_to_global(local_matrix, local_s, local_dof_indices, system_matrix_, system_rhs_);

  } // for cell
// for (int i=0; i<system_matrix_.n(); ++i)
// {
//   if (system_matrix_(i,i)==0)
//   {
//     std::cout<<system_matrix_(i,i)<<" K "<<std::endl;
//   }
// }

  // rhs += C/t * solution
  system_matrix_c_.vmult_add(system_rhs_, solution_);

  // Apply Dirichlet boundary conditions
  {
    DeformationDirichletBoundary<dim> deformation_dirichlet;
    deformation_dirichlet.get_geometry(inner_radius_, outer_radius_);
    PressureDirichletBoundary<dim> pressure_dirichlet;
    pressure_dirichlet.get_geometry(inner_radius_, outer_radius_);
    TemperatureDirichletBoundary<dim> temperature_dirichlet;
    temperature_dirichlet.get_geometry(inner_radius_, outer_radius_);

//     FEValuesExtractors::Vector deformations_bottom(0);
//     FEValuesExtractors::Scalar pressure(dim);
//     FEValuesExtractors::Scalar temperature(dim);
//     std::vector<bool> deformations_side{true, true, false, false};

//     for (int i = 1; i <= 4; ++i) {
//       std::map<types::global_dof_index, double> deformation_side_values;
//       VectorTools::interpolate_boundary_values(dof_handler_,
//                                                i,
//                                                deformation_dirichlet,
//                                                deformation_side_values,
//                                                fe_.component_mask(deformations_side));
//       MatrixTools::apply_boundary_values(deformation_side_values, system_matrix_, solution_, system_rhs_);
//     }

//     std::map<types::global_dof_index, double> deformation_bottom_values;
//     VectorTools::interpolate_boundary_values(dof_handler_,
//                                              5,
//                                              deformation_dirichlet,
//                                              deformation_bottom_values,
//                                              fe_.component_mask(deformations_bottom));
//     MatrixTools::apply_boundary_values(deformation_bottom_values, system_matrix_, solution_, system_rhs_);

//     std::map<types::global_dof_index, double> pressure_values;
//     VectorTools::interpolate_boundary_values(dof_handler_,
//                                              6,
//                                              pressure_dirichlet,
//                                              pressure_values,
//                                              fe_.component_mask(pressure));
//     MatrixTools::apply_boundary_values(pressure_values, system_matrix_, solution_, system_rhs_);
//   }
     FEValuesExtractors::Vector deformation(0);
     FEValuesExtractors::Scalar pressure(dim);
     FEValuesExtractors::Scalar temperature(dim+1);
     std::map<types::global_dof_index, double> deformation_values;
     VectorTools::interpolate_boundary_values(dof_handler_,
                                             1,
                                             deformation_dirichlet,
                                             deformation_values,
                                             fe_.component_mask(deformation));
        //  VectorTools::interpolate_boundary_values(dof_handler_,
        //                                      2,
        //                                      deformation_dirichlet,
        //                                      deformation_values,
        //                                      fe_.component_mask(deformation));
        //  VectorTools::interpolate_boundary_values(dof_handler_,
        //                                      3,
        //                                      deformation_dirichlet,
        //                                      deformation_values,
        //                                      fe_.component_mask(deformation));
     MatrixTools::apply_boundary_values(deformation_values, system_matrix_, solution_, system_rhs_);
     std::map<types::global_dof_index, double> pressure_values;
     VectorTools::interpolate_boundary_values(dof_handler_,
                                             2,
                                             pressure_dirichlet,
                                             pressure_values,
                                             fe_.component_mask(pressure));
     MatrixTools::apply_boundary_values(pressure_values, system_matrix_, solution_, system_rhs_);
    std::map<types::global_dof_index, double> temperature_values;
     VectorTools::interpolate_boundary_values(dof_handler_,
                                             2,
                                             temperature_dirichlet,
                                             temperature_values,
                                             fe_.component_mask(temperature));
     MatrixTools::apply_boundary_values(temperature_values, system_matrix_, solution_, system_rhs_);
 }
}

// GMRES solver
template <int dim>
void THMConsolidation<dim>::solve(unsigned step)
{
  std::cout<<"solver started"<<std::endl;
  //std::ofstream out("sparsity_pattern.svg");
  //sparsity_pattern_.print_svg(out);
  //  double mxmax=system_matrix_.el(0,0);
  // for(int i=0;  i<system_matrix_.m(); i++){
  //   for(int j=0;  j<system_matrix_.n(); j++){
  //     // std::cout<<i<<" "<<j<<" i j "<<std::endl;
  //     if(mxmax<system_matrix_.el(i,j)){
  //       mxmax=system_matrix_.el(i,j);

  //     }
  //   }
  // }
  // // std::cout<<mxmax<<" mxmax "<<std::endl;
  // // std::cout<<system_matrix_.m()<<" m "<<system_matrix_.n()<<" n "<<std::endl;

  //  double mxcmax=system_matrix_c_.el(0,0);
  // for(int i=0;  i<system_matrix_c_.m(); i++){
  //   for(int j=0;  j<system_matrix_c_.n(); j++){
  //     // std::cout<<i<<" "<<j<<" i j "<<std::endl;
  //     if(mxcmax<system_matrix_c_.el(i,j)){
  //       mxcmax=system_matrix_c_.el(i,j);

  //     }
  //   }
  // }
  // std::cout<<mxcmax<<" mxmax "<<std::endl;

  //  std::ofstream out1("matrix.txt");
  //  system_matrix_.print_as_numpy_arrays(out1);
  //  std::ofstream out2("matrixc.txt");
  //  system_matrix_c_.print_as_numpy_arrays(out2);

if (step > 0)
{ std::cout<<system_rhs_.l2_norm()<<" l2fnorm "<<std::endl   ;
  SolverControl               solver_control(std::max<std::size_t>(10000,
                                                      system_rhs_.size()/100),
                                1e-3* system_rhs_.l2_norm(),true,true);

   SolverGMRES<Vector<double>> solver(solver_control);
  //SolverBicgstab<Vector<double>> solver(solver_control);
   //PreconditionSOR<SparseMatrix<double>> preconditioner;
  // PreconditionIdentity preconditioner;
   //preconditioner.initialize(system_matrix_,1.3);
   PreconditionSSOR<SparseMatrix<double> > preconditioner;
   preconditioner.initialize(system_matrix_, PreconditionSSOR<SparseMatrix<double>>::AdditionalData(.6));
   solver.solve(system_matrix_, solution_, system_rhs_, preconditioner);

   Vector<double> residual(dof_handler_.n_dofs());

   system_matrix_.vmult(residual, solution_);
   residual -= system_rhs_;
   std::cout << "   Iterations required for convergence: "
             << solver_control.last_step() << '\n'
             << "   Max norm of residual:                "
             << residual.linfty_norm() << '\n';
             }

else
{
    SparseDirectUMFPACK A_direct;
  A_direct.initialize(system_matrix_);
  //A_direct.vmult(solution_, system_rhs_);
  A_direct.factorize(system_matrix_);
  A_direct.solve(system_rhs_);
  solution_ = system_rhs_;
}
/*
  // Direct solver

*/
/*
    std::cout
             << "   Max norm of rhs: "
             << system_rhs_.linfty_norm() << '\n' << std::endl;
    std::cout
             << "   Max norm of matrix: "
             << system_matrix_.linfty_norm() << '\n' << std::endl;
    std::cout << "Solving linear system... ";
    Timer timer;
    SparseDirectUMFPACK A_direct;
    A_direct.initialize(system_matrix_);
    A_direct.vmult(solution_, system_rhs_);
    timer.stop();
    std::cout << "done (" << timer.cpu_time() << "s)" << std::endl;
    std::cout
             << "   Max norm of solution: "
             << solution_.linfty_norm() << '\n' << std::endl;
    Vector<double> residual(dof_handler_.n_dofs());
    system_matrix_.vmult(residual, solution_);
    residual -= system_rhs_;
    std::cout
             << "   Max norm of residual: "
             << residual.linfty_norm() << '\n' << std::endl;
*/
  //constraints_.distribute(solution_);
}

// Output results
template <int dim>
void THMConsolidation<dim>::output_results(unsigned int time_step, unsigned current_time) const
{
  std::vector<std::string> solution_names={ "ux", "uy", "uz", "p", "T"};

  // std::vector<DataComponentInterpretation::DataComponentInterpretation>
  //   data_component_interpretation(
  //     dim, DataComponentInterpretation::component_is_part_of_vector);
  // data_component_interpretation.push_back(
  //   DataComponentInterpretation::component_is_scalar);

  DataOut<dim> data_out;
  data_out.attach_dof_handler(dof_handler_);
  data_out.add_data_vector(solution_,
                           solution_names);
  data_out.build_patches();
  std::ofstream time_file ("time.txt",std::ios::app);
  double value       = current_time_;
    //myfile.write (*conversion, strlen (conversion));
  time_file <<value<<"\n";
  time_file.close();

  std::ofstream output(
    "outputfiles/solution-" + Utilities::int_to_string(time_step, 2) + ".vtk");
  data_out.write_vtk(output);
}

template <int dim>
void THMConsolidation<dim>::run()
{
  Timer timer_grid;
  make_grid();

  // Apply initial values
  {
    std::vector<bool> deformations{true, true, true, false, false};
    std::vector<bool> pressure{false, false, false, true, false};
    std::vector<bool> temperature{false, false, false, false, true};

    // PressureInitial<dim> pressure_initial;
    // pressure_initial.get_pressure(pressure_in_);

    VectorTools::interpolate(dof_handler_, DeformationInitial<dim>(),
                             solution_, deformations);
    VectorTools::interpolate(dof_handler_, PressureInitial<dim>(),
                             solution_, pressure);
    VectorTools::interpolate(dof_handler_, TemperatureInitial<dim>(),
                             solution_, temperature);
  }
  std::cout<<"output step 0"<<std::endl;
  output_results(0,0);
  std::cout<<"output step 0 end"<<std::endl;
  timer_grid.stop();
  std::cout << "Initialize mesh and initial conditions: " << timer_grid.cpu_time() << "s" << std::endl;

// // Normal time step
//   first_step_=time_step_;
//   // std::cout<<step_torr_;
//   current_time_=0;
//   double err=Q_in_*std::sqrt(system_rhs_.size())+1;
//   old_solution_=0;
//   for(unsigned step = 1; step<max_steps_ ; ++step) {
//     if (step==1) {
//       time_step_=first_step_;
//     }

//     Timer timer_assembler;
//     std::cout<<"assembler started"<<std::endl;
//     assemble_system();
//     timer_assembler.stop();
//     std::cout << "Step: "<<step<<", assembler: " << timer_assembler.cpu_time() << "s" << std::endl;
//     std::cout << "l2norm of rhs: " << system_rhs_.l2_norm()<<std::endl;

//     Timer timer_solver;
//     solve(step);
//     timer_solver.stop();
//     std::cout << "Step: "<<step<<", solver: " << timer_solver.cpu_time() << "s" << std::endl;
//     solution_var_=old_solution_;
//     solution_var_-=solution_;
//     std::cout<<solution_var_.l2_norm()<<" norm"<<std::endl;
//     std::cout<<time_step_<<" adaptive_time_step"<<std::endl;
//     current_time_+=time_step_;
//     std::cout<<current_time_<<" current_time"<<std::endl;
//     old_solution_=solution_;
//     if (step>1){
//       err=solution_var_.l2_norm();
//     }
//     // std::cout<<err<<" err"<<std::endl;
//     // std::cout<<1e-2*pressure_in_*std::sqrt(system_rhs_.size())<<" err1"<<std::endl;

//     Timer timer_out;
//     output_results(step,current_time_);
//     timer_out.stop();
//     std::cout << "Step: "<<step<<", output vtk files: " << timer_out.cpu_time() << "s" << std::endl;

//   }
// //Normal time step ends

  //Adaptive time step
  first_step_=0.01*time_step_*gamma_w*(inner_radius_/subdivision_)*(inner_radius_/subdivision_)/(6*2*mu_in_*k_in_);
  step_torr_=0.01*Q_in_*std::sqrt(system_rhs_.size());
  // std::cout<<inner_radius_<<" inner"<<std::endl;
  std::cout<<step_torr_<<" step_torr"<<std::endl;
  std::cout<<first_step_<<" frist_step"<<std::endl;
  current_time_=0;
  double err=Q_in_*std::sqrt(system_rhs_.size())+1;
  old_solution_=0;
  //for(unsigned step = 1; err>1e-12*Q_in_*std::sqrt(system_rhs_.size()) ; ++step) {
  for(unsigned step = 1; current_time_< 20000; ++step) {

    if (step==1) {
      time_step_=first_step_;
    }

    Timer timer_assembler;
    std::cout<<"assembler started"<<std::endl;
    assemble_system();
    timer_assembler.stop();
    std::cout << "Step: "<<step<<", assembler: " << timer_assembler.cpu_time() << "s" << std::endl;
    std::cout << "l2norm of rhs: " << system_rhs_.l2_norm()<<std::endl;

    Timer timer_solver;
    solve(step);
    timer_solver.stop();
    std::cout << "Step: "<<step<<", solver: " << timer_solver.cpu_time() << "s" << std::endl;
    solution_var_=old_solution_;
    solution_var_-=solution_;
    std::cout<<solution_var_.l2_norm()<<" norm"<<std::endl;
    std::cout<<time_step_<<" adaptive_time_step"<<std::endl;
    current_time_+=time_step_;
    std::cout<<current_time_<<" current_time"<<std::endl;
    old_solution_=solution_;
    if (step>1){
      err=solution_var_.l2_norm();
    }
    // std::cout<<err<<" err"<<std::endl;
    // std::cout<<1e-2*pressure_in_*std::sqrt(system_rhs_.size())<<" err1"<<std::endl;

    Timer timer_out;
    output_results(step,current_time_);
    timer_out.stop();
    std::cout << "Step: "<<step<<", output vtk files: " << timer_out.cpu_time() << "s" << std::endl;
    std::cout<<"err: "<<err<<std::endl;
    if (err<step_torr_/10){
      time_step_*=1.5;
      std::cout<<"larger step"<<std::endl;
    }
    else if (err>step_torr_*10){
      time_step_/=2;
      std::cout<<"smaller step"<<std::endl;
      if (time_step_<first_step_){
        time_step_=first_step_;
      }
    }
  }

// adaptive time step ends
}

int main(int argc, char** argv) {

  try {

    if (argc != 2) {
      std::cout << "Usage: ./thm /path/to/input.json\n";
      throw std::runtime_error("Incorrect number of input arguments");
    }

    const std::string filename = argv[1];

    // Input file
    std::ifstream in(filename);
    const nlohmann::json input_json = Json::parse(in);

    std::string title = input_json["title"].template get<std::string>();
    unsigned degree = input_json["degree"].template get<unsigned>();
    std::cout<<title<<", with degree: "<<degree<<std::endl;

    THMConsolidation<3> thm_consolidation_solver(degree, input_json);
    thm_consolidation_solver.run();

  } catch (std::exception& exc) {
    std::cerr << std::endl
              << std::endl
              << "----------------------------------------------------"
              << std::endl;
    std::cerr << "Exception on processing: " << std::endl
              << exc.what() << std::endl
              << "Aborting!" << std::endl
              << "----------------------------------------------------"
              << std::endl;
    return 1;
  } catch (...) {
    std::cerr << std::endl
              << std::endl
              << "----------------------------------------------------"
              << std::endl;
    std::cerr << "Unknown exception!" << std::endl
              << "Aborting!" << std::endl
              << "----------------------------------------------------"
              << std::endl;
    return 1;
  }
  return 0;

}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2007-2011 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2011 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2011 Mateusz Loskot, London, UK.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <geometry_test_common.hpp>

#include <boost/geometry/algorithms/make.hpp>

#include <boost/geometry/domains/gis/io/wkt/write_wkt.hpp>

#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/geometries/adapted/c_array.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
#include <test_common/test_point.hpp>

BOOST_GEOMETRY_REGISTER_C_ARRAY_CS(cs::cartesian)
BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)

template <typename T, typename P>
void test_point_2d()
{
    P p = bg::make<P>((T) 123, (T) 456);
    BOOST_CHECK_CLOSE(bg::get<0>(p), 123.0, 1.0e-6);
    BOOST_CHECK_CLOSE(bg::get<1>(p), 456.0, 1.0e-6);
}

template <typename T, typename P>
void test_point_3d()
{
    P p = bg::make<P>((T) 123, (T) 456, (T) 789);
    BOOST_CHECK_CLOSE( bg::get<0>(p), 123.0, 1.0e-6);
    BOOST_CHECK_CLOSE( bg::get<1>(p), 456.0, 1.0e-6);
    BOOST_CHECK_CLOSE( bg::get<2>(p), 789.0, 1.0e-6);
}

template <typename T, typename P>
void test_box_2d()
{
    typedef bg::model::box<P> B;
    B b = bg::make<B>((T) 123, (T) 456, (T) 789, (T) 1011);
    BOOST_CHECK_CLOSE( (bg::get<bg::min_corner, 0>(b)), 123.0, 1.0e-6);
    BOOST_CHECK_CLOSE( (bg::get<bg::min_corner, 1>(b)), 456.0, 1.0e-6);
    BOOST_CHECK_CLOSE( (bg::get<bg::max_corner, 0>(b)), 789.0, 1.0e-6);
    BOOST_CHECK_CLOSE( (bg::get<bg::max_corner, 1>(b)), 1011.0, 1.0e-6);

    b = bg::make_inverse<B>();
}

template <typename T, typename P>
void test_linestring_2d()
{
    typedef bg::model::linestring<P> L;

    T coors[][2] = {{1,2}, {3,4}};

    L line = bg::detail::make::make_points<L>(coors);

    BOOST_CHECK_EQUAL(line.size(), 2u);
}

template <typename T, typename P>
void test_linestring_3d()
{
    typedef bg::model::linestring<P> L;

    T coors[][3] = {{1,2,3}, {4,5,6}};

    L line = bg::detail::make::make_points<L>(coors);

    BOOST_CHECK_EQUAL(line.size(), 2u);
    //std::cout << dsv(line) << std::endl;

}

template <typename T, typename P>
void test_2d_t()
{
    test_point_2d<T, P>();
    test_box_2d<T, P>();
    test_linestring_2d<T, P>();
}

template <typename P>
void test_2d()
{
    test_2d_t<int, P>();
    test_2d_t<float, P>();
    test_2d_t<double, P>();
}

template <typename T, typename P>
void test_3d_t()
{
    test_linestring_3d<T, P>();
//  test_point_3d<T, test_point>();
}

template <typename P>
void test_3d()
{
    test_3d_t<int, P>();
    test_3d_t<float, P>();
    test_3d_t<double, P>();
}

int test_main(int, char* [])
{
    //test_2d<int[2]>();
    //test_2d<float[2]>();
    //test_2d<double[2]>();
    test_2d<bg::model::point<int, 2, bg::cs::cartesian> >();
    test_2d<bg::model::point<float, 2, bg::cs::cartesian> >();
    test_2d<bg::model::point<double, 2, bg::cs::cartesian> >();

    test_3d<bg::model::point<double, 3, bg::cs::cartesian> >();

#if defined(HAVE_TTMATH)
    test_2d<bg::model::point<ttmath_big, 2, bg::cs::cartesian> >();
    test_3d<bg::model::point<ttmath_big, 3, bg::cs::cartesian> >();
#endif

    return 0;
}


#include "precompiled.h"

#ifndef AUTOMATIC_PRECOMPILATION
#include <boost/throw_exception.hpp>
#include <boost/property_tree/ptree.hpp>
#include <string>
#include <pretty_printer.h>
#endif

#include "exceptions.h"
#include "configuration_parser.h"
#include "expectation_value_config.h"
#include "Math.h"
#include "global_config.h"

using namespace fipster;
using namespace fipster::expectation_values;

// #################################################
// ##############   PHASE 1:			############
// ##############   CONFIGURATION       ############
// #################################################

expect_future_t::expect_future_t(btime field_time, btime rannacher_time
																 , plain_spacetime_field exercise
																 ,boost::optional<double> entropic_theta
																 ,double discounting_factor)
	: field_time(field_time)
	, rannacher(field_time==rannacher_time)
		//apply rannacher to damp the oscillations caused by
		//discontinuities in the derivative of the payoff
	,exercise(exercise)
{
	details.entropic_theta=entropic_theta;
	details.discounting_factor = discounting_factor;
}

config_t::config_t( const ptree& pt )
	: id(betterGet<string>(pt,"<xmlattr>.id","expectation value"))
	, steps(betterChild(pt,"timeStepping",id),id+".timeStepping")
{

	boundary_conditions_s = pt.get<string>("boundaryConditions");
	fd_weights_s = pt.get<string>("finiteDifferenceWeights");

	strang_symmetrization = pt.get<bool>("strangSymmetrization");

	auto ran=betterChild(pt,"rannacherSteps",id);
	auto id2=id+".rannacherSteps";
	rannacher_step_size = getBtime(ran,"stepSize",id2);
	rannacher_steps = betterGet<int>(ran,"<xmlattr>.steps",id2);
	rannacher_theta = betterGet<double>(ran,"theta",id2);
	rannacher_strang_symmetrization = betterGet<bool>(ran,"strangSymmetrization",id2);

	theta = betterGet<double>(pt,"theta",id);

	if(theta<0 || theta>1)
		FIPSTER_THROW_EXCEPTION(runtime_error(toS(theta)+" is no valid Time Stepping theta value"));

}


#include <alglib/optimization.h>
#include <functional>
#include <limits>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include "perceive/foundation.hpp"

#include "levenberg-marquardt.hpp"

using namespace alglib;

namespace perceive
{
static void opt_fun(const real_1d_array& x, real_1d_array& fi, void* ptr)
{
   using cost_fun_t = std::function<double(const double*)>;
   fi[0] = (static_cast<cost_fun_t*>(ptr))->operator()(x.getcontent());
}

// ---------------------------------------- Levenberg-Marquardt (double version)

void levenberg_marquardt(std::function<double(const double*)> fn,
                         unsigned n,
                         double start[],
                         double xmin[],
                         double reqmin,
                         double diffstep, // 0.0001
                         int kcount,
                         int& icount,
                         int& ifault)
{
   levenberg_marquardt(
       fn, n, start, xmin, reqmin, diffstep, 1, kcount, icount, ifault);
   // real_1d_array x;
   // minlmstate state;
   // minlmreport rep;

   // icount = ifault = 0;

   // // Copy in start point
   // x.setlength(n);
   // for(unsigned i = 0; i < n; ++i)
   //     x[i] = start[i];

   // minlmcreatev(n, 1, x, diffstep, state);           // Initialize state
   // minlmsetcond(state, reqmin, kcount);              // constraints
   // alglib::minlmoptimize(state, opt_fun, NULL, &fn); // Optimize
   // minlmresults(state, x, rep);                      // Get results

   // // Unpack
   // for(unsigned i = 0; i < n; ++i)
   //     xmin[i] = x[i];

   // icount = rep.iterationscount;
   // ifault = rep.terminationtype;
}

// ----------------------------------------- Levenberg-Marquardt (float version)

void levenberg_marquardt(std::function<float(const float*)> fn,
                         unsigned n,
                         float start[],
                         float xmin[],
                         float reqmin,
                         float diffstep, // 0.0001
                         int kcount,
                         int& icount,
                         int& ifault)
{
   std::vector<float> buffer(n);
   std::vector<double> start_d(n);
   std::vector<double> xmin_d(n);

   for(unsigned i = 0; i < n; ++i) start_d[i] = double(start[i]);
   for(unsigned i = 0; i < n; ++i) xmin_d[i] = double(xmin[i]);

   auto cost_fn = [&](const double* X) -> double {
      for(auto& val : buffer) val = float(*X++);
      return double(fn(&buffer[0]));
   };

   levenberg_marquardt(cost_fn,
                       n,
                       &start_d[0],
                       &xmin_d[0],
                       double(reqmin),
                       double(diffstep),
                       kcount,
                       icount,
                       ifault);

   for(unsigned i = 0; i < n; ++i) xmin[i] = float(xmin_d[i]);
}

// --------------------------------------------------------------- With restarts

void levenberg_marquardt(std::function<double(const double*)> fn,
                         unsigned n,
                         double start[],
                         double xmin[],
                         double reqmin,
                         double diffstep,
                         int numres,
                         int kcount,
                         int& icount,
                         int& ifault)
{
   real_1d_array x;
   minlmstate state;
   minlmreport rep;
   unsigned final_count = 0;

   icount = ifault = 0;

   double best_val = std::numeric_limits<double>::max();
   std::function<double(const double*)> fn2z = [&](const double* X) {
      auto val = fn(X);
      if(val < best_val) {
         best_val = val;
         memcpy(&xmin[0], X, sizeof(double) * n);
      }
      return val;
   };

   // Copy in start point
   x.setlength(n);
   for(unsigned i = 0; i < n; ++i) x[i] = start[i];

   try {
      minlmcreatev(n, 1, x, diffstep, state);             // Initialize state
      minlmsetcond(state, reqmin, kcount);                // constraints
      alglib::minlmoptimize(state, opt_fun, NULL, &fn2z); // Optimize
      minlmresults(state, x, rep);                        // Get results

      // Unpack
      icount += rep.iterationscount;
      for(unsigned i = 0; i < n; ++i) xmin[i] = x[i];

      for(int itr = 1; itr < numres && rep.terminationtype == 2; ++itr) {
         // cout << "------------------------------------------------------"
         //      << endl;
         for(unsigned i = 0; i < n; ++i) x[i] = xmin[i];
         diffstep *= 0.1;
         minlmcreatev(n, 1, x, diffstep, state);             // Initialize state
         minlmsetcond(state, reqmin, kcount);                // constraints
         alglib::minlmoptimize(state, opt_fun, NULL, &fn2z); // Optimize
         minlmresults(state, x, rep);
         icount += rep.iterationscount;
         for(unsigned i = 0; i < n; ++i) xmin[i] = x[i];
      }

      ifault = int(rep.terminationtype);
   } catch(alglib::ap_error& e) {
      ifault = -9;
      LOG_ERR(format("Exception in levenberg-marquardt: {}", e.msg));
   } catch(std::exception& e) {
      ifault = -9;
      LOG_ERR(format("Exception in levenberg-marquardt: {}", e.what()));
   } catch(...) {
      ifault = -9;
   }
}

void levenberg_marquardt(std::function<float(const float*)> fn,
                         unsigned n,
                         float start[],
                         float xmin[],
                         float reqmin,
                         float diffstep,
                         int numres, // diffstep *= 0.1 between each restart
                         int kcount,
                         int& icount,
                         int& ifault)
{
   std::vector<float> buffer(n);
   std::vector<double> start_d(n);
   std::vector<double> xmin_d(n);

   for(unsigned i = 0; i < n; ++i) start_d[i] = double(start[i]);
   for(unsigned i = 0; i < n; ++i) xmin_d[i] = double(xmin[i]);

   auto cost_fn = [&](const double* X) -> double {
      for(auto& val : buffer) val = float(*X++);
      return double(fn(&buffer[0]));
   };

   levenberg_marquardt(cost_fn,
                       n,
                       &start_d[0],
                       &xmin_d[0],
                       double(reqmin),
                       double(diffstep),
                       numres,
                       kcount,
                       icount,
                       ifault);

   for(unsigned i = 0; i < n; ++i) xmin[i] = float(xmin_d[i]);
}

// ----------------------------------------------- Levenberg-Marquardt fault-str

const char* levenberg_marquardt_fault_str(int ifault)
{
   switch(ifault) {
   case -9: return "exception";
   case -8: return "NAN/INF value detected either in function or Jacobian";
   case -7: return "derivative correctness check failed";
   case -5: return "inappropriate solver was used";
   case -3: return "constraints are inconsistent";
   case 2: return "relative step is no more than EpsX.";
   case 5: return "MaxIts steps was taken";
   case 7: return "stopping conditions forbid further improvement";
   case 8: return "terminated by  user via MinLMRequestTermination()";
   default: return "Unknown ifault condition";
   }
}

} // namespace perceive


//
// Created by phili on 26.12.2019.
//

#define _USE_MATH_DEFINES
#include <iostream>
#include <Eigen/Dense>
#include <vector>

#include <cmath>

#ifndef M_PI
  #define M_PI 3.14159265358979323846
#endif

using namespace Eigen;

std::vector<double> f(MatrixXd &M, MatrixXd &V, const MatrixXd &D, double rr){
  MatrixXd tt;
  Matrix<bool, Eigen::Dynamic, 1> is_covered;
  std::vector<double> d;

  for (size_t k=0; k<10; k++){
    tt = M.colwise()-V.col(k);
    tt = D*tt;
    is_covered = tt.colwise().squaredNorm().array() <= rr;
    std::cout << is_covered << std::endl;
    for(size_t i=0; i<M.cols(); i++){
      if(is_covered(i)){
        d.push_back(M(0,i));
      }
    }
  }
  return d;
}

int main(){

  MatrixXd M;
  MatrixXd D;
  MatrixXd V;
  std::vector<double> blub;

  M = MatrixXd::Random(10,1000);
  V = MatrixXd::Random(10,10);
  D = MatrixXd::Random(10,10);
  for (double k=0; k<1000; k+=10.){
    blub = f(M,V,D, k);
    for (auto bbb : blub){
      std::cout << bbb << std::endl;
    }
  }
  return 0;
}


#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <cmath>
#include <typeinfo>
#include <typeindex>
#include <cassert>
#include <unordered_map>
#include <functional>

#include <boost/program_options.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>

#include <logging.hpp>
#include <matrix_serialization.hpp>
#include <lapack.hpp>

namespace po = boost::program_options;

auto init_options() -> po::options_description
{
	po::options_description desc;

	desc.add_options()
		( "help", "Produce the help message." )
		( "type", po::value<std::string>()->required()
		, "Type of an element of the hamiltonian matrix. It may be one of: "
		  "float, double, cfloat, cdouble." )
		( "energies", po::value<std::string>()->required()
		, "Name of the file where to save the eigenenergies of the system." )
		( "states", po::value<std::string>()->required()
		, "Name of the file where to save the eigenestates of the system." );

	return desc;
}

auto element_type(std::string input) -> std::type_index
{
	using namespace std::string_literals;
	static std::unordered_map<std::string, std::type_index> const types =
		{ { "float"s,   std::type_index(typeid(float))                }
		, { "double"s,  std::type_index(typeid(double))               }
		, { "cfloat"s,  std::type_index(typeid(std::complex<float>))  }
		, { "cdouble"s, std::type_index(typeid(std::complex<double>)) }
		};

	boost::to_lower(input);
	try {
		return types.at(input);
	} catch(std::out_of_range & e) {
		std::cerr << "Invalid element type `" + input + "`!\n";
		throw;
	}
}

template<class _Help, class _Run>
auto process_command_line( int argc, char** argv
                         , _Help&& help
						 , _Run&& run ) -> void
{
	auto const description = init_options();
	po::variables_map vm;

	po::store(po::command_line_parser(argc, argv).options(description).run()
	         , vm );

	if(vm.count("help")) {
		help(description);
		return;
	}

	po::notify(vm);

	run( element_type(vm["type"].as<std::string>())
	   , vm["energies"].as<std::string>()
	   , vm["states"].as<std::string>()
	   );
}

template<class _T, class _IStream, class _OStream1, class _OStream2>
auto solve( _IStream & input
          , _OStream1 & energies_output
		  , _OStream2 & states_output ) -> void
{
	boost::log::sources::severity_logger<tcm::severity_level> lg;

	LOG(lg, info) << "Reading Hamiltonian...";
	tcm::Matrix<_T> H;
	boost::archive::binary_iarchive input_archive{input};
	input_archive >> H;

	tcm::Matrix<tcm::utils::Base<_T>>   E{H.height(), 1};
	tcm::Matrix<_T>                   Psi{H.height(), H.height()};

	LOG(lg, info) << "Diagonalizing...";
	tcm::lapack::heevr(H, E, Psi);

	LOG(lg, info) << "Saving results...";
	boost::archive::binary_oarchive energies_archive{energies_output};
	energies_archive << E;
	boost::archive::binary_oarchive states_archive{states_output};
	states_archive << Psi;

	LOG(lg, info) << "Done!";
}

auto run( std::type_index type
        , std::string const& energies_filename
		, std::string const& states_filename ) -> void
{
	std::ofstream energies_file{energies_filename};
	if(not energies_file) {
		throw std::runtime_error{ "Could not open `" + energies_filename
		                        + "` for writing." };
	}
	std::ofstream states_file{states_filename};
	if(not states_file) {
		throw std::runtime_error{ "Could not open `" + states_filename
		                        + "` for writing." };
	}

	using solve_function_t = std::function<void()>;
	static std::unordered_map<std::type_index, solve_function_t> const
	dispatch = {
		{ std::type_index(typeid(float))
		, [&energies_file, &states_file]()
		  {solve<float>(std::cin, energies_file, states_file); } },
		{ std::type_index(typeid(double))
		, [&energies_file, &states_file]()
		  {solve<double>(std::cin, energies_file, states_file); } },
		{ std::type_index(typeid(std::complex<float>))
		, [&energies_file, &states_file]()
		  {solve<std::complex<float>>(std::cin, energies_file, states_file); } },
		{ std::type_index(typeid(std::complex<double>))
		, [&energies_file, &states_file]()
		  {solve<std::complex<double>>(std::cin, energies_file, states_file);} }
	};

	tcm::setup_console_logging();
	dispatch.at(type)();
}

int main(int argc, char** argv)
{
	process_command_line
		( argc, argv
		, [](auto desc) { std::cout << desc << '\n'; }
		, &run
		);
	return 0;
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#define _USE_MATH_DEFINES
#include <gtest/gtest.h>
#include <Eigen/Core>
#include <cmath>

#include "beanmachine/graph/graph.h"

using namespace beanmachine::graph;

TEST(testdistrib, lognormal) {
  Graph g;
  // LOG_MEAN and LOG_STD are the mean and standard deviation of the log of
  // lognormal distribution
  const double LOG_MEAN = -11.0;
  const double LOG_STD = 3.0;
  const double LOG_STD_SQ = 9.0;
  const double MEAN = std::exp(LOG_MEAN + LOG_STD * LOG_STD / 2);
  auto real1 = g.add_constant(LOG_MEAN);
  auto pos1 = g.add_constant_pos_real(LOG_STD);

  // negative tests that log normal has two parents
  EXPECT_THROW(
      g.add_distribution(
          DistributionType::LOG_NORMAL,
          AtomicType::POS_REAL,
          std::vector<uint>{}),
      std::invalid_argument);
  EXPECT_THROW(
      g.add_distribution(
          DistributionType::LOG_NORMAL,
          AtomicType::POS_REAL,
          std::vector<uint>{real1}),
      std::invalid_argument);
  EXPECT_THROW(
      g.add_distribution(
          DistributionType::LOG_NORMAL,
          AtomicType::POS_REAL,
          std::vector<uint>{real1, pos1, real1}),
      std::invalid_argument);

  // negative test the parents must be a real and a positive
  EXPECT_THROW(
      g.add_distribution(
          DistributionType::LOG_NORMAL,
          AtomicType::POS_REAL,
          std::vector<uint>{real1, real1}),
      std::invalid_argument);

  // test creation of a distribution
  auto log_normal_dist = g.add_distribution(
      DistributionType::LOG_NORMAL,
      AtomicType::POS_REAL,
      std::vector<uint>{real1, pos1});

  // test distribution of mean and variance
  auto real_val =
      g.add_operator(OperatorType::SAMPLE, std::vector<uint>{log_normal_dist});
  auto real_sq_val = g.add_operator(
      OperatorType::MULTIPLY, std::vector<uint>{real_val, real_val});
  g.query(real_val);
  g.query(real_sq_val);
  const std::vector<double>& means =
      g.infer_mean(100000, InferenceType::REJECTION);
  double variance = (std::exp(LOG_STD_SQ) - 1) * exp(2 * LOG_MEAN + LOG_STD_SQ);
  EXPECT_NEAR(means[0], MEAN, 0.1);
  EXPECT_NEAR(means[1] - means[0] * means[0], variance, 0.1);

  // test log_prob and gradients
  g.observe(real_val, M_E);

  // f(x) = - log(3) - 0.5 log(2*pi) - 0.5 (log(x) + 11)^2 / 3^2 - log(x)
  // f'(x) = (-11 - log(x) - 3^2) / (x * 3^2)
  // f''(x) = (3^2 + log(x) + 11 - 1) / (3^2 * x^2)
  // f(e) = approx. -11.0176;
  // f'(e) = -7/3e (approx. -0.85838 )
  // f''(e) = 20/9e^2 (approx. 0.30074)

  EXPECT_NEAR(g.log_prob(real_val), -11.0176, 0.001);
  double grad1 = 0;
  double grad2 = 0;
  g.gradient_log_prob(real_val, grad1, grad2);
  EXPECT_NEAR(grad1, -0.85838, 0.01);
  EXPECT_NEAR(grad2, 0.30074, 0.01);

  // test gradient of log_prob w.r.t. sigma
  const double SCALE = 3.5;
  auto pos_scale = g.add_constant_pos_real(SCALE);
  auto half_cauchy_dist = g.add_distribution(
      DistributionType::HALF_CAUCHY,
      AtomicType::POS_REAL,
      std::vector<uint>{pos_scale});
  auto pos_val =
      g.add_operator(OperatorType::SAMPLE, std::vector<uint>{half_cauchy_dist});
  g.observe(pos_val, 7.0);
  auto pos_sq_val = g.add_operator(
      OperatorType::MULTIPLY, std::vector<uint>{pos_val, pos_val});
  auto normal_dist3 = g.add_distribution(
      DistributionType::LOG_NORMAL,
      AtomicType::POS_REAL,
      std::vector<uint>{real1, pos_sq_val});
  auto real_val3 =
      g.add_operator(OperatorType::SAMPLE, std::vector<uint>{normal_dist3});
  g.observe(real_val3, std::exp(5.0));
  grad1 = grad2 = 0;
  g.gradient_log_prob(pos_val, grad1, grad2);
  EXPECT_NEAR(grad1, -0.483822, 1e-6);
  EXPECT_NEAR(grad2, 0.038648, 1e-6);
}

TEST(testdistrib, backward_lognormal_lognormal) {
  Graph g;
  uint zero = g.add_constant(0.0);
  uint pos_one = g.add_constant_pos_real(1.0);
  uint two = g.add_constant((natural_t)2);

  uint lognormal_dist = g.add_distribution(
      DistributionType::LOG_NORMAL,
      AtomicType::POS_REAL,
      std::vector<uint>{zero, pos_one});
  uint pos_mu =
      g.add_operator(OperatorType::SAMPLE, std::vector<uint>{lognormal_dist});
  uint mu = g.add_operator(OperatorType::TO_REAL, std::vector<uint>{pos_mu});

  uint dist_y = g.add_distribution(
      DistributionType::LOG_NORMAL,
      AtomicType::POS_REAL,
      std::vector<uint>{mu, pos_one});
  uint y =
      g.add_operator(OperatorType::IID_SAMPLE, std::vector<uint>{dist_y, two});
  g.observe(pos_mu, 0.1);
  Eigen::MatrixXd yobs(2, 1);
  yobs << std::exp(0.5), std::exp(-0.5);
  g.observe(y, yobs);

  // test backward_param(), backward_value() and
  // backward_param_iid(), backward_value_iid():
  // To verify the grad1 results with pyTorch:
  // mu = torch.tensor([0.1], requires_grad=True)
  // y = torch.tensor([math.e**0.5, math.e**-0.5], requires_grad=True)
  // log_p = (
  //   torch.distributions.LogNormal(mu, torch.tensor(1.0)).log_prob(y).sum() +
  //   torch.distributions.LogNormal(torch.tensor(0.0),
  //   torch.tensor(1.0)).log_prob(mu)
  // )
  // torch.autograd.grad(log_p, mu) -> 12.8259
  // torch.autograd.grad(log_p, y) -> [-0.8491, -0.6595]
  std::vector<DoubleMatrix*> grad1;
  g.eval_and_grad(grad1);
  EXPECT_EQ(grad1.size(), 2);
  EXPECT_NEAR((*grad1[0]), 12.8259, 1e-3);
  EXPECT_NEAR(grad1[1]->coeff(0), -0.8491, 1e-3);
  EXPECT_NEAR(grad1[1]->coeff(1), -0.6595, 1e-3);

  // test log_prob() on vector value:
  double log_prob_y = g.log_prob(y);
  EXPECT_NEAR(log_prob_y, -2.0979, 0.001);
}