//
//  fpt.hpp
//  indigox
//
//  Created by Welsh, Ivan on 4/12/17.
//  Copyright © 2017 Allison Group. All rights reserved.
//

#ifndef INDIGOX_FORMALBONDS_FPT_ALGO_HPP
#define INDIGOX_FORMALBONDS_FPT_ALGO_HPP

#include <map>
#include <vector>

#include <boost/dynamic_bitset.hpp>
#include "../../api.hpp"
#include "../../classes/molecular_graph.hpp"
#include "../../classes/nicetreedecomp.hpp"

#include "electron_optimisation_algorithm.hpp"

namespace indigox {
  namespace algorithm {
    struct TDVertScore;

    typedef boost::dynamic_bitset<> VertMask;
    typedef VertMask ForgetMask;
    typedef VertMask BagMask;
    typedef std::multimap<Score, ForgetMask> MaskScores;
    typedef std::map<BagMask, MaskScores> BagScores;
    typedef std::map<uint32_t, BagScores> ProperScoreMatrix;

    class FPTOptimisation : public ElectronOptimisationAlgorithm {
      friend struct TDVertScore;
    private:
      FPTOptimisation() = default;

    public:
      FPTOptimisation(ElectronOpt* parent);

    public:
      void Run() override;

    private:
      void Initalise();
      void DetermineMinMax();
      void PopulateReferenceVectors();
      Score ScoreVertex(MolVertPair v, VertMask mask);
      ElnDist VertMaskToElnDist(const VertMask& m);

    private:
      NTDecomp_p td_;
      std::map<NTDVertex, std::shared_ptr<TDVertScore>> scorematrices_;
      std::vector<MolVertPair> possibleStates_;
      std::map<MolVertPair, VertMask> pairMasks_;
      VertMask placedMask_;
    };

    struct TDVertScore {
      FPTOptimisation* parent;
      NTDVertProp* tdProperties;
      ProperScoreMatrix score;
      uint32_t min_e, max_e;
      VertMask fMask;

      TDVertScore(FPTOptimisation* p, NTDVertProp* prop)
      : parent(p), tdProperties(prop) {}

      void PopulateScoreMatrix();
      VertMask IntroduceCountToMask(MolVertPair, size_t);
      void LeafPropagate();
      void ForgetPropagate(std::shared_ptr<TDVertScore> a);
      void IntroducePropagate(std::shared_ptr<TDVertScore> a);
      void JoinPropagate(std::shared_ptr<TDVertScore> a, std::shared_ptr<TDVertScore> b);

      String VertMaskToNiceString(VertMask);
      String ToString();
      String KindToString();
    };

  }
}

#endif /* INDIGOX_FORMALBONDS_FPT_ALGO_HPP */


// Copyright (c) 2017-2018 The PIVX developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "test_veil.h"
#include "libzerocoin/Denominations.h"
#include "amount.h"
#include "chainparams.h"
#include "consensus/params.h"
#include "wallet/coincontrol.h"
//nclude "libzerocoin/ZerocoinDefines.h"
//#include "main.h"
#include "wallet/wallet.h"
#include "wallet/walletdb.h"
#include "txdb.h"
#include <boost/test/unit_test.hpp>
#include <iostream>
#include "libzerocoin/ParamGeneration.h"
#include "libzerocoin/Denominations.h"
#include "libzerocoin/Coin.h"

using namespace libzerocoin;
/*
CBigNum GetTestModulus()
{
    static CBigNum testModulus(0);

    // TODO: should use a hard-coded RSA modulus for testing
    if (!testModulus) {
        CBigNum p, q;

        // Note: we are NOT using safe primes for testing because
        // they take too long to generate. Don't do this in real
        // usage. See the paramgen utility for better code.
        p = CBigNum::generatePrime(1024, false);
        q = CBigNum::generatePrime(1024, false);
        testModulus = p * q;
    }

    return testModulus;
}*/
BOOST_FIXTURE_TEST_SUITE(zerocoin_transaction, BasicTestingSetup)

//static CWallet cWallet("unlocked.dat");

BOOST_AUTO_TEST_CASE(spend_nsequence)
{
    CTxIn in;
    in.nSequence = libzerocoin::CoinDenomination::ZQ_TEN;
    in.nSequence |= CTxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG;

    CAmount nAmount = (in.nSequence & CTxIn::SEQUENCE_LOCKTIME_MASK);
    BOOST_CHECK_MESSAGE(nAmount == 10, "nSequence did not properly decode for 10");

    in.nSequence = libzerocoin::CoinDenomination::ZQ_ONE_HUNDRED;
    in.nSequence |= CTxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG;
    nAmount = (in.nSequence & CTxIn::SEQUENCE_LOCKTIME_MASK);
    BOOST_CHECK_MESSAGE(nAmount == 100, "nSequence did not properly decode for 100");

    in.nSequence = libzerocoin::CoinDenomination::ZQ_ONE_THOUSAND;
    in.nSequence |= CTxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG;
    nAmount = (in.nSequence & CTxIn::SEQUENCE_LOCKTIME_MASK);
    BOOST_CHECK_MESSAGE(nAmount == 1000, "nSequence did not properly decode for 1000");

    in.nSequence = libzerocoin::CoinDenomination::ZQ_TEN_THOUSAND;
    in.nSequence |= CTxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG;
    nAmount = (in.nSequence & CTxIn::SEQUENCE_LOCKTIME_MASK);
    BOOST_CHECK_MESSAGE(nAmount == 10000, "nSequence did not properly decode for 10000");
}

BOOST_AUTO_TEST_CASE(zerocoin_spend_test)
{

/*
    SelectParams(CBaseChainParams::MAIN);
    ZerocoinParams *ZCParams = new ZerocoinParams(GetTestModulus());
    (void)ZCParams;

    //cWallet.zpivTracker = unique_ptr<CzPIVTracker>(new CzPIVTracker(cWallet.strWalletFile));
    CMutableTransaction tx;
    CWalletTx* wtx = new CWalletTx(&cWallet, tx);
    bool fMintChange=true;
    bool fMinimizeChange=true;
    std::vector<CZerocoinSpend> vSpends;
    std::vector<CZerocoinMint> vMints;
    CAmount nAmount = COIN;
    int nSecurityLevel = 100;

    CZerocoinSpendReceipt receipt;
    cWallet.SpendZerocoin(nAmount, nSecurityLevel, *wtx, receipt, vMints, fMintChange, fMinimizeChange);

    BOOST_CHECK_MESSAGE(receipt.GetStatus() == ZPIV_TRX_FUNDS_PROBLEMS, "Failed Invalid Amount Check");

    nAmount = 1;
    CZerocoinSpendReceipt receipt2;
    cWallet.SpendZerocoin(nAmount, nSecurityLevel, *wtx, receipt2, vMints, fMintChange, fMinimizeChange);

    // if using "wallet.dat", instead of "unlocked.dat" need this
    /// BOOST_CHECK_MESSAGE(vString == "Error: Wallet locked, unable to create transaction!"," Locked Wallet Check Failed");

    BOOST_CHECK_MESSAGE(receipt2.GetStatus() == ZPIV_TRX_FUNDS_PROBLEMS, "Failed Invalid Amount Check");
*/
}

BOOST_AUTO_TEST_SUITE_END()


#ifndef STAN_MCMC_HMC_NUTS_BASE_XHMC_HPP
#define STAN_MCMC_HMC_NUTS_BASE_XHMC_HPP

#include <stan/callbacks/logger.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <stan/mcmc/hmc/base_hmc.hpp>
#include <stan/mcmc/hmc/hamiltonians/ps_point.hpp>
#include <algorithm>
#include <cmath>
#include <limits>
#include <string>
#include <vector>
#include <utility>

