/*

Copyright (c) 2005-2017, University of Oxford.
All rights reserved.

University of Oxford means the Chancellor, Masters and Scholars of the
University of Oxford, having an administrative office at Wellington
Square, Oxford OX1 2JD, UK.

This file is part of Chaste.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * Neither the name of the University of Oxford nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef CorriasBuistSMCModified_HPP_
#define CorriasBuistSMCModified_HPP_

#include "ChasteSerialization.hpp"
#include <boost/serialization/base_object.hpp>
#include "AbstractCardiacCell.hpp"
#include "AbstractStimulusFunction.hpp"
/**
 * This class is a modified version of the model of a gastric
 * Smooth Muscle Cell.
 *
 * Reference publication is:
 *
 * Corrias A, Buist ML.
 * "A quantitative model of gastric smooth muscle cellular activation."
 * Ann Biomed Eng. 2007 Sep;35(9):1595-607. Epub 2007 May 8.
 *
 * Modifications include:
 * - ability to include/exclude built-in fake ICC stimulus
 * - ability to set K+ channels-affecting CO concentrations
 */
class CorriasBuistSMCModified : public AbstractCardiacCell
{
    friend class boost::serialization::access;
    /**
     * Boost Serialization method for archiving/checkpointing.
     * Archives the object and its member variables.
     *
     * @param archive  The boost archive.
     * @param version  The current version of this class.
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        archive & boost::serialization::base_object<AbstractCardiacCell >(*this);
    }

private:

    /**
     * Scale factor for CO-affected currents
     * Note that this the number that multiply the currents, hence it is not [CO],
     * but a function of [CO] (for example, 2.8*[CO] - 0.1)
     */
    double mScaleFactorCarbonMonoxide;

    /**
     * True if the fake built-in ICC stimulus is present
     */
    bool mFakeIccStimulusPresent;

    double Cm;/**< membrane capacitance, pF*/

    double Asurf_in_cm_square;/**< Surface area in cm^2*/
    double Asurf;/**< surface area (mm^2)*/

    double VolCell;/**< cell volume (mm^3)*/
    double hCa;/**< conc for half inactivation of fCa */
    double sCa;/**< lope factor for inactivation of fCa */

    /* concentrations */
    double Ki;       /**< intra K conc (mM)*/
    double Nai;        /**< intra Na conc (mM)*/
    double ACh;       /**< acetylcholine conc (mM)*/
    double CaiRest;     /**< baseline Ca conc (mM)*/

    /* maximum conductances*/
    double gLVA_max; /**< max conductance of ILVA*/                 // (0.18 nS) * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2
    double gCaL_max; /**< max conductance of ICaL*/                 // (65.0 nS) * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2
    double gBK_max; /**< max conductance of IBK)*/                 // (45.7 nS) * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2
    double gKb_max; /**< max conductance of IKb*/                  // (0.0144 nS) * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2
    double gKA_max; /**< max conductance of IKA*/                  // (9.0  nS) * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2
    double gKr_max; /**< max conductance of IKr*/                  // (35.0 nS) * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2
    double gNa_max;  /**< max conductance of INa*/                  // (3.0  nS) * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2
    double gnsCC_max; /**< max conductance of InsCC*/                // (50.0 nS) * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2
    double gcouple;   /**<  coupling conductance bewteen fake ICC and SMC*/        // 1.3 nS * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2
    double JCaExt_max;     /**< max flux of CaSR (mM/ms)*/

    /* Temperature corrections */
    double Q10Ca;         /**< (dim)*/
    double Q10K;         /**< (dim)*/ //1.365
    double Q10Na;        /**< (dim)*/
    double Texp;       /**< (degK)*/

    double T_correct_Ca ;/**< temperature correction for Ca (dim)*/
    double T_correct_K ;  /**< temperature correction for K (dim)*/
    double T_correct_Na;/**< temperature correction for Na (dim)*/
    double T_correct_gBK; /**< temperature correction for gBK*/  // (nS) * 1e-6 (mS/nS) / Asurf (mm2) = mS/mm2

    /* Nernst potentials */
    double EK;                  /**< Nernst potential for K (mV)*/
    double ENa ;               /**< Nernst potential for Na (mV)*/
    double EnsCC;                          /**< Nernst potential for nsCC (mV)*/

    double Ca_o;         /**<  mM */
    double K_o;          /**< mM */
    double Na_o;         /**< mM */

    /* Nernst parameters */
    double R;    /**<  pJ/nmol/K*/
    double T;       /**<  degK*/
    double F;     /**<  nC/nmol*/
    double FoRT;     /**<  1/mV*/
    double RToF;    /**<  mV*/

public:

    /**
     * Constructor
     *
     * @param pSolver is a pointer to the ODE solver
     * @param pIntracellularStimulus is a pointer to the intracellular stimulus
     */
    CorriasBuistSMCModified(boost::shared_ptr<AbstractIvpOdeSolver> pSolver, boost::shared_ptr<AbstractStimulusFunction> pIntracellularStimulus);

    /**
     * Destructor
     */
    ~CorriasBuistSMCModified();

    /**
     * Now empty
     */
    void VerifyStateVariables();

    /**
     * Calculates the ionic current
     *
     * @param pStateVariables the state variables of this model
     * @return the total ionic current
     */
    double GetIIonic(const std::vector<double>* pStateVariables=NULL);

    /**
     * Compute the RHS of the FitHugh-Nagumo system of ODEs
     *
     * @param time  the current time, in milliseconds
     * @param rY  current values of the state variables
     * @param rDY  to be filled in with derivatives
     */
    void EvaluateYDerivatives(double time, const std::vector<double>& rY, std::vector<double>& rDY);

    /**
     * Set whether we want the fake ICC stimulus or not.
     * It changes the member variable mFakeIccStimulusPresent (which is true by default).
     *
     * @param present - true if we want the fake ICC stimulus, false otherwise
     */
    void SetFakeIccStimulusPresent(bool present);

    /**
     * @return true if the fake ICC stimulus is present
     */
    bool GetFakeIccStimulusPresent();

    /**
     * @return the Carbon Monoxide scale for
     */
    double SetCarbonMonoxideScaleFactor();

    /**
     * Set the carbon monoxide scale factor.
     * This will multiply the following currents: I_kr, I_Ka, Ibk
     *
     * @param scaleFactor the scale factor that multiply the currents.
     */
    void SetCarbonMonoxideScaleFactor(double scaleFactor);

    /**
     * @return the Carbon Monoxide scale factor
     */
    double GetCarbonMonoxideScaleFactor();
};

// Needs to be included last
#include "SerializationExportWrapper.hpp"
CHASTE_CLASS_EXPORT(CorriasBuistSMCModified)

namespace boost
{
    namespace serialization
    {
        template<class Archive>
        inline void save_construct_data(
            Archive & ar, const CorriasBuistSMCModified * t, const unsigned int fileVersion)
        {
            const boost::shared_ptr<AbstractIvpOdeSolver> p_solver = t->GetSolver();
            const boost::shared_ptr<AbstractStimulusFunction> p_stimulus = t->GetStimulusFunction();
            ar << p_solver;
            ar << p_stimulus;
        }

        template<class Archive>
        inline void load_construct_data(
            Archive & ar, CorriasBuistSMCModified * t, const unsigned int fileVersion)
        {
            boost::shared_ptr<AbstractIvpOdeSolver> p_solver;
            boost::shared_ptr<AbstractStimulusFunction> p_stimulus;
            ar >> p_solver;
            ar >> p_stimulus;
            ::new(t)CorriasBuistSMCModified(p_solver, p_stimulus);
        }
    }
}

#endif // CorriasBuistSMCModified_HPP_


// ====================================================================
// This file is part of FlexibleSUSY.
//
// FlexibleSUSY is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// FlexibleSUSY is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with FlexibleSUSY.  If not, see
// <http://www.gnu.org/licenses/>.
// ====================================================================

// File generated at Sat 27 Aug 2016 12:48:00

#ifndef MSSMatMGUT_INPUT_PARAMETERS_H
#define MSSMatMGUT_INPUT_PARAMETERS_H

#include <complex>
#include <Eigen/Core>

namespace flexiblesusy {

struct MSSMatMGUT_input_parameters {
   double TanBeta;
   int SignMu;
   double mHd2IN;
   double mHu2IN;
   Eigen::Matrix<double,3,3> Aeij;
   Eigen::Matrix<double,3,3> Adij;
   Eigen::Matrix<double,3,3> Auij;
   Eigen::Matrix<double,3,3> mq2Input;
   Eigen::Matrix<double,3,3> ml2Input;
   Eigen::Matrix<double,3,3> md2Input;
   Eigen::Matrix<double,3,3> mu2Input;
   Eigen::Matrix<double,3,3> me2Input;
   double MassBInput;
   double MassWBInput;
   double MassGInput;

   MSSMatMGUT_input_parameters()
      : TanBeta(0), SignMu(1), mHd2IN(0), mHu2IN(0), Aeij(Eigen::Matrix<double,3,3
   >::Zero()), Adij(Eigen::Matrix<double,3,3>::Zero()), Auij(Eigen::Matrix<
   double,3,3>::Zero()), mq2Input(Eigen::Matrix<double,3,3>::Zero()), ml2Input(
   Eigen::Matrix<double,3,3>::Zero()), md2Input(Eigen::Matrix<double,3,3>::Zero
   ()), mu2Input(Eigen::Matrix<double,3,3>::Zero()), me2Input(Eigen::Matrix<
   double,3,3>::Zero()), MassBInput(0), MassWBInput(0), MassGInput(0)

   {}

   Eigen::ArrayXd get() const;
   void set(const Eigen::ArrayXd&);
};

std::ostream& operator<<(std::ostream&, const MSSMatMGUT_input_parameters&);

} // namespace flexiblesusy

#endif


#include "engine/routing_algorithms/alternative_path.hpp"
#include "engine/routing_algorithms/routing_base_ch.hpp"

