//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2014 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2012 - 2014 MetaScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_SWAR_FUNCTIONS_SIMD_SSE_SSE2_REVERSE_HPP_INCLUDED
#define BOOST_SIMD_SWAR_FUNCTIONS_SIMD_SSE_SSE2_REVERSE_HPP_INCLUDED
#ifdef BOOST_SIMD_HAS_SSE2_SUPPORT

#include <boost/simd/swar/functions/reverse.hpp>
#include <boost/simd/include/functions/simd/bitwise_cast.hpp>
#include <boost/dispatch/meta/as_floating.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( reverse_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((simd_ < single_<A0>
                                              , boost::simd::tag::sse_
                                              >
                                      ))
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL(1)
    {
      return _mm_shuffle_ps(a0,a0, _MM_SHUFFLE(0,1,2,3));
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( reverse_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((simd_ < double_<A0>
                                              , boost::simd::tag::sse_
                                              >
                                      ))
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL(1)
    {
      return _mm_shuffle_pd(a0, a0, _MM_SHUFFLE2(0,1));
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( reverse_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((simd_ < ints32_<A0>
                                              , boost::simd::tag::sse_
                                              >
                                      ))
                                    )
  {
    typedef A0 result_type;
    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL(1)
    {
      return _mm_shuffle_epi32(a0, _MM_SHUFFLE(0,1,2,3));
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( reverse_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((simd_ < ints64_<A0>
                                              , boost::simd::tag::sse_
                                              >
                                      ))
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL(1)
    {
      typedef typename dispatch::meta::as_floating<A0>::type f_t;
      return  simd::bitwise_cast<A0>(reverse( simd::bitwise_cast<f_t>(a0)));
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( reverse_
                                    , boost::simd::tag::sse2_
                                    , (A0)
                                    , ((simd_ < ints16_<A0>
                                              , boost::simd::tag::sse_
                                              >
                                      ))
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL(1)
    {
      A0  that = _mm_shuffle_epi32  (a0  , _MM_SHUFFLE(0,1,2,3));
          that = _mm_shufflelo_epi16(that, _MM_SHUFFLE(2,3,0,1));

      return _mm_shufflehi_epi16(that, _MM_SHUFFLE(2,3,0,1));
    }
  };
} } }

#endif
#endif


// TRENTO: Reduced Thickness Event-by-event Nuclear Topology
// Copyright 2015 Jonah E. Bernhard, J. Scott Moreland
// TRENTO3D: Three-dimensional extension of TRENTO by Weiyao Ke
// MIT License

#include "collider.h"

#include <cmath>
#include <string>
#include <vector>

#include <boost/program_options/variables_map.hpp>

#include "fwd_decl.h"
#include "nucleus.h"
#include <iostream>

namespace trento {

namespace {

// Helper functions for Collider ctor.

// Create one nucleus from the configuration.
NucleusPtr create_nucleus(const VarMap& var_map, std::size_t index) {
  const auto& species = var_map["projectile"]
                        .as<std::vector<std::string>>().at(index);
  const auto& nucleon_dmin = var_map["nucleon-min-dist"].as<double>();
  const auto& nucleon_width = var_map["nucleon-width"].as<double>();
  return Nucleus::create(species, nucleon_width, nucleon_dmin);
}

// Determine the maximum impact parameter.  If the configuration contains a
// non-negative value for bmax, use it; otherwise, fall back to the minimum-bias
// default.
double determine_bmax(const VarMap& var_map,
    const Nucleus& A, const Nucleus& B, const NucleonProfile& profile) {
  auto bmax = var_map["b-max"].as<double>();
  if (bmax < 0.)
    bmax = A.radius() + B.radius() + profile.max_impact();
  return bmax;
}

// Determine the asymmetry parameter (Collider::asymmetry_) for a pair of
// nuclei.  It's just rA/(rA+rB), falling back to 1/2 if both radii are zero
// (i.e. for proton-proton).
double determine_asym(const Nucleus& A, const Nucleus& B) {
  double rA = A.radius();
  double rB = B.radius();
  double sum = rA + rB;
  if (sum < 0.1)
    return 0.5;
  else
    return rA/sum;
}

}  // unnamed namespace

// Lots of members to initialize...
// Several helper functions are defined above.
Collider::Collider(const VarMap& var_map)
    : nucleusA_(create_nucleus(var_map, 0)),
      nucleusB_(create_nucleus(var_map, 1)),
      nucleon_profile_(var_map),
      nevents_(var_map["number-events"].as<int>()),
      ntrys_(0),
      bmin_(var_map["b-min"].as<double>()),
      bmax_(determine_bmax(var_map, *nucleusA_, *nucleusB_, nucleon_profile_)),
      npartmin_(var_map["npart-min"].as<int>()),
      npartmax_(var_map["npart-max"].as<int>()),
      stotmin_(var_map["s-min"].as<double>()),
      stotmax_(var_map["s-max"].as<double>()),
      asymmetry_(determine_asym(*nucleusA_, *nucleusB_)),
      event_(var_map),
      output_(var_map),
      with_ncoll_(var_map["ncoll"].as<bool>())
{
  // Constructor body begins here.
  // Set random seed if requested.
  auto seed = var_map["random-seed"].as<int64_t>();
  if (seed > 0)
    random::engine.seed(static_cast<random::Engine::result_type>(seed));
}

// See header for explanation.
Collider::~Collider() = default;

void Collider::run_events() {
  // The main event loop.
  for (int n = 0; n < nevents_; ++n) {
    // Sampling the impact parameter also implicitly prepares the nuclei for
    // event computation, i.e. by sampling nucleon positions and participants.

    // WK: an extra do-while loop, sample events, until it meets the Npart or
    // Entropy cut provided from command lines
    bool fullfil_Npart_cut=false, fullfil_Entropy_cut=false;
    double b;
    do{
    	b = sample_impact_param();
    	// Pass the prepared nuclei to the Event.  It computes the entropy profile
    	// (thickness grid) and other event observables.
    	event_.compute(*nucleusA_, *nucleusB_, nucleon_profile_);
        fullfil_Npart_cut = (npartmin_ < event_.npart())
								&& (event_.npart() <= npartmax_);
        fullfil_Entropy_cut = (stotmin_ < event_.multiplicity())
								&& (event_.multiplicity() <= stotmax_);
	}while( (!fullfil_Npart_cut) || (!fullfil_Entropy_cut) );
    // Write event data.
    output_(n, b, event_);
  }
  double cross_section = nevents_*M_PI*(bmax_*bmax_ - bmin_*bmin_)/ntrys_;
  double cross_section_err = cross_section/std::sqrt(1.*nevents_);
  //std::cout << "# cross-section = " << cross_section
  //			<< " +/- " << cross_section_err <<" [fm^2]" << std::endl;
}

double Collider::sample_impact_param() {
  // Sample impact parameters until at least one nucleon-nucleon pair
  // participates.  The bool 'collision' keeps track -- it is effectively a
  // logical OR over all possible participant pairs.
  double b;
  bool collision = false;

  do {
    // Sample b from P(b)db = 2*pi*b.
    b = bmin_ + (bmax_ - bmin_) * std::sqrt(random::canonical<double>());

    // Offset each nucleus depending on the asymmetry parameter (see header).
    nucleusA_->sample_nucleons(asymmetry_ * b);
    nucleusB_->sample_nucleons((asymmetry_ - 1.) * b);

    // Check each nucleon-nucleon pair.
    for (auto&& A : *nucleusA_) {
      for (auto&& B : *nucleusB_) {
		bool AB_collide = nucleon_profile_.participate(A, B);

        if (with_ncoll_) {
			// WK: only init Ncoll and Ncoll density at the first binary collision:
			if (AB_collide && (!collision) ) event_.clear_TAB();
			// WK: to calculate binary collision denstiy, each collision
			// contribute independently its Tpp. Therefore, if one pair collide,
			// it calls the event object to accumulate Tpp to the Ncoll density
			// Ncoll density = Sum Tpp
			if (AB_collide) event_.accumulate_TAB(A, B, nucleon_profile_);
		}

		// update collision flag
        collision = AB_collide || collision;
      }
    }
    ntrys_ ++;
  } while (!collision);

  return b;
}

}  // namespace trento


#include <mvp/Image/OrbitalImageCatalog.h>

#include <mvp/Core/Settings.h>

#include <vw/FileIO/DiskImageResource.h>
#include <vw/Camera/PinholeModel.h>

#include <boost/foreach.hpp>
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>

namespace mvp {
namespace image {

OrbitalImageCatalog::CatalogEntry::CatalogEntry(std::string const& i, std::string const& c) :
  image_path(i), camera_path(c)
{
  // TODO: camera loader (in core/CameraHelpers.h)?
  camera.reset(new vw::camera::PinholeModel(camera_path));

  // TODO: image size checker (in core/ImageHelpers.h)?
  boost::scoped_ptr<vw::DiskImageResource> rsrc(vw::DiskImageResource::open(image_path));
  image_bbox = vw::BBox2i(0, 0, rsrc->cols(), rsrc->rows());
}

void OrbitalImageCatalog::add_image(std::string const& image_path, std::string const& camera_path) {
  m_entries.push_back(CatalogEntry(image_path, camera_path));
}

static std::vector<std::string> paths_from_pattern(boost::filesystem::path pattern) {
  namespace fs = boost::filesystem;

  fs::path dir(pattern.parent_path());
  boost::regex filter(pattern.filename().string());

  std::vector<std::string> result_paths;

  fs::directory_iterator end_itr;
  for(fs::directory_iterator i(dir); i != end_itr; i++) {
    if (!fs::is_regular_file(i->status())) {
      continue;
    }

    boost::smatch what;
    if (boost::regex_match(i->path().filename().string(), what, filter)) {
      result_paths.push_back(i->path().string());
    }
  }

  sort(result_paths.begin(), result_paths.end());

  return result_paths;
}

void OrbitalImageCatalog::add_image_pattern(std::string const& image_pattern, std::string const& camera_pattern) {
  std::vector<std::string> image_paths = paths_from_pattern(image_pattern);
  std::vector<std::string> camera_paths = paths_from_pattern(camera_pattern);

  if (image_paths.size() != camera_paths.size()) {
    vw::vw_throw(vw::InputErr() << "The number of orbital images does not match the number of cameras");
  }

  for (unsigned i = 0; i < image_paths.size(); i++) {
    add_image(image_paths[i], camera_paths[i]);
  }
}

ConvexPolygon OrbitalImageCatalog::find_image_roi(ConvexPolygon const& map_roi,
                                                  vw::camera::CameraModel const& camera,
                                                  vw::cartography::Datum const& datum,
                                                  vw::Vector2 const& alt_limits) {
  // TODO: Cull out points on the other side of the globe that should be occluded
  std::vector<vw::Vector2> result;
  BOOST_FOREACH(vw::Vector2 const& v, map_roi.vertices()) {
    result.push_back(camera.point_to_pixel(datum.geodetic_to_cartesian(vw::Vector3(v.x(), v.y(), alt_limits[0]))));
    result.push_back(camera.point_to_pixel(datum.geodetic_to_cartesian(vw::Vector3(v.x(), v.y(), alt_limits[1]))));
  }
  return ConvexPolygon(result);
}

std::vector<OrbitalImageDesc> OrbitalImageCatalog::images_in_region(ConvexPolygon const& map_roi,
                                                                    vw::Vector2i const& padding) const {
  std::vector<OrbitalImageDesc> result;
  BOOST_FOREACH(CatalogEntry const& e, m_entries) {
    ConvexPolygon image_roi = find_image_roi(map_roi, *e.camera, m_datum, m_alt_limits);

    if (image_roi.intersects(ConvexPolygon(e.image_bbox))) {
      vw::BBox2i cropbox = vw::grow_bbox_to_int(image_roi.bounding_box());
      cropbox.min() = cropbox.min() - padding;
      cropbox.max() = cropbox.max() + padding;
      cropbox.crop(e.image_bbox);

      if (!cropbox.empty()) {
        OrbitalImageDesc i;
        i.set_image_path(e.image_path);
        i.set_camera_path(e.camera_path);
        if (mvp_settings().render().no_orbital_image_cropping()) {
          i.set_minx(0);
          i.set_miny(0);
          i.set_width(e.image_bbox.width());
          i.set_height(e.image_bbox.height());
        } else {
          i.set_minx(cropbox.min().x());
          i.set_miny(cropbox.min().y());
          i.set_width(cropbox.width());
          i.set_height(cropbox.height());
        }
        result.push_back(i);
      }
    }
  }

  return result;
}

}} // namespace image,mvp


#include <iostream>
#include <fstream>

#include <boost/algorithm/hex.hpp>
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>

#include <nil/crypto3/zk/snark/schemes/ppzksnark/r1cs_gg_ppzksnark.hpp>
#include <nil/crypto3/zk/snark/schemes/ppzksnark/r1cs_gg_ppzksnark/marshalling.hpp>

#include "./types.h"

std::string convert_byteblob_to_hex_string(std::vector<std::uint8_t> blob) {
    // convert byte_blob to hex string and print it to output
    std::string hex;
    hex.reserve(blob.size() * 2);
    boost::algorithm::hex(blob.begin(), blob.end(), back_inserter(hex));
    return hex;
}

void save_byteblob(std::vector<std::uint8_t> byteblob, boost::filesystem::path fname) {
    boost::filesystem::ofstream out(fname);
    for (const auto &v : byteblob) {
        out << v;
    }
    out.close();
}

std::vector<std::uint8_t> load_byteblob(boost::filesystem::path fname) {
    boost::filesystem::ifstream stream(fname, std::ios::in | std::ios::binary);
    std::vector<std::uint8_t> contents((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
    if (contents.size() == 0) {
        throw std::ios_base::failure("Empty file");
    }
    return contents;
}

// proving key

void save_proving_key(scheme_type::proving_key_type pk, boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = nil::marshalling::verifier_input_serializer_tvm<scheme_type>::process(pk);
    save_byteblob(byteblob, fname);
}

scheme_type::proving_key_type load_proving_key(boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = load_byteblob(fname);
    nil::marshalling::status_type processingStatus = nil::marshalling::status_type::success;
    return nil::marshalling::verifier_input_deserializer_tvm<scheme_type>::proving_key_process(
        byteblob.cbegin(),
        byteblob.cend(),
        processingStatus);
}

// verification key

void save_verification_key(scheme_type::verification_key_type vk, boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = nil::marshalling::verifier_input_serializer_tvm<scheme_type>::process(vk);
    save_byteblob(byteblob, fname);
}

scheme_type::verification_key_type load_verification_key(boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = load_byteblob(fname);
    nil::marshalling::status_type processingStatus = nil::marshalling::status_type::success;
    return nil::marshalling::verifier_input_deserializer_tvm<scheme_type>::verification_key_process(
        byteblob.cbegin(),
        byteblob.cend(),
        processingStatus);
}

// proof

void save_proof(scheme_type::proof_type proof, boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = nil::marshalling::verifier_input_serializer_tvm<scheme_type>::process(proof);
    save_byteblob(byteblob, fname);
}

scheme_type::proof_type load_proof(boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = load_byteblob(fname);
    nil::marshalling::status_type processingStatus = nil::marshalling::status_type::success;
    return nil::marshalling::verifier_input_deserializer_tvm<scheme_type>::proof_process(
        byteblob.cbegin(),
        byteblob.cend(),
        processingStatus);
}

//  primary input

void save_primary_input(zk::snark::r1cs_primary_input<field_type> primary_input, boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = nil::marshalling::verifier_input_serializer_tvm<scheme_type>::process(primary_input);
    save_byteblob(byteblob, fname);
}

zk::snark::r1cs_primary_input<field_type> load_primary_input(boost::filesystem::path fname) {
    std::vector<std::uint8_t> byteblob = load_byteblob(fname);
    nil::marshalling::status_type processingStatus = nil::marshalling::status_type::success;
    return nil::marshalling::verifier_input_deserializer_tvm<scheme_type>::primary_input_process(
        byteblob.cbegin(),
        byteblob.cend(),
        processingStatus);
}


#include "storm/storage/bisimulation/DeterministicModelBisimulationDecomposition.h"

#include <algorithm>
#include <unordered_map>
#include <chrono>
#include <iomanip>
#include <boost/iterator/zip_iterator.hpp>

#include "storm/adapters/RationalFunctionAdapter.h"
#include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h"

#include "storm/models/sparse/Dtmc.h"
#include "storm/models/sparse/Ctmc.h"
#include "storm/models/sparse/StandardRewardModel.h"

#include "storm/utility/graph.h"
#include "storm/utility/constants.h"
#include "storm/utility/ConstantsComparator.h"
#include "storm/exceptions/IllegalFunctionCallException.h"
#include "storm/exceptions/InvalidArgumentException.h"

#include "storm/settings/SettingsManager.h"
#include "storm/settings/modules/GeneralSettings.h"

namespace storm {
    namespace storage {

        using namespace bisimulation;

        template<typename ModelType>
        DeterministicModelBisimulationDecomposition<ModelType>::DeterministicModelBisimulationDecomposition(ModelType const& model, typename BisimulationDecomposition<ModelType, DeterministicModelBisimulationDecomposition::BlockDataType>::Options const& options) : BisimulationDecomposition<ModelType, DeterministicModelBisimulationDecomposition::BlockDataType>(model, options), probabilitiesToCurrentSplitter(model.getNumberOfStates(), storm::utility::zero<ValueType>()) {
            // Intentionally left empty.
        }

        template<typename ModelType>
        std::pair<storm::storage::BitVector, storm::storage::BitVector> DeterministicModelBisimulationDecomposition<ModelType>::getStatesWithProbability01() {
            return storm::utility::graph::performProb01(this->backwardTransitions, this->options.phiStates.get(), this->options.psiStates.get());
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::splitOffDivergentStates() {
            std::vector<storm::storage::sparse::state_type> stateStack;
            stateStack.reserve(this->model.getNumberOfStates());
            storm::storage::BitVector nondivergentStates(this->model.getNumberOfStates());

            uint_fast64_t currentSize = this->partition.size();
            for (uint_fast64_t blockIndex = 0; blockIndex < currentSize; ++blockIndex) {
                auto& block = *this->partition.getBlocks()[blockIndex];
                nondivergentStates.clear();

                for (auto stateIt = this->partition.begin(block), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) {
                    if (nondivergentStates.get(*stateIt)) {
                        continue;
                    }

                    // Now traverse the forward transitions of the current state and check whether there is a
                    // transition to some other block.
                    bool isDirectlyNonDivergent = false;
                    for (auto const& successor : this->model.getRows(*stateIt)) {
                        // If there is such a transition, then we can mark all states in the current block that can
                        // reach the state as non-divergent.
                        if (this->partition.getBlock(successor.getColumn()) != block) {
                            isDirectlyNonDivergent = true;
                            break;
                        }
                    }

                    if (isDirectlyNonDivergent) {
                        stateStack.push_back(*stateIt);

                        while (!stateStack.empty()) {
                            storm::storage::sparse::state_type currentState = stateStack.back();
                            stateStack.pop_back();
                            nondivergentStates.set(currentState);

                            for (auto const& predecessor : this->backwardTransitions.getRow(currentState)) {
                                if (this->partition.getBlock(predecessor.getColumn()) == block && !nondivergentStates.get(predecessor.getColumn())) {
                                    stateStack.push_back(predecessor.getColumn());
                                }
                            }
                        }
                    }
                }

                if (!nondivergentStates.empty() && nondivergentStates.getNumberOfSetBits() != block.getNumberOfStates()) {
                    // After performing the split, the current block will contain the divergent states only.
                    this->partition.splitStates(block, nondivergentStates);

                    // Since the remaining states in the block are divergent, we can mark the block as absorbing.
                    // This also guarantees that the self-loop will be added to the state of the quotient
                    // representing this block of states.
                    block.data().setAbsorbing(true);
                } else if (nondivergentStates.empty()) {
                    // If there are only diverging states in the block, we need to make it absorbing.
                    block.data().setAbsorbing(true);
                }
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::initializeSilentProbabilities() {
            silentProbabilities.resize(this->model.getNumberOfStates(), storm::utility::zero<ValueType>());
            for (storm::storage::sparse::state_type state = 0; state < this->model.getNumberOfStates(); ++state) {
                Block<BlockDataType> const* currentBlockPtr = &this->partition.getBlock(state);
                for (auto const& successorEntry : this->model.getRows(state)) {
                    if (&this->partition.getBlock(successorEntry.getColumn()) == currentBlockPtr) {
                        silentProbabilities[state] += successorEntry.getValue();
                    }
                }
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::initializeWeakDtmcBisimulation() {
            // If we are creating the initial partition for weak bisimulation on DTMCs, we need to (a) split off all
            // divergent states of each initial block and (b) initialize the vector of silent probabilities.
            this->splitOffDivergentStates();
            this->initializeSilentProbabilities();
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::postProcessInitialPartition() {
            if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc) {
                this->initializeWeakDtmcBisimulation();
            }

            if (this->options.getKeepRewards() && this->model.hasRewardModel() && this->options.getType() == BisimulationType::Weak) {
                // For a weak bisimulation that is to preserve reward properties, we have to flag all blocks of states
                // with non-zero reward as reward blocks so they can be refined wrt. strong bisimulation.

                // Here, we assume that the initial partition already respects state (and action) rewards. Therefore, it suffices to
                // check the first state of each block for a non-zero reward.
                boost::optional<std::vector<ValueType>> const& optionalStateRewardVector = this->model.getUniqueRewardModel().getOptionalStateRewardVector();
                boost::optional<std::vector<ValueType>> const& optionalStateActionRewardVector = this->model.getUniqueRewardModel().getOptionalStateActionRewardVector();
                for (auto& block : this->partition.getBlocks()) {
                    auto state = *this->partition.begin(*block);
                    block->data().setHasRewards((optionalStateRewardVector && !storm::utility::isZero(optionalStateRewardVector.get()[state])) || (optionalStateActionRewardVector && !storm::utility::isZero(optionalStateActionRewardVector.get()[state])));
                }
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::initializeMeasureDrivenPartition() {
            BisimulationDecomposition<ModelType, BlockDataType>::initializeMeasureDrivenPartition();
            postProcessInitialPartition();
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::initializeLabelBasedPartition() {
            BisimulationDecomposition<ModelType, BlockDataType>::initializeLabelBasedPartition();
            postProcessInitialPartition();
        }

        template<typename ModelType>
        typename DeterministicModelBisimulationDecomposition<ModelType>::ValueType const& DeterministicModelBisimulationDecomposition<ModelType>::getProbabilityToSplitter(storm::storage::sparse::state_type const& state) const {
            return probabilitiesToCurrentSplitter[state];
        }

        template<typename ModelType>
        bool DeterministicModelBisimulationDecomposition<ModelType>::isSilent(storm::storage::sparse::state_type const& state) const {
            return this->comparator.isOne(silentProbabilities[state]);
        }

        template<typename ModelType>
        bool DeterministicModelBisimulationDecomposition<ModelType>::hasNonZeroSilentProbability(storm::storage::sparse::state_type const& state) const {
            return !this->comparator.isZero(silentProbabilities[state]);
        }

        template<typename ModelType>
        typename DeterministicModelBisimulationDecomposition<ModelType>::ValueType DeterministicModelBisimulationDecomposition<ModelType>::getSilentProbability(storm::storage::sparse::state_type const& state) const {
            return silentProbabilities[state];
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::refinePredecessorBlockOfSplitterStrong(bisimulation::Block<BlockDataType>& block, std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
            STORM_LOG_TRACE("Refining predecessor " << block.getId() << " of splitter");

            // Depending on the actions we need to take, the block to refine changes, so we need to keep track of it.
            Block<BlockDataType>* blockToRefineProbabilistically = █

            bool split = false;
            // If the new begin index has shifted to a non-trivial position, we need to split the block.
            if (block.getBeginIndex() != block.data().marker1() && block.getEndIndex() != block.data().marker1()) {
                split = true;
                this->partition.splitBlock(block, block.data().marker1());
                blockToRefineProbabilistically = block.getPreviousBlockPointer();

                // Keep track of whether this is a block with reward states.
                blockToRefineProbabilistically->data().setHasRewards(block.data().hasRewards());
            }

            split |= this->partition.splitBlock(*blockToRefineProbabilistically,
                                                [this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) {
                                                    return this->comparator.isLess(getProbabilityToSplitter(state1), getProbabilityToSplitter(state2));
                                                },
                                                [&splitterQueue,&block] (Block<BlockDataType>& newBlock) {
                                                    splitterQueue.emplace_back(&newBlock);
                                                    newBlock.data().setSplitter();

                                                    // Keep track of whether this is a block with reward states.
                                                    newBlock.data().setHasRewards(block.data().hasRewards());
                                                });

            // If the predecessor block was split, we need to insert it into the splitter vector if it is not already
            // marked as a splitter.
            if (split && !blockToRefineProbabilistically->data().splitter()) {
                splitterQueue.emplace_back(blockToRefineProbabilistically);
                blockToRefineProbabilistically->data().setSplitter();
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::refinePredecessorBlocksOfSplitterStrong(std::list<Block<BlockDataType>*> const& predecessorBlocks, std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
            for (auto block : predecessorBlocks) {
                refinePredecessorBlockOfSplitterStrong(*block, splitterQueue);

                // If the block was *not* split, we need to reset the markers by notifying the data.
                block->resetMarkers();

                // Remember that we have refined the block.
                block->data().setNeedsRefinement(false);
            }
        }

        template<typename ModelType>
        bool DeterministicModelBisimulationDecomposition<ModelType>::possiblyNeedsRefinement(bisimulation::Block<BlockDataType> const& predecessorBlock) const {
            return predecessorBlock.getNumberOfStates() > 1 && !predecessorBlock.data().absorbing();
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::increaseProbabilityToSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType> const& predecessorBlock, ValueType const& value) {
            STORM_LOG_TRACE("Increasing probability of " << predecessor << " to splitter by " << value << ".");
            storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);

            // If the position of the state is to the right of marker1, we have not seen it before.
            if (predecessorPosition >= predecessorBlock.data().marker1()) {
                // Then, we just set the value.
                probabilitiesToCurrentSplitter[predecessor] = value;
            } else {
                // If the state was seen as a predecessor before, we add the value to the existing value.
                probabilitiesToCurrentSplitter[predecessor] += value;
            }
        }

        template <typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::moveStateToMarker1(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock) {
            this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker1()));
            predecessorBlock.data().incrementMarker1();
        }

        template <typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::moveStateToMarker2(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock) {
            this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker2()));
            predecessorBlock.data().incrementMarker2();
        }

        template <typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::moveStateInSplitter(storm::storage::sparse::state_type predecessor, bisimulation::Block<BlockDataType>& predecessorBlock, storm::storage::sparse::state_type currentPositionInSplitter, uint_fast64_t& elementsToSkip) {
            storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);

            // If the predecessors of the given predecessor were already explored, we can move it easily.
            if (predecessorPosition <= currentPositionInSplitter + elementsToSkip) {
                this->partition.swapStates(predecessor, this->partition.getState(predecessorBlock.data().marker1()));
                predecessorBlock.data().incrementMarker1();
            } else {
                // Otherwise, we need to move the predecessor, but we need to make sure that we explore its
                // predecessors later. We do this by moving it to a range at the beginning of the block that will hold
                // all predecessors in the splitter whose predecessors have yet to be explored.
                if (predecessorBlock.data().marker2() == predecessorBlock.data().marker1()) {
                    this->partition.swapStatesAtPositions(predecessorBlock.data().marker2(), predecessorPosition);
                    this->partition.swapStatesAtPositions(predecessorPosition, currentPositionInSplitter + elementsToSkip + 1);
                } else {
                    this->partition.swapStatesAtPositions(predecessorBlock.data().marker2(), predecessorPosition);
                    this->partition.swapStatesAtPositions(predecessorPosition, predecessorBlock.data().marker1());
                    this->partition.swapStatesAtPositions(predecessorPosition, currentPositionInSplitter + elementsToSkip + 1);
                }

                // Since we had to move an already explored state to the right of the current position,
                ++elementsToSkip;
                predecessorBlock.data().incrementMarker1();
                predecessorBlock.data().incrementMarker2();
            }
        }

        template <typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::exploreRemainingStatesOfSplitter(bisimulation::Block<BlockDataType>& splitter, std::list<bisimulation::Block<BlockDataType>*>& predecessorBlocks) {
            for (auto splitterIt = this->partition.begin(splitter), splitterIte = this->partition.begin(splitter) + (splitter.data().marker2() - splitter.getBeginIndex()); splitterIt != splitterIte; ++splitterIt) {
                storm::storage::sparse::state_type currentState = *splitterIt;

                for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) {
                    storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn();
                    Block<BlockDataType>& predecessorBlock = this->partition.getBlock(predecessor);

                    // If the block does not need to be refined, we skip it.
                    if (!possiblyNeedsRefinement(predecessorBlock)) {
                        continue;
                    }

                    // If we are computing a weak bisimulation on CTMCs and the predecessor block is the splitter, we
                    // need to ignore it and proceed to the next predecessor.
                    if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Ctmc && predecessorBlock == splitter) {
                        continue;
                    }

                    // We keep track of the probability of the predecessor moving to the splitter.
                    increaseProbabilityToSplitter(predecessor, predecessorBlock, predecessorEntry.getValue());

                    // Only move the state if it has not been seen as a predecessor before.
                    storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
                    if (predecessorPosition >= predecessorBlock.data().marker1()) {
                        moveStateToMarker1(predecessor, predecessorBlock);
                    }

                    // We must not insert the the splitter itself if we are not computing a weak bisimulation on CTMCs.
                    if (this->options.getType() != BisimulationType::Weak || this->model.getType() != storm::models::ModelType::Ctmc || predecessorBlock != splitter) {
                        insertIntoPredecessorList(predecessorBlock, predecessorBlocks);
                    }
                }
            }

            // Finally, we can reset the second marker.
            splitter.data().setMarker2(splitter.getBeginIndex());
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::updateSilentProbabilitiesBasedOnProbabilitiesToSplitter(bisimulation::Block<BlockDataType>& block) {
            // For all predecessors, we can set the probability to the current probability of moving to the splitter.
            for (auto stateIt = this->partition.begin(block), stateIte = this->partition.begin() + block.data().marker1(); stateIt != stateIte; ++stateIt) {
                silentProbabilities[*stateIt] = probabilitiesToCurrentSplitter[*stateIt];
            }
            // All non-predecessors have a silent probability of zero.
            for (auto stateIt = this->partition.begin() + block.data().marker1(), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) {
                silentProbabilities[*stateIt] = storm::utility::zero<ValueType>();
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::updateSilentProbabilitiesBasedOnTransitions(bisimulation::Block<BlockDataType>& block) {
            for (auto stateIt = this->partition.begin(block), stateIte = this->partition.end(block); stateIt != stateIte; ++stateIt) {
                if (hasNonZeroSilentProbability(*stateIt)) {
                    ValueType newSilentProbability = storm::utility::zero<ValueType>();
                    for (auto const& successorEntry : this->model.getTransitionMatrix().getRow(*stateIt)) {
                        if (this->partition.getBlock(successorEntry.getColumn()) == block) {
                            newSilentProbability += successorEntry.getValue();
                        }
                    }
                    silentProbabilities[*stateIt] = newSilentProbability;
                }
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::computeConditionalProbabilitiesForNonSilentStates(bisimulation::Block<BlockDataType>& block) {
            for (auto stateIt = this->partition.begin() + block.getBeginIndex(), stateIte = this->partition.begin() + block.data().marker1(); stateIt != stateIte; ++stateIt) {
                if (!this->comparator.isOne(getSilentProbability(*stateIt))) {
                    probabilitiesToCurrentSplitter[*stateIt] /= storm::utility::one<ValueType>() - getSilentProbability(*stateIt);
                }
            }
        }

        template<typename ModelType>
        std::vector<uint_fast64_t> DeterministicModelBisimulationDecomposition<ModelType>::computeNonSilentBlocks(bisimulation::Block<BlockDataType>& block) {
            auto less = [this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) { return probabilitiesToCurrentSplitter[state1] < probabilitiesToCurrentSplitter[state2]; };
            this->partition.sortRange(block.getBeginIndex(), block.data().marker1(), less);
            return this->partition.computeRangesOfEqualValue(block.getBeginIndex(), block.data().marker1(), less);
        }

        template<typename ModelType>
        std::vector<storm::storage::BitVector> DeterministicModelBisimulationDecomposition<ModelType>::computeWeakStateLabelingBasedOnNonSilentBlocks(bisimulation::Block<BlockDataType> const& block, std::vector<uint_fast64_t> const& nonSilentBlockIndices) {
            // Now that we have the split points of the non-silent states, we perform a backward search from
            // each non-silent state and label the predecessors with the class of the non-silent state.
            std::vector<storm::storage::BitVector> stateLabels(block.getNumberOfStates(), storm::storage::BitVector(nonSilentBlockIndices.size() - 1));

            std::vector<storm::storage::sparse::state_type> stateStack;
            stateStack.reserve(block.getNumberOfStates());
            for (uint_fast64_t stateClassIndex = 0; stateClassIndex < nonSilentBlockIndices.size() - 1; ++stateClassIndex) {
                for (auto stateIt = this->partition.begin() + nonSilentBlockIndices[stateClassIndex], stateIte = this->partition.begin() + nonSilentBlockIndices[stateClassIndex + 1]; stateIt != stateIte; ++stateIt) {

                    stateStack.push_back(*stateIt);
                    stateLabels[this->partition.getPosition(*stateIt) - block.getBeginIndex()].set(stateClassIndex);
                    while (!stateStack.empty()) {
                        storm::storage::sparse::state_type currentState = stateStack.back();
                        stateStack.pop_back();

                        for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) {
                            storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn();

                            if (this->comparator.isZero(predecessorEntry.getValue())) {
                                continue;
                            }

                            // Only if the state is in the same block, is a silent state and it has not yet been
                            // labeled with the current label.
                            if (this->partition.getBlock(predecessor) == block && isSilent(predecessor) && !stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].get(stateClassIndex)) {
                                stateStack.push_back(predecessor);
                                stateLabels[this->partition.getPosition(predecessor) - block.getBeginIndex()].set(stateClassIndex);
                            }
                        }
                    }
                }
            }

            return stateLabels;
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::refinePredecessorBlockOfSplitterWeak(bisimulation::Block<BlockDataType>& block, std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
            // First, we need to turn the one-step probabilities to go to the splitter to the conditional probabilities
            // for all non-silent states.
            computeConditionalProbabilitiesForNonSilentStates(block);

            // Then, we need to compute a labeling of the states that expresses which of the non-silent blocks they can reach.
            std::vector<uint_fast64_t> nonSilentBlockIndices = computeNonSilentBlocks(block);
            std::vector<storm::storage::BitVector> weakStateLabels = computeWeakStateLabelingBasedOnNonSilentBlocks(block, nonSilentBlockIndices);

            // Then split the block according to this labeling.
            // CAUTION: that this assumes that the positions of the states in the partition are not update until after
            // the sorting is over. Otherwise, this interferes with the data used in the sorting process.
            storm::storage::sparse::state_type originalBlockIndex = block.getBeginIndex();
            auto split = this->partition.splitBlock(block,
                                                     [&weakStateLabels,originalBlockIndex,this] (storm::storage::sparse::state_type state1, storm::storage::sparse::state_type state2) {
                                                         return weakStateLabels[this->partition.getPosition(state1) - originalBlockIndex] < weakStateLabels[this->partition.getPosition(state2) - originalBlockIndex];
                                                     },
                                                     [this, &splitterQueue, &block] (bisimulation::Block<BlockDataType>& newBlock) {
                                                         updateSilentProbabilitiesBasedOnTransitions(newBlock);

                                                         // Insert the new block as a splitter.
                                                         newBlock.data().setSplitter();
                                                         splitterQueue.emplace_back(&newBlock);

                                                         // Keep track of whether this is a block with reward states.
                                                         newBlock.data().setHasRewards(block.data().hasRewards());
                                                     });

            // If the block was split, we also update the silent probabilities.
            if (split) {
                updateSilentProbabilitiesBasedOnTransitions(block);

                if (!block.data().splitter()) {
                    // Insert the new block as a splitter.
                    block.data().setSplitter();
                    splitterQueue.emplace_back(&block);
                }
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::refinePredecessorBlocksOfSplitterWeak(bisimulation::Block<BlockDataType>& splitter, std::list<bisimulation::Block<BlockDataType>*> const& predecessorBlocks, std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
            for (auto block : predecessorBlocks) {
                if (block->data().hasRewards()) {
                    refinePredecessorBlockOfSplitterStrong(*block, splitterQueue);
                } else {
                    if (*block != splitter) {
                        refinePredecessorBlockOfSplitterWeak(*block, splitterQueue);
                    } else {
                        // If the block to split is the splitter itself, we must not do any splitting here.
                    }
                }

                block->resetMarkers();
                block->data().setNeedsRefinement(false);
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::insertIntoPredecessorList(bisimulation::Block<BlockDataType>& predecessorBlock, std::list<bisimulation::Block<BlockDataType>*>& predecessorBlocks) {
            // Insert the block into the list of blocks to refine (if that has not already happened).
            if (!predecessorBlock.data().needsRefinement()) {
                predecessorBlocks.emplace_back(&predecessorBlock);
                predecessorBlock.data().setNeedsRefinement();
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::refinePartitionBasedOnSplitter(bisimulation::Block<BlockDataType>& splitter, std::vector<bisimulation::Block<BlockDataType>*>& splitterQueue) {
            STORM_LOG_TRACE("Refining partition based on splitter " << splitter.getId());

            // The outline of the refinement is as follows.
            //
            // We iterate over all states of the splitter and determine for each predecessor the state the probability
            // entering the splitter. These probabilities are written to a member vector so that after the iteration
            // process we have the probabilities of all predecessors of the splitter of entering the splitter in one
            // step. To directly separate the states having a transition into the splitter from the ones who do not,
            // we move the states to certain locations. That is, on encountering a predecessor of the splitter, it is
            // moved to the beginning of its block. If the predecessor is in the splitter itself, we have to be a bit
            // careful about where to move states.
            //
            // After this iteration, there may be states of the splitter whose predecessors have not yet been explored,
            // so this needs to be done now.
            //
            // Finally, we use the information obtained in the first part for the actual splitting process in which all
            // predecessor blocks of the splitter are split based on the probabilities computed earlier.
            std::list<Block<BlockDataType>*> predecessorBlocks;
            storm::storage::sparse::state_type currentPosition = splitter.getBeginIndex();
            bool splitterIsPredecessorBlock = false;
            for (auto splitterIt = this->partition.begin(splitter), splitterIte = this->partition.end(splitter); splitterIt != splitterIte; ++splitterIt, ++currentPosition) {
                storm::storage::sparse::state_type currentState = *splitterIt;

                uint_fast64_t elementsToSkip = 0;
                for (auto const& predecessorEntry : this->backwardTransitions.getRow(currentState)) {
                    storm::storage::sparse::state_type predecessor = predecessorEntry.getColumn();
                    storm::storage::sparse::state_type predecessorPosition = this->partition.getPosition(predecessor);
                    Block<BlockDataType>& predecessorBlock = this->partition.getBlock(predecessor);

                    // If the block does not need to be refined, we skip it.
                    if (!possiblyNeedsRefinement(predecessorBlock)) {
                        continue;
                    }

                    // If we are computing a weak bisimulation on CTMCs and the predecessor block is the splitter, we
                    // need to ignore it and proceed to the next predecessor.
                    if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Ctmc && predecessorBlock == splitter) {
                        continue;
                    }

                    // We keep track of the probability of the predecessor moving to the splitter.
                    increaseProbabilityToSplitter(predecessor, predecessorBlock, predecessorEntry.getValue());

                    // We only need to move the predecessor if its not already known as a predecessor already.
                    if (predecessorPosition >= predecessorBlock.data().marker1()) {
                        // If the predecessor block is not the splitter, we can move the state easily.
                        if (predecessorBlock != splitter) {
                            moveStateToMarker1(predecessor, predecessorBlock);
                        } else {
                            // If the predecessor is in the splitter, we need to be a bit more careful.
                            splitterIsPredecessorBlock = true;
                            moveStateInSplitter(predecessor, predecessorBlock, currentPosition, elementsToSkip);
                        }

                        insertIntoPredecessorList(predecessorBlock, predecessorBlocks);
                    }
                }

                // If, as a consequence of shifting states, we need to skip some elements, do so now.
                splitterIt += elementsToSkip;
                currentPosition += elementsToSkip;
            }

            // If the splitter was a predecessor block of itself, we potentially need to explore some states that have
            // not been explored yet.
            if (splitterIsPredecessorBlock) {
                exploreRemainingStatesOfSplitter(splitter, predecessorBlocks);
            }

            // Finally, we split the block based on the precomputed probabilities and the chosen bisimulation type.
            if (this->options.getType() == BisimulationType::Strong || this->model.getType() == storm::models::ModelType::Ctmc) {
                // In the case of CTMCs and weak bisimulation, we still call the "splitStrong" method, but we already have
                // taken care of not adding the splitter to the predecessor blocks, so this is safe.
                refinePredecessorBlocksOfSplitterStrong(predecessorBlocks, splitterQueue);
            } else {
                // If the splitter is a predecessor of we can use the computed probabilities to update the silent
                // probabilities.
                if (splitterIsPredecessorBlock) {
                    updateSilentProbabilitiesBasedOnProbabilitiesToSplitter(splitter);
                }

                refinePredecessorBlocksOfSplitterWeak(splitter, predecessorBlocks, splitterQueue);
            }
        }

        template<typename ModelType>
        void DeterministicModelBisimulationDecomposition<ModelType>::buildQuotient() {
            // In order to create the quotient model, we need to construct
            // (a) the new transition matrix,
            // (b) the new labeling,
            // (c) the new reward structures.

            // Prepare a matrix builder for (a).
            storm::storage::SparseMatrixBuilder<ValueType> builder(this->size(), this->size());

            // Prepare the new state labeling for (b).
            storm::models::sparse::StateLabeling newLabeling(this->size());
            std::set<std::string> atomicPropositionsSet = this->options.respectedAtomicPropositions.get();
            atomicPropositionsSet.insert("init");
            std::vector<std::string> atomicPropositions = std::vector<std::string>(atomicPropositionsSet.begin(), atomicPropositionsSet.end());
            for (auto const& ap : atomicPropositions) {
                newLabeling.addLabel(ap);
            }

            // If the model had state rewards, we need to build the state rewards for the quotient as well.
            boost::optional<std::vector<ValueType>> stateRewards;
            if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
                stateRewards = std::vector<ValueType>(this->blocks.size());
            }

            // Now build (a) and (b) by traversing all blocks.
            for (uint_fast64_t blockIndex = 0; blockIndex < this->blocks.size(); ++blockIndex) {
                auto const& block = this->blocks[blockIndex];

                // Pick one representative state. For strong bisimulation it doesn't matter which state it is, because
                // they all behave equally.
                storm::storage::sparse::state_type representativeState = *block.begin();

                // However, for weak bisimulation, we need to make sure the representative state is a non-silent one (if
                // there is any such state).
                if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc) {
                    for (auto const& state : block) {
                        if (!isSilent(state)) {
                            representativeState = state;
                            break;
                        }
                    }
                }

                Block<BlockDataType> const& oldBlock = this->partition.getBlock(representativeState);

                // If the block is absorbing, we simply add a self-loop.
                if (oldBlock.data().absorbing()) {
                    builder.addNextValue(blockIndex, blockIndex, storm::utility::one<ValueType>());

                    // If the block has a special representative state, we retrieve it now.
                    if (oldBlock.data().hasRepresentativeState()) {
                        representativeState = oldBlock.data().representativeState();
                    }

                    // Add all of the selected atomic propositions that hold in the representative state to the state
                    // representing the block.
                    for (auto const& ap : atomicPropositions) {
                        if (this->model.getStateLabeling().getStateHasLabel(ap, representativeState)) {
                            newLabeling.addLabelToState(ap, blockIndex);
                        }
                    }
                } else {
                    // Compute the outgoing transitions of the block.
                    std::map<storm::storage::sparse::state_type, ValueType> blockProbability;
                    for (auto const& entry : this->model.getTransitionMatrix().getRow(representativeState)) {
                        storm::storage::sparse::state_type targetBlock = this->partition.getBlock(entry.getColumn()).getId();

                        // If we are computing a weak bisimulation quotient, there is no need to add self-loops.
                        if ((this->options.getType() == BisimulationType::Weak) && targetBlock == blockIndex && !oldBlock.data().hasRewards()) {
                            continue;
                        }

                        auto probIterator = blockProbability.find(targetBlock);
                        if (probIterator != blockProbability.end()) {
                            probIterator->second += entry.getValue();
                        } else {
                            blockProbability[targetBlock] = entry.getValue();
                        }
                    }

                    // Now add them to the actual matrix.
                    for (auto const& probabilityEntry : blockProbability) {
                        if (this->options.getType() == BisimulationType::Weak && this->model.getType() == storm::models::ModelType::Dtmc && !oldBlock.data().hasRewards()) {
                            builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second / (storm::utility::one<ValueType>() - getSilentProbability(representativeState)));
                        } else {
                            builder.addNextValue(blockIndex, probabilityEntry.first, probabilityEntry.second);
                        }
                    }

                    // Otherwise add all atomic propositions to the equivalence class that the representative state
                    // satisfies.
                    for (auto const& ap : atomicPropositions) {
                        if (this->model.getStateLabeling().getStateHasLabel(ap, representativeState)) {
                            newLabeling.addLabelToState(ap, blockIndex);
                        }
                    }
                }

                // If the model has state rewards, we simply copy the state reward of the representative state, because
                // all states in a block are guaranteed to have the same state reward.
                if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
                    auto const& rewardModel = this->model.getUniqueRewardModel();
                    if (rewardModel.hasStateRewards()) {
                        stateRewards.get()[blockIndex] = rewardModel.getStateRewardVector()[representativeState];
                    }
                    if (rewardModel.hasStateActionRewards()) {
                        stateRewards.get()[blockIndex] += rewardModel.getStateActionRewardVector()[representativeState];
                    }
                }
            }

            // Now check which of the blocks of the partition contain at least one initial state.
            for (auto initialState : this->model.getInitialStates()) {
                Block<BlockDataType> const& initialBlock = this->partition.getBlock(initialState);
                newLabeling.addLabelToState("init", initialBlock.getId());
            }

            // Construct the reward model mapping.
            std::unordered_map<std::string, typename ModelType::RewardModelType> rewardModels;
            if (this->options.getKeepRewards() && this->model.hasRewardModel()) {
                STORM_LOG_THROW(this->model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException, "Cannot preserve more than one reward model.");
                typename std::unordered_map<std::string, typename ModelType::RewardModelType>::const_iterator nameRewardModelPair = this->model.getRewardModels().begin();
                rewardModels.insert(std::make_pair(nameRewardModelPair->first, typename ModelType::RewardModelType(stateRewards)));
            }

            // Finally construct the quotient model.
            this->quotient = std::shared_ptr<ModelType>(new ModelType(builder.build(), std::move(newLabeling), std::move(rewardModels)));
        }

        template class DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<double>>;
        template class DeterministicModelBisimulationDecomposition<storm::models::sparse::Ctmc<double>>;

#ifdef STORM_HAVE_CARL
        template class DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<storm::RationalNumber>>;
        template class DeterministicModelBisimulationDecomposition<storm::models::sparse::Ctmc<storm::RationalNumber>>;

        template class DeterministicModelBisimulationDecomposition<storm::models::sparse::Dtmc<storm::RationalFunction>>;
        template class DeterministicModelBisimulationDecomposition<storm::models::sparse::Ctmc<storm::RationalFunction>>;
#endif
    }
}


// -*- C++ -*-
//

#include <sstream>
#include <boost/python.hpp>
#include "mccomponents/kernels/sample/phonon/IncoherentInelastic_EnergyFocusing.h"
#include "mccomponents/boostpython_binding/wrap_kernel.h"
#include "mccomponents/kernels/sample/phonon/AbstractDOS.h"
#include "mccomponents/kernels/sample/phonon/AbstractDebyeWallerFactor.h"

namespace wrap_mccomponents {

  void wrap_Phonon_IncoherentInelastic_EnergyFocusing_kernel()
  {
    using namespace boost::python;
    using namespace mccomponents::boostpython_binding;

    typedef mccomponents::kernels::phonon::IncoherentInelastic_EnergyFocusing w_t;

    kernel_wrapper<w_t>::wrap
      ( "Phonon_IncoherentInelastic_EnergyFocusing_kernel",
	init<
	const w_t::atoms_t &, // atoms
	w_t::float_t, // unitcell_vol. AA**3
	w_t::float_t, w_t::float_t, // Ef, dEf
	w_t::dos_t &, // DOS
	w_t::dwcalculator_t &, // Debye Waller calculator
	w_t::float_t, // temperature. K
	w_t::float_t, // average mass. atomic weight. default 0
	w_t::float_t, // total scattering xs. barn. default 0
	w_t::float_t  // total absorption xs. barn. default 0
	> ()
	[with_custodian_and_ward<1,2,
	 with_custodian_and_ward<1,6,
	 with_custodian_and_ward<1,7 > > > () ]
	)
      ;

  }

}

// End of file