namespace stan {
namespace mcmc {

/**
 * Exhaustive Hamiltonian Monte Carlo (XHMC) with multinomial sampling.
 * See http://arxiv.org/abs/1601.00225.
 */
template <class Model, template <class, class> class Hamiltonian,
          template <class> class Integrator, class BaseRNG>
class base_xhmc : public base_hmc<Model, Hamiltonian, Integrator, BaseRNG> {
 public:
  base_xhmc(const Model& model, BaseRNG& rng)
      : base_hmc<Model, Hamiltonian, Integrator, BaseRNG>(model, rng),
        depth_(0),
        max_depth_(5),
        max_deltaH_(1000),
        x_delta_(0.1),
        n_leapfrog_(0),
        divergent_(0),
        energy_(0) {}

  ~base_xhmc() {}

  void set_max_depth(int d) {
    if (d > 0)
      max_depth_ = d;
  }

  void set_max_deltaH(double d) { max_deltaH_ = d; }

  void set_x_delta(double d) {
    if (d > 0)
      x_delta_ = d;
  }

  int get_max_depth() { return this->max_depth_; }
  double get_max_deltaH() { return this->max_deltaH_; }
  double get_x_delta() { return this->x_delta_; }

  sample transition(sample& init_sample, callbacks::logger& logger) {
    // Initialize the algorithm
    this->sample_stepsize();

    this->seed(init_sample.cont_params());

    this->hamiltonian_.sample_p(this->z_, this->rand_int_);
    this->hamiltonian_.init(this->z_, logger);

    ps_point z_plus(this->z_);
    ps_point z_minus(z_plus);

    ps_point z_sample(z_plus);
    ps_point z_propose(z_plus);

    double ave = this->hamiltonian_.dG_dt(this->z_, logger);
    double log_sum_weight = 0;  // log(exp(H0 - H0))

    double H0 = this->hamiltonian_.H(this->z_);
    int n_leapfrog = 0;
    double sum_metro_prob = 1;  // exp(H0 - H0)

    // Build a trajectory until the NUTS criterion is no longer satisfied
    this->depth_ = 0;
    this->divergent_ = 0;

    while (this->depth_ < this->max_depth_) {
      // Build a new subtree in a random direction
      bool valid_subtree = false;
      double ave_subtree = 0;
      double log_sum_weight_subtree = -std::numeric_limits<double>::infinity();

      if (this->rand_uniform_() > 0.5) {
        this->z_.ps_point::operator=(z_plus);
        valid_subtree = build_tree(this->depth_, z_propose, ave_subtree,
                                   log_sum_weight_subtree, H0, 1, n_leapfrog,
                                   sum_metro_prob, logger);
        z_plus.ps_point::operator=(this->z_);
      } else {
        this->z_.ps_point::operator=(z_minus);
        valid_subtree = build_tree(this->depth_, z_propose, ave_subtree,
                                   log_sum_weight_subtree, H0, -1, n_leapfrog,
                                   sum_metro_prob, logger);
        z_minus.ps_point::operator=(this->z_);
      }

      if (!valid_subtree)
        break;
      std::tie(ave, log_sum_weight) = stable_sum(
          ave, log_sum_weight, ave_subtree, log_sum_weight_subtree);

      // Sample from an accepted subtree
      ++(this->depth_);

      double accept_prob = std::exp(log_sum_weight_subtree - log_sum_weight);
      if (this->rand_uniform_() < accept_prob)
        z_sample = z_propose;

      // Break if exhaustion criterion is satisfied
      if (std::fabs(ave) < x_delta_)
        break;
    }

    this->n_leapfrog_ = n_leapfrog;

    // Compute average acceptance probabilty across entire trajectory,
    // even over subtrees that may have been rejected
    double accept_prob = sum_metro_prob / static_cast<double>(n_leapfrog + 1);

    this->z_.ps_point::operator=(z_sample);
    this->energy_ = this->hamiltonian_.H(this->z_);
    return sample(this->z_.q, -this->z_.V, accept_prob);
  }

  void get_sampler_param_names(std::vector<std::string>& names) {
    names.push_back("stepsize__");
    names.push_back("treedepth__");
    names.push_back("n_leapfrog__");
    names.push_back("divergent__");
    names.push_back("energy__");
  }

  void get_sampler_params(std::vector<double>& values) {
    values.push_back(this->epsilon_);
    values.push_back(this->depth_);
    values.push_back(this->n_leapfrog_);
    values.push_back(this->divergent_);
    values.push_back(this->energy_);
  }

  /**
   * Recursively build a new subtree to completion or until
   * the subtree becomes invalid.  Returns validity of the
   * resulting subtree.
   *
   * @param depth Depth of the desired subtree
   * @param z_propose State proposed from subtree
   * @param ave Weighted average of dG/dt across trajectory
   * @param log_sum_weight Log of summed weights across trajectory
   * @param H0 Hamiltonian of initial state
   * @param sign Direction in time to built subtree
   * @param n_leapfrog Summed number of leapfrog evaluations
   * @param sum_metro_prob Summed Metropolis probabilities across trajectory
   * @param logger Logger for messages
   * @return whether built tree is valid
   */
  bool build_tree(int depth, ps_point& z_propose, double& ave,
                  double& log_sum_weight, double H0, double sign,
                  int& n_leapfrog, double& sum_metro_prob,
                  callbacks::logger& logger) {
    // Base case
    if (depth == 0) {
      this->integrator_.evolve(this->z_, this->hamiltonian_,
                               sign * this->epsilon_, logger);
      ++n_leapfrog;

      double h = this->hamiltonian_.H(this->z_);
      if (boost::math::isnan(h))
        h = std::numeric_limits<double>::infinity();

      if ((h - H0) > this->max_deltaH_)
        this->divergent_ = true;

      double dG_dt = this->hamiltonian_.dG_dt(this->z_, logger);

      std::tie(ave, log_sum_weight)
          = stable_sum(ave, log_sum_weight, dG_dt, H0 - h);

      if (H0 - h > 0)
        sum_metro_prob += 1;
      else
        sum_metro_prob += std::exp(H0 - h);

      z_propose = this->z_;

      return !this->divergent_;
    }
    // General recursion

    // Build the left subtree
    double ave_left = 0;
    double log_sum_weight_left = -std::numeric_limits<double>::infinity();

    bool valid_left
        = build_tree(depth - 1, z_propose, ave_left, log_sum_weight_left, H0,
                     sign, n_leapfrog, sum_metro_prob, logger);

    if (!valid_left)
      return false;
    std::tie(ave, log_sum_weight)
        = stable_sum(ave, log_sum_weight, ave_left, log_sum_weight_left);

    // Build the right subtree
    ps_point z_propose_right(this->z_);
    double ave_right = 0;
    double log_sum_weight_right = -std::numeric_limits<double>::infinity();

    bool valid_right = build_tree(depth - 1, z_propose_right, ave_right,
                                  log_sum_weight_right, H0, sign, n_leapfrog,
                                  sum_metro_prob, logger);

    if (!valid_right)
      return false;
    std::tie(ave, log_sum_weight)
        = stable_sum(ave, log_sum_weight, ave_right, log_sum_weight_right);

    // Multinomial sample from right subtree
    double ave_subtree;
    double log_sum_weight_subtree;
    std::tie(ave_subtree, log_sum_weight_subtree) = stable_sum(
        ave_left, log_sum_weight_left, ave_right, log_sum_weight_right);

    double accept_prob
        = std::exp(log_sum_weight_right - log_sum_weight_subtree);
    if (this->rand_uniform_() < accept_prob)
      z_propose = z_propose_right;

    return std::abs(ave_subtree) >= x_delta_;
  }