#include "util/integer_range.hpp"

#include <boost/assert.hpp>

#include <algorithm>
#include <iterator>
#include <memory>
#include <unordered_set>
#include <vector>

namespace osrm
{
namespace engine
{
namespace routing_algorithms
{

// Unqualified calls below are from the ch namespace.
// This alternative implementation works only for ch.
using namespace ch;

namespace
{
const double constexpr VIAPATH_ALPHA = 0.25;   // alternative is local optimum on 25% sub-paths
const double constexpr VIAPATH_EPSILON = 0.15; // alternative at most 15% longer
const double constexpr VIAPATH_GAMMA = 0.75;   // alternative shares at most 75% with the shortest.

using QueryHeap = SearchEngineData<Algorithm>::QueryHeap;
using SearchSpaceEdge = std::pair<NodeID, NodeID>;

struct RankedCandidateNode
{
    RankedCandidateNode(const NodeID node, const EdgeWeight weight, const EdgeWeight sharing)
        : node(node), weight(weight), sharing(sharing)
    {
    }

    NodeID node;
    EdgeWeight weight;
    EdgeWeight sharing;

    bool operator<(const RankedCandidateNode &other) const
    {
        return (2 * weight + sharing) < (2 * other.weight + other.sharing);
    }
};

// todo: reorder parameters
template <bool DIRECTION>
void alternativeRoutingStep(const DataFacade<Algorithm> &facade,
                            QueryHeap &heap1,
                            QueryHeap &heap2,
                            NodeID *middle_node,
                            EdgeWeight *upper_bound_to_shortest_path_weight,
                            std::vector<NodeID> &search_space_intersection,
                            std::vector<SearchSpaceEdge> &search_space,
                            const EdgeWeight min_edge_offset)
{
    QueryHeap &forward_heap = DIRECTION == FORWARD_DIRECTION ? heap1 : heap2;
    QueryHeap &reverse_heap = DIRECTION == FORWARD_DIRECTION ? heap2 : heap1;

    const NodeID node = forward_heap.DeleteMin();
    const EdgeWeight weight = forward_heap.GetKey(node);

    const auto scaled_weight =
        static_cast<EdgeWeight>((weight + min_edge_offset) / (1. + VIAPATH_EPSILON));
    if ((INVALID_EDGE_WEIGHT != *upper_bound_to_shortest_path_weight) &&
        (scaled_weight > *upper_bound_to_shortest_path_weight))
    {
        forward_heap.DeleteAll();
        return;
    }

    search_space.emplace_back(forward_heap.GetData(node).parent, node);

    if (reverse_heap.WasInserted(node))
    {
        search_space_intersection.emplace_back(node);
        const EdgeWeight new_weight = reverse_heap.GetKey(node) + weight;
        if (new_weight < *upper_bound_to_shortest_path_weight)
        {
            if (new_weight >= 0)
            {
                *middle_node = node;
                *upper_bound_to_shortest_path_weight = new_weight;
            }
            else
            {
                // check whether there is a loop present at the node
                const auto loop_weight = std::get<0>(getLoopWeight<false>(facade, node));
                const EdgeWeight new_weight_with_loop = new_weight + loop_weight;
                if (loop_weight != INVALID_EDGE_WEIGHT &&
                    new_weight_with_loop <= *upper_bound_to_shortest_path_weight)
                {
                    *middle_node = node;
                    *upper_bound_to_shortest_path_weight = loop_weight;
                }
            }
        }
    }

    for (auto edge : facade.GetAdjacentEdgeRange(node))
    {
        const auto &data = facade.GetEdgeData(edge);
        if (DIRECTION == FORWARD_DIRECTION ? data.forward : data.backward)
        {
            const NodeID to = facade.GetTarget(edge);
            const EdgeWeight edge_weight = data.weight;

            BOOST_ASSERT(edge_weight > 0);
            const EdgeWeight to_weight = weight + edge_weight;

            // New Node discovered -> Add to Heap + Node Info Storage
            if (!forward_heap.WasInserted(to))
            {
                forward_heap.Insert(to, to_weight, node);
            }
            // Found a shorter Path -> Update weight
            else if (to_weight < forward_heap.GetKey(to))
            {
                // new parent
                forward_heap.GetData(to).parent = node;
                // decreased weight
                forward_heap.DecreaseKey(to, to_weight);
            }
        }
    }
}

void retrievePackedAlternatePath(const QueryHeap &forward_heap1,
                                 const QueryHeap &reverse_heap1,
                                 const QueryHeap &forward_heap2,
                                 const QueryHeap &reverse_heap2,
                                 const NodeID s_v_middle,
                                 const NodeID v_t_middle,
                                 std::vector<NodeID> &packed_path)
{
    // fetch packed path [s,v)
    std::vector<NodeID> packed_v_t_path;
    retrievePackedPathFromHeap(forward_heap1, reverse_heap2, s_v_middle, packed_path);
    packed_path.pop_back(); // remove middle node. It's in both half-paths

    // fetch patched path [v,t]
    retrievePackedPathFromHeap(forward_heap2, reverse_heap1, v_t_middle, packed_v_t_path);

    packed_path.insert(packed_path.end(), packed_v_t_path.begin(), packed_v_t_path.end());
}

// TODO: reorder parameters
// compute and unpack <s,..,v> and <v,..,t> by exploring search spaces
// from v and intersecting against queues. only half-searches have to be
// done at this stage
void computeWeightAndSharingOfViaPath(SearchEngineData<Algorithm> &engine_working_data,
                                      const DataFacade<Algorithm> &facade,
                                      const NodeID via_node,
                                      EdgeWeight *real_weight_of_via_path,
                                      EdgeWeight *sharing_of_via_path,
                                      const std::vector<NodeID> &packed_shortest_path,
                                      const EdgeWeight min_edge_offset)
{
    engine_working_data.InitializeOrClearSecondThreadLocalStorage(facade.GetNumberOfNodes());

    auto &existing_forward_heap = *engine_working_data.forward_heap_1;
    auto &existing_reverse_heap = *engine_working_data.reverse_heap_1;
    auto &new_forward_heap = *engine_working_data.forward_heap_2;
    auto &new_reverse_heap = *engine_working_data.reverse_heap_2;

    std::vector<NodeID> packed_s_v_path;
    std::vector<NodeID> packed_v_t_path;

    std::vector<NodeID> partially_unpacked_shortest_path;
    std::vector<NodeID> partially_unpacked_via_path;

    NodeID s_v_middle = SPECIAL_NODEID;
    EdgeWeight upper_bound_s_v_path_weight = INVALID_EDGE_WEIGHT;
    new_reverse_heap.Insert(via_node, 0, via_node);
    // compute path <s,..,v> by reusing forward search from s
    while (!new_reverse_heap.Empty())
    {
        routingStep<REVERSE_DIRECTION>(facade,
                                       new_reverse_heap,
                                       existing_forward_heap,
                                       s_v_middle,
                                       upper_bound_s_v_path_weight,
                                       min_edge_offset,
                                       DO_NOT_FORCE_LOOPS,
                                       DO_NOT_FORCE_LOOPS);
    }
    // compute path <v,..,t> by reusing backward search from node t
    NodeID v_t_middle = SPECIAL_NODEID;
    EdgeWeight upper_bound_of_v_t_path_weight = INVALID_EDGE_WEIGHT;
    new_forward_heap.Insert(via_node, 0, via_node);
    while (!new_forward_heap.Empty())
    {
        routingStep<FORWARD_DIRECTION>(facade,
                                       new_forward_heap,
                                       existing_reverse_heap,
                                       v_t_middle,
                                       upper_bound_of_v_t_path_weight,
                                       min_edge_offset,
                                       DO_NOT_FORCE_LOOPS,
                                       DO_NOT_FORCE_LOOPS);
    }
    *real_weight_of_via_path = upper_bound_s_v_path_weight + upper_bound_of_v_t_path_weight;

    if (SPECIAL_NODEID == s_v_middle || SPECIAL_NODEID == v_t_middle)
    {
        return;
    }

    // retrieve packed paths
    retrievePackedPathFromHeap(
        existing_forward_heap, new_reverse_heap, s_v_middle, packed_s_v_path);
    retrievePackedPathFromHeap(
        new_forward_heap, existing_reverse_heap, v_t_middle, packed_v_t_path);

    // partial unpacking, compute sharing
    // First partially unpack s-->v until paths deviate, note length of common path.
    const auto s_v_min_path_size =
        std::min(packed_s_v_path.size(), packed_shortest_path.size()) - 1;
    for (const auto current_node : util::irange<std::size_t>(0UL, s_v_min_path_size))
    {
        if (packed_s_v_path[current_node] == packed_shortest_path[current_node] &&
            packed_s_v_path[current_node + 1] == packed_shortest_path[current_node + 1])
        {
            EdgeID edgeID = facade.FindEdgeInEitherDirection(packed_s_v_path[current_node],
                                                             packed_s_v_path[current_node + 1]);
            *sharing_of_via_path += facade.GetEdgeData(edgeID).weight;
        }
        else
        {
            if (packed_s_v_path[current_node] == packed_shortest_path[current_node])
            {
                unpackEdge(facade,
                           packed_s_v_path[current_node],
                           packed_s_v_path[current_node + 1],
                           partially_unpacked_via_path);
                unpackEdge(facade,
                           packed_shortest_path[current_node],
                           packed_shortest_path[current_node + 1],
                           partially_unpacked_shortest_path);
                break;
            }
        }
    }
    // traverse partially unpacked edge and note common prefix
    const int64_t packed_path_length =
        static_cast<int64_t>(
            std::min(partially_unpacked_via_path.size(), partially_unpacked_shortest_path.size())) -
        1;
    for (int64_t current_node = 0; (current_node < packed_path_length) &&
                                   (partially_unpacked_via_path[current_node] ==
                                        partially_unpacked_shortest_path[current_node] &&
                                    partially_unpacked_via_path[current_node + 1] ==
                                        partially_unpacked_shortest_path[current_node + 1]);
         ++current_node)
    {
        EdgeID selected_edge =
            facade.FindEdgeInEitherDirection(partially_unpacked_via_path[current_node],
                                             partially_unpacked_via_path[current_node + 1]);
        *sharing_of_via_path += facade.GetEdgeData(selected_edge).weight;
    }

    // Second, partially unpack v-->t in reverse order until paths deviate and note lengths
    int64_t via_path_index = static_cast<int64_t>(packed_v_t_path.size()) - 1;
    int64_t shortest_path_index = static_cast<int64_t>(packed_shortest_path.size()) - 1;
    for (; via_path_index > 0 && shortest_path_index > 0; --via_path_index, --shortest_path_index)
    {
        if (packed_v_t_path[via_path_index - 1] == packed_shortest_path[shortest_path_index - 1] &&
            packed_v_t_path[via_path_index] == packed_shortest_path[shortest_path_index])
        {
            EdgeID edgeID = facade.FindEdgeInEitherDirection(packed_v_t_path[via_path_index - 1],
                                                             packed_v_t_path[via_path_index]);
            *sharing_of_via_path += facade.GetEdgeData(edgeID).weight;
        }
        else
        {
            if (packed_v_t_path[via_path_index] == packed_shortest_path[shortest_path_index])
            {
                unpackEdge(facade,
                           packed_v_t_path[via_path_index - 1],
                           packed_v_t_path[via_path_index],
                           partially_unpacked_via_path);
                unpackEdge(facade,
                           packed_shortest_path[shortest_path_index - 1],
                           packed_shortest_path[shortest_path_index],
                           partially_unpacked_shortest_path);
                break;
            }
        }
    }

    via_path_index = static_cast<int64_t>(partially_unpacked_via_path.size()) - 1;
    shortest_path_index = static_cast<int64_t>(partially_unpacked_shortest_path.size()) - 1;
    for (; via_path_index > 0 && shortest_path_index > 0; --via_path_index, --shortest_path_index)
    {
        if (partially_unpacked_via_path[via_path_index - 1] ==
                partially_unpacked_shortest_path[shortest_path_index - 1] &&
            partially_unpacked_via_path[via_path_index] ==
                partially_unpacked_shortest_path[shortest_path_index])
        {
            EdgeID edgeID =
                facade.FindEdgeInEitherDirection(partially_unpacked_via_path[via_path_index - 1],
                                                 partially_unpacked_via_path[via_path_index]);
            *sharing_of_via_path += facade.GetEdgeData(edgeID).weight;
        }
        else
        {
            break;
        }
    }
    // finished partial unpacking spree! Amount of sharing is stored to appropriate pointer
    // variable
}

// conduct T-Test
bool viaNodeCandidatePassesTTest(SearchEngineData<Algorithm> &engine_working_data,
                                 const DataFacade<Algorithm> &facade,
                                 QueryHeap &existing_forward_heap,
                                 QueryHeap &existing_reverse_heap,
                                 QueryHeap &new_forward_heap,
                                 QueryHeap &new_reverse_heap,
                                 const RankedCandidateNode &candidate,
                                 const EdgeWeight weight_of_shortest_path,
                                 EdgeWeight *weight_of_via_path,
                                 NodeID *s_v_middle,
                                 NodeID *v_t_middle,
                                 const EdgeWeight min_edge_offset)
{
    new_forward_heap.Clear();
    new_reverse_heap.Clear();
    std::vector<NodeID> packed_s_v_path;
    std::vector<NodeID> packed_v_t_path;

    *s_v_middle = SPECIAL_NODEID;
    EdgeWeight upper_bound_s_v_path_weight = INVALID_EDGE_WEIGHT;
    // compute path <s,..,v> by reusing forward search from s
    new_reverse_heap.Insert(candidate.node, 0, candidate.node);
    while (new_reverse_heap.Size() > 0)
    {
        routingStep<REVERSE_DIRECTION>(facade,
                                       new_reverse_heap,
                                       existing_forward_heap,
                                       *s_v_middle,
                                       upper_bound_s_v_path_weight,
                                       min_edge_offset,
                                       DO_NOT_FORCE_LOOPS,
                                       DO_NOT_FORCE_LOOPS);
    }

    if (INVALID_EDGE_WEIGHT == upper_bound_s_v_path_weight)
    {
        return false;
    }

    // compute path <v,..,t> by reusing backward search from t
    *v_t_middle = SPECIAL_NODEID;
    EdgeWeight upper_bound_of_v_t_path_weight = INVALID_EDGE_WEIGHT;
    new_forward_heap.Insert(candidate.node, 0, candidate.node);
    while (new_forward_heap.Size() > 0)
    {
        routingStep<FORWARD_DIRECTION>(facade,
                                       new_forward_heap,
                                       existing_reverse_heap,
                                       *v_t_middle,
                                       upper_bound_of_v_t_path_weight,
                                       min_edge_offset,
                                       DO_NOT_FORCE_LOOPS,
                                       DO_NOT_FORCE_LOOPS);
    }

    if (INVALID_EDGE_WEIGHT == upper_bound_of_v_t_path_weight)
    {
        return false;
    }

    *weight_of_via_path = upper_bound_s_v_path_weight + upper_bound_of_v_t_path_weight;

    // retrieve packed paths
    retrievePackedPathFromHeap(
        existing_forward_heap, new_reverse_heap, *s_v_middle, packed_s_v_path);

    retrievePackedPathFromHeap(
        new_forward_heap, existing_reverse_heap, *v_t_middle, packed_v_t_path);

    NodeID s_P = *s_v_middle, t_P = *v_t_middle;
    if (SPECIAL_NODEID == s_P)
    {
        return false;
    }

    if (SPECIAL_NODEID == t_P)
    {
        return false;
    }
    const EdgeWeight T_threshold = static_cast<EdgeWeight>(VIAPATH_ALPHA * weight_of_shortest_path);
    EdgeWeight unpacked_until_weight = 0;

    std::stack<SearchSpaceEdge> unpack_stack;
    // Traverse path s-->v
    for (std::size_t i = packed_s_v_path.size() - 1; (i > 0) && unpack_stack.empty(); --i)
    {
        const EdgeID current_edge_id =
            facade.FindEdgeInEitherDirection(packed_s_v_path[i - 1], packed_s_v_path[i]);
        const EdgeWeight weight_of_current_edge = facade.GetEdgeData(current_edge_id).weight;
        if ((weight_of_current_edge + unpacked_until_weight) >= T_threshold)
        {
            unpack_stack.emplace(packed_s_v_path[i - 1], packed_s_v_path[i]);
        }
        else
        {
            unpacked_until_weight += weight_of_current_edge;
            s_P = packed_s_v_path[i - 1];
        }
    }

    while (!unpack_stack.empty())
    {
        const SearchSpaceEdge via_path_edge = unpack_stack.top();
        unpack_stack.pop();
        EdgeID edge_in_via_path_id =
            facade.FindEdgeInEitherDirection(via_path_edge.first, via_path_edge.second);

        if (SPECIAL_EDGEID == edge_in_via_path_id)
        {
            return false;
        }

        const auto &current_edge_data = facade.GetEdgeData(edge_in_via_path_id);
        const bool current_edge_is_shortcut = current_edge_data.shortcut;
        if (current_edge_is_shortcut)
        {
            const NodeID via_path_middle_node_id = current_edge_data.turn_id;
            const EdgeID second_segment_edge_id =
                facade.FindEdgeInEitherDirection(via_path_middle_node_id, via_path_edge.second);
            const auto second_segment_weight = facade.GetEdgeData(second_segment_edge_id).weight;
            // attention: !unpacking in reverse!
            // Check if second segment is the one to go over treshold? if yes add second segment
            // to stack, else push first segment to stack and add weight of second one.
            if (unpacked_until_weight + second_segment_weight >= T_threshold)
            {
                unpack_stack.emplace(via_path_middle_node_id, via_path_edge.second);
            }
            else
            {
                unpacked_until_weight += second_segment_weight;
                unpack_stack.emplace(via_path_edge.first, via_path_middle_node_id);
            }
        }
        else
        {
            // edge is not a shortcut, set the start node for T-Test to end of edge.
            unpacked_until_weight += current_edge_data.weight;
            s_P = via_path_edge.first;
        }
    }

    EdgeWeight t_test_path_weight = unpacked_until_weight;
    unpacked_until_weight = 0;
    // Traverse path s-->v
    BOOST_ASSERT(!packed_v_t_path.empty());
    for (unsigned i = 0, packed_path_length = static_cast<unsigned>(packed_v_t_path.size() - 1);
         (i < packed_path_length) && unpack_stack.empty();
         ++i)
    {
        const EdgeID edgeID =
            facade.FindEdgeInEitherDirection(packed_v_t_path[i], packed_v_t_path[i + 1]);
        auto weight_of_current_edge = facade.GetEdgeData(edgeID).weight;
        if (weight_of_current_edge + unpacked_until_weight >= T_threshold)
        {
            unpack_stack.emplace(packed_v_t_path[i], packed_v_t_path[i + 1]);
        }
        else
        {
            unpacked_until_weight += weight_of_current_edge;
            t_P = packed_v_t_path[i + 1];
        }
    }

    while (!unpack_stack.empty())
    {
        const SearchSpaceEdge via_path_edge = unpack_stack.top();
        unpack_stack.pop();
        EdgeID edge_in_via_path_id =
            facade.FindEdgeInEitherDirection(via_path_edge.first, via_path_edge.second);
        if (SPECIAL_EDGEID == edge_in_via_path_id)
        {
            return false;
        }

        const auto &current_edge_data = facade.GetEdgeData(edge_in_via_path_id);
        const bool IsViaEdgeShortCut = current_edge_data.shortcut;
        if (IsViaEdgeShortCut)
        {
            const NodeID middleOfViaPath = current_edge_data.turn_id;
            EdgeID edgeIDOfFirstSegment =
                facade.FindEdgeInEitherDirection(via_path_edge.first, middleOfViaPath);
            auto weightOfFirstSegment = facade.GetEdgeData(edgeIDOfFirstSegment).weight;
            // Check if first segment is the one to go over treshold? if yes first segment to
            // stack, else push second segment to stack and add weight of first one.
            if (unpacked_until_weight + weightOfFirstSegment >= T_threshold)
            {
                unpack_stack.emplace(via_path_edge.first, middleOfViaPath);
            }
            else
            {
                unpacked_until_weight += weightOfFirstSegment;
                unpack_stack.emplace(middleOfViaPath, via_path_edge.second);
            }
        }
        else
        {
            // edge is not a shortcut, set the start node for T-Test to end of edge.
            unpacked_until_weight += current_edge_data.weight;
            t_P = via_path_edge.second;
        }
    }

    t_test_path_weight += unpacked_until_weight;
    // Run actual T-Test query and compare if weight equal.
    engine_working_data.InitializeOrClearThirdThreadLocalStorage(facade.GetNumberOfNodes());

    QueryHeap &forward_heap3 = *engine_working_data.forward_heap_3;
    QueryHeap &reverse_heap3 = *engine_working_data.reverse_heap_3;
    EdgeWeight upper_bound = INVALID_EDGE_WEIGHT;
    NodeID middle = SPECIAL_NODEID;

    forward_heap3.Insert(s_P, 0, s_P);
    reverse_heap3.Insert(t_P, 0, t_P);
    // exploration from s and t until deletemin/(1+epsilon) > _lengt_oO_sShortest_path
    while ((forward_heap3.Size() + reverse_heap3.Size()) > 0)
    {
        if (!forward_heap3.Empty())
        {
            routingStep<FORWARD_DIRECTION>(facade,
                                           forward_heap3,
                                           reverse_heap3,
                                           middle,
                                           upper_bound,
                                           min_edge_offset,
                                           DO_NOT_FORCE_LOOPS,
                                           DO_NOT_FORCE_LOOPS);
        }
        if (!reverse_heap3.Empty())
        {
            routingStep<REVERSE_DIRECTION>(facade,
                                           reverse_heap3,
                                           forward_heap3,
                                           middle,
                                           upper_bound,
                                           min_edge_offset,
                                           DO_NOT_FORCE_LOOPS,
                                           DO_NOT_FORCE_LOOPS);
        }
    }
    return (upper_bound <= t_test_path_weight);
}
} // namespace

InternalManyRoutesResult alternativePathSearch(SearchEngineData<Algorithm> &engine_working_data,
                                               const DataFacade<Algorithm> &facade,
                                               const PhantomNodes &phantom_node_pair,
                                               unsigned /*number_of_alternatives*/)
{
    InternalRouteResult primary_route;
    InternalRouteResult secondary_route;

    primary_route.segment_end_coordinates = {phantom_node_pair};
    secondary_route.segment_end_coordinates = {phantom_node_pair};

    std::vector<NodeID> alternative_path;
    std::vector<NodeID> via_node_candidate_list;
    std::vector<SearchSpaceEdge> forward_search_space;
    std::vector<SearchSpaceEdge> reverse_search_space;

    // Init queues, semi-expensive because access to TSS invokes a sys-call
    engine_working_data.InitializeOrClearFirstThreadLocalStorage(facade.GetNumberOfNodes());
    engine_working_data.InitializeOrClearSecondThreadLocalStorage(facade.GetNumberOfNodes());
    engine_working_data.InitializeOrClearThirdThreadLocalStorage(facade.GetNumberOfNodes());

    auto &forward_heap1 = *engine_working_data.forward_heap_1;
    auto &reverse_heap1 = *engine_working_data.reverse_heap_1;
    auto &forward_heap2 = *engine_working_data.forward_heap_2;
    auto &reverse_heap2 = *engine_working_data.reverse_heap_2;

    EdgeWeight upper_bound_to_shortest_path_weight = INVALID_EDGE_WEIGHT;
    NodeID middle_node = SPECIAL_NODEID;
    const EdgeWeight min_edge_offset =
        std::min(phantom_node_pair.source_phantom.forward_segment_id.enabled
                     ? -phantom_node_pair.source_phantom.GetForwardWeightPlusOffset()
                     : 0,
                 phantom_node_pair.source_phantom.reverse_segment_id.enabled
                     ? -phantom_node_pair.source_phantom.GetReverseWeightPlusOffset()
                     : 0);

    insertNodesInHeaps(forward_heap1, reverse_heap1, phantom_node_pair);

    // search from s and t till new_min/(1+epsilon) > weight_of_shortest_path
    while (0 < (forward_heap1.Size() + reverse_heap1.Size()))
    {
        if (0 < forward_heap1.Size())
        {
            alternativeRoutingStep<FORWARD_DIRECTION>(facade,
                                                      forward_heap1,
                                                      reverse_heap1,
                                                      &middle_node,
                                                      &upper_bound_to_shortest_path_weight,
                                                      via_node_candidate_list,
                                                      forward_search_space,
                                                      min_edge_offset);
        }
        if (0 < reverse_heap1.Size())
        {
            alternativeRoutingStep<REVERSE_DIRECTION>(facade,
                                                      forward_heap1,
                                                      reverse_heap1,
                                                      &middle_node,
                                                      &upper_bound_to_shortest_path_weight,
                                                      via_node_candidate_list,
                                                      reverse_search_space,
                                                      min_edge_offset);
        }
    }

    if (INVALID_EDGE_WEIGHT == upper_bound_to_shortest_path_weight)
    {
        return InternalManyRoutesResult{std::move(primary_route)};
    }

    std::sort(begin(via_node_candidate_list), end(via_node_candidate_list));
    auto unique_end = std::unique(begin(via_node_candidate_list), end(via_node_candidate_list));
    via_node_candidate_list.resize(unique_end - begin(via_node_candidate_list));

    std::vector<NodeID> packed_forward_path;
    std::vector<NodeID> packed_reverse_path;

    const bool path_is_a_loop =
        upper_bound_to_shortest_path_weight !=
        forward_heap1.GetKey(middle_node) + reverse_heap1.GetKey(middle_node);
    if (path_is_a_loop)
    {
        // Self Loop
        packed_forward_path.push_back(middle_node);
        packed_forward_path.push_back(middle_node);
    }
    else
    {

        retrievePackedPathFromSingleHeap(forward_heap1, middle_node, packed_forward_path);
        retrievePackedPathFromSingleHeap(reverse_heap1, middle_node, packed_reverse_path);
    }

    // this set is is used as an indicator if a node is on the shortest path
    std::unordered_set<NodeID> nodes_in_path(packed_forward_path.size() +
                                             packed_reverse_path.size());
    nodes_in_path.insert(packed_forward_path.begin(), packed_forward_path.end());
    nodes_in_path.insert(middle_node);
    nodes_in_path.insert(packed_reverse_path.begin(), packed_reverse_path.end());

    std::unordered_map<NodeID, EdgeWeight> approximated_forward_sharing;
    std::unordered_map<NodeID, EdgeWeight> approximated_reverse_sharing;

    // sweep over search space, compute forward sharing for each current edge (u,v)
    for (const SearchSpaceEdge &current_edge : forward_search_space)
    {
        const NodeID u = current_edge.first;
        const NodeID v = current_edge.second;

        if (nodes_in_path.find(v) != nodes_in_path.end())
        {
            // current_edge is on shortest path => sharing(v):=queue.GetKey(v);
            approximated_forward_sharing.emplace(v, forward_heap1.GetKey(v));
        }
        else
        {
            // current edge is not on shortest path. Check if we know a value for the other
            // endpoint
            const auto sharing_of_u_iterator = approximated_forward_sharing.find(u);
            if (sharing_of_u_iterator != approximated_forward_sharing.end())
            {
                approximated_forward_sharing.emplace(v, sharing_of_u_iterator->second);
            }
        }
    }

    // sweep over search space, compute backward sharing
    for (const SearchSpaceEdge &current_edge : reverse_search_space)
    {
        const NodeID u = current_edge.first;
        const NodeID v = current_edge.second;
        if (nodes_in_path.find(v) != nodes_in_path.end())
        {
            // current_edge is on shortest path => sharing(u):=queue.GetKey(u);
            approximated_reverse_sharing.emplace(v, reverse_heap1.GetKey(v));
        }
        else
        {
            // current edge is not on shortest path. Check if we know a value for the other
            // endpoint
            const auto sharing_of_u_iterator = approximated_reverse_sharing.find(u);
            if (sharing_of_u_iterator != approximated_reverse_sharing.end())
            {
                approximated_reverse_sharing.emplace(v, sharing_of_u_iterator->second);
            }
        }
    }

    std::vector<NodeID> preselected_node_list;
    for (const NodeID node : via_node_candidate_list)
    {
        if (node == middle_node)
            continue;
        const auto fwd_iterator = approximated_forward_sharing.find(node);
        const EdgeWeight fwd_sharing =
            (fwd_iterator != approximated_forward_sharing.end()) ? fwd_iterator->second : 0;
        const auto rev_iterator = approximated_reverse_sharing.find(node);
        const EdgeWeight rev_sharing =
            (rev_iterator != approximated_reverse_sharing.end()) ? rev_iterator->second : 0;

        const EdgeWeight approximated_sharing = fwd_sharing + rev_sharing;
        const EdgeWeight approximated_weight =
            forward_heap1.GetKey(node) + reverse_heap1.GetKey(node);
        const bool weight_passes =
            (approximated_weight < upper_bound_to_shortest_path_weight * (1 + VIAPATH_EPSILON));
        const bool sharing_passes =
            (approximated_sharing <= upper_bound_to_shortest_path_weight * VIAPATH_GAMMA);
        const bool stretch_passes =
            (approximated_weight - approximated_sharing) <
            ((1. + VIAPATH_EPSILON) * (upper_bound_to_shortest_path_weight - approximated_sharing));

        if (weight_passes && sharing_passes && stretch_passes)
        {
            preselected_node_list.emplace_back(node);
        }
    }

    std::vector<NodeID> &packed_shortest_path = packed_forward_path;
    if (!path_is_a_loop)
    {
        std::reverse(packed_shortest_path.begin(), packed_shortest_path.end());
        packed_shortest_path.emplace_back(middle_node);
        packed_shortest_path.insert(
            packed_shortest_path.end(), packed_reverse_path.begin(), packed_reverse_path.end());
    }
    std::vector<RankedCandidateNode> ranked_candidates_list;

    // prioritizing via nodes for deep inspection
    for (const NodeID node : preselected_node_list)
    {
        EdgeWeight weight_of_via_path = 0, sharing_of_via_path = 0;
        computeWeightAndSharingOfViaPath(engine_working_data,
                                         facade,
                                         node,
                                         &weight_of_via_path,
                                         &sharing_of_via_path,
                                         packed_shortest_path,
                                         min_edge_offset);
        const EdgeWeight maximum_allowed_sharing =
            static_cast<EdgeWeight>(upper_bound_to_shortest_path_weight * VIAPATH_GAMMA);
        if (sharing_of_via_path <= maximum_allowed_sharing &&
            weight_of_via_path <= upper_bound_to_shortest_path_weight * (1 + VIAPATH_EPSILON))
        {
            ranked_candidates_list.emplace_back(node, weight_of_via_path, sharing_of_via_path);
        }
    }
    std::sort(ranked_candidates_list.begin(), ranked_candidates_list.end());

    NodeID selected_via_node = SPECIAL_NODEID;
    EdgeWeight weight_of_via_path = INVALID_EDGE_WEIGHT;
    NodeID s_v_middle = SPECIAL_NODEID, v_t_middle = SPECIAL_NODEID;
    for (const RankedCandidateNode &candidate : ranked_candidates_list)
    {
        if (viaNodeCandidatePassesTTest(engine_working_data,
                                        facade,
                                        forward_heap1,
                                        reverse_heap1,
                                        forward_heap2,
                                        reverse_heap2,
                                        candidate,
                                        upper_bound_to_shortest_path_weight,
                                        &weight_of_via_path,
                                        &s_v_middle,
                                        &v_t_middle,
                                        min_edge_offset))
        {
            // select first admissable
            selected_via_node = candidate.node;
            break;
        }
    }

    // Unpack shortest path and alternative, if they exist
    if (INVALID_EDGE_WEIGHT != upper_bound_to_shortest_path_weight)
    {
        BOOST_ASSERT(!packed_shortest_path.empty());
        primary_route.unpacked_path_segments.resize(1);
        primary_route.source_traversed_in_reverse.push_back(
            (packed_shortest_path.front() !=
             phantom_node_pair.source_phantom.forward_segment_id.id));
        primary_route.target_traversed_in_reverse.push_back((
            packed_shortest_path.back() != phantom_node_pair.target_phantom.forward_segment_id.id));

        unpackPath(facade,
                   // -- packed input
                   packed_shortest_path.begin(),
                   packed_shortest_path.end(),
                   // -- start of route
                   phantom_node_pair,
                   // -- unpacked output
                   primary_route.unpacked_path_segments.front());
        primary_route.shortest_path_weight = upper_bound_to_shortest_path_weight;
    }

    if (SPECIAL_NODEID != selected_via_node)
    {
        std::vector<NodeID> packed_alternate_path;
        // retrieve alternate path
        retrievePackedAlternatePath(forward_heap1,
                                    reverse_heap1,
                                    forward_heap2,
                                    reverse_heap2,
                                    s_v_middle,
                                    v_t_middle,
                                    packed_alternate_path);

        secondary_route.unpacked_path_segments.resize(1);
        secondary_route.source_traversed_in_reverse.push_back(
            (packed_alternate_path.front() !=
             phantom_node_pair.source_phantom.forward_segment_id.id));
        secondary_route.target_traversed_in_reverse.push_back(
            (packed_alternate_path.back() !=
             phantom_node_pair.target_phantom.forward_segment_id.id));

        // unpack the alternate path
        unpackPath(facade,
                   packed_alternate_path.begin(),
                   packed_alternate_path.end(),
                   phantom_node_pair,
                   secondary_route.unpacked_path_segments.front());

        secondary_route.shortest_path_weight = weight_of_via_path;
    }
    else
    {
        BOOST_ASSERT(secondary_route.shortest_path_weight == INVALID_EDGE_WEIGHT);
    }

    return InternalManyRoutesResult{{std::move(primary_route), std::move(secondary_route)}};
}

} // namespace routing_algorithms
} // namespace engine
} // namespace osrm