  /**
   * a1 and a2 are running averages of the form
   *   \f$ a1 =   ( \sum_{n \in N1} w_{n} f_{n} )
   *            / ( \sum_{n \in N1}  w_{n} ) \f$
   *   \f$ a2 =   ( \sum_{n \in N2} w_{n} f_{n} )
   *            / ( \sum_{n \in N2}  w_{n} ) \f$
   * and the weights are the respective normalizing constants
   *   \f$ w1 = \sum_{n \in N1} w_{n} \f$
   *   \f$ w2 = \sum_{n \in N2} w_{n}. \f$
   *
   * This function returns the pooled average
   *   \f$ sum_a =   ( \sum_{n \in N1 \cup N2} w_{n} f_{n} )
   *               / ( \sum_{n \in N1 \cup N2}  w_{n} ) \f$
   * and the pooled weights
   *   \f$ log_sum_w = log(w1 + w2). \f$
   *
   * @param[in] a1 First running average, f1 / w1
   * @param[in] log_w1 Log of first summed weight
   * @param[in] a2 Second running average
   * @param[in] log_w2 Log of second summed weight
   * @return Pair of average of input running averages and log of summed input
   * weights
   */
  static std::pair<double, double> stable_sum(double a1, double log_w1,
                                              double a2, double log_w2) {
    if (log_w2 > log_w1) {
      const double e = std::exp(log_w1 - log_w2);
      return std::make_pair((e * a1 + a2) / (1 + e), log_w2 + std::log1p(e));
    } else {
      const double e = std::exp(log_w2 - log_w1);
      return std::make_pair((a1 + e * a2) / (1 + e), log_w1 + std::log1p(e));
    }
  }

  int depth_;
  int max_depth_;
  double max_deltaH_;
  double x_delta_;

  int n_leapfrog_;
  bool divergent_;
  double energy_;
};

}  // namespace mcmc
}  // namespace stan
#endif


// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "opencv2/slideio/imagetools.hpp"
#include "opencv2/slideio.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/slideio/memory_stream.hpp"

#include <openjpeg.h>

#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <fstream>

using namespace cv;

void slideio::ImageTools::readJp2KFile(const std::string& filePath, cv::OutputArray output)
{
    auto fileSize = boost::filesystem::file_size(filePath);
    if(fileSize<=0)
        throw std::runtime_error(
            (boost::format("Invalid file: %1%") % filePath).str());

    std::ifstream file(filePath, std::ios::binary);
    // Stop eating new lines in binary mode!!!
    file.unsetf(std::ios::skipws);
    // reserve capacity
    std::vector<uint8_t> vec;
    vec.reserve(fileSize);
    // read the data:
    vec.insert(vec.begin(),
        std::istream_iterator<uint8_t>(file),
        std::istream_iterator<uint8_t>());
    decodeJp2KStream(vec, output);
}

static int getComponentDataType(const opj_image_comp_t* comp)
{
    switch(comp->prec)
    {
    case 8:
        return ((comp->sgnd)?CV_8S:CV_8U);
    case 16:
        return ((comp->sgnd)?CV_16S:CV_16U);
    case 32:
        return CV_32S;
    }
    throw std::runtime_error(
        (boost::format("Unknown data type of data: %1%") % (int)comp->bpp).str());
}

static OPJ_CODEC_FORMAT getJP2KCodec(const std::vector<uint8_t>& data)
{
    static const unsigned char jpc_header[] = {0xff, 0x4f};
    static const unsigned char jp2_box_jp[] = {0x6a, 0x50, 0x20, 0x20}; /* 'jP  ' */

    const uint8_t *buf = data.data();
    size_t len = data.size();

    OPJ_CODEC_FORMAT eCodecFormat;
    if (len >= sizeof(jpc_header) &&
            memcmp(buf, jpc_header, sizeof(jpc_header)) == 0) {
        eCodecFormat = OPJ_CODEC_J2K;
    }
    else if (len >= 4 + sizeof(jp2_box_jp) &&
               memcmp(buf + 4, jp2_box_jp, sizeof(jp2_box_jp)) == 0) {
        eCodecFormat = OPJ_CODEC_JP2;
    }
    else
    {
        throw std::runtime_error("Unknown file format");
    }
    return eCodecFormat;
}

void slideio::ImageTools::decodeJp2KStream(
    const std::vector<uint8_t>& data,
    cv::OutputArray output,
    const std::vector<int>& channelIndices,
    bool forceYUV)
{
    opj_codec_t* codec(nullptr);
    opj_image_t* image(nullptr);
    opj_stream_t* stream(nullptr);
    try
    {
        OPJ_CODEC_FORMAT codecId = getJP2KCodec(data);
        OPJStreamUserData userData((uint8_t*)data.data(), data.size());
        stream = createOPJMemoryStream(&userData, data.size(), true);
        codec = opj_create_decompress(codecId);
        if(!codec)
            throw std::runtime_error("Cannot get required codec");
        opj_dparameters_t jp2dParams;
        opj_set_default_decoder_parameters(&jp2dParams);
        if (!opj_setup_decoder(codec, &jp2dParams)){
            throw std::runtime_error("Cannot setup codec");
        }
        if(!opj_read_header(stream, codec, &image) || (image->numcomps == 0)){
            throw std::runtime_error("Error reading image header");
        }
        if(forceYUV)
            image->color_space = OPJ_CLRSPC_SYCC;
        // decode the image
        OPJ_BOOL ret = opj_decode(codec, stream, image);
        if(!ret)
            throw std::runtime_error("Error by decoding of Jp2K stream");

        opj_end_decompress(codec,stream);
        opj_destroy_codec(codec);
        codec=nullptr;
        opj_stream_destroy(stream);
        stream = nullptr;

        const OPJ_UINT32 imageWidth = image->x1 - image->x0;
        const OPJ_UINT32 imageHeight = image->y1 - image->y0;
        const OPJ_UINT32 numComps = image->numcomps;
        const int dt = getComponentDataType(image->comps);

        output.create(imageHeight, imageWidth,CV_MAKETYPE(dt, image->numcomps));
        const cv::Size imageSize(imageWidth, imageHeight);

        std::vector<cv::Mat> imagePlanes;
        std::vector<int> channels(channelIndices);

        for(OPJ_UINT32 channel=0; channel<numComps; channel++)
        {
            const opj_image_comp& component = image->comps[channel];
            // create a cv::Mat object with the buffer
            cv::Mat compRaster32S(component.h, component.w, CV_MAKETYPE(CV_32S, 1), component.data);
            // convert raster from 32 bit integer to the original type
            cv::Mat compRaster;
            compRaster32S.convertTo(compRaster, CV_MAKETYPE(dt,1));
            // check if we need to resize the component
            if(component.w!=imageWidth || component.h!=imageHeight)
            {
                // resize the component so it fits to the image size
                cv::Mat resized(imageSize.height, imageSize.width, CV_MAKETYPE(dt, 1));
                cv::resize(compRaster, resized, imageSize);
                imagePlanes.push_back(resized);
            }
            else
            {
                imagePlanes.push_back(compRaster);
            }
        }
        cv::Mat targetImage;
        if(forceYUV)
        {
            cv::Mat cvImage;
            cv::merge(imagePlanes, cvImage);
            cv::cvtColor(cvImage, targetImage, cv::COLOR_YUV2RGB);
        }
        else
        {
            cv::merge(imagePlanes, targetImage);
        }
        if(channels.empty())
        {
            // if no channel is defined - return all channels
            targetImage.copyTo(output);
        }
        else
        {
            std::vector<cv::Mat> targetChannels;
            for(const int& channel : channels)
            {
                cv::Mat channelRaster;
                cv::extractChannel(targetImage,channelRaster, channel);
                targetChannels.push_back(channelRaster);
            }
            if(targetChannels.size()==1)
            {
                targetChannels[0].copyTo(output);
            }
            else
            {
                cv::merge(targetChannels, output);
            }
        }
        opj_image_destroy(image);
    }
    catch(std::exception& ex)
    {
        if(codec)
            opj_destroy_codec(codec);
        if(image)
            opj_image_destroy(image);
        if(stream)
            opj_stream_destroy(stream);
        throw ex;
    }
}


#include "storm/abstraction/jani/EdgeAbstractor.h"

#include <chrono>

#include <boost/iterator/transform_iterator.hpp>

#include "storm/abstraction/BottomStateResult.h"
#include "storm/abstraction/AbstractionInformation.h"

#include "storm/storage/dd/DdManager.h"
#include "storm/storage/dd/Add.h"

#include "storm/storage/jani/Edge.h"
#include "storm/storage/jani/EdgeDestination.h"

#include "storm/utility/solver.h"
#include "storm/utility/macros.h"

#include "storm-config.h"
#include "storm/adapters/RationalFunctionAdapter.h"

namespace storm {
    namespace abstraction {
        namespace jani {
            template <storm::dd::DdType DdType, typename ValueType>
            EdgeAbstractor<DdType, ValueType>::EdgeAbstractor(uint64_t edgeId, storm::jani::Edge const& edge, AbstractionInformation<DdType>& abstractionInformation, std::shared_ptr<storm::utility::solver::SmtSolverFactory> const& smtSolverFactory, bool useDecomposition, bool addPredicatesForValidBlocks, bool debug) : smtSolver(smtSolverFactory->create(abstractionInformation.getExpressionManager())), abstractionInformation(abstractionInformation), edgeId(edgeId), edge(edge), localExpressionInformation(abstractionInformation), evaluator(abstractionInformation.getExpressionManager()), relevantPredicatesAndVariables(), cachedDd(abstractionInformation.getDdManager().getBddZero(), 0), decisionVariables(), useDecomposition(useDecomposition), addPredicatesForValidBlocks(addPredicatesForValidBlocks), skipBottomStates(false), forceRecomputation(true), abstractGuard(abstractionInformation.getDdManager().getBddZero()), bottomStateAbstractor(abstractionInformation, {!edge.getGuard()}, smtSolverFactory), debug(debug) {

                // Make the second component of relevant predicates have the right size.
                relevantPredicatesAndVariables.second.resize(edge.getNumberOfDestinations());

                // Assert all constraints to enforce legal variable values.
                for (auto const& constraint : abstractionInformation.getConstraints()) {
                    smtSolver->add(constraint);
                    bottomStateAbstractor.constrain(constraint);
                }

                // Assert the guard of the command.
                smtSolver->add(edge.getGuard());

                // Construct assigned variables.
                for (auto const& destination : edge.getDestinations()) {
                    for (auto const& assignment : destination.getOrderedAssignments()) {
                        assignedVariables.insert(assignment.getExpressionVariable());
                    }
                }

                // Log whether or not predicates are added to ensure valid blocks.
                if (this->addPredicatesForValidBlocks) {
                    STORM_LOG_DEBUG("Adding more predicates to ensure valid blocks.");
                }
            }

            template <storm::dd::DdType DdType, typename ValueType>
            void EdgeAbstractor<DdType, ValueType>::refine(std::vector<uint_fast64_t> const& predicates) {
                // Add all predicates to the variable partition.
                for (auto predicateIndex : predicates) {
                    localExpressionInformation.addExpression(predicateIndex);
                }

                // Next, we check whether there is work to be done by recomputing the relevant predicates and checking
                // whether they changed.
                std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> newRelevantPredicates = this->computeRelevantPredicates();

                // Check whether we need to recompute the abstraction.
                bool relevantPredicatesChanged = this->relevantPredicatesChanged(newRelevantPredicates);
                if (relevantPredicatesChanged) {
                    addMissingPredicates(newRelevantPredicates);
                }
                forceRecomputation |= relevantPredicatesChanged;

                // Refine bottom state abstractor. Note that this does not trigger a recomputation yet.
                bottomStateAbstractor.refine(predicates);
            }

            template <storm::dd::DdType DdType, typename ValueType>
            storm::expressions::Expression const& EdgeAbstractor<DdType, ValueType>::getGuard() const {
                return edge.get().getGuard();
            }

            template <storm::dd::DdType DdType, typename ValueType>
            uint64_t EdgeAbstractor<DdType, ValueType>::getNumberOfUpdates(uint64_t player1Choice) const {
                return edge.get().getNumberOfDestinations();
            }

            template <storm::dd::DdType DdType, typename ValueType>
            std::map<storm::expressions::Variable, storm::expressions::Expression> EdgeAbstractor<DdType, ValueType>::getVariableUpdates(uint64_t auxiliaryChoice) const {
                return edge.get().getDestination(auxiliaryChoice).getAsVariableToExpressionMap();
            }

            template <storm::dd::DdType DdType, typename ValueType>
            std::set<storm::expressions::Variable> const& EdgeAbstractor<DdType, ValueType>::getAssignedVariables() const {
                return assignedVariables;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            void EdgeAbstractor<DdType, ValueType>::recomputeCachedBdd() {
                if (useDecomposition) {
                    recomputeCachedBddWithDecomposition();
                } else {
                    recomputeCachedBddWithoutDecomposition();
                }
            }

            template <storm::dd::DdType DdType, typename ValueType>
            void EdgeAbstractor<DdType, ValueType>::recomputeCachedBddWithDecomposition() {
                STORM_LOG_TRACE("Recomputing BDD for edge with id " << edgeId << " and guard " << edge.get().getGuard() << " using the decomposition.");
                auto start = std::chrono::high_resolution_clock::now();

                // compute a decomposition of the command
                //  * start with all relevant blocks: blocks of assignment variables and variables in the rhs of assignments
                //  * go through all assignments of all updates and merge relevant blocks that are related via an assignment
                //  * repeat this until nothing changes anymore
                //  * the resulting blocks are the decomposition

                // Start by constructing the relevant blocks.
                std::set<uint64_t> allRelevantBlocks;
                std::map<storm::expressions::Variable, uint64_t> variableToBlockIndex;
                for (auto const& destination : edge.get().getDestinations()) {
                    for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) {
                        allRelevantBlocks.insert(localExpressionInformation.getBlockIndexOfVariable(assignment.getExpressionVariable()));

                        auto rhsVariableBlocks = localExpressionInformation.getBlockIndicesOfVariables(assignment.getAssignedExpression().getVariables());
                        allRelevantBlocks.insert(rhsVariableBlocks.begin(), rhsVariableBlocks.end());
                    }
                }
                STORM_LOG_TRACE("Found " << allRelevantBlocks.size() << " relevant block(s).");

                // Create a block partition.
                std::vector<std::set<uint64_t>> relevantBlockPartition;
                std::map<storm::expressions::Variable, uint64_t> variableToLocalBlockIndex;
                uint64_t index = 0;
                for (auto const& blockIndex : allRelevantBlocks) {
                    relevantBlockPartition.emplace_back(std::set<uint64_t>({blockIndex}));
                    for (auto const& variable : localExpressionInformation.getVariableBlockWithIndex(blockIndex)) {
                        variableToLocalBlockIndex[variable] = index;
                    }
                    ++index;
                }

                // Merge all blocks that are related via the right-hand side of assignments.
                for (auto const& destination : edge.get().getDestinations()) {
                    for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) {
                        std::set<storm::expressions::Variable> rhsVariables = assignment.getAssignedExpression().getVariables();

                        if (!rhsVariables.empty()) {
                            uint64_t blockToKeep = variableToLocalBlockIndex.at(*rhsVariables.begin());
                            for (auto const& variable : rhsVariables) {
                                uint64_t block = variableToLocalBlockIndex.at(variable);
                                if (block != blockToKeep) {
                                    for (auto const& blockIndex : relevantBlockPartition[block]) {
                                        for (auto const& variable : localExpressionInformation.getVariableBlockWithIndex(blockIndex)) {
                                            variableToLocalBlockIndex[variable] = blockToKeep;
                                        }
                                    }
                                    relevantBlockPartition[blockToKeep].insert(relevantBlockPartition[block].begin(), relevantBlockPartition[block].end());
                                    relevantBlockPartition[block].clear();
                                }
                            }
                        }
                    }
                }