// All content Copyright (C) 2018 Genomics plc
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#include "io/read.hpp"
#include "io/readIntervalTree.hpp"
#include "io/readRange.hpp"
#include "io/readSummaries.hpp"
#include "caller/diploid/referenceCalling.hpp"
#include "vcf/field.hpp"
#include <cmath>

using Read = wecall::io::Read;
using Cigar = wecall::alignment::Cigar;
using wecall::caller::Region;
using wecall::caller::Call;
using wecall::io::RegionsReads;
using wecall::utils::BasePairSequence;
using wecall::caller::model::buildRefCall;
using Annotation = wecall::caller::Annotation;
using wecall::vcf::info::DP_key;

BOOST_AUTO_TEST_CASE( shouldCallRefFor1ReadAnd1Sample )
{
    Region region = Region( "1", 0, 5 );
    auto refSequence = std::make_shared< wecall::utils::ReferenceSequence >( region, std::string( 5, 'A' ) );

    const int64_t startPos = 0;
    const auto read1 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    wecall::io::readIntervalTree_t readContainer( 0, 100 );
    readContainer.insert( read1 );

    RegionsReads regionSetReads( region, readContainer.getFullRange(), 0 );

    wecall::io::perSampleRegionsReads_t perSampleReads = {{"sample1", regionSetReads}};

    const auto refCalls = buildRefCall( region, perSampleReads, 10, {2}, 0.2 );

    BOOST_REQUIRE_EQUAL( refCalls.size(), 2 );

    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::BEG ), 1 );
    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::END ), 4 );
    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::LEN ), 4 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[0].getAnnotation( Annotation::MIN_DP ), 1 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[0].getAnnotation( Annotation::FORMAT_DP ), 1 );
    BOOST_CHECK_CLOSE( refCalls[0].samples[0].getAnnotation( Annotation::GQ ), 3.0103, 1 );

    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::BEG ), 5 );
    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::END ), 5 );
    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::LEN ), 1 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[0].getAnnotation( Annotation::MIN_DP ), 0 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[0].getAnnotation( Annotation::FORMAT_DP ), 0 );
    BOOST_CHECK_EQUAL( std::isnan( refCalls[1].samples[0].getAnnotation( Annotation::GQ ) ), true );
}

BOOST_AUTO_TEST_CASE( shouldCallRefFor2OverlappingReadsAnd1Sample )
{
    Region region = Region( "1", 0, 5 );
    auto refSequence = std::make_shared< wecall::utils::ReferenceSequence >( region, std::string( 5, 'A' ) );

    const int64_t startPos = 0;
    const auto read1 = std::make_shared< Read >( BasePairSequence( 3, 'A' ), std::string( 3, 'Q' ), "0", Cigar( "3M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read2 = std::make_shared< Read >( BasePairSequence( 3, 'A' ), std::string( 3, 'Q' ), "0", Cigar( "3M" ),
                                                 0, startPos + 2, 0, 0, 0, 0, 0, refSequence );

    wecall::io::readIntervalTree_t readContainer( 0, 100 );
    readContainer.insert( read1 );
    readContainer.insert( read2 );

    RegionsReads regionSetReads( region, readContainer.getFullRange(), 0 );

    wecall::io::perSampleRegionsReads_t perSampleReads = {{"sample1", regionSetReads}};

    const auto refCalls = buildRefCall( region, perSampleReads, 10, {2}, 0.2 );

    BOOST_REQUIRE_EQUAL( refCalls.size(), 3 );

    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::BEG ), 1 );
    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::END ), 2 );
    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::LEN ), 2 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[0].getAnnotation( Annotation::MIN_DP ), 1 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[0].getAnnotation( Annotation::FORMAT_DP ), 1 );
    BOOST_CHECK_CLOSE( refCalls[0].samples[0].getAnnotation( Annotation::GQ ), 3.0103, 1 );

    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::BEG ), 3 );
    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::END ), 3 );
    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::LEN ), 1 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[0].getAnnotation( Annotation::MIN_DP ), 2 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[0].getAnnotation( Annotation::FORMAT_DP ), 2 );
    BOOST_CHECK_CLOSE( refCalls[1].samples[0].getAnnotation( Annotation::GQ ), 5.9159, 1 );

    BOOST_CHECK_EQUAL( refCalls[2].getAnnotation( Annotation::BEG ), 4 );
    BOOST_CHECK_EQUAL( refCalls[2].getAnnotation( Annotation::END ), 5 );
    BOOST_CHECK_EQUAL( refCalls[2].getAnnotation( Annotation::LEN ), 2 );
    BOOST_CHECK_EQUAL( refCalls[2].samples[0].getAnnotation( Annotation::MIN_DP ), 1 );
    BOOST_CHECK_EQUAL( refCalls[2].samples[0].getAnnotation( Annotation::FORMAT_DP ), 1 );
    BOOST_CHECK_CLOSE( refCalls[2].samples[0].getAnnotation( Annotation::GQ ), 3.0103, 1 );
}