                // Proceed by relating the blocks via assignment-variables and the expressions of their assigned expressions.
                bool changed = false;
                do {
                    changed = false;
                    for (auto const& destination : edge.get().getDestinations()) {
                        for (auto const& assignment : destination.getOrderedAssignments().getAllAssignments()) {
                            std::set<storm::expressions::Variable> rhsVariables = assignment.getAssignedExpression().getVariables();

                            if (!rhsVariables.empty()) {
                                storm::expressions::Variable const& representativeVariable = *rhsVariables.begin();
                                uint64_t representativeBlock = variableToLocalBlockIndex.at(representativeVariable);
                                uint64_t assignmentVariableBlock = variableToLocalBlockIndex.at(assignment.getExpressionVariable());

                                // If the blocks are different, we merge them now
                                if (assignmentVariableBlock != representativeBlock) {
                                    changed = true;

                                    for (auto const& blockIndex : relevantBlockPartition[assignmentVariableBlock]) {
                                        for (auto const& variable : localExpressionInformation.getVariableBlockWithIndex(blockIndex)) {
                                            variableToLocalBlockIndex[variable] = representativeBlock;
                                        }
                                    }
                                    relevantBlockPartition[representativeBlock].insert(relevantBlockPartition[assignmentVariableBlock].begin(), relevantBlockPartition[assignmentVariableBlock].end());
                                    relevantBlockPartition[assignmentVariableBlock].clear();
                                }
                            }
                        }
                    }
                } while (changed);

                // Now remove all blocks that are empty and obtain the partition.
                std::vector<std::set<uint64_t>> cleanedRelevantBlockPartition;
                for (auto& outerBlock : relevantBlockPartition) {
                    if (!outerBlock.empty()) {
                        cleanedRelevantBlockPartition.emplace_back();

                        for (auto const& innerBlock : outerBlock) {
                            if (!localExpressionInformation.getExpressionBlock(innerBlock).empty()) {
                                cleanedRelevantBlockPartition.back().insert(innerBlock);
                            }
                        }

                        if (cleanedRelevantBlockPartition.back().empty()) {
                            cleanedRelevantBlockPartition.pop_back();
                        }
                    }
                }
                relevantBlockPartition = std::move(cleanedRelevantBlockPartition);

                STORM_LOG_TRACE("Decomposition into " << relevantBlockPartition.size() << " blocks.");
                if (this->debug) {
                    uint64_t blockIndex = 0;
                    for (auto const& block : relevantBlockPartition) {
                        STORM_LOG_TRACE("Predicates of block " << blockIndex << ":");
                        std::set<uint64_t> blockPredicateIndices;
                        for (auto const& innerBlock : block) {
                            blockPredicateIndices.insert(localExpressionInformation.getExpressionBlock(innerBlock).begin(), localExpressionInformation.getExpressionBlock(innerBlock).end());
                        }

                        for (auto const& predicateIndex : blockPredicateIndices) {
                            STORM_LOG_TRACE(abstractionInformation.get().getPredicateByIndex(predicateIndex));
                        }

                        ++blockIndex;
                    }
                }

                std::set<storm::expressions::Variable> variablesContainedInGuard = edge.get().getGuard().getVariables();

                // Check whether we need to enumerate the guard. This is the case if the blocks related by the guard
                // are not contained within a single block of our decomposition.
                bool enumerateAbstractGuard = true;
                std::set<uint64_t> guardBlocks = localExpressionInformation.getBlockIndicesOfVariables(variablesContainedInGuard);
                for (auto const& block : relevantBlockPartition) {
                    bool allContained = true;
                    for (auto const& guardBlock : guardBlocks) {
                        if (block.find(guardBlock) == block.end()) {
                            allContained = false;
                            break;
                        }
                    }
                    if (allContained) {
                        enumerateAbstractGuard = false;
                    }
                }

                uint64_t numberOfSolutions = 0;
                uint64_t numberOfTotalSolutions = 0;

                // If we need to enumerate the guard, do it only once now.
                if (enumerateAbstractGuard) {
                    std::set<uint64_t> relatedGuardPredicates = localExpressionInformation.getRelatedExpressions(variablesContainedInGuard);
                    std::vector<storm::expressions::Variable> guardDecisionVariables;
                    std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> guardVariablesAndPredicates;
                    for (auto const& element : relevantPredicatesAndVariables.first) {
                        if (relatedGuardPredicates.find(element.second) != relatedGuardPredicates.end()) {
                            guardDecisionVariables.push_back(element.first);
                            guardVariablesAndPredicates.push_back(element);
                        }
                    }
                    abstractGuard = this->getAbstractionInformation().getDdManager().getBddZero();
                    smtSolver->allSat(guardDecisionVariables, [this,&guardVariablesAndPredicates,&numberOfSolutions] (storm::solver::SmtSolver::ModelReference const& model) {
                        abstractGuard |= getSourceStateBdd(model, guardVariablesAndPredicates);
                        ++numberOfSolutions;
                        return true;
                    });
                    STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions for abstract guard.");

                    // Now that we have the abstract guard, we can add it as an assertion to the solver before enumerating
                    // the other solutions.

                    // Create a new backtracking point before adding the guard.
                    smtSolver->push();

                    // Create the guard constraint.
                    std::pair<std::vector<storm::expressions::Expression>, std::unordered_map<uint_fast64_t, storm::expressions::Variable>> result = abstractGuard.toExpression(this->getAbstractionInformation().getExpressionManager());

                    // Then add it to the solver.
                    for (auto const& expression : result.first) {
                        smtSolver->add(expression);
                    }

                    // Finally associate the level variables with the predicates.
                    for (auto const& indexVariablePair : result.second) {
                        smtSolver->add(storm::expressions::iff(indexVariablePair.second, this->getAbstractionInformation().getPredicateForDdVariableIndex(indexVariablePair.first)));
                    }
                }

                // Then enumerate the solutions for each of the blocks of the decomposition
                uint64_t usedNondeterminismVariables = 0;
                uint64_t blockCounter = 0;
                std::vector<storm::dd::Bdd<DdType>> blockBdds;
                for (auto const& block : relevantBlockPartition) {
                    std::set<uint64_t> relevantPredicates;
                    for (auto const& innerBlock : block) {
                        relevantPredicates.insert(localExpressionInformation.getExpressionBlock(innerBlock).begin(), localExpressionInformation.getExpressionBlock(innerBlock).end());
                    }

                    if (relevantPredicates.empty()) {
                        STORM_LOG_TRACE("Block does not contain relevant predicates, skipping it.");
                        continue;
                    }

                    std::vector<storm::expressions::Variable> transitionDecisionVariables;
                    std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> sourceVariablesAndPredicates;
                    for (auto const& element : relevantPredicatesAndVariables.first) {
                        if (relevantPredicates.find(element.second) != relevantPredicates.end()) {
                            transitionDecisionVariables.push_back(element.first);
                            sourceVariablesAndPredicates.push_back(element);
                        }
                    }

                    std::vector<std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>>> destinationVariablesAndPredicates;
                    for (uint64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) {
                        destinationVariablesAndPredicates.emplace_back();
                        for (auto const& assignment : edge.get().getDestination(destinationIndex).getOrderedAssignments().getAllAssignments()) {
                            uint64_t assignmentVariableBlockIndex = localExpressionInformation.getBlockIndexOfVariable(assignment.getVariable().getExpressionVariable());

                            if (block.find(assignmentVariableBlockIndex) != block.end()) {
                                std::set<uint64_t> const& assignmentVariableBlock = localExpressionInformation.getExpressionBlock(assignmentVariableBlockIndex);
                                for (auto const& element : relevantPredicatesAndVariables.second[destinationIndex]) {
                                    if (assignmentVariableBlock.find(element.second) != assignmentVariableBlock.end()) {
                                        destinationVariablesAndPredicates.back().push_back(element);
                                        transitionDecisionVariables.push_back(element.first);
                                    }
                                }
                            }
                        }
                    }

                    std::unordered_map<storm::dd::Bdd<DdType>, std::vector<storm::dd::Bdd<DdType>>> sourceToDistributionsMap;
                    numberOfSolutions = 0;
                    smtSolver->allSat(transitionDecisionVariables, [&sourceToDistributionsMap,this,&numberOfSolutions,&sourceVariablesAndPredicates,&destinationVariablesAndPredicates] (storm::solver::SmtSolver::ModelReference const& model) {
                        sourceToDistributionsMap[getSourceStateBdd(model, sourceVariablesAndPredicates)].push_back(getDistributionBdd(model, destinationVariablesAndPredicates));
                        ++numberOfSolutions;
                        return true;
                    });
                    STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions for block " << blockCounter << ".");
                    numberOfTotalSolutions += numberOfSolutions;

                    // Now we search for the maximal number of choices of player 2 to determine how many DD variables we
                    // need to encode the nondeterminism.
                    uint_fast64_t maximalNumberOfChoices = 0;
                    for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
                        maximalNumberOfChoices = std::max(maximalNumberOfChoices, static_cast<uint_fast64_t>(sourceDistributionsPair.second.size()));
                    }