BOOST_AUTO_TEST_CASE( shouldCallRefForManyReadsFor1Sample )
{
    Region region = Region( "1", 0, 5 );
    auto refSequence = std::make_shared< wecall::utils::ReferenceSequence >( region, std::string( 5, 'A' ) );

    const int64_t startPos = 0;
    const auto read1 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read2 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read3 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read4 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read5 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read6 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read7 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read8 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read9 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read10 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                  0, startPos + 1, 0, 0, 0, 0, 0, refSequence );

    wecall::io::readIntervalTree_t readContainer( 0, 100 );
    readContainer.insert( read1 );
    readContainer.insert( read2 );
    readContainer.insert( read3 );
    readContainer.insert( read4 );
    readContainer.insert( read5 );
    readContainer.insert( read6 );
    readContainer.insert( read7 );
    readContainer.insert( read8 );
    readContainer.insert( read9 );
    readContainer.insert( read10 );

    RegionsReads regionSetReads( region, readContainer.getFullRange(), 0 );

    wecall::io::perSampleRegionsReads_t perSampleReads = {{"sample1", regionSetReads}};

    const auto refCalls = buildRefCall( region, perSampleReads, 10, {2}, 0.1 );

    BOOST_REQUIRE_EQUAL( refCalls.size(), 2 );

    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::BEG ), 1 );
    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::END ), 4 );
    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::LEN ), 4 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[0].getAnnotation( Annotation::MIN_DP ), 9 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[0].getAnnotation( Annotation::FORMAT_DP ), 10 );  // rounded 9.75
    BOOST_CHECK_CLOSE( refCalls[0].samples[0].getAnnotation( Annotation::GQ ), 23.85277, 1 );

    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::BEG ), 5 );
    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::END ), 5 );
    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::LEN ), 1 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[0].getAnnotation( Annotation::MIN_DP ), 1 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[0].getAnnotation( Annotation::FORMAT_DP ), 1 );
    BOOST_CHECK_CLOSE( refCalls[1].samples[0].getAnnotation( Annotation::GQ ), 3.0103, 1 );
}

BOOST_AUTO_TEST_CASE( shouldCallRefFor1ReadsFor2Samples )
{
    Region region = Region( "1", 0, 5 );
    auto refSequence = std::make_shared< wecall::utils::ReferenceSequence >( region, std::string( 5, 'A' ) );

    const int64_t startPos = 0;
    const auto read1 = std::make_shared< Read >( BasePairSequence( 3, 'A' ), std::string( 3, 'Q' ), "0", Cigar( "3M" ),
                                                 0, startPos, 0, 0, 0, 0, 0, refSequence );

    const auto read2 = std::make_shared< Read >( BasePairSequence( 4, 'A' ), std::string( 4, 'Q' ), "0", Cigar( "4M" ),
                                                 0, startPos + 1, 0, 0, 0, 0, 0, refSequence );

    wecall::io::readIntervalTree_t readContainer1( 0, 100 );
    readContainer1.insert( read1 );
    RegionsReads regionSetReads1( region, readContainer1.getFullRange(), 0 );

    wecall::io::readIntervalTree_t readContainer2( 0, 100 );
    readContainer2.insert( read2 );
    RegionsReads regionSetReads2( region, readContainer2.getFullRange(), 0 );

    wecall::io::perSampleRegionsReads_t perSampleReads = {{"sample1", regionSetReads1}, {"sample2", regionSetReads2}};

    const auto refCalls = buildRefCall( region, perSampleReads, 10, {2, 2}, 0.2 );

    BOOST_REQUIRE_EQUAL( refCalls.size(), 3 );

    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::BEG ), 1 );
    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::END ), 1 );
    BOOST_CHECK_EQUAL( refCalls[0].getAnnotation( Annotation::LEN ), 1 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[0].getAnnotation( Annotation::MIN_DP ), 1 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[0].getAnnotation( Annotation::FORMAT_DP ), 1 );
    BOOST_CHECK_CLOSE( refCalls[0].samples[0].getAnnotation( Annotation::GQ ), 3.0103, 1 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[1].getAnnotation( Annotation::MIN_DP ), 0 );
    BOOST_CHECK_EQUAL( refCalls[0].samples[1].getAnnotation( Annotation::FORMAT_DP ), 0 );
    BOOST_CHECK_EQUAL( std::isnan( refCalls[0].samples[1].getAnnotation( Annotation::GQ ) ), true );

    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::BEG ), 2 );
    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::END ), 3 );
    BOOST_CHECK_EQUAL( refCalls[1].getAnnotation( Annotation::LEN ), 2 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[0].getAnnotation( Annotation::MIN_DP ), 1 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[0].getAnnotation( Annotation::FORMAT_DP ), 1 );
    BOOST_CHECK_CLOSE( refCalls[1].samples[0].getAnnotation( Annotation::GQ ), 3.0103, 1 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[1].getAnnotation( Annotation::MIN_DP ), 1 );
    BOOST_CHECK_EQUAL( refCalls[1].samples[1].getAnnotation( Annotation::FORMAT_DP ), 1 );
    BOOST_CHECK_CLOSE( refCalls[1].samples[1].getAnnotation( Annotation::GQ ), 3.0103, 1 );

    BOOST_CHECK_EQUAL( refCalls[2].getAnnotation( Annotation::BEG ), 4 );
    BOOST_CHECK_EQUAL( refCalls[2].getAnnotation( Annotation::END ), 5 );
    BOOST_CHECK_EQUAL( refCalls[2].getAnnotation( Annotation::LEN ), 2 );
    BOOST_CHECK_EQUAL( refCalls[2].samples[0].getAnnotation( Annotation::MIN_DP ), 0 );
    BOOST_CHECK_EQUAL( refCalls[2].samples[0].getAnnotation( Annotation::FORMAT_DP ), 0 );
    BOOST_CHECK_EQUAL( std::isnan( refCalls[2].samples[0].getAnnotation( Annotation::GQ ) ), true );
    BOOST_CHECK_EQUAL( refCalls[2].samples[1].getAnnotation( Annotation::MIN_DP ), 1 );
    BOOST_CHECK_EQUAL( refCalls[2].samples[1].getAnnotation( Annotation::FORMAT_DP ), 1 );
    BOOST_CHECK_CLOSE( refCalls[2].samples[1].getAnnotation( Annotation::GQ ), 3.0103, 1 );
}


#include "dynet/nodes.h"
#include "dynet/dynet.h"
#include "dynet/training.h"
#include "dynet/timing.h"
#include "dynet/rnn.h"
#include "dynet/gru.h"
#include "dynet/lstm.h"
#include "dynet/fast-lstm.h"
#include "dynet/expr.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <type_traits>
#include <sys/types.h>
#include <sys/stat.h>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/program_options.hpp>

#include "s2s/dynet/dict.h"
#include "s2s/corpus/options.hpp"
#include "s2s/corpus/corpora.hpp"
#include "s2s/corpus/dicts.hpp"
#include "s2s/corpus/batch.hpp"
#include "s2s/nn/encdec.hpp"

#ifndef INCLUDE_GUARD_ENCODER_DECODER_LAHAN_HPP
#define INCLUDE_GUARD_ENCODER_DECODER_LAHAN_HPP

namespace s2s {

class encoder_decoder_slahan : public encoder_decoder {

public:

    dynet::Parameter p_Wad;
    dynet::Parameter p_Uad;
    dynet::Parameter p_vad;

    dynet::Parameter p_Wctx;
    dynet::Parameter p_bctx;

    std::vector<dynet::Parameter> p_Wahf;

    std::vector<dynet::Parameter> p_Wahb;

    dynet::Parameter p_select_W; // {rnn_size*2,rnn_size*7}
    dynet::Parameter p_select_b; // {rnn_size*2}

    std::vector<dynet::Expression> h_bi;
    std::vector<std::vector<dynet::Expression> > f_alpha_d;
    std::vector<std::vector<dynet::Expression> > b_alpha_d;

    dynet::Expression i_h_enc;
    dynet::Expression i_h_fin;
    dynet::Expression i_feed;

    // for visualization
    std::vector<dynet::Expression> gate_list;

    explicit encoder_decoder_slahan(dynet::ParameterCollection& model, const s2s_options& opts, dicts& d) : encoder_decoder(model,opts,d) {

        flag_drop_out = true;

        unsigned int dec_context_size = 0;
        unsigned int dec_feeding_size = 0;

        assert(opts.dec_feature_vocab_size.size() == opts.dec_feature_vec_size.size());

        unsigned int cell_ratio = 2; // In GRU, cell_ratio = 1;

        p_dec_init_w.resize(opts.num_layers * cell_ratio);
        for(unsigned int i = 0; i < opts.num_layers * cell_ratio; i++){
            p_dec_init_w[i] = model.add_parameters({opts.rnn_size, opts.rnn_size});
        }
        p_dec_init_bias.resize(opts.num_layers * cell_ratio);
        for(unsigned int i = 0; i < opts.num_layers * cell_ratio; i++){
            p_dec_init_bias[i] = model.add_parameters({opts.rnn_size});
        }

        p_Wad = model.add_parameters({opts.att_size, opts.rnn_size * 2});
        p_Uad = model.add_parameters({opts.att_size, opts.rnn_size * 2});
        p_vad = model.add_parameters({opts.att_size});

        p_Wctx = model.add_parameters({opts.rnn_size, opts.rnn_size*5});
        p_bctx = model.add_parameters({opts.rnn_size});

        for(unsigned int d = 0; d < opts.rec_attn_dep.size(); d++){
            p_Wahf.push_back(model.add_parameters({opts.rnn_size, opts.rnn_size * 2}));
        }

        for(unsigned int d = 0; d < opts.rec_attn_dep.size(); d++){
            p_Wahb.push_back(model.add_parameters({opts.rnn_size, opts.rnn_size * 2}));
        }

        if(opts.context_type == "both"){
            p_select_W = model.add_parameters({opts.rnn_size*2, opts.rnn_size*5});
            p_select_b = model.add_parameters({opts.rnn_size*2});
            dec_context_size = opts.rnn_size * 5;
        }else if(opts.context_type == "parent" or opts.context_type == "child"){
            p_select_W = model.add_parameters({opts.rnn_size*2, opts.rnn_size*3});
            p_select_b = model.add_parameters({opts.rnn_size*2});
            dec_context_size = opts.rnn_size * 5;
        }else{
            assert(false);
        }

        if(opts.additional_output_layer == true){
            dec_feeding_size = opts.rnn_size;
            p_add_W = model.add_parameters({dec_feeding_size, dec_context_size});
            p_add_bias = model.add_parameters({dec_feeding_size});
            p_out_R = model.add_parameters({d.dict_set_trg.d_word.size(), dec_feeding_size});
            p_out_bias = model.add_parameters({d.dict_set_trg.d_word.size()});
        }else{
            dec_feeding_size = dec_context_size;
            p_out_R = model.add_parameters({d.dict_set_trg.d_word.size(), dec_feeding_size});
            p_out_bias = model.add_parameters({d.dict_set_trg.d_word.size()});
        }

        dec_builder = dynet::VanillaLSTMBuilder(
            opts.num_layers,
            (rev_enc_builder.input_dim + dec_feeding_size + 3),
            opts.rnn_size,
            model
        );
    }