                    // We now compute how many variables we need to encode the choices. We add one to the maximal number of
                    // choices to account for a possible transition to a bottom state.
                    uint_fast64_t numberOfVariablesNeeded = (maximalNumberOfChoices > 1) ? (static_cast<uint_fast64_t>(std::ceil(std::log2(maximalNumberOfChoices + (blockCounter == 0 ? 1 : 0))))) : (blockCounter == 0 ? 1 : 0);

                    // Finally, build overall result.
                    storm::dd::Bdd<DdType> resultBdd = this->getAbstractionInformation().getDdManager().getBddZero();

                    for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
                        STORM_LOG_ASSERT(!sourceDistributionsPair.first.isZero(), "The source BDD must not be empty.");
                        STORM_LOG_ASSERT(!sourceDistributionsPair.second.empty(), "The distributions must not be empty.");

                        // We start with the distribution index of 1, because 0 is reserved for a potential bottom choice.
                        uint_fast64_t distributionIndex = blockCounter == 0 ? 1 : 0;
                        storm::dd::Bdd<DdType> allDistributions = this->getAbstractionInformation().getDdManager().getBddZero();
                        for (auto const& distribution : sourceDistributionsPair.second) {
                            allDistributions |= distribution && this->getAbstractionInformation().encodePlayer2Choice(distributionIndex, usedNondeterminismVariables, usedNondeterminismVariables + numberOfVariablesNeeded);
                            ++distributionIndex;
                            STORM_LOG_ASSERT(!allDistributions.isZero(), "The BDD must not be empty.");
                        }
                        resultBdd |= sourceDistributionsPair.first && allDistributions;
                        STORM_LOG_ASSERT(!resultBdd.isZero(), "The BDD must not be empty.");
                    }
                    usedNondeterminismVariables += numberOfVariablesNeeded;

                    blockBdds.push_back(resultBdd);
                    ++blockCounter;
                }

                if (enumerateAbstractGuard) {
                    smtSolver->pop();
                }

                // multiply the results
                storm::dd::Bdd<DdType> resultBdd = getAbstractionInformation().getDdManager().getBddOne();
                uint64_t blockIndex = 0;
                for (auto const& blockBdd : blockBdds) {
                    resultBdd &= blockBdd;
                    ++blockIndex;
                }

                // If we did not explicitly enumerate the guard, we can construct it from the result BDD.
                if (!enumerateAbstractGuard) {
                    std::set<storm::expressions::Variable> allVariables(getAbstractionInformation().getSuccessorVariables());
                    auto player2Variables = getAbstractionInformation().getPlayer2VariableSet(usedNondeterminismVariables);
                    allVariables.insert(player2Variables.begin(), player2Variables.end());
                    auto auxVariables = getAbstractionInformation().getAuxVariableSet(0, getAbstractionInformation().getAuxVariableCount());
                    allVariables.insert(auxVariables.begin(), auxVariables.end());

                    std::set<storm::expressions::Variable> variablesToAbstract;
                    std::set_intersection(allVariables.begin(), allVariables.end(), resultBdd.getContainedMetaVariables().begin(), resultBdd.getContainedMetaVariables().end(), std::inserter(variablesToAbstract, variablesToAbstract.begin()));

                    abstractGuard = resultBdd.existsAbstract(variablesToAbstract);
                } else {
                    // Multiply the abstract guard as it can contain predicates that are not mentioned in the blocks.
                    resultBdd &= abstractGuard;
                }

                // multiply with missing identities
                resultBdd &= computeMissingDestinationIdentities();