    encoder_decoder_slahan(const encoder_decoder_slahan&) = delete;
    encoder_decoder_slahan& operator=(const encoder_decoder_slahan&) = delete;
    ~encoder_decoder_slahan() = default;

    void init_feed(const batch &batch_local, dynet::ComputationGraph& cg){
        i_feed = dynet::zeroes(cg, dynet::Dim({p_out_R.dim().d[1]}, batch_local.batch_size()));
    }

    void encoder(const batch &batch_local, dynet::ComputationGraph& cg) {
        // initialize
        init(batch_local, cg);
        // embedding
        embedding(batch_local, cg);
        init_feed(batch_local, cg);
        gate_list.clear();

        std::vector<dynet::Expression> h_fwd(slen);
        std::vector<dynet::Expression> h_bwd(slen);
        h_bi.resize(slen);
        // forward encoder
        fwd_enc_builder.new_graph(cg);
        fwd_enc_builder.start_new_sequence();
        if(flag_drop_out == true && opts.dropout_rate_lstm_word > 0.f){
            fwd_enc_builder.set_dropout_masks(batch_size);
        }
        for (unsigned int t_i = 0; t_i < slen; ++t_i) {
            fwd_enc_builder.add_input(h_lookup[t_i]);
            h_fwd[t_i] = fwd_enc_builder.back();
        }
        // backward encoder
        rev_enc_builder.new_graph(cg);
        rev_enc_builder.start_new_sequence();
        if(flag_drop_out == true && opts.dropout_rate_lstm_word > 0.f){
            rev_enc_builder.set_dropout_masks(batch_size);
        }
        for (unsigned int ind = 0; ind < slen; ++ind) {
            unsigned int t_i = (slen - 1) - ind;
            rev_enc_builder.add_input(h_lookup[t_i]);
            h_bwd[t_i] = rev_enc_builder.back();
        }
        // bidirectional encoding
        h_bi[0] = dynet::zeroes(cg, dynet::Dim({opts.rnn_size * 2}, batch_size));
        for (unsigned int t_i = 1; t_i < slen; ++t_i) {
            h_bi[t_i] = concatenate(std::vector<dynet::Expression>({h_fwd[t_i], h_bwd[t_i]}));
        }
        i_h_enc = concatenate_cols(h_bi);
        i_h_fin = concatenate(std::vector<dynet::Expression>({rev_enc_builder.h.back().back(), fwd_enc_builder.h.back().back()}));
        // Initialize decoder
        std::vector<dynet::Expression> vec_enc_final_state;
        vec_enc_final_state = rev_enc_builder.final_s();
        dec_builder.new_graph(cg);
        if(opts.additional_connect_layer){
            std::vector<dynet::Expression> vec_dec_init_state;
            for (unsigned int i = 0; i < vec_enc_final_state.size(); i++){
                dynet::Expression i_dec_init_w = parameter(cg, p_dec_init_w[i]);
                dynet::Expression i_dec_init_bias = parameter(cg, p_dec_init_bias[i]);
                vec_dec_init_state.push_back(tanh(i_dec_init_w * vec_enc_final_state[i] + i_dec_init_bias));
            }
            dec_builder.start_new_sequence(vec_dec_init_state);
        }else{
            dec_builder.start_new_sequence(vec_enc_final_state);
        }
        if(flag_drop_out == true && opts.dropout_rate_lstm_word > 0.f){
            dec_builder.set_dropout_masks(batch_size);
        }
    }

    std::vector<dynet::Expression> attention(dynet::ComputationGraph& cg, const batch &batch_local){
        if(opts.self_attn_type == "given"){
            return given_attention(cg, batch_local);
        }else if(opts.self_attn_type == "self"){
            return self_attention(cg, batch_local);
        }else{
            std::cerr << "Attention type does not match." << std::endl;
            assert(false);
        }
    }

    std::vector<dynet::Expression> given_attention(dynet::ComputationGraph& cg, const batch &batch_local){
        std::vector<dynet::Expression> h_att_self(slen);
        for(unsigned int child_pos = 0; child_pos < slen; child_pos++){
            std::vector<dynet::real> mask_real_zero_one(batch_size * slen);
            std::vector<dynet::real> mask_real_zero_min_flt_max(batch_size * slen);
            for(unsigned int batch_id = 0; batch_id < batch_size; batch_id++){
                for(unsigned int parent_pos = 0; parent_pos < slen; parent_pos++){
                    unsigned int v_idx = slen * batch_id + parent_pos;
                    if(parent_pos == batch_local.align[child_pos][batch_id]){
                        mask_real_zero_one[v_idx] = 0.0f;
                        mask_real_zero_min_flt_max[v_idx] = 0.0f;
                    }else{
                        mask_real_zero_one[v_idx] = 0.0f;
                        mask_real_zero_min_flt_max[v_idx] = opts.dummy_flt_min;
                    }
                }
            }
            h_att_self[child_pos] = dynet::input(cg, dynet::Dim({slen}, batch_size), mask_real_zero_one);
            h_att_self[child_pos] = h_att_self[child_pos] + dynet::input(cg, dynet::Dim({slen}, batch_size), mask_real_zero_min_flt_max);
        }
        return h_att_self;
    }

    std::vector<dynet::Expression> self_attention(dynet::ComputationGraph& cg, const batch &batch_local){
        std::vector<dynet::Expression> h_att_self(slen);
        dynet::Expression i_vad = parameter(cg, p_vad);
        dynet::Expression i_Wad = parameter(cg, p_Wad);
        dynet::Expression i_Uad = parameter(cg, p_Uad);
        dynet::Expression i_Uadhj = i_Uad * i_h_enc;
        for(unsigned int child_pos = 0; child_pos < slen; child_pos++){
            std::vector<dynet::real> mask_real_zero_one(batch_size * slen);
            std::vector<dynet::real> mask_real_zero_min_flt_max(batch_size * slen);
            for(unsigned int batch_id = 0; batch_id < batch_size; batch_id++){
                for(unsigned int parent_pos = 0; parent_pos < slen; parent_pos++){
                    unsigned int v_idx = slen * batch_id + parent_pos;
                    if(child_pos > 0 && parent_pos == child_pos){
                        mask_real_zero_one[v_idx] = 0.0f;
                        mask_real_zero_min_flt_max[v_idx] = opts.dummy_flt_min;
                    }else if(child_pos > 0 && parent_pos != child_pos){
                        mask_real_zero_one[v_idx] = 1.0f;
                        mask_real_zero_min_flt_max[v_idx] = 0.0f;
                    }else if(child_pos == 0 && parent_pos == child_pos){
                        mask_real_zero_one[v_idx] = 1.0f;
                        mask_real_zero_min_flt_max[v_idx] = 0.0f;
                    }else if(child_pos == 0 && parent_pos != child_pos){
                        mask_real_zero_one[v_idx] = 0.0f;
                        mask_real_zero_min_flt_max[v_idx] = opts.dummy_flt_min;
                    }
                }
            }
            dynet::Expression i_wadh = i_Wad * h_bi[child_pos];
            dynet::Expression i_Wadh = concatenate_cols(std::vector<dynet::Expression>(slen, i_wadh));
            dynet::Expression i_att = transpose(tanh(i_Wadh + i_Uadhj)) * i_vad;
            h_att_self[child_pos] = cmult(i_att, dynet::input(cg, dynet::Dim({slen}, batch_size), mask_real_zero_one));
            h_att_self[child_pos] = h_att_self[child_pos] + dynet::input(cg, dynet::Dim({slen}, batch_size), mask_real_zero_min_flt_max);
        }
        return h_att_self;
    }

    void recursive_attention(dynet::ComputationGraph& cg, std::vector<dynet::Expression>& h_att_self){
        // initialize forward and backward attention at d=0
        f_alpha_d.resize(opts.rec_attn_dep.back());
        b_alpha_d.resize(opts.rec_attn_dep.back());
        f_alpha_d[0].resize(slen);
        b_alpha_d[0].resize(slen);
        std::vector<dynet::Expression> i_f_alpha_d(opts.rec_attn_dep.back());
        for(unsigned int child_pos = 0; child_pos < slen; child_pos++){
            f_alpha_d[0][child_pos] = softmax(h_att_self[child_pos]);
        }
        i_f_alpha_d[0] = concatenate_cols(f_alpha_d[0]);
        for(unsigned int child_pos = 0; child_pos < slen; child_pos++){
            b_alpha_d[0][child_pos] = pick(i_f_alpha_d[0], child_pos, 0);
        }
        // calculate recursive attentions
        for(unsigned int d = 1; d < opts.rec_attn_dep.back(); d++){
            // To avoid the rounding errors
            unsigned int order = d + 1;
            unsigned int d_left = (order / 2 + order % 2) - 1;
            unsigned int d_right = order / 2 - 1;
            i_f_alpha_d[d] = i_f_alpha_d[d_left] * i_f_alpha_d[d_right];
            f_alpha_d[d].resize(slen);
            b_alpha_d[d].resize(slen);
            for(unsigned int child_pos = 0; child_pos < slen; child_pos++){
                f_alpha_d[d][child_pos] = pick(i_f_alpha_d[d], child_pos, 1);
                b_alpha_d[d][child_pos] = pick(i_f_alpha_d[d], child_pos, 0);
            }
        }
    }