                // cache and return result
                resultBdd &= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount());

                // Cache the result.
                cachedDd = GameBddResult<DdType>(resultBdd, usedNondeterminismVariables);

                auto end = std::chrono::high_resolution_clock::now();

                STORM_LOG_TRACE("Enumerated " << numberOfTotalSolutions << " solutions in " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms.");
                forceRecomputation = false;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            void EdgeAbstractor<DdType, ValueType>::recomputeCachedBddWithoutDecomposition() {
                STORM_LOG_TRACE("Recomputing BDD for edge with id " << edgeId << " and guard " << edge.get().getGuard());
                auto start = std::chrono::high_resolution_clock::now();

                // Create a mapping from source state DDs to their distributions.
                std::unordered_map<storm::dd::Bdd<DdType>, std::vector<storm::dd::Bdd<DdType>>> sourceToDistributionsMap;
                uint64_t numberOfSolutions = 0;
                smtSolver->allSat(decisionVariables, [&sourceToDistributionsMap,this,&numberOfSolutions] (storm::solver::SmtSolver::ModelReference const& model) {
                    sourceToDistributionsMap[getSourceStateBdd(model, relevantPredicatesAndVariables.first)].push_back(getDistributionBdd(model, relevantPredicatesAndVariables.second));
                    ++numberOfSolutions;
                    return true;
                });

                // Now we search for the maximal number of choices of player 2 to determine how many DD variables we
                // need to encode the nondeterminism.
                uint_fast64_t maximalNumberOfChoices = 0;
                for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
                    maximalNumberOfChoices = std::max(maximalNumberOfChoices, static_cast<uint_fast64_t>(sourceDistributionsPair.second.size()));
                }

                // We now compute how many variables we need to encode the choices. We add one to the maximal number of
                // choices to account for a possible transition to a bottom state.
                uint_fast64_t numberOfVariablesNeeded = static_cast<uint_fast64_t>(std::ceil(std::log2(maximalNumberOfChoices + 1)));

                // Finally, build overall result.
                storm::dd::Bdd<DdType> resultBdd = this->getAbstractionInformation().getDdManager().getBddZero();
                if (!skipBottomStates) {
                    abstractGuard = this->getAbstractionInformation().getDdManager().getBddZero();
                }
                for (auto const& sourceDistributionsPair : sourceToDistributionsMap) {
                    if (!skipBottomStates) {
                        abstractGuard |= sourceDistributionsPair.first;
                    }

                    STORM_LOG_ASSERT(!sourceDistributionsPair.first.isZero(), "The source BDD must not be empty.");
                    STORM_LOG_ASSERT(!sourceDistributionsPair.second.empty(), "The distributions must not be empty.");
                    // We start with the distribution index of 1, becase 0 is reserved for a potential bottom choice.
                    uint_fast64_t distributionIndex = 1;
                    storm::dd::Bdd<DdType> allDistributions = this->getAbstractionInformation().getDdManager().getBddZero();
                    for (auto const& distribution : sourceDistributionsPair.second) {
                        allDistributions |= distribution && this->getAbstractionInformation().encodePlayer2Choice(distributionIndex, 0, numberOfVariablesNeeded);
                        ++distributionIndex;
                        STORM_LOG_ASSERT(!allDistributions.isZero(), "The BDD must not be empty.");
                    }
                    resultBdd |= sourceDistributionsPair.first && allDistributions;
                    STORM_LOG_ASSERT(!resultBdd.isZero(), "The BDD must not be empty.");
                }

                resultBdd &= computeMissingDestinationIdentities();
                resultBdd &= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount());
                STORM_LOG_ASSERT(sourceToDistributionsMap.empty() || !resultBdd.isZero(), "The BDD must not be empty, if there were distributions.");

                // Cache the result.
                cachedDd = GameBddResult<DdType>(resultBdd, numberOfVariablesNeeded);
                auto end = std::chrono::high_resolution_clock::now();

                STORM_LOG_TRACE("Enumerated " << numberOfSolutions << " solutions in " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms.");
                forceRecomputation = false;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            std::pair<std::set<uint_fast64_t>, std::set<uint_fast64_t>> EdgeAbstractor<DdType, ValueType>::computeRelevantPredicates(storm::jani::OrderedAssignments const& assignments) const {
                std::pair<std::set<uint_fast64_t>, std::set<uint_fast64_t>> result;

                std::set<storm::expressions::Variable> assignedVariables;
                for (auto const& assignment : assignments.getAllAssignments()) {
                    // Also, variables appearing on the right-hand side of an assignment are relevant for source state.
                    auto const& rightHandSidePredicates = localExpressionInformation.getExpressionsUsingVariables(assignment.getAssignedExpression().getVariables());
                    result.first.insert(rightHandSidePredicates.begin(), rightHandSidePredicates.end());

                    // Variables that are being assigned are relevant for the successor state.
                    storm::expressions::Variable const& assignedVariable = assignment.getExpressionVariable();
                    auto const& leftHandSidePredicates = localExpressionInformation.getExpressionsUsingVariable(assignedVariable);
                    result.second.insert(leftHandSidePredicates.begin(), leftHandSidePredicates.end());

                    // Predicates that are indirectly related to the assigned variables are relevant for the source state (if requested).
                    if (this->addPredicatesForValidBlocks) {
                        auto const& assignedVariableBlock = localExpressionInformation.getRelatedExpressions(assignedVariable);
                        result.first.insert(assignedVariableBlock.begin(), assignedVariableBlock.end());
                    }
                }

                return result;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> EdgeAbstractor<DdType, ValueType>::computeRelevantPredicates() const {
                std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> result;

                // To start with, all predicates related to the guard are relevant source predicates.
                result.first = localExpressionInformation.getExpressionsUsingVariables(edge.get().getGuard().getVariables());

                // Then, we add the predicates that become relevant, because of some update.
                for (auto const& destination : edge.get().getDestinations()) {
                    std::pair<std::set<uint_fast64_t>, std::set<uint_fast64_t>> relevantUpdatePredicates = computeRelevantPredicates(destination.getOrderedAssignments());
                    result.first.insert(relevantUpdatePredicates.first.begin(), relevantUpdatePredicates.first.end());
                    result.second.push_back(relevantUpdatePredicates.second);
                }

                return result;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            bool EdgeAbstractor<DdType, ValueType>::relevantPredicatesChanged(std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> const& newRelevantPredicates) const {
                if (newRelevantPredicates.first.size() > relevantPredicatesAndVariables.first.size()) {
                    return true;
                }

                for (uint_fast64_t index = 0; index < edge.get().getNumberOfDestinations(); ++index) {
                    if (newRelevantPredicates.second[index].size() > relevantPredicatesAndVariables.second[index].size()) {
                        return true;
                    }
                }

                return false;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            void EdgeAbstractor<DdType, ValueType>::addMissingPredicates(std::pair<std::set<uint_fast64_t>, std::vector<std::set<uint_fast64_t>>> const& newRelevantPredicates) {
                // Determine and add new relevant source predicates.
                std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> newSourceVariables = this->getAbstractionInformation().declareNewVariables(relevantPredicatesAndVariables.first, newRelevantPredicates.first);
                for (auto const& element : newSourceVariables) {
                    allRelevantPredicates.insert(element.second);
                    smtSolver->add(storm::expressions::iff(element.first, this->getAbstractionInformation().getPredicateByIndex(element.second)));
                    decisionVariables.push_back(element.first);
                }

                // Insert the new variables into the record of relevant source variables.
                relevantPredicatesAndVariables.first.insert(relevantPredicatesAndVariables.first.end(), newSourceVariables.begin(), newSourceVariables.end());
                std::sort(relevantPredicatesAndVariables.first.begin(), relevantPredicatesAndVariables.first.end(), [] (std::pair<storm::expressions::Variable, uint_fast64_t> const& first, std::pair<storm::expressions::Variable, uint_fast64_t> const& second) { return first.second < second.second; } );

                // Do the same for every update.
                for (uint_fast64_t index = 0; index < edge.get().getNumberOfDestinations(); ++index) {
                    std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> newSuccessorVariables = this->getAbstractionInformation().declareNewVariables(relevantPredicatesAndVariables.second[index], newRelevantPredicates.second[index]);
                    for (auto const& element : newSuccessorVariables) {
                        allRelevantPredicates.insert(element.second);
                        smtSolver->add(storm::expressions::iff(element.first, this->getAbstractionInformation().getPredicateByIndex(element.second).substitute(edge.get().getDestination(index).getAsVariableToExpressionMap())));
                        decisionVariables.push_back(element.first);
                    }

                    relevantPredicatesAndVariables.second[index].insert(relevantPredicatesAndVariables.second[index].end(), newSuccessorVariables.begin(), newSuccessorVariables.end());
                    std::sort(relevantPredicatesAndVariables.second[index].begin(), relevantPredicatesAndVariables.second[index].end(), [] (std::pair<storm::expressions::Variable, uint_fast64_t> const& first, std::pair<storm::expressions::Variable, uint_fast64_t> const& second) { return first.second < second.second; } );
                }
            }

            template <storm::dd::DdType DdType, typename ValueType>
            storm::dd::Bdd<DdType> EdgeAbstractor<DdType, ValueType>::getSourceStateBdd(storm::solver::SmtSolver::ModelReference const& model, std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>> const& variablePredicates) const {
                storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddOne();
                for (auto variableIndexPairIt = variablePredicates.rbegin(), variableIndexPairIte = variablePredicates.rend(); variableIndexPairIt != variableIndexPairIte; ++variableIndexPairIt) {
                    auto const& variableIndexPair = *variableIndexPairIt;
                    if (model.getBooleanValue(variableIndexPair.first)) {
                        result &= this->getAbstractionInformation().encodePredicateAsSource(variableIndexPair.second);
                    } else {
                        result &= !this->getAbstractionInformation().encodePredicateAsSource(variableIndexPair.second);
                    }
                }

                STORM_LOG_ASSERT(!result.isZero(), "Source must not be empty.");
                return result;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            storm::dd::Bdd<DdType> EdgeAbstractor<DdType, ValueType>::getDistributionBdd(storm::solver::SmtSolver::ModelReference const& model, std::vector<std::vector<std::pair<storm::expressions::Variable, uint_fast64_t>>> const& variablePredicates) const {
                storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddZero();

                for (uint_fast64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) {
                    storm::dd::Bdd<DdType> updateBdd = this->getAbstractionInformation().getDdManager().getBddOne();

                    // Translate block variables for this update into a successor block.
                    for (auto variableIndexPairIt = variablePredicates[destinationIndex].rbegin(), variableIndexPairIte = variablePredicates[destinationIndex].rend(); variableIndexPairIt != variableIndexPairIte; ++variableIndexPairIt) {
                        auto const& variableIndexPair = *variableIndexPairIt;
                        if (model.getBooleanValue(variableIndexPair.first)) {
                            updateBdd &= this->getAbstractionInformation().encodePredicateAsSuccessor(variableIndexPair.second);
                        } else {
                            updateBdd &= !this->getAbstractionInformation().encodePredicateAsSuccessor(variableIndexPair.second);
                        }
                    }

                    updateBdd &= this->getAbstractionInformation().encodeAux(destinationIndex, 0, this->getAbstractionInformation().getAuxVariableCount());
                    result |= updateBdd;
                }

                STORM_LOG_ASSERT(!result.isZero(), "Distribution must not be empty.");
                return result;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            storm::dd::Bdd<DdType> EdgeAbstractor<DdType, ValueType>::computeMissingDestinationIdentities() const {
                storm::dd::Bdd<DdType> result = this->getAbstractionInformation().getDdManager().getBddZero();

                for (uint_fast64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) {
                    // Compute the identities that are missing for this update.
                    auto updateRelevantIt = relevantPredicatesAndVariables.second[destinationIndex].rbegin();
                    auto updateRelevantIte = relevantPredicatesAndVariables.second[destinationIndex].rend();

                    storm::dd::Bdd<DdType> updateIdentity = this->getAbstractionInformation().getDdManager().getBddOne();
                    for (uint_fast64_t predicateIndex = this->getAbstractionInformation().getNumberOfPredicates() - 1;; --predicateIndex) {
                        if (updateRelevantIt == updateRelevantIte || updateRelevantIt->second != predicateIndex) {
                            updateIdentity &= this->getAbstractionInformation().getPredicateIdentity(predicateIndex);
                        } else {
                            ++updateRelevantIt;
                        }

                        if (predicateIndex == 0) {
                            break;
                        }
                    }

                    result |= updateIdentity && this->getAbstractionInformation().encodeAux(destinationIndex, 0, this->getAbstractionInformation().getAuxVariableCount());
                }
                return result;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            GameBddResult<DdType> EdgeAbstractor<DdType, ValueType>::abstract() {
                if (forceRecomputation) {
                    this->recomputeCachedBdd();
                } else {
                    cachedDd.bdd &= computeMissingDestinationIdentities();
                }

                STORM_LOG_TRACE("Edge produces " << cachedDd.bdd.getNonZeroCount() << " transitions.");

                return cachedDd;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            BottomStateResult<DdType> EdgeAbstractor<DdType, ValueType>::getBottomStateTransitions(storm::dd::Bdd<DdType> const& reachableStates, uint_fast64_t numberOfPlayer2Variables, boost::optional<std::pair<storm::expressions::Variable, storm::expressions::Variable>> const& locationVariables) {

                STORM_LOG_TRACE("Computing bottom state transitions of edge with index " << edgeId << ".");
                BottomStateResult<DdType> result(this->getAbstractionInformation().getDdManager().getBddZero(), this->getAbstractionInformation().getDdManager().getBddZero());

                // If the guard of this command is a predicate, there are not bottom states/transitions.
                if (skipBottomStates) {
                    STORM_LOG_TRACE("Skipping bottom state computation for this edge.");
                    return result;
                }

                storm::dd::Bdd<DdType> reachableStatesWithEdge = reachableStates && abstractGuard;
                // needed?
//                if (locationVariables) {
//                    reachableStatesWithEdge = (reachableStates && abstractGuard && this->getAbstractionInformation().encodeLocation(locationVariables.get().first, edge.get().getSourceLocationIndex())).existsAbstract(this->getAbstractionInformation().getSourceLocationVariables());
//                } else {
//                    reachableStatesWithEdge = (reachableStates && abstractGuard).existsAbstract(this->getAbstractionInformation().getSourceLocationVariables());
//                }

                // Use the state abstractor to compute the set of abstract states that has this command enabled but
                // still has a transition to a bottom state.
                bottomStateAbstractor.constrain(reachableStatesWithEdge);
                if (locationVariables) {
                    result.states = bottomStateAbstractor.getAbstractStates() && reachableStatesWithEdge && this->getAbstractionInformation().encodeLocation(locationVariables.get().first, edge.get().getSourceLocationIndex());
                } else {
                    result.states = bottomStateAbstractor.getAbstractStates() && reachableStatesWithEdge;
                }

                // If the result is empty one time, we can skip the bottom state computation from now on.
                if (result.states.isZero()) {
                    skipBottomStates = true;
                }

                // Now equip all these states with an actual transition to a bottom state.
                result.transitions = result.states && this->getAbstractionInformation().getAllPredicateIdentities() && this->getAbstractionInformation().getBottomStateBdd(false, false);

                // Mark the states as bottom states.
                result.states &= this->getAbstractionInformation().getBottomStateBdd(true, false);

                // Add the command encoding and the next free player 2 encoding.
                result.transitions &= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount()) && this->getAbstractionInformation().encodePlayer2Choice(0, 0, numberOfPlayer2Variables) && this->getAbstractionInformation().encodeAux(0, 0, this->getAbstractionInformation().getAuxVariableCount());

                return result;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            storm::dd::Add<DdType, ValueType> EdgeAbstractor<DdType, ValueType>::getEdgeDecoratorAdd(boost::optional<std::pair<storm::expressions::Variable, storm::expressions::Variable>> const& locationVariables) const {
                storm::dd::Add<DdType, ValueType> result = this->getAbstractionInformation().getDdManager().template getAddZero<ValueType>();
                for (uint_fast64_t destinationIndex = 0; destinationIndex < edge.get().getNumberOfDestinations(); ++destinationIndex) {
                    storm::dd::Add<DdType, ValueType> tmp = this->getAbstractionInformation().encodeAux(destinationIndex, 0, this->getAbstractionInformation().getAuxVariableCount()).template toAdd<ValueType>() * this->getAbstractionInformation().getDdManager().getConstant(evaluator.asRational(edge.get().getDestination(destinationIndex).getProbability()));
                    if (locationVariables) {
                        tmp *= this->getAbstractionInformation().encodeLocation(locationVariables.get().second, edge.get().getDestination(destinationIndex).getLocationIndex()).template toAdd<ValueType>();
                    }
                    result += tmp;
                }

                storm::dd::Add<DdType, ValueType> tmp = this->getAbstractionInformation().getDdManager().template getAddOne<ValueType>();
                if (locationVariables) {
                    tmp *= this->getAbstractionInformation().encodeLocation(locationVariables.get().first, edge.get().getSourceLocationIndex()).template toAdd<ValueType>();
                }
                tmp *= this->getAbstractionInformation().encodePlayer1Choice(edgeId, this->getAbstractionInformation().getPlayer1VariableCount()).template toAdd<ValueType>();

                result *= tmp;

                return result;
            }

            template <storm::dd::DdType DdType, typename ValueType>
            storm::jani::Edge const& EdgeAbstractor<DdType, ValueType>::getConcreteEdge() const {
                return edge.get();
            }

            template <storm::dd::DdType DdType, typename ValueType>
            AbstractionInformation<DdType> const& EdgeAbstractor<DdType, ValueType>::getAbstractionInformation() const {
                return abstractionInformation.get();
            }

            template <storm::dd::DdType DdType, typename ValueType>
            AbstractionInformation<DdType>& EdgeAbstractor<DdType, ValueType>::getAbstractionInformation() {
                return abstractionInformation.get();
            }

            template <storm::dd::DdType DdType, typename ValueType>
            void EdgeAbstractor<DdType, ValueType>::notifyGuardIsPredicate() {
                skipBottomStates = true;
            }

            template class EdgeAbstractor<storm::dd::DdType::CUDD, double>;
            template class EdgeAbstractor<storm::dd::DdType::Sylvan, double>;
#ifdef STORM_HAVE_CARL
            template class EdgeAbstractor<storm::dd::DdType::Sylvan, storm::RationalNumber>;
#endif
        }
    }
}