    dynet::Expression decoder_output(dynet::ComputationGraph& cg, const unsigned int t, const std::vector<unsigned int>& prev){
        return decoder_output(cg, t, prev, dec_builder.state());
    }

    dynet::Expression decoder_output(dynet::ComputationGraph& cg, const unsigned int t, const std::vector<unsigned int>& prev, const dynet::RNNPointer pointer_prev){
        // t should be always larger than 0.
        assert(t > 0);
        // convert previous label to the bit features.
        std::vector<dynet::real> bit_features;
        const unsigned int start_id = d.dict_set_trg.start_id_word;
        const unsigned int kep_id = d.dict_set_trg.keep_id_word;
        const unsigned int del_id = d.dict_set_trg.delete_id_word;
        for(unsigned int batch_id = 0; batch_id < prev.size(); batch_id++){
            // start
            if(prev[batch_id] == start_id){
                bit_features.push_back(opts.bit_size_flt);
            }else{
                bit_features.push_back(0.0f);
            }
            // keep
            if(prev[batch_id] == kep_id){
                bit_features.push_back(opts.bit_size_flt);
            }else{
                bit_features.push_back(0.0f);
            }
            // delete
            if(prev[batch_id] == del_id){
                bit_features.push_back(opts.bit_size_flt);
            }else{
                bit_features.push_back(0.0f);
            }
        }
        // construct vectors for decoder feeding
        dynet::Expression i_x_t = dynet::input(cg, dynet::Dim({3}, batch_size), bit_features);
        dynet::Expression i_dec_input = concatenate(std::vector<dynet::Expression>({i_x_t, i_feed}));
        i_dec_input = concatenate(std::vector<dynet::Expression>({i_dec_input, h_lookup[t]}));
        // update decoder states
        dec_builder.add_input(pointer_prev, i_dec_input);
        dynet::Expression i_h_dec = dec_builder.h.back().back();
        // create a current state vector
        dynet::Expression i_h_ctx = concatenate(std::vector<dynet::Expression>({i_h_dec, i_h_fin}));
        i_h_ctx = concatenate(std::vector<dynet::Expression>({i_h_ctx, h_bi[t]}));
        dynet::Expression i_Wctx = parameter(cg, p_Wctx);
        dynet::Expression i_bctx = parameter(cg, p_bctx);
        dynet::Expression i_h_ctx_attn = tanh(i_Wctx * i_h_ctx + i_bctx);
        // extract encoder hidden states from a forward attention layer
        dynet::Expression i_f_c_t = forward_attention(cg, t, i_h_ctx_attn);
        // extract encoder hidden states from a backward attention layer
        dynet::Expression i_b_c_t = backward_attention(cg, t, i_h_ctx_attn);
        // balance gate
        dynet::Expression i_c_t = balance_layer(cg, i_f_c_t, i_b_c_t, i_h_ctx_attn);
        // final context
        i_c_t = concatenate(std::vector<dynet::Expression>({h_bi[t], i_c_t}));
        i_c_t = concatenate(std::vector<dynet::Expression>({i_h_dec, i_c_t}));
        // to output layer
        if(opts.additional_output_layer){
            dynet::Expression i_add_W = parameter(cg, p_add_W);
            dynet::Expression i_add_bias = parameter(cg, p_add_bias);
            i_feed = tanh(i_add_W * i_c_t + i_add_bias);
        }else{
            i_feed = i_c_t;
        }
        // output layer
        dynet::Expression i_out_R = parameter(cg, p_out_R);
        dynet::Expression i_out_bias = parameter(cg, p_out_bias);
        dynet::Expression i_out_pred_t = i_out_R * i_feed + i_out_bias;

        return i_out_pred_t;
    }

    // forward attention layer
    dynet::Expression forward_attention(dynet::ComputationGraph& cg, const unsigned int t, dynet::Expression& i_h_ctx_attn){
        std::vector<dynet::Expression> f_c_t(opts.rec_attn_dep.size());
        std::vector<dynet::Expression> h_f(opts.rec_attn_dep.size());
        for(unsigned int d = 0; d < opts.rec_attn_dep.size(); d++){
            f_c_t[d] = i_h_enc * f_alpha_d[opts.rec_attn_dep[d]-1][t];
            h_f[d] = parameter(cg, p_Wahf[d]) * f_c_t[d];
        }
        dynet::Expression i_h_f_a = concatenate_cols(h_f);
        // hierarchical general attention
        dynet::Expression i_f_alpha_t = softmax(transpose(i_h_f_a) * i_h_ctx_attn);
        dynet::Expression i_f_c_t = concatenate_cols(f_c_t);
        i_f_c_t = i_f_c_t * i_f_alpha_t;
        return i_f_c_t;
    }

    // backward attention layer
    dynet::Expression backward_attention(dynet::ComputationGraph& cg, const unsigned int t, dynet::Expression& i_h_ctx_attn){
        std::vector<dynet::Expression> b_c_t(opts.rec_attn_dep.size());
        std::vector<dynet::Expression> h_b(opts.rec_attn_dep.size());
        for(unsigned int d = 0; d < opts.rec_attn_dep.size(); d++){
            // reflect recursive attention weights
            dynet::Expression i_filtered = cmult(transpose(b_alpha_d[opts.rec_attn_dep[d]-1][t]), i_h_enc);
            // general attention
            b_c_t[d] = maxpooling2d(i_filtered, std::vector<unsigned>({1,slen}), std::vector<unsigned>({1,slen}));
            h_b[d] = parameter(cg, p_Wahb[d]) * b_c_t[d];
        }
        dynet::Expression i_h_b_a = concatenate_cols(h_b);
        // hierarchical general attention
        dynet::Expression i_b_alpha_t = softmax(transpose(i_h_b_a) * i_h_ctx_attn);
        // output
        dynet::Expression i_b_c_t = concatenate_cols(b_c_t);
        i_b_c_t = i_b_c_t * i_b_alpha_t;
        return i_b_c_t;
    }

    // balance gate
    dynet::Expression balance_layer(dynet::ComputationGraph& cg, dynet::Expression& i_f_c_t, dynet::Expression& i_b_c_t, dynet::Expression& i_h_ctx_attn){
        dynet::Expression i_c_t;
        dynet::Expression i_select_W = parameter(cg, p_select_W);
        dynet::Expression i_select_b = parameter(cg, p_select_b);
        if(opts.context_type == "both"){
            dynet::Expression i_fb_c_t = concatenate(std::vector<dynet::Expression>({i_f_c_t, i_b_c_t}));
            if(opts.selective_gate == true){
                dynet::Expression i_fb = dynet::affine_transform({i_select_b, i_select_W, concatenate(std::vector<dynet::Expression>({i_fb_c_t, i_h_ctx_attn}))});
                dynet::Expression i_s = logistic(i_fb);
                gate_list.push_back(i_s);
                i_c_t = cmult(i_s, i_f_c_t) + cmult(1.f - i_s, i_b_c_t);
            }else{
                i_c_t = (i_f_c_t + i_b_c_t) / 2.f;
            }
        }else if(opts.context_type == "parent"){
            if(opts.selective_gate == true){
                dynet::Expression i_f = dynet::affine_transform({i_select_b, i_select_W, concatenate(std::vector<dynet::Expression>({i_f_c_t, i_h_ctx_attn}))});
                dynet::Expression i_s = logistic(i_f);
                gate_list.push_back(i_s);
                i_c_t = cmult(i_s, i_f_c_t);
            }else{
                i_c_t = i_f_c_t;
            }
        }else if(opts.context_type == "child"){
            if(opts.selective_gate == true){
                dynet::Expression i_b = dynet::affine_transform({i_select_b, i_select_W, concatenate(std::vector<dynet::Expression>({i_b_c_t, i_h_ctx_attn}))});
                dynet::Expression i_s = logistic(i_b);
                gate_list.push_back(i_s);
                i_c_t = cmult(i_s, i_b_c_t);
            }else{
                i_c_t = i_b_c_t;
            }
        }else{
            assert(false);
        }
        return i_c_t;
    }

    void disable_dropout(){
        fwd_char_enc_builder.disable_dropout();
        rev_char_enc_builder.disable_dropout();
        fwd_enc_builder.disable_dropout();
        rev_enc_builder.disable_dropout();
        dec_builder.disable_dropout();
        flag_drop_out = false;
    }

    void enable_dropout(){
        fwd_char_enc_builder.set_dropout(opts.dropout_rate_lstm_char, 0.f);
        rev_char_enc_builder.set_dropout(opts.dropout_rate_lstm_char, 0.f);
        fwd_enc_builder.set_dropout(opts.dropout_rate_lstm_word, 0.f);
        rev_enc_builder.set_dropout(opts.dropout_rate_lstm_word, 0.f);
        dec_builder.set_dropout(opts.dropout_rate_lstm_word, 0.f);
        flag_drop_out = true;
    }

private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version) {
        ar & p_word_enc;
        ar & p_char_enc;
        ar & p_feat_enc;
        ar & p_dec_init_bias;
        ar & p_dec_init_w;
        ar & p_select_W;
        ar & p_select_b;
        ar & p_Wad;
        ar & p_Uad;
        ar & p_vad;
        ar & p_Wctx;
        ar & p_bctx;
        ar & p_Wahf;
        ar & p_Wahb;
        ar & p_layer_w;
        ar & p_add_W;
        ar & p_add_bias;
        ar & p_out_R;
        ar & p_out_bias;
        ar & dec_builder;
        ar & rev_char_enc_builder;
        ar & fwd_char_enc_builder;
        ar & rev_enc_builder;
        ar & fwd_enc_builder;
    }

};

}

#endif