/*!
 * Copyright (C) tkornuta, IBM Corporation 2015-2019
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*!
 * \file Layer.hpp
 * \brief Contains a template class representing a layer.
 * \author tkornut
 * \date Mar 31, 2016
 */

#ifndef SRC_MLNN_LAYER_HPP_
#define SRC_MLNN_LAYER_HPP_

#include <iostream>
#include <string>

#include<types/MatrixTypes.hpp>
#include<types/MatrixArray.hpp>
#include <optimization/OptimizationFunctionTypes.hpp>
#include <optimization/OptimizationArray.hpp>

#include <boost/serialization/serialization.hpp>
// include this header to serialize vectors
#include <boost/serialization/vector.hpp>
// include this header to serialize arrays
#include <boost/serialization/array.hpp>
#include <boost/serialization/version.hpp>

#include <logger/Log.hpp>

// Forward declaration of class boost::serialization::access
namespace boost {
namespace serialization {
class access;
}//: serialization
}//: access

namespace mic {
namespace mlnn {

/*!
 * \brief Enumeration of possible layer types.
 * \author tkornuta
 */
enum class LayerTypes : short
{
	// activation
	ELU = 0,
	ReLU,
	Sigmoid,
	// convolution
	Convolution,
	Cropping,
	Padding,
	MaxPooling,
	// cost_function
	Softmax,
	// fully_connected
	Linear,
	SparseLinear,
	HebbianLinear,
    BinaryCorrelator,
	// regularization
    Dropout,
    // Experimental
    ConvHebbian
};

// Forward declaration of MultiLayerNeuralNetwork - required for "lazy connection".
template <typename eT>
class MultiLayerNeuralNetwork;

/*!
 * Template base (abstract) class representing a layer.
 * \author tkornuta/krocki
 * \tparam eT Template parameter denoting precision of variables (float for calculations/double for testing).
 */
template <typename eT=float>
class Layer {
public:
	/*!
	 * Default constructor of the layer parent class. Sets the input-output dimensions, layer type and name.
	 * @param input_height_ Height of the input sample.
	 * @param input_width_ Width of the input sample.
	 * @param input_depth_ Depth of the input sample.
	 * @param output_height_ Width of the output sample.
	 * @param output_width_ Height of the output sample.
	 * @param output_depth_ Depth of the output sample.
	 * @param layer_type_ Type of the layer.
	 * @param name_ Name of the layer.
	 */
	Layer(size_t input_height_, size_t input_width_, size_t input_depth_,
			size_t output_height_, size_t output_width_, size_t output_depth_,
			LayerTypes layer_type_, std::string name_ = "layer") :
		// Set "reduced" input dimensions.
		input_height(input_height_),
		input_width(input_width_),
		input_depth(input_depth_),
		// Set "reduced" output dimensions.
		output_height(output_height_),
		output_width(output_width_),
		output_depth(output_depth_),
		// Set batch size.
		batch_size(1),
		// Set layer type and name.
		layer_type(layer_type_),
		layer_name(name_),
		// Initialize matrice arrays.
		s("state"),
		g("gradients"),
		p("parameters"),
		m("memory")
	{
		// State.
		s.add ( "x", input_depth*input_height*input_width, batch_size ); 	// inputs
		s.add ( "y", output_depth*output_height*output_width, batch_size); 	// outputs

		// Gradients.
		g.add ( "x", input_depth*input_height*input_width, batch_size ); 	// inputs
		g.add ( "y", output_depth*output_height*output_width, batch_size); 	// outputs

		// Allocate (temporary) memory for "input sample" - column vector.
		m.add ("xs", input_depth*input_height*input_width, 1);
		// Allocate (temporary) memory for "input channel" - column vector.
		m.add ("xc", input_height*input_width, 1);

		// Allocate (temporary) memory for "output sample" - a column vector of all channels of a given sample.
		m.add ("ys", output_depth*output_height*output_width, 1);

		// Allocate (temporary) memory for "output sample" - a column vector.
		m.add ("yc", output_height * output_width, 1);

	};

	/*!
	 * Virtual destructor - required for the correct destruction of objects of derived classes.
	 */
	virtual ~Layer() {};

	/*!
	 * Abstract method responsible for processing the data from the inputs to outputs. To be overridden in the derived classes.
	 * @param test Test mode - used for dropout-alike regularization techniques.
	 */
	virtual void forward(bool test = false) = 0;

	/*!
	 * Forwards the activations of the neural network.
	 */
	mic::types::MatrixPtr<eT> forward(mic::types::MatrixPtr<eT> x_, bool test = false) {
		// Copy "input" sample/batch.
		(*s["x"]) = (*x_);

		// Call the (abstract, implemented by a given layer) forward pass.
		forward(test);

		// Return "output".
		return s["y"];
	}

	/*!
	 * Abstract method responsible for processing the gradients from outputs to inputs (i.e. in the opposite direction). To be overridden in the derived classes.
	 */
	virtual void backward() = 0;

	/*!
	 * Backward pass - backpropagation.
	 */
	mic::types::MatrixPtr<eT> backward(mic::types::MatrixPtr<eT> dy_) {
		// Copy "output" sample/batch gradient.
		(*g["y"]) = (*dy_);

		// Call the (abstract, implemented by a given layer) backward pass.
		backward();

		// Return "input" gradient.
		return g["x"];
	}

	/*!
	 * Changes the size of the batch. By default it resizes state (x,y) and gradients (x,y).
	 * @param New size of the batch.
	 */
	virtual void resizeBatch(size_t batch_size_) {
		// Change the "value". (depricated)
		batch_size = batch_size_;
		// Reshape the inputs...
		s["x"]->resize(s["x"]->rows(), batch_size_);
		g["x"]->resize(g["x"]->rows(), batch_size_);
		// ... and outputs.
		s["y"]->resize(s["y"]->rows(), batch_size_);
		g["y"]->resize(g["y"]->rows(), batch_size_);
	}

	/*!
	 * Calculates the numerical gradient.
	 * @param x_ Input vector.
	 * @param target_y_ Target (desired) output.
	 * @param loss_ Loss function.
	 * @param delta_ Delta.
	 * @tparam loss Loss function type.
	 */
	template<typename loss>
	mic::types::MatrixPtr<eT> calculateNumericalGradient(mic::types::MatrixPtr<eT> x_, mic::types::MatrixPtr<eT> target_y_, mic::types::MatrixPtr<eT> param_, loss loss_, eT delta_) {
		// Allocate memory.
		mic::types::MatrixPtr<eT> nGrad = MAKE_MATRIX_PTR(eT, param_->rows(), param_->cols());
		for (size_t i=0; i<(size_t)param_->size(); i++) {
			// Add delta.
			(*param_)[i] += delta_;
			// Calculate loss.
			eT p = loss_.calculateLoss(target_y_, forward(x_));
			// Substract delta.
			(*param_)[i] -= 2*delta_;
			// Calculate loss.
			eT m = loss_.calculateLoss(target_y_, forward(x_));

			// Store numerical gradient.
			(*nGrad)[i] = (p-m)/(2*delta_);
			// Set original value.
			(*param_)[i] += delta_;

		}//: for
		return nGrad;
	}

	/*!
	 * Reset gradients. Virtual empty method - to be implemented by the inherited classes.
	 */
	virtual void resetGrads() {};

	/*!
	 * Performs the update according to the calculated gradients and injected optimization method. Abstract.
	 * @param alpha_ Learning rate - passed to the optimization functions of all layers.
	 * @param decay_ Weight decay rate (determining that the "unused/unupdated" weights will decay to 0) (DEFAULT=0.0 - no decay).
	 */
	virtual void update(eT alpha_, eT decay_  = 0.0f) = 0;

	/// Returns size (length) of inputs.
	inline size_t inputSize() {
		return input_height*input_width*input_depth;
	}

	/// Returns size (length) of outputs.
	inline size_t outputSize() {
		return output_height*output_width*output_depth;
	}

	/// Returns size (length) of (mini)batch.
	inline size_t batchSize() {
		return batch_size;
	}

	/// Returns name of the layer.
	inline const std::string name() const {
		return layer_name;
	}

	/*!
	 * Returns the pointer to a parameter (matrix) (or throws an exception!)
	 */
	mic::types::MatrixPtr<eT> getParam(std::string name_) {
		return p[name_];
	}

	/*!
	 * Returns the pointer to state (matrix) (or throws an exception!)
	 */
	mic::types::MatrixPtr<eT> getState(std::string name_) {
		return s[name_];
	}

	/*!
	 * Returns the pointer to gradient (matrix) (or throws an exception!)
	 */
	mic::types::MatrixPtr<eT> getGradient(std::string name_) {
		return g[name_];
	}

	/*!
	 * Sets state (matrix) (or throws an exception!)
	 */
	void setState(std::string name_, mic::types::MatrixPtr<eT> mat_ptr_) {
		(*s[name_]) = (*mat_ptr_);
	}

	/*!
	 * Sets the optimization method.
	 * @tparam omT Optimization method type
	 */
	template<typename omT>
	void setOptimization () {
		// Remove all previous optimization functions.
		opt.clear();

		// Iterate through parameters and add a separate optimization function for each parameter.
		for (auto& i: p.keys()) {
			opt.add(
					i.first,
					std::make_shared< omT > (omT ( (p[i.second])->rows(), (p[i.second])->cols() ))
					);
		}//: for keys
	}

	/*!
	 * Returns the type of layer.
	 */
	const std::string type() const {
		switch(layer_type) {
		// activation
		case(LayerTypes::ELU):
			return "ELU";
		case(LayerTypes::ReLU):
			return "ReLU";
		case(LayerTypes::Sigmoid):
			return "Sigmoid";
		// convolution
		case(LayerTypes::Convolution):
			return "Convolution";
		case(LayerTypes::Padding):
			return "Padding";
		case(LayerTypes::MaxPooling):
			return "MaxPooling";
		// cost_function
		case(LayerTypes::Softmax):
			return "Softmax";
		// fully_connected
		case(LayerTypes::Linear):
			return "Linear";
		case(LayerTypes::SparseLinear):
			return "SparseLinear";
		case(LayerTypes::HebbianLinear):
			return "HebbianLinear";
		case(LayerTypes::BinaryCorrelator):
			return "BinaryCorrelator";
		// regularization
		case(LayerTypes::Dropout):
			return "Dropout";
		default:
			return "Undefined";
		}//: switch
	}

	/*!
	 * Stream layer parameters.
	 * @return Ostream object.
	 */
	virtual std::string streamLayerParameters() {
		std::ostringstream os_;
		// Display id/type.
		os_ << "  [" << type() << "]: " << layer_name << ": " << inputSize() << "x" << batch_size << " -> " << outputSize() << "x" << batch_size << "\n";

		// Display dimensions.
		os_<<"    * input_height = " << input_height <<std::endl;
		os_<<"    * input_width = " << input_width <<std::endl;
		os_<<"    * input_channels = " << input_depth <<std::endl;
		os_<<"    * output_height = " << output_height <<std::endl;
		os_<<"    * output_width = " << output_width <<std::endl;
		os_<<"    * output_channels = " << output_depth;

		return os_.str();
	}

	/*!
	 * Stream operator enabling to print neural network.
	 * @param os_ Ostream object.
	 * @param obj_ Tensor object.
	 */
	friend std::ostream& operator<<(std::ostream& os_, Layer& obj_) {
		// Display dimensions.
		os_ << obj_.streamLayerParameters();

		// Display inputs.
		os_ << "    [" << obj_.s.name() << "]:\n";
		for (auto& i: obj_.s.keys()) {
			// Display elements.
			os_ << "      [" << i.first << "]: ";
			os_ << (obj_.s[i.second])->cols() << "x" << (obj_.s[i.second])->rows() << std::endl;
		}//: for keys

		// Display gradients.
		os_ << "    [" << obj_.g.name() << "]:\n";
		for (auto& i: obj_.g.keys()) {
			// Display elements.
			os_ << "      [" << i.first << "]: ";
			os_ << (obj_.g[i.second])->cols() << "x" << (obj_.g[i.second])->rows() << std::endl;
		}//: for keys

		// Display parameters.
		os_ << "    [" << obj_.p.name() << "]:\n";
		for (auto& i: obj_.p.keys()) {
			// Display elements.
			os_ << "      [" << i.first << "]: ";
			os_ << (obj_.p[i.second])->cols() << "x" << (obj_.p[i.second])->rows() << std::endl;
		}//: for keys

		// Display gradients.
		os_ << "    [" << obj_.m.name() << "]:\n";
		for (auto& i: obj_.m.keys()) {
			// Display elements.
			os_ << "      [" << i.first << "]: ";
			os_ << (obj_.m[i.second])->cols() << "x" << (obj_.m[i.second])->rows() << std::endl;
		}//: for keys

		return os_;
	}

	/*!
	 * Returns sample from batch. If a given memory array does not contain such a memory pointer - it (lazy) allocates it.
	 * OMP secured - critical section inside.
	 * @param batch_ptr_ Pointer to a batch.
	 * @param array_ Array of matrices where a given matrix (sample) is/will be stored.
	 * @param id_ Sample id (variable prefix).
	 * @param sample_number_ Number of the sample in batch.
	 * @param sample_size_ Size of the sample.
	 */
	mic::types::MatrixPtr<eT> lazyReturnSampleFromBatch (mic::types::MatrixPtr<eT> batch_ptr_, mic::types::MatrixArray<eT> & array_, std::string id_, size_t sample_number_, size_t sample_size_){
		// Generate "unique id" for a given sample.
		std::string sample_id = id_ + std::to_string(sample_number_);
		mic::types::MatrixPtr<eT> sample;

		#pragma omp critical
		{
			if (!array_.keyExists(sample_id)) {
				// Allocate memory.
				array_.add(sample_id, sample_size_, 1);
			}//: if

			// Get array.
			sample = m[sample_id];
			// Copy data.
			(*sample) = batch_ptr_->col(sample_number_);
		}//: end OMP critical section

		// Return it.
		return sample;
	}

	/*!
	 * Returns input sample, with lazy matrix ptr allocation.
	 * @param batch_ptr_ Pointer to a batch.
	 * @param sample_number_ Number of the sample in batch.
	 */
	inline mic::types::MatrixPtr<eT> lazyReturnInputSample (mic::types::MatrixPtr<eT> batch_ptr_, size_t sample_number_){
		return lazyReturnSampleFromBatch(batch_ptr_, m, "xs", sample_number_, Layer<eT>::inputSize());
	}

	/*!
	 * Returns output sample, with lazy matrix ptr allocation.
	 * @param batch_ptr_ Pointer to a batch.
	 * @param sample_number_ Number of the sample in batch.
	 */
	inline mic::types::MatrixPtr<eT> lazyReturnOutputSample (mic::types::MatrixPtr<eT> batch_ptr_, size_t sample_number_){
		return lazyReturnSampleFromBatch(batch_ptr_, m, "ys", sample_number_, Layer<eT>::outputSize());
	}

	/*!
	 * Returns channel from a sample. If a given memory array does not contain such a memory pointer - it (lazy) allocates it.
	 * Assumes that: a) a sample is a column vector and b) there is one "channel memory ptr" for each sample (so the whole batch can be processed in parallel).
	 * OMP secured - critical section inside.
	 * @param sample_ptr Pointer to a sample.
	 * @param array_ Array of matrices where a given matrix (channel) is/will be stored.
	 * @param id_ Channel id (variable prefix).
	 * @param sample_number_ Number of the sample in batch.
	 * @param channel_number_ Number of the channel.
	 * @param height_ Height of the channel.
	 * @param width_ Width of the channel.
	 */
	mic::types::MatrixPtr<eT> lazyReturnChannelFromSample (mic::types::MatrixPtr<eT> sample_ptr_, mic::types::MatrixArray<eT> & array_, std::string id_, size_t sample_number_, size_t channel_number_, size_t height_, size_t width_){
		// Generate "unique id" for a given sample.
		std::string channel_id = id_ + std::to_string(channel_number_);
		mic::types::MatrixPtr<eT> channel;

		#pragma omp critical
		{
			if (!array_.keyExists(channel_id)) {
				// Allocate memory.
				array_.add(channel_id, height_*width_, 1);
			}//: if

			// Get array.
			channel = m[channel_id];
			// Just in case - resize.
			sample_ptr_->resize(sample_ptr_->size(), 1);
			// Copy data.
			(*channel) = sample_ptr_->block(channel_number_*height_*width_, 0, height_*width_, 1);
			// Resize channel.
			channel-> resize(height_, width_);
		}//: end OMP critical section

		// Return it.
		return channel;
	}

	/*!
	 * Returns input channel, with lazy matrix ptr allocation.
	 * @param batch_ptr_ Pointer to a batch.
	 * @param sample_number_ Number of the sample in batch.
	 * @param channel_number_ Number of the channel in sample.
	 */
	inline mic::types::MatrixPtr<eT> lazyReturnInputChannel (mic::types::MatrixPtr<eT> sample_ptr_, size_t sample_number_, size_t channel_number_){
		return lazyReturnChannelFromSample(sample_ptr_, m, "xc", sample_number_, channel_number_, input_height, input_width);
	}

	/*!
	 * Returns output sample, with lazy matrix ptr allocation.
	 * @param batch_ptr_ Pointer to a batch.
	 * @param sample_number_ Number of the sample in batch.
	 * @param channel_number_ Number of the channel in sample.
	 */
	inline mic::types::MatrixPtr<eT> lazyReturnOutputChannel (mic::types::MatrixPtr<eT> sample_ptr_, size_t sample_number_, size_t channel_number_){
		return lazyReturnChannelFromSample(sample_ptr_, m, "yc", sample_number_, channel_number_, output_height, output_width);
	}

	/*!
	 * Allocates memory to a matrix vector (lazy).
	 * @param vector_ Vector that will store the matrices.
	 * @param vector_size Number of matrices to be added.
	 * @param matrix_height_ Height of matrices.
	 * @param matrix_width_ Width of matrices.
	 */
	void lazyAllocateMatrixVector(std::vector< std::shared_ptr <mic::types::Matrix<eT> > > & vector_, size_t vector_size_, size_t matrix_height_, size_t matrix_width_) {
		// Check if memory for the activations was allocated.
		if (vector_.size() != vector_size_) {
			// Free memory.
			vector_.clear();
			// Allocate.
			for (size_t i=0; i < vector_size_; i++) {
				// Allocate memory for activation of every neuron.
				mic::types::MatrixPtr<eT> m = MAKE_MATRIX_PTR(eT, matrix_height_, matrix_width_);
				vector_.push_back(m);
			}//: for
		}//: if
	}

	/*!
	 * Normalizes the matrix. to the range <-1.0, 1.0>, e.g. for the visualization purposes.
	 * DEPRICATED - functionality moved to mi-visualization!
	 * @param matrix_ Matrix to be normalized.
	 */
	/*void normalizeMatrixForVisualization(mic::types::MatrixPtr<eT> matrix_) {
		// Epsilon added for numerical stability.
		//eT eps = 1e-5;
		//eT l2 = matrix_->norm() + eps;

		// Calculate the norm.
		eT max = matrix_->maxCoeff();
		eT min = matrix_->minCoeff();
		eT ultimate_max=  (max > -min) ? max : -min;

		//std::cout << "before: min:" << (*matrix_).minCoeff() <<" max: " << (*matrix_).maxCoeff() << std::endl;
		// Normalize the inputs to range <0.0, 1.0>.
		// Check if we can normalize.
		if (ultimate_max != 0.0) {
            (*matrix_) = matrix_->unaryExpr ( [&] ( eT x ) { return ( (x)/ultimate_max ); } ).template cast<eT>();
			//std::cout << "after: min:" << (*matrix_).minCoeff() <<" max: " << (*matrix_).maxCoeff() << std::endl;
		}//: else: do nothing, all values are ~0 already.
	}*/

	/*!
	 * Returns activations of input neurons.
	 */
	virtual std::vector< std::shared_ptr <mic::types::Matrix<eT> > > & getInputActivations() {

 		// Allocate memory.
		lazyAllocateMatrixVector(x_activations, input_depth * batch_size, input_height*input_width, 1);

		// Get y batch.
		mic::types::MatrixPtr<eT> batch_x = s['x'];

		// Iterate through filters and generate "activation image" for each one.
		for (size_t ib=0; ib< batch_size; ib++) {

			// Get input sample from batch!
			mic::types::MatrixPtr<eT> sample_x = m["xs"];
			(*sample_x) = batch_x->col(ib);

			// Iterate through input channels.
			for (size_t ic=0; ic< input_depth; ic++) {
				// Get activation "row".
				mic::types::MatrixPtr<eT> row = x_activations[ib*input_depth + ic];

				// Copy "channel block" from given dx sample.
				(*row) = sample_x->block(ic*input_height*input_width, 0, input_height*input_width, 1);
				row->resize(input_height, input_width);

			}//: for channel
		}//: for batch

		// Return output activations.
		return x_activations;
	}

	/*!
	 * Returns activations of input gradients (dx).
	 */
	virtual std::vector< std::shared_ptr <mic::types::Matrix<eT> > > & getInputGradientActivations() {

		// Allocate memory.
		lazyAllocateMatrixVector(dx_activations, batch_size * input_depth, input_height*input_width, 1);

		// Get dx batch.
		mic::types::MatrixPtr<eT> batch_dx = g['x'];

		// Iterate through filters and generate "activation image" for each one.
		for (size_t ib=0; ib< batch_size; ib++) {

			// Get input sample from batch!
			mic::types::MatrixPtr<eT> sample_dx = m["xs"];
			(*sample_dx) = batch_dx->col(ib);

			// Iterate through input channels.
			for (size_t ic=0; ic< input_depth; ic++) {
				// Get dx "row".
				mic::types::MatrixPtr<eT> row = dx_activations[ib*input_depth + ic];

				// Copy "channel block" from given dx sample.
				(*row) = sample_dx->block(ic*input_height*input_width, 0, input_height*input_width, 1);
				row->resize(input_height, input_width);

			}//: for channel
		}//: for batch

		// Return dx activations.
		return dx_activations;
	}

	/*!
	 * Returns activations of output neurons.
	 */
	virtual std::vector< std::shared_ptr <mic::types::Matrix<eT> > > & getOutputActivations() {

		// Allocate memory.
		lazyAllocateMatrixVector(y_activations, batch_size*output_depth, output_height*output_width, 1);

		// Get y batch.
		mic::types::MatrixPtr<eT> batch_y = s['y'];

		// Iterate through filters and generate "activation image" for each one.
		for (size_t ib=0; ib< batch_size; ib++) {

			// Get input sample from batch!
			mic::types::MatrixPtr<eT> sample_y = m["ys"];
			(*sample_y) = batch_y->col(ib);

			// Iterate through output channels.
			for (size_t oc=0; oc< output_depth; oc++) {
				// Get y "row".
				mic::types::MatrixPtr<eT> row = y_activations[ib*output_depth + oc];

				// Copy "channel block" from given dx sample.
				(*row) = sample_y->block(oc*output_height*output_width, 0, output_height*output_width, 1);
				row->resize(output_height, output_width);

			}//: for channel
		}//: for batch

		// Return output activations.
		return y_activations;
	}

	/*!
	 * Returns activations of gradients of output neurons.
	 */
	virtual std::vector< std::shared_ptr <mic::types::Matrix<eT> > > & getOutputGradientActivations() {

		// Allocate memory.
		lazyAllocateMatrixVector(dy_activations, output_depth*batch_size, output_height*output_width, 1);

		// Get dy batch.
		mic::types::MatrixPtr<eT> batch_dy = g['y'];

		// Iterate through filters and generate "activation image" for each one.
		for (size_t ib=0; ib< batch_size; ib++) {

			// Get input sample from batch!
			mic::types::MatrixPtr<eT> sample_dy = m["ys"];
			(*sample_dy) = batch_dy->col(ib);

			// Iterate through output channels.
			for (size_t oc=0; oc< output_depth; oc++) {
				// Get y "row".
				mic::types::MatrixPtr<eT> row = dy_activations[ib*output_depth + oc];

				// Copy "channel block" from given dx sample.
				(*row) = sample_dy->block(oc*output_height*output_width, 0, output_height*output_width, 1);
				row->resize(output_height, output_width);

			}//: for channel
		}//: for batch

		// Return output activations.
		return dy_activations;
	}

protected:

    /// Height of the input (e.g. 28 for MNIST).
    size_t input_height;

    /// Width of the input (e.g. 28 for MNIST).
    size_t input_width;

    /// Number of channels of the input (e.g. 3 for RGB images).
    size_t input_depth;

	/// Number of receptive fields in a single channel - vertical direction.
	size_t output_height;

	/// Number of receptive fields in a single channel - horizontal direction.
	size_t output_width;

	/// Number of filters = number of output channels.
	size_t output_depth;

	/// Size (length) of (mini)batch.
	size_t batch_size;

	/// Type of the layer.
	LayerTypes layer_type;

	/// Name (identifier of the type) of the layer.
	std::string layer_name;

	/// States - contains input [x] and output [y] matrices.
	mic::types::MatrixArray<eT> s;

	/// Gradients - contains input [x] and output [y] matrices.
	mic::types::MatrixArray<eT> g;

	/// Parameters - parameters of the layer, to be used by the derived classes.
	mic::types::MatrixArray<eT> p;

	/// Memory - a list of temporal parameters, to be used by the derived classes.
	mic::types::MatrixArray<eT> m;

	/// Array of optimization functions.
	mic::neural_nets::optimization::OptimizationArray<eT> opt;

	/// Vector containing activations of input neurons - used in visualization.
	std::vector< std::shared_ptr <mic::types::Matrix<eT> > > x_activations;

	/// Vector containing activations of gradients of inputs (dx) - used in visualization.
	std::vector< std::shared_ptr <mic::types::Matrix<eT> > > dx_activations;

	/// Vector containing activations of output neurons - used in visualization.
	std::vector< std::shared_ptr <mic::types::Matrix<eT> > > y_activations;

	/// Vector containing activations of gradients of outputs (dy) - used in visualization.
	std::vector< std::shared_ptr <mic::types::Matrix<eT> > > dy_activations;

	/*!
	 * Protected constructor, used only by the derived classes during the serialization. Empty!!
	 */
	Layer () { }

private:
	// Friend class - required for using boost serialization.
	template<typename tmp> friend class MultiLayerNeuralNetwork;
	template<typename tmp> friend class BackpropagationNeuralNetwork;
	template<typename tmp> friend class HebbianNeuralNetwork;

	// Friend class - required for using boost serialization.
    friend class boost::serialization::access;

    /*!
     * Serializes the layer to and from archive.
     * @param ar Used archive.
     * @param version Version of the layer class (not used currently).
     */
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version) {
        // Archive parameters.
        ar & input_height;
        ar & input_width;
        ar & input_depth;
        ar & output_height;
        ar & output_width;
        ar & output_depth;
        ar & batch_size;
        ar & layer_type;
        ar & layer_name;
        // Archive four matrix arrays.
        ar & s;
        ar & g;
        ar & p;
        ar & m;
        // TODO: serialize optimization function!
    }

};

} /* namespace mlnn */
} /* namespace mic */

// Just in the case that something important will change in the Layer class - set version.
BOOST_CLASS_VERSION(mic::mlnn::Layer<float>, 2)
BOOST_CLASS_VERSION(mic::mlnn::Layer<double>, 2)

#endif /* SRC_MLNN_LAYER_HPP_ */


//  Unit test for boost::lexical_cast.
//
//  See http://www.boost.org for most recent version, including documentation.
//
//  Copyright Antony Polukhin, 2011-2019.
//
//  Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt).

#include <boost/config.hpp>

#if defined(__INTEL_COMPILER)
#pragma warning(disable: 193 383 488 981 1418 1419)
#elif defined(BOOST_MSVC)
#pragma warning(disable: 4097 4100 4121 4127 4146 4244 4245 4511 4512 4701 4800)
#endif

#include <boost/lexical_cast.hpp>

#include <boost/math/special_functions/sign.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/tools/floating_point_comparison.hpp>

#if defined(BOOST_NO_STRINGSTREAM) || defined(BOOST_NO_STD_WSTRING)
#define BOOST_LCAST_NO_WCHAR_T
#endif

using namespace boost;

template <class T>
bool is_pos_inf(T value)
{
    return (boost::math::isinf)(value) && !(boost::math::signbit)(value);
}

template <class T>
bool is_neg_inf(T value)
{
    return (boost::math::isinf)(value) && (boost::math::signbit)(value);
}

template <class T>
bool is_pos_nan(T value)
{
    return (boost::math::isnan)(value) && !(boost::math::signbit)(value);
}

template <class T>
bool is_neg_nan(T value)
{
    /* There is some strange behaviour on Itanium platform with -nan nuber for long double.
    * It is a IA64 feature, or it is a boost::math feature, not a lexical_cast bug */
#if defined(__ia64__) || defined(_M_IA64)
    return (boost::math::isnan)(value)
            && ( boost::is_same<T, long double >::value || (boost::math::signbit)(value) );
#else
    return (boost::math::isnan)(value) && (boost::math::signbit)(value);
#endif
}

template <class T>
void test_inf_nan_templated()
{
    typedef T test_t;

    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("inf") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("INF") ) );

    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>("-inf") ) );
    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>("-INF") ) );

    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("+inf") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("+INF") ) );

    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("infinity") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("INFINITY") ) );

    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>("-infinity") ) );
    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>("-INFINITY") ) );

    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("+infinity") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("+INFINITY") ) );

    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("iNfiNity") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>("INfinity") ) );

    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>("-inFINITY") ) );
    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>("-INFINITY") ) );

    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("nan") ) );
    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("NAN") ) );

    BOOST_CHECK( is_neg_nan( lexical_cast<test_t>("-nan") ) );
    BOOST_CHECK( is_neg_nan( lexical_cast<test_t>("-NAN") ) );

    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("+nan") ) );
    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("+NAN") ) );

    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("nAn") ) );
    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("NaN") ) );

    BOOST_CHECK( is_neg_nan( lexical_cast<test_t>("-nAn") ) );
    BOOST_CHECK( is_neg_nan( lexical_cast<test_t>("-NaN") ) );

    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("+Nan") ) );
    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("+nAN") ) );

    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("nan()") ) );
    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>("NAN(some string)") ) );
    BOOST_CHECK_THROW( lexical_cast<test_t>("NAN(some string"), bad_lexical_cast );

    BOOST_CHECK(lexical_cast<std::string>( (boost::math::changesign)(std::numeric_limits<test_t >::infinity()))
                == "-inf" );
    BOOST_CHECK(lexical_cast<std::string>( std::numeric_limits<test_t >::infinity()) == "inf" );
    BOOST_CHECK(lexical_cast<std::string>( std::numeric_limits<test_t >::quiet_NaN()) == "nan" );
#if !defined(__ia64__) && !defined(_M_IA64)
    BOOST_CHECK(lexical_cast<std::string>(
                (boost::math::changesign)(std::numeric_limits<test_t >::quiet_NaN()))
                == "-nan" );
#endif

#ifndef  BOOST_LCAST_NO_WCHAR_T
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"inf") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"INF") ) );

    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>(L"-inf") ) );
    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>(L"-INF") ) );

    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"+inf") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"+INF") ) );

    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"infinity") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"INFINITY") ) );

    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>(L"-infinity") ) );
    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>(L"-INFINITY") ) );

    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"+infinity") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"+INFINITY") ) );

    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>(L"-infINIty") ) );
    BOOST_CHECK( is_neg_inf( lexical_cast<test_t>(L"-INFiniTY") ) );

    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"+inFINIty") ) );
    BOOST_CHECK( is_pos_inf( lexical_cast<test_t>(L"+INfinITY") ) );

    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>(L"nan") ) );
    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>(L"NAN") ) );

    BOOST_CHECK( is_neg_nan( lexical_cast<test_t>(L"-nan") ) );
    BOOST_CHECK( is_neg_nan( lexical_cast<test_t>(L"-NAN") ) );

    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>(L"+nan") ) );
    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>(L"+NAN") ) );

    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>(L"nan()") ) );
    BOOST_CHECK( is_pos_nan( lexical_cast<test_t>(L"NAN(some string)") ) );
    BOOST_CHECK_THROW( lexical_cast<test_t>(L"NAN(some string"), bad_lexical_cast );

    BOOST_CHECK(lexical_cast<std::wstring>( (boost::math::changesign)(std::numeric_limits<test_t >::infinity()))
                == L"-inf" );
    BOOST_CHECK(lexical_cast<std::wstring>( std::numeric_limits<test_t >::infinity()) == L"inf" );
    BOOST_CHECK(lexical_cast<std::wstring>( std::numeric_limits<test_t >::quiet_NaN()) == L"nan" );
#if !defined(__ia64__) && !defined(_M_IA64)
    BOOST_CHECK(lexical_cast<std::wstring>(
                (boost::math::changesign)(std::numeric_limits<test_t >::quiet_NaN()))
                == L"-nan" );
#endif

#endif
}

void test_inf_nan_float()
{
    test_inf_nan_templated<float >();
}

void test_inf_nan_double()
{
    test_inf_nan_templated<double >();
}

void test_inf_nan_long_double()
{
// We do not run tests on compilers with bugs
#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
    test_inf_nan_templated<long double >();
#endif
    BOOST_CHECK(true);
}

unit_test::test_suite *init_unit_test_suite(int, char *[])
{
    unit_test::test_suite *suite =
        BOOST_TEST_SUITE("lexical_cast inf anf nan parsing unit test");
    suite->add(BOOST_TEST_CASE(&test_inf_nan_float));
    suite->add(BOOST_TEST_CASE(&test_inf_nan_double));
    suite->add(BOOST_TEST_CASE(&test_inf_nan_long_double));

    return suite;
}


#include <boost/filesystem.hpp>
#include "particionar.cpp"
#include "../config.hpp"

int main()
{
    arma_rng::set_seed_random();

    mat datos;
    datos.load(config::sourceDir + "/guia1/icgtp1datos/concentlite.csv");

    auto particiones = ic::particionar(datos, 10, 80);
    string carpetaParticiones = config::sourceDir + "/guia1/icgtp1datos/particionesConcent/";
    boost::filesystem::create_directory(carpetaParticiones);
    ic::guardarParticiones(particiones, carpetaParticiones);

    return 0;
}


#pragma once

#include <boost/variant.hpp>
#include <boost/mpl/vector.hpp>

namespace metaSMT {

  namespace SAT {
    namespace tag {

    // sat literal tag
    // 0 is invalid
    // n is variable n
    // -m is variable n negated
    struct lit_tag {
      int id;
      template<typename STREAM>
      friend STREAM & operator<< (STREAM & out, lit_tag const & self)
      {  out << "sat_lit[" << self.id  << "]"; return out; }
      bool operator< (lit_tag const & other) const { return id < other.id; }
      lit_tag operator- () const { lit_tag l = { -id }; return l; }
      int var() const {return id >= 0 ? id: -id; }
    };

    struct c_tag {
      template<typename STREAM>
      friend STREAM & operator<< (STREAM & out, c_tag const & )
      {  out << "or"; return out; }
      bool operator< (c_tag const & ) const { return false; }
    };

    struct not_tag {
      template<typename STREAM>
      friend STREAM & operator<< (STREAM & out, not_tag const & )
      {  out << "not"; return out; }
      bool operator< (not_tag const & ) const { return false; }
    };

      // tag variant SAT
      typedef boost::mpl::vector<
          lit_tag
        , c_tag
      >::type SAT_Tags;

      typedef boost::make_variant_over< SAT_Tags >::type SAT_Tag;

    } // namespace tag
  } // namespace SAT
} // namespace metaSMT

//  vim: ft=cpp:ts=2:sw=2:expandtab


//
//  main.cpp
//  genome_generator
//
//  Created by Miloš Šimek on 10.03.15.
//  Copyright (c) 2015 Miloš Šimek. All rights reserved.
//

#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <exception>
#include <fstream>
#include <boost/program_options.hpp>

#include "DnaGenOptions.hpp"

namespace po = boost::program_options;
using namespace std;
typedef unsigned long long ULL;

char generateBase(Probabilities prob);

int main(int argc, const char * argv[]) {

    //load options
    DnaGenOptions options(argc, argv);
    if (options.optionsState() == OPS_HELP) return 0;
    if (options.optionsState() == OPS_ERR)  return 1;

    //set random generator seed
    if (options.getSeed()) srand(*options.getSeed());
    else                   srand(static_cast<unsigned>(time(nullptr)));

    //init output file
    ofstream genomeFile(options.getFileName());
    if (!genomeFile.is_open()) {
        cerr << "Could not open output file" << endl << endl;
        exit(1);
    }

    //GENERATING FRAGMENTS
    cout << "Generating" << endl;
    Probabilities probs = options.getProbabilities();
    string line(80, '_');   //line length set up to 80

    for (unsigned fragment = 0; fragment < options.getNum(); fragment++) {
        genomeFile << ">Randomly generated fragment number " << fragment + 1 << "\n";
        ULL baseCount = options.getLenthg();

        //generate lines
        while (baseCount > 80) {
            for (char& base: line) base = generateBase(probs);
            genomeFile << line << "\n";
            baseCount -= 80;
        }

        //generate last reminder line
        if (baseCount > 0) {
            while (baseCount > 0) {
                genomeFile << generateBase(probs);
                baseCount--;
            }
            genomeFile << "\n";
        }

        if (fragment + 1 < options.getNum()) genomeFile << "\n";
    }

    cout << "Done" << endl;
    return 0;
}

#define END_A prob.A
#define END_C prob.A + prob.C
#define END_G prob.A + prob.C + prob.G

char generateBase(Probabilities prob) {
    int randNumber = (rand() % 100) + 1;

    if (randNumber <= END_A) return 'A';
    if (randNumber <= END_C) return 'C';
    if (randNumber <= END_G) return 'G';
    return 'T';
}


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

#ifndef OBSTACLE_CRUISE_PLANNER__PID_BASED_PLANNER__PID_CONTROLLER_HPP_
#define OBSTACLE_CRUISE_PLANNER__PID_BASED_PLANNER__PID_CONTROLLER_HPP_

#include <boost/optional.hpp>

class PIDController
{
public:
  PIDController(const double kp, const double ki, const double kd)
  : kp_(kp), ki_(ki), kd_(kd), error_sum_(0.0)
  {
  }

  double calc(const double error)
  {
    error_sum_ += error;

    // TODO(murooka) use time for d gain calculation
    const double output =
      kp_ * error + ki_ * error_sum_ + (prev_error_ ? kd_ * (error - prev_error_.get()) : 0.0);
    prev_error_ = error;
    return output;
  }

  double getKp() const { return kp_; }
  double getKi() const { return ki_; }
  double getKd() const { return kd_; }

  void updateParam(const double kp, const double ki, const double kd)
  {
    kp_ = kp;
    ki_ = ki;
    kd_ = kd;

    error_sum_ = 0.0;
    prev_error_ = {};
  }

private:
  double kp_;
  double ki_;
  double kd_;

  double error_sum_;
  boost::optional<double> prev_error_;
};

#endif  // OBSTACLE_CRUISE_PLANNER__PID_BASED_PLANNER__PID_CONTROLLER_HPP_


// Copyright (C) 2016-2018 T. Zachary Laine
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/yap/expression.hpp>

template<typename T>
using term = boost::yap::terminal<boost::yap::expression, T>;

template<long long I>
using place_term =
    boost::yap::terminal<boost::yap::expression, boost::yap::placeholder<I>>;

template<typename T>
using ref = boost::yap::expression_ref<boost::yap::expression, T>;

namespace yap = boost::yap;
namespace bh = boost::hana;

void compile_placeholders()
{
    using namespace boost::yap::literals;

    {
        place_term<1> p1 = 1_p;
        (void)p1;
    }

    {
        place_term<1> p1 = 1_p;
        term<double> unity{1.0};
        yap::expression<
            yap::expr_kind::plus,
            bh::tuple<ref<place_term<1> &>, ref<term<double> &>>>
            expr = p1 + unity;
        (void)expr;
    }

    {
        place_term<1> p1 = 1_p;
        yap::expression<
            yap::expr_kind::plus,
            bh::tuple<ref<place_term<1> &>, place_term<2>>>
            expr = p1 + 2_p;
        (void)expr;
    }
}


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

#include <boost/random/uniform_real.hpp>
#include <boost/math/distributions/normal.hpp>

#include <util/evaluation.h>
#include <util/io.h>

#include <mf/mf.h>

using namespace std;
using namespace mf;
using namespace rg;
using namespace boost::numeric::ublas;

log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("main"));

int main(int argc, char* argv[]) {
	boost::mpi::communicator& world = mfInit(argc, argv);
	mfStart();

	if (world.rank() == 0)
	{
#ifndef NDEBUG
		LOG4CXX_WARN(logger, "Debug mode activated (runtimes may be slow).");
#endif
		Random32 random;
		unsigned tasksPerRank = 2;
		mf_size_type size1 = 200;
		mf_size_type size2 = 100;
		mf_size_type blocks1 = world.size();
		mf_size_type blocks2 = tasksPerRank;
		mf_size_type nnz = 500;

		// generate a random data matrix without empty rows/columns
		SparseMatrix v(size1, size2);
		generateRandom(v, nnz, random, boost::normal_distribution<>(0, 1));
		projectFrequent(v, 0);
		size1 = v.size1(); size2 = v.size2();
		LOG4CXX_INFO(logger, "Data matrix: "
			<< v.size1() << " x " << v.size2() << ", " << v.nnz() << " nonzeros");

		// take a small sample and remove empty rows/columns
		ProjectedSparseMatrix Vsample;
		projectRandomSubmatrix(random, v, Vsample, v.size1()/3, v.size2()/3);
		projectFrequent(Vsample, 0);
		LOG4CXX_INFO(logger, "Sample matrix: "
			<< Vsample.data.size1() << " x " << Vsample.data.size2()
			<< ", " << Vsample.data.nnz() << " nonzeros");

		// generate factor matrices
		mf_size_type r = 10;
		DenseMatrix w(size1, r);
		DenseMatrixCM h(r, size2);
		generateRandom(w, random, boost::uniform_real<>(-0.5, 0.5));
		generateRandom(h, random, boost::uniform_real<>(-0.5, 0.5));

		// extract the rows and columns corresponding to the sample
		DenseMatrix wSample;
		DenseMatrixCM hSample;
		project1(w, wSample, Vsample.map1);
		project2(h, hSample, Vsample.map2);
		LOG4CXX_INFO(logger, "Projected row factors:  " << wSample);
		LOG4CXX_INFO(logger, "Projected column factors:  " << hSample);

		// distribute the factor matrices
		DistributedDenseMatrix dw = distributeMatrix("w", blocks1, 1, true, w);
		DistributedDenseMatrixCM dh = distributeMatrix("h", 1, blocks2, false, h);

		// extract the same rows/columns from the distributed matrix
		DenseMatrix dwSample;
		DenseMatrixCM dhSample;
		project1(dw, dwSample, Vsample.map1);
		project2(dh, dhSample, Vsample.map2);
		LOG4CXX_INFO(logger, "Projected row factors:  " << dwSample);
		LOG4CXX_INFO(logger, "Projected column factors:  " << dhSample);
	}

	mfStop();
	mfFinalize();

	return 0;
}


/*

PICCANTE
The hottest HDR imaging library!
http://vcg.isti.cnr.it/piccante

Copyright (C) 2014
Visual Computing Laboratory - ISTI CNR
http://vcg.isti.cnr.it
First author: Francesco Banterle

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

*/

#ifndef PIC_UTIL_RASTERIZER_HPP
#define PIC_UTIL_RASTERIZER_HPP

#include <algorithm>
#include <cstdlib>

#include "../base.hpp"
#include "../image.hpp"
#include "../util/vec.hpp"
#include "../util/math.hpp"

#ifndef PIC_EIGEN_NOT_BUNDLED
    #include "../externals/Eigen/Dense"
#else
    #include <Eigen/Dense>
#endif

namespace pic {

/**
 * @brief drawLine renders a line (v0, v1) with color into img.
 * @param img is the image where to render the line (v0, v1).
 * @param v0 is the first vertex of the line.
 * @param v1 is the second vertex of the line.
 * @param color is the color of the line (v0, v1).
 */
PIC_INLINE void drawLine(Image *img, Vec2i v0, Vec2i v1, float *color)
{
    if(img == NULL || color == NULL) {
        return;
    }

    CLAMP(v0[0], img->width);
    CLAMP(v1[0], img->width);
    CLAMP(v0[1], img->height);
    CLAMP(v1[1], img->height);

    float *data = img->data;

    if(v0[0] > v1[0]) {
        std::swap(v0, v1);
    }

    int dx = v1[0] - v0[0];
    int dy = v1[1] - v0[1];

    //Vertical line
    if(dx == 0){
        if(v0[1] > v1[1]) {
            std::swap(v0, v1);
        }

        for(int y = v0[1]; y < v1[1]; y++) {
            int ind = (v0[0] + y * img->width) * img->channels;

            for(int k = 0; k < img->channels; k++) {
                data[ind + k] = color[k];
            }
        }
        return;
    }

    //Horizontal line
    if(dy == 0) {
        int ind_y = v0[1] * img->width;

        for(int x = v0[0]; x < v1[0]; x++) {
            int ind = (x + ind_y) * img->channels;

            for(int k = 0; k < img->channels; k++) {
                data[ind + k] = color[k];
            }
        }
    }

    //General case
    if(std::abs(dy) < std::abs(dx)) {
        //m < 1
        int e = 0;
        int s;
        if(dy < 0) {
            s  = -1;
            dy = -dy;
        } else {
            s = 1;
        }

        int y = v0[1];
        for(int x = v0[0]; x <= v1[0]; x++) {
            int ind = (x + y * img->width) * img->channels;

            for(int k = 0; k < img->channels; k++) {
                data[ind + k] = color[k];
            }

            e += dy;
            if((e << 1) >= dx) {
              y += s;
              e -= dx;
            }
        }
    } else {
        //m > 1
        if(v0[1] > v1[1]){
            std::swap(v0, v1);
        }

        dx = v1[0] - v0[0];
        dy = v1[1] - v0[1];

        int e = 0;
        int s;
        if(dx < 0){
            s  = -1;
            dx = -dx;
        } else {
            s = 1;
        }

        int x = v0[0];

        for(int y = v0[1]; y <= v1[1]; y++) {

            int ind = (x + y * img->width) * img->channels;

            for(int k = 0; k < img->channels; k++) {
                data[ind + k] = color[k];
            }

            e += dx;
            if((e << 1) >= dy) {
              x += s;
              e -= dy;
            }
        }
    }
}

/**
 * @brief drawPoints
 * @param img
 * @param points
 */
#ifndef PIC_DISABLE_EIGEN
PIC_INLINE void drawPoints(Image *img, std::vector< Eigen::Vector2f > &points, float *color)
{
    if(img == NULL) {
        return;
    }

    if(color == NULL) {
        color = new float[img->channels];
        for(int i = 0; i < img->channels; i++) {
            color[i] = 1.0f;
        }
    }

    for(size_t i = 0; i < points.size(); i++) {
        int x = int(points[i][0]);
        int y = int(points[i][1]);
        float *pixel = (*img)(x, y);

        for(int j = 0; j < img->channels; j++) {
            pixel[j] = color[j];
        }
    }
}
#endif

/**
 * @brief evaluateGaussian renders a Gaussian function which is centred
 * in the image.
 * @param img is an input image
 * @param sigma is the standard deviation of the Gaussian function.
 * @param bNormTerm is a boolean value. If it is true the Gaussian function
 * is normalized, false otherwise.
 */
PIC_INLINE void evaluateGaussian(Image *img, float sigma = -1.0f,
                                 bool bNormTerm = false)
{
    if(img != NULL) {
        return;
    }

    if(sigma < 0.0f) {
        sigma = float(MIN(img->width, img->height)) / 5.0f;
    }

    float sigma2 = (sigma * sigma * 2.0f);

    int halfWidth  = img->width  >> 1;
    int halfHeight = img->height >> 1;

    float normTerm = bNormTerm ? sigma * sqrtf(C_PI) : 1.0f ;

    #pragma omp parallel for

    for(int j = 0; j < img->height; j++) {
        int j_squared = j - halfHeight;
        j_squared = j_squared * j_squared;

        for(int i = 0; i < img->width; i++) {
            int i_squared = i - halfWidth;
            i_squared = i_squared * i_squared;

            float gaussVal = expf(-float(i_squared + j_squared) / sigma2) / normTerm;

            float *tmp_data = (*img)(i, j);

            for(int k = 0; k < img->channels; k++) {
                tmp_data[k] = gaussVal;
            }
        }
    }
}

/**
 * @brief evaluateSolid renders a centred circle.
 * @param img is an input image
 */
PIC_INLINE void evaluateSolid(Image *img)
{
    if(img == NULL) {
        return;
    }

    int halfWidth  = img->width  >> 1;
    int halfHeight = img->height >> 1;

    int radius_squared = (halfWidth * halfWidth + halfHeight * halfHeight) >> 1;

    #pragma omp parallel for

    for(int j = 0; j < img->height; j++) {
        int j_squared = j - halfHeight;
        j_squared = j_squared * j_squared;

        for(int i = 0; i < img->width; i++) {
            int i_squared = i - halfWidth;
            i_squared = i_squared * i_squared;

            float val = 0.0f;

            if((i_squared + j_squared) < radius_squared) {
                val = 1.0f;
            }

            float *tmp_data = (*img)(i, j);

            for(int k = 0; k < img->channels; k++) {
                tmp_data[k] = val;
            }
        }
    }
}

}

#endif //


//==================================================================================================
/*
  Copyright 2017 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/algorithm/find_if.hpp>
#include <numeric>
#include <vector>
#include <simd_test.hpp>

using namespace boost::simd;

template<typename T> struct f
{
  using p_t = pack<T>;
  f(const T & n) :n_(n), vn_(n){}

  bool                  operator()(T a)           const {return a >= n_;  }
  bs::as_logical_t<p_t> operator()(const p_t & a) const {return a >= vn_; }
  T n_;
  p_t vn_;
};

STF_CASE_TPL( "Check unary simd::find_if", STF_NUMERIC_TYPES )
{
  static const int N = pack<T>::static_size;

  std::vector<T> values(2*N+1);
  std::iota(values.begin(), values.end(),T(1));
  {
    auto f1 = std::find_if(values.begin(), values.end(), [](T e) { return e >= N; } );
    auto f2 = boost::simd::find_if(values.data(), values.data()+2*N+1, f<T>(T(N)));
    STF_EQUAL ( *f1, *f2 );
    auto f3 = std::find_if(values.begin(), values.end(), [](T e) { return e >= 104; } );
    auto f4 = boost::simd::find_if(values.data(), values.data()+2*N+1, f<T>(T(104)) );
    STF_EQUAL ( *f3, *f4 );
  }

  {
    auto f1 = std::find_if(values.begin(), values.end(), [](T e) { return e >= 0; } );
    auto f2 = boost::simd::find_if(values.data(), values.data()+2*N+1, f<T>(T(0)) );
    STF_EQUAL ( *f1, *f2 );
    auto f3 = std::find_if(values.begin(), values.end(), [](T e) { return e >= 2*N; });
    auto f4 = boost::simd::find_if(values.data(), values.data()+2*N+1, f<T>(T(2*N)));
    STF_EQUAL ( *f3, *f4 );
  }
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_IF_MINUS_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_IF_MINUS_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-boolean
    Function object implementing if_minus capabilities

    The function returns the second entry or the difference of the
    second and third entries, according to the first entry being @ref False
    or @ref True

    @par Semantic:

    For every parameters @c c of type @c C and @c x, @c y of type @c T:

    @code
    T1 r = if_minus(c, x, y);
    @endcode

    is similar to:

    @code
    T1 r = c ? x-y : x;
    @endcode
  **/
  Value if_minus(Value const& c, Value const& x, Value const&  y);

  //@overload
  Value if_minus(LogicalValue const& c, Value const& x, Value const& y);
} }
#endif

#include <boost/simd/function/scalar/if_minus.hpp>
#include <boost/simd/function/simd/if_minus.hpp>

#endif


#define BOOST_TEST_MODULE fc_test_mod
#include <boost/test/included/unit_test.hpp>

#include <fc/uint256.hpp>
using namespace fc;

BOOST_AUTO_TEST_SUITE(fc_test_mod)

BOOST_AUTO_TEST_CASE(test_uint256) try {
    constexpr __uint128_t uint128max = std::numeric_limits<__uint128_t>::max();

    auto test_max_uint256 = uint256(uint128max, uint128max);
    auto test_min_uint256 = uint256();

    BOOST_CHECK_EQUAL(test_min_uint256, uint256::min());
    BOOST_CHECK_EQUAL(test_max_uint256, uint256::max());
} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_SUITE_END()


//==================================================================================================
/*
  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================

#include <boost/simd/pack.hpp>
#include <boost/simd/function/load.hpp>
#include <list>
#include <numeric>
#include <simd_test.hpp>

template <typename T, std::size_t N, typename Env>
void test(Env& $)
{
  std::list<T> v(N);
  std::iota(v.begin(), v.end(), T{1});

  boost::simd::pack<T, N> p(v.begin(), v.end());
  STF_ALL_EQUAL(p, v);
}

STF_CASE_TPL( "Check that pack constructs from iterators pair" , STF_NUMERIC_TYPES)
{
  test<T,  1>($);
  test<T,  2>($);
  test<T,  4>($);
  test<T,  8>($);
  test<T, 16>($);
  test<T, 32>($);
}


#pragma once
#include <trajopt_utils/macros.h>
TRAJOPT_IGNORE_WARNINGS_PUSH
#include <Eigen/Core>
#include <qpOASES.hpp>
TRAJOPT_IGNORE_WARNINGS_POP

#include <trajopt_sco/solver_interface.hpp>
#include <trajopt_utils/macros.h>

namespace sco
{
/**
 * qpOASESModel uses the LGPL solver qpOASES to solve a linearly constrained QP.
 * qpOASES solves a problem in the form:
 * ```
 * min   1/2*x'Hx + x'g
 * s.t.  lb  <=  x <= ub
 * lbA <= Ax <= ubA
 * ```
 *
 * More informations about the solver are available at:
 * https://projects.coin-or.org/qpOASES
 */
class qpOASESModel : public Model
{
  /** pointer to a qpOASES Sequential Quadratic Problem*/
  std::shared_ptr<qpOASES::SQProblem> qpoases_problem_;
  qpOASES::Options qpoases_options_; /**< qpOASES solver options */

  qpOASES::SymSparseMat H_; /**< Quadratic cost matrix */
  qpOASES::SparseMatrix A_; /**< Constraints matrix */

  /** Updates qpOASES Hessian matrix from QuadExpr expression.
   *  Transforms QuadExpr objective_ into the qpOASES sparse matrix H_*/
  void updateObjective();

  /** Updates qpOASES constraints from AffExpr expression.
   *  Transforms AffExpr cntr_exprs_ into the qpOASES sparse matrix A_, and
   *  vectors lbA_ and ubA_ */
  void updateConstraints();

  /**
   * Instantiates a new qpOASES problem if it has not been instantiated yet
   * or if the size of the problem has changed.
   *
   * @returns true if a new qpOASES problem has been instantiated
   */
  bool updateSolver();

  /**
   * Instantiates a new qpOASES problem
   */
  void createSolver();

  VarVector vars_;                 /**< model variables */
  CntVector cnts_;                 /**< model's constraints sizes */
  DblVec lb_, ub_;                 /**< variables bounds */
  AffExprVector cnt_exprs_;        /**< constraints expressions */
  ConstraintTypeVector cnt_types_; /**< constraints types */
  DblVec solution_;                /**< optimizizer's solution for current model */

  IntVec H_row_indices_;     /**< row indices for Hessian, CSC format */
  IntVec H_column_pointers_; /**< column pointers for Hessian, CSC format */
  DblVec H_csc_data_;        /**< Hessian values in CSC format */
  Eigen::VectorXd g_;        /**< gradient of the optimization problem */

  IntVec A_row_indices_;     /**< row indices for constraint matrix, CSC format */
  IntVec A_column_pointers_; /**< column pointers for constraint matrix, CSC format */
  DblVec A_csc_data_;        /**< constraint matrix values in CSC format */
  DblVec lbA_, ubA_;         /**< linear constraints upper and lower limits */

  QuadExpr objective_; /**< objective QuadExpr expression */

public:
  qpOASESModel();
  virtual ~qpOASESModel();

  Var addVar(const std::string& name) override;
  Cnt addEqCnt(const AffExpr&, const std::string& name) override;
  Cnt addIneqCnt(const AffExpr&, const std::string& name) override;
  Cnt addIneqCnt(const QuadExpr&, const std::string& name) override;
  void removeVars(const VarVector& vars) override;
  void removeCnts(const CntVector& cnts) override;

  void update() override;
  void setVarBounds(const VarVector& vars, const DblVec& lower, const DblVec& upper) override;
  DblVec getVarValues(const VarVector& vars) const override;
  virtual CvxOptStatus optimize() override;
  virtual void setObjective(const AffExpr&) override;
  virtual void setObjective(const QuadExpr&) override;
  virtual void writeToFile(const std::string& fname) override;
  virtual VarVector getVars() const override;
};
}


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

#include "test/test_earn_save_invest_repeat.h"

#include <array>
#include <stdexcept>

#include "crypto/sha256.h"
#include "sapling/address.h"
#include "sapling/note.h"
#include "sapling/noteencryption.h"
#include "sapling/prf.h"
#include "sapling/sapling_util.h"

#include <boost/test/unit_test.hpp>
#include <librustzcash.h>
#include <sodium.h>

BOOST_FIXTURE_TEST_SUITE(noteencryption_tests, BasicTestingSetup)

BOOST_AUTO_TEST_CASE(note_plain_text_test)
{
    auto xsk = libzcash::SaplingSpendingKey(uint256()).expanded_spending_key();
    auto fvk = xsk.full_viewing_key();
    auto ivk = fvk.in_viewing_key();
    libzcash::SaplingPaymentAddress addr = *ivk.address({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});

    std::array<unsigned char, ZC_MEMO_SIZE> memo;
    for (size_t i = 0; i < ZC_MEMO_SIZE; i++) {
        // Fill the message with dummy data
        memo[i] = (unsigned char) i;
    }

    libzcash::SaplingNote note(addr, 39393);
    auto cmu_opt = note.cmu();
    if (!cmu_opt) {
        BOOST_ERROR("SaplingNote cm failed");
    }
    uint256 cmu = cmu_opt.get();
    libzcash::SaplingNotePlaintext pt(note, memo);

    auto res = pt.encrypt(addr.pk_d);
    if (!res) {
        BOOST_ERROR("SaplingNotePlaintext encrypt failed");
    }

    auto enc = res.get();

    auto ct = enc.first;
    auto encryptor = enc.second;
    auto epk = encryptor.get_epk();

    // Try to decrypt with incorrect commitment
    BOOST_CHECK(!libzcash::SaplingNotePlaintext::decrypt(
        ct,
        ivk,
        epk,
        uint256()
    ));

    // Try to decrypt with correct commitment
    auto foo = libzcash::SaplingNotePlaintext::decrypt(
        ct,
        ivk,
        epk,
        cmu
    );

    if (!foo) {
        BOOST_ERROR("SaplingNotePlaintext decrypt failed");
    }

    auto bar = foo.get();

    BOOST_CHECK(bar.value() == pt.value());
    BOOST_CHECK(bar.memo() == pt.memo());
    BOOST_CHECK(bar.d == pt.d);
    BOOST_CHECK(bar.rcm == pt.rcm);

    auto foobar = bar.note(ivk);

    if (!foobar) {
        // Improve this message
        BOOST_ERROR("Invalid note");
    }

    auto new_note = foobar.get();

    BOOST_CHECK(note.value() == new_note.value());
    BOOST_CHECK(note.d == new_note.d);
    BOOST_CHECK(note.pk_d == new_note.pk_d);
    BOOST_CHECK(note.r == new_note.r);
    BOOST_CHECK(note.cmu() == new_note.cmu());

    libzcash::SaplingOutgoingPlaintext out_pt;
    out_pt.pk_d = note.pk_d;
    out_pt.esk = encryptor.get_esk();

    auto ovk = random_uint256();
    auto cv = random_uint256();
    auto cm = random_uint256();

    auto out_ct = out_pt.encrypt(
        ovk,
        cv,
        cm,
        encryptor
    );

    auto decrypted_out_ct = out_pt.decrypt(
        out_ct,
        ovk,
        cv,
        cm,
        encryptor.get_epk()
    );

    if (!decrypted_out_ct) {
        BOOST_ERROR("SaplingOutgoingPlaintext decrypt failed");
    }

    auto decrypted_out_ct_unwrapped = decrypted_out_ct.get();

    BOOST_CHECK(decrypted_out_ct_unwrapped.pk_d == out_pt.pk_d);
    BOOST_CHECK(decrypted_out_ct_unwrapped.esk == out_pt.esk);

    // Test sender won't accept invalid commitments
    BOOST_CHECK(!libzcash::SaplingNotePlaintext::decrypt(
        ct,
        epk,
        decrypted_out_ct_unwrapped.esk,
        decrypted_out_ct_unwrapped.pk_d,
        uint256()
    ));

    // Test sender can decrypt the note ciphertext.
    foo = libzcash::SaplingNotePlaintext::decrypt(
        ct,
        epk,
        decrypted_out_ct_unwrapped.esk,
        decrypted_out_ct_unwrapped.pk_d,
        cmu
    );

    if (!foo) {
        BOOST_ERROR("Sender decrypt note ciphertext failed.");
    }

    bar = foo.get();

    BOOST_CHECK(bar.value() == pt.value());
    BOOST_CHECK(bar.memo() == pt.memo());
    BOOST_CHECK(bar.d == pt.d);
    BOOST_CHECK(bar.rcm == pt.rcm);
}

BOOST_AUTO_TEST_CASE(SaplingApi_test)
{
    // Create recipient addresses
    auto sk = libzcash::SaplingSpendingKey(uint256()).expanded_spending_key();
    auto vk = sk.full_viewing_key();
    auto ivk = vk.in_viewing_key();
    libzcash::SaplingPaymentAddress pk_1 = *ivk.address({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
    libzcash::SaplingPaymentAddress pk_2 = *ivk.address({4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});

    // Blob of stuff we're encrypting
    std::array<unsigned char, ZC_SAPLING_ENCPLAINTEXT_SIZE> message;
    for (size_t i = 0; i < ZC_SAPLING_ENCPLAINTEXT_SIZE; i++) {
    // Fill the message with dummy data
    message[i] = (unsigned char) i;
    }

    std::array<unsigned char, ZC_SAPLING_OUTPLAINTEXT_SIZE> small_message;
    for (size_t i = 0; i < ZC_SAPLING_OUTPLAINTEXT_SIZE; i++) {
    // Fill the message with dummy data
    small_message[i] = (unsigned char) i;
    }

    // Invalid diversifier
    BOOST_CHECK(boost::none == libzcash::SaplingNoteEncryption::FromDiversifier({1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));

    // Encrypt to pk_1
    auto enc = *libzcash::SaplingNoteEncryption::FromDiversifier(pk_1.d);
    auto ciphertext_1 = *enc.encrypt_to_recipient(
            pk_1.pk_d,
            message
    );
    auto epk_1 = enc.get_epk();
    {
        uint256 test_epk;
        uint256 test_esk = enc.get_esk();
        BOOST_CHECK(librustzcash_sapling_ka_derivepublic(pk_1.d.begin(), test_esk.begin(), test_epk.begin()));
        BOOST_CHECK(test_epk == epk_1);
    }
    auto cv_1 = random_uint256();
    auto cm_1 = random_uint256();
    auto out_ciphertext_1 = enc.encrypt_to_ourselves(
            sk.ovk,
            cv_1,
            cm_1,
            small_message
    );

    // Encrypt to pk_2
    enc = *libzcash::SaplingNoteEncryption::FromDiversifier(pk_2.d);
    auto ciphertext_2 = *enc.encrypt_to_recipient(
            pk_2.pk_d,
            message
    );
    auto epk_2 = enc.get_epk();

    auto cv_2 = random_uint256();
    auto cm_2 = random_uint256();
    auto out_ciphertext_2 = enc.encrypt_to_ourselves(
            sk.ovk,
            cv_2,
            cm_2,
            small_message
    );

    // Test nonce-reuse resistance of API
    {
        auto tmp_enc = *libzcash::SaplingNoteEncryption::FromDiversifier(pk_1.d);

        tmp_enc.encrypt_to_recipient(
                pk_1.pk_d,
                message
                );

        BOOST_CHECK_THROW(tmp_enc.encrypt_to_recipient(
                pk_1.pk_d,
                message
                ), std::logic_error);

        tmp_enc.encrypt_to_ourselves(
                sk.ovk,
                cv_2,
                cm_2,
                small_message
                );

        BOOST_CHECK_THROW(tmp_enc.encrypt_to_ourselves(
                sk.ovk,
                cv_2,
                cm_2,
                small_message
                ), std::logic_error);
    }

    // Try to decrypt
    auto plaintext_1 = *AttemptSaplingEncDecryption(
            ciphertext_1,
            ivk,
            epk_1
    );
    BOOST_CHECK(message == plaintext_1);

    auto small_plaintext_1 = *libzcash::AttemptSaplingOutDecryption(
            out_ciphertext_1,
            sk.ovk,
            cv_1,
            cm_1,
            epk_1
    );
    BOOST_CHECK(small_message == small_plaintext_1);

    auto plaintext_2 = *AttemptSaplingEncDecryption(
            ciphertext_2,
            ivk,
            epk_2
    );
    BOOST_CHECK(message == plaintext_2);

    auto small_plaintext_2 = *libzcash::AttemptSaplingOutDecryption(
            out_ciphertext_2,
            sk.ovk,
            cv_2,
            cm_2,
            epk_2
    );
    BOOST_CHECK(small_message == small_plaintext_2);

    // Try to decrypt out ciphertext with wrong key material
    BOOST_CHECK(!libzcash::AttemptSaplingOutDecryption(
            out_ciphertext_1,
            random_uint256(),
            cv_1,
            cm_1,
            epk_1
    ));
    BOOST_CHECK(!libzcash::AttemptSaplingOutDecryption(
            out_ciphertext_1,
            sk.ovk,
            random_uint256(),
            cm_1,
            epk_1
    ));
    BOOST_CHECK(!libzcash::AttemptSaplingOutDecryption(
            out_ciphertext_1,
            sk.ovk,
            cv_1,
            random_uint256(),
            epk_1
    ));
    BOOST_CHECK(!libzcash::AttemptSaplingOutDecryption(
            out_ciphertext_1,
            sk.ovk,
            cv_1,
            cm_1,
            random_uint256()
    ));

    // Try to decrypt with wrong ephemeral key
    BOOST_CHECK(!AttemptSaplingEncDecryption(
            ciphertext_1,
            ivk,
            epk_2
    ));
    BOOST_CHECK(!AttemptSaplingEncDecryption(
            ciphertext_2,
            ivk,
            epk_1
    ));

    // Try to decrypt with wrong ivk
    BOOST_CHECK(!libzcash::AttemptSaplingEncDecryption(
            ciphertext_1,
            uint256(),
            epk_1
    ));
    BOOST_CHECK(!libzcash::AttemptSaplingEncDecryption(
            ciphertext_2,
            uint256(),
            epk_2
    ));
}

BOOST_AUTO_TEST_SUITE_END()


#include <iostream>
#include <chrono>
#include <fstream>
#include <memory>
#include <boost/mpi.hpp>

#include "ga/Hipercube.h"
#include "functions/Rastrigin.h"
#include "selection/Tournament.h"
#include "operators/mutations/GaussianMutator.h"
#include "operators/xover/LinearXOver.h"
#include "ga/HAEA/AbstractHAEA.h"
#include "ga/HAEA/PosixHAEA.h"
#include "ga/HAEA/OpenMPHAEA.h"

using namespace std;
using ns = chrono::microseconds;
using get_time = chrono::steady_clock;

void test();
void test1();

int main() {
    return 0;
}

void test1() {
    Hipercube h(-5.12, 5.12, 10000);
    vector<double> a = h.getRandomIndividual();
    vector<double> b = h.getRandomIndividual();
    vector<vector<double>> inds;
    inds.push_back(a);
    inds.push_back(b);

    GaussianMutator gaussianMutator(0.0, 0.3, 0.1);
    LinearXOver linearXOver;
    auto start = get_time::now();
    for(int i = 0; i < 100; ++i) {
        h.repair(a);
    }
    auto end = get_time::now();
    auto diff = end - start;
    cout << std::chrono::duration_cast<ns>(diff).count() << " ";
}

void test() {
    size_t popSize[] = {50};
    for(int i = 0; i < 1; ++i) {
        cout << "population size: " << popSize[i] << endl;
        for(size_t j = 1; j <= 5; j += 7) {

            ofstream file;
            file.open(to_string(popSize[i]) + "_" + to_string(j) + ".txt");

            cout << "threads: " << j << endl;
            size_t ITERS = 500;
            size_t POP = popSize[i];
            size_t DIM = 1000;
            size_t THREADS = j;
            int sampling = 3;

            Hipercube space(-5.12, 5.12, DIM);
            Rastrigin optimizationFunction;
            Tournament selection(optimizationFunction, 4);

            cout << optimizationFunction.apply(space.getRandomIndividual()) << endl;

            vector< shared_ptr<Operator<vector<double> > > > opers;
            opers.push_back(make_shared<GaussianMutator>(0.0, 0.3, 0.1));
            opers.push_back(make_shared<LinearXOver>());

            PosixHAEA<vector<double>> search(selection, opers, POP, ITERS);
            search.setThreads(THREADS);

            double rmean = 0.0;

            for(int k = 0; k < sampling; ++k) {
                cout << "iter: " << k << endl;
                auto start = get_time::now();
                vector<vector<double>> pop = search.solve(&space, &optimizationFunction);
                double bestInd = 10e8;
                for(size_t z = 0; z < pop.size(); ++z) {
                    double elem = optimizationFunction.apply(pop[z]);
                    if(elem < bestInd) {
                        bestInd = elem;
                    }
                }

                cout << bestInd << endl;
                auto end = get_time::now();
                auto diff = end - start;
                file  << chrono::duration_cast<ns>(diff).count() << " ";
                /*vector<vector<double> > result = search.solve(&space, &optimizationFunction, THREADS);
                double mean = 0.0;

                for(size_t i = 0; i < result.size(); ++i) {
                    for(size_t j = 0; j < result[0].size(); ++j) {
                        cout << result[i][j] << " ";
                    }
                    mean += optimizationFunction.apply(result[i]);
                }

                mean /= result.size();
                rmean += mean;*/
            }
            file.close();
        }
    }
}


#pragma once

#include <bitset>
#include <functional>
#include <numeric>
#include <stdexcept>
#include <vector>

#include <dtl/dtl.hpp>
#include <dtl/bits.hpp>
#include <dtl/math.hpp>
#include <dtl/simd.hpp>
#include <dtl/filter/blocked_bloomfilter/vector_helper.hpp>

#include "immintrin.h"

#include <boost/integer/static_min_max.hpp>

namespace dtl {

namespace {

//===----------------------------------------------------------------------===//
// Recursive template to work with multi-word sectors.
//
// Specialization for the case where sector count < word_cnt ('sltw'),
// which results in a random access pattern within the block (and sector).
// I.e., for every bit to set, the corresponding word needs to be determined
// and loaded.
//
// Same as in the 'sgew' case, we process the block sector by sector,
// however, a sector consists of more than one word.
//===----------------------------------------------------------------------===//
template<
    typename key_t,               // the key type
    typename word_t,              // the word type
    u32 word_cnt,                 // the number of words per sector
    u32 k,                        // the number of bits to set/test
    template<typename Ty, u32 i> class hasher,      // the hash function family to use
    typename hash_value_t,        // the hash value type to use

    u32 hash_fn_idx,              // current hash function index (used for recursion)
    u32 remaining_hash_bit_cnt,   // the number of remaining hash bits (used for recursion)
    u32 remaining_k_cnt           // the remaining number of bits to set in the sector (used for recursion)
>
struct multiword_sector {

  //===----------------------------------------------------------------------===//
  // Static part
  //===----------------------------------------------------------------------===//

  static constexpr u32 word_cnt_log2 = dtl::ct::log_2<word_cnt>::value;
  static constexpr u32 word_cnt_log2_mask = (1u << word_cnt_log2) - 1;
  static_assert(dtl::is_power_of_two(word_cnt), "Parameter 'word_cnt' must be a power of two.");
  static constexpr u32 word_bitlength = sizeof(word_t) * 8;
  static constexpr u32 word_bitlength_log2 = dtl::ct::log_2<word_bitlength>::value;
  static constexpr u32 word_bitlength_log2_mask = (1u << word_bitlength_log2) - 1;

  static constexpr u32 hash_value_bitlength = sizeof(hash_value_t) * 8;

  static constexpr u32 k_cnt_per_sector = k;

  static constexpr u32 current_k_idx() { return k - remaining_k_cnt; }

  static constexpr u32 hash_bit_cnt_per_k = word_cnt_log2 + word_bitlength_log2;

  static constexpr u1 rehash = remaining_hash_bit_cnt < hash_bit_cnt_per_k;
  static constexpr u32 remaining_hash_bit_cnt_after_rehash = rehash ? hash_value_bitlength : remaining_hash_bit_cnt;
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Insert
  //===----------------------------------------------------------------------===//
  __forceinline__
  static void
  insert(word_t* __restrict sector_ptr, const key_t key) noexcept {

    hash_value_t hash_val = 0;

    // Call the recursive function
    static constexpr u32 remaining_hash_bits = 0;
    multiword_sector<key_t, word_t, word_cnt, k,
                     hasher, hash_value_t, hash_fn_idx, remaining_hash_bits,
                     remaining_k_cnt>
                     ::insert_atomic(sector_ptr, key, hash_val);
//                     ::insert(sector_ptr, key, hash_val);
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Insert (Recursive)
  //===----------------------------------------------------------------------===//
  __forceinline__
  static void
  insert(word_t* __restrict sector_ptr, const key_t key, hash_value_t& hash_val) noexcept {

    hash_val = rehash ? hasher<key_t, hash_fn_idx>::hash(key) : hash_val;

    // Determine the word of interest
    constexpr u32 word_idx_shift = remaining_hash_bit_cnt_after_rehash - word_cnt_log2;
    $u32 word_idx = ((hash_val >> word_idx_shift) & word_cnt_log2_mask);

    // Load the word of interest
    word_t word = sector_ptr[word_idx];

    // Set a bit in the given word
    constexpr u32 bit_idx_shift = remaining_hash_bit_cnt_after_rehash - word_cnt_log2 - word_bitlength_log2;
    $u32 bit_idx = ((hash_val >> bit_idx_shift) & word_bitlength_log2_mask);
    word |= word_t(1) << bit_idx;

    // Update the bit vector
    sector_ptr[word_idx] = word;

    // Process remaining k's recursively, if any
    multiword_sector<key_t, word_t, word_cnt, k,
                     hasher, hash_value_t,
                     rehash ? hash_fn_idx + 1 : hash_fn_idx,
                     remaining_hash_bit_cnt_after_rehash >= hash_bit_cnt_per_k ? remaining_hash_bit_cnt_after_rehash - hash_bit_cnt_per_k : 0,
                     remaining_k_cnt - 1>
      ::insert(sector_ptr, key, hash_val);
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Insert Atomic (Recursive)
  //===----------------------------------------------------------------------===//
  __forceinline__
  static void
  insert_atomic(word_t* __restrict sector_ptr, const key_t key, hash_value_t& hash_val) noexcept {

    hash_val = rehash ? hasher<key_t, hash_fn_idx>::hash(key) : hash_val;

    // Determine the word of interest
    constexpr u32 word_idx_shift = remaining_hash_bit_cnt_after_rehash - word_cnt_log2;
    u32 word_idx = ((hash_val >> word_idx_shift) & word_cnt_log2_mask);

    // Set a bit in the given word
    constexpr u32 bit_idx_shift = remaining_hash_bit_cnt_after_rehash - word_cnt_log2 - word_bitlength_log2;
    u32 bit_idx = ((hash_val >> bit_idx_shift) & word_bitlength_log2_mask);
    const word_t which_bit = word_t(1) << bit_idx;

    word_t* word_ptr = &sector_ptr[word_idx];
    std::atomic<word_t>* atomic_word_ptr = reinterpret_cast<std::atomic<word_t>*>(word_ptr);
    $u1 success = false;
    do {
      // Load the word of interest
      word_t word = atomic_word_ptr->load();
      // Update the bit vector
      word_t updated_word = word | which_bit;
      success = atomic_word_ptr->compare_exchange_weak(word, updated_word);
    } while (!success);

    // Process remaining k's recursively, if any
    multiword_sector<key_t, word_t, word_cnt, k,
                     hasher, hash_value_t,
                     rehash ? hash_fn_idx + 1 : hash_fn_idx,
                     remaining_hash_bit_cnt_after_rehash >= hash_bit_cnt_per_k ? remaining_hash_bit_cnt_after_rehash - hash_bit_cnt_per_k : 0,
                     remaining_k_cnt - 1>
      ::insert_atomic(sector_ptr, key, hash_val);
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains
  //===----------------------------------------------------------------------===//
  __forceinline__ __unroll_loops__ __host__ __device__
  static u1
  contains(const word_t* __restrict sector_ptr, const key_t key) noexcept {

    hash_value_t hash_val = 0;

    // Call the recursive function
    static constexpr u32 remaining_hash_bits = 0;
    return multiword_sector<key_t, word_t, word_cnt, k,
                            hasher, hash_value_t,
                            hash_fn_idx,
                            remaining_hash_bits,
                            remaining_k_cnt>
      ::contains(sector_ptr, key, hash_val, true);
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains (Recursive)
  //===----------------------------------------------------------------------===//
  __forceinline__ __unroll_loops__ __host__ __device__
  static u1
  contains(const word_t* __restrict sector_ptr, const key_t key, hash_value_t& hash_val, u1 is_contained_in_sector) noexcept {

    hash_val = rehash ? hasher<key_t, hash_fn_idx>::hash(key) : hash_val;

    // Determine the word of interest
    constexpr u32 word_idx_shift = remaining_hash_bit_cnt_after_rehash - word_cnt_log2;
    $u32 word_idx = ((hash_val >> word_idx_shift) & word_cnt_log2_mask);

    // Load the word of interest
    word_t word = sector_ptr[word_idx];

    // Test a bit in the given word
    constexpr u32 bit_idx_shift = remaining_hash_bit_cnt_after_rehash - word_cnt_log2 - word_bitlength_log2;
    $u32 bit_idx = ((hash_val >> bit_idx_shift) & word_bitlength_log2_mask);
    u1 found_in_word = word & (word_t(1) << bit_idx);

    // Process remaining k's recursively, if any
    return multiword_sector<key_t, word_t, word_cnt, k,
                            hasher, hash_value_t,
                            rehash ? hash_fn_idx + 1 : hash_fn_idx,
                            remaining_hash_bit_cnt_after_rehash >= hash_bit_cnt_per_k ? remaining_hash_bit_cnt_after_rehash - hash_bit_cnt_per_k : 0,
                            remaining_k_cnt - 1>
                            ::contains(sector_ptr, key, hash_val, found_in_word & is_contained_in_sector);
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains (SIMD)
  //===----------------------------------------------------------------------===//
  template<u64 n>
  __forceinline__ __unroll_loops__
  static auto
  contains(const vec<key_t,n>& keys,
           const word_t* __restrict bitvector_base_address,
           const vec<key_t,n>& sector_start_word_idxs) noexcept {

    vec<hash_value_t, n> hash_vals(0);
    auto is_contained_in_sector_mask = vec<word_t,n>::mask::make_all_mask();

    // Call recursive function
    static constexpr u32 remaining_hash_bits = 0;
    return multiword_sector<key_t, word_t, word_cnt, k,
                            hasher, hash_value_t,
                            hash_fn_idx,
                            remaining_hash_bits,
                            remaining_k_cnt>
      ::contains(keys, hash_vals, bitvector_base_address, sector_start_word_idxs, is_contained_in_sector_mask);
  }

  //===----------------------------------------------------------------------===//
  // Contains (SIMD, Recursive)
  //===----------------------------------------------------------------------===//
  template<u64 n>
  __forceinline__ __unroll_loops__
  static auto
  contains(const vec<key_t,n>& keys,
           vec<hash_value_t,n>& hash_vals,
           const word_t* __restrict bitvector_base_address,
           const vec<hash_value_t,n>& sector_start_word_idxs,
           const typename vec<word_t,n>::mask is_contained_in_sector_mask) noexcept {

    // Typedef the vector types
    using key_vt = vec<key_t, n>;
    using word_vt = vec<word_t, n>;

    hash_vals = rehash ? hasher<key_vt, hash_fn_idx>::hash(keys) : hash_vals;

    // Determine the word of interest
    constexpr u32 word_idx_shift = remaining_hash_bit_cnt_after_rehash - word_cnt_log2;
    const auto in_sector_word_idxs = (hash_vals >> word_idx_shift) & static_cast<hash_value_t>(word_cnt_log2_mask);
    const auto word_idxs = sector_start_word_idxs + in_sector_word_idxs;

    // Gather the words of interest
    const word_vt words = internal::vector_gather<word_t, hash_value_t, n>::gather(bitvector_base_address, word_idxs);

    // Test a bit in the given word
    constexpr u32 bit_idx_shift = remaining_hash_bit_cnt_after_rehash - word_cnt_log2 - word_bitlength_log2;
    const auto bit_idx = (hash_vals >> bit_idx_shift) & static_cast<hash_value_t>(word_bitlength_log2_mask);
    const word_vt bits_to_test = word_vt(1) << internal::vector_convert<hash_value_t, word_t, n>::convert(bit_idx);
    const auto found_in_word_mask = (words & bits_to_test) == bits_to_test;

    // Process remaining k's recursively, if any
    return multiword_sector<key_t, word_t, word_cnt, k,
                            hasher, hash_value_t,
                            rehash ? hash_fn_idx + 1 : hash_fn_idx,
                            remaining_hash_bit_cnt_after_rehash >= hash_bit_cnt_per_k ? remaining_hash_bit_cnt_after_rehash - hash_bit_cnt_per_k : 0,
                            remaining_k_cnt - 1>
      ::contains(keys, hash_vals, bitvector_base_address, sector_start_word_idxs, found_in_word_mask & is_contained_in_sector_mask);

  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // The number of required hash functions.
  //===----------------------------------------------------------------------===//
  static constexpr u32 hash_fn_idx_end =
      multiword_sector<key_t, word_t, word_cnt, k,
                       hasher, hash_value_t,
                       (rehash ? hash_fn_idx + 1 : hash_fn_idx), // increment the hash function index
                       remaining_hash_bit_cnt_after_rehash >= hash_bit_cnt_per_k
                         ? remaining_hash_bit_cnt_after_rehash - hash_bit_cnt_per_k
                         : 0, // the number of remaining hash bits
                       remaining_k_cnt - 1> // decrement the remaining k counter
      ::hash_fn_idx_end;
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // The number of required hash bits.
  //===----------------------------------------------------------------------===//
  static constexpr u32 remaining_hash_bits =
      multiword_sector<key_t, word_t, word_cnt, k,
                 hasher, hash_value_t,
                 (rehash ? hash_fn_idx + 1 : hash_fn_idx), // increment the hash function index
                 remaining_hash_bit_cnt_after_rehash >= hash_bit_cnt_per_k
                   ? remaining_hash_bit_cnt_after_rehash - hash_bit_cnt_per_k
                   : 0, // the number of remaining hash bits
                 remaining_k_cnt - 1> // decrement the remaining k counter
      ::remaining_hash_bits;
  //===----------------------------------------------------------------------===//

};

template<
    typename key_t,               // the key type
    typename word_t,              // the word type
    u32 word_cnt,                 // the number of words per sector
    u32 k,                        // the number of bits to set/test
    template<typename Ty, u32 i> class hasher,      // the hash function family to use
    typename hash_value_t,        // the hash value type to use

    u32 hash_fn_idx,              // current hash function index (used for recursion)
    u32 remaining_hash_bit_cnt    // the number of remaining hash bits (used for recursion)
>
struct multiword_sector<key_t, word_t, word_cnt, k, hasher, hash_value_t, hash_fn_idx, remaining_hash_bit_cnt, 0 /* no more k's */> {

  //===----------------------------------------------------------------------===//
  // Insert
  //===----------------------------------------------------------------------===//
  __forceinline__
  static void
  insert(word_t* __restrict sector_ptr, const key_t key, hash_value_t& hash_val) noexcept {
    // End of recursion.
  }
  __forceinline__
  static void
  insert_atomic(word_t* __restrict sector_ptr, const key_t key, hash_value_t& hash_val) noexcept {
    // End of recursion.
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains
  //===----------------------------------------------------------------------===//
  __forceinline__ __host__ __device__
  static u1
  contains(const word_t* __restrict sector_ptr, const key_t key, hash_value_t& hash_val, u1 is_contained_in_sector) noexcept {
    // End of recursion.
    return is_contained_in_sector;
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains (SIMD)
  //===----------------------------------------------------------------------===//
  template<u64 n>
  __forceinline__ __unroll_loops__
  static auto
  contains(const vec<key_t,n>& keys,
           vec<hash_value_t,n>& hash_vals,
           const word_t* __restrict bitvector_base_address,
           const vec<hash_value_t,n>& sector_start_word_idxs,
           const typename vec<word_t,n>::mask is_contained_in_sector_mask) noexcept {
    // End of recursion.
    return is_contained_in_sector_mask;
  }
  //===----------------------------------------------------------------------===//

  static constexpr u32 hash_fn_idx_end = hash_fn_idx;

  static constexpr u32 remaining_hash_bits = remaining_hash_bit_cnt;

};

} // anonymous namespace

//===----------------------------------------------------------------------===//
// Recursive template to work with multiple multi-word sectors.
//
// Specialization for the case where sector count < word_cnt ('sltw'),
// which results in a random access pattern within the block (and sector).
// I.e., for every bit to set/test, the corresponding word needs to be determined
// and loaded.
//
// Same as in the 'sgew' case, we process the block sector by sector,
// however, a sector consists of more than one word.
//===----------------------------------------------------------------------===//
template<
    typename _key_t,              // the key type
    typename _word_t,             // the word type
    u32 _word_cnt,                // the number of words per block
    u32 _s,                       // the numbers of sectors (must be a power of two)
    u32 _k,                       // the number of bits to set/test
    template<typename Ty, u32 i> class hasher,      // the hash function family to use
    typename hash_value_t,        // the hash value type to use

    u32 hash_fn_idx,              // current hash function index (used for recursion)
    u32 remaining_hash_bit_cnt,   // the number of remaining hash bits (used for recursion)
    u32 remaining_sector_cnt,     // the remaining number of sector (used for recursion)

    u1 early_out = false          // allows for branching out during lookups (before the next sector is tested)
>
struct multisector_block {

  //===----------------------------------------------------------------------===//
  // Static part
  //===----------------------------------------------------------------------===//
  using key_t = _key_t;
  using word_t = _word_t;

  static constexpr u32 k = _k;
  static constexpr u32 word_cnt = _word_cnt;
  static constexpr u32 sector_cnt = _s;
  static_assert(dtl::is_power_of_two(sector_cnt), "Parameter 'sector_cnt' must be a power of two.");
  static constexpr u32 sector_cnt_log2 = dtl::ct::log_2<sector_cnt>::value;

  static constexpr u32 current_sector_idx = sector_cnt - remaining_sector_cnt;

  static_assert(dtl::is_power_of_two(word_cnt), "Parameter 'word_cnt' must be a power of two.");
  static constexpr u32 word_cnt_per_sector = word_cnt / sector_cnt;
  static constexpr u32 k_cnt_per_sector = k / sector_cnt;
  static_assert(k % sector_cnt == 0, "Parameter 'k' must be dividable by 's'.");

  //===----------------------------------------------------------------------===//
  // Insert
  //===----------------------------------------------------------------------===//
  __forceinline__
  static void
  insert(word_t* __restrict block_ptr, const key_t key) noexcept {

    hash_value_t hash_val = 0;

    // Call the recursive function
    static constexpr u32 remaining_hash_bits = 0;
    multisector_block<key_t, word_t, word_cnt, sector_cnt, k,
                      hasher, hash_value_t, hash_fn_idx, remaining_hash_bits,
                      remaining_sector_cnt, early_out>
      ::insert(block_ptr, key, hash_val);
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Insert (Recursive)
  //===----------------------------------------------------------------------===//
  __forceinline__
  static void
  insert(word_t* __restrict block_ptr, const key_t key, hash_value_t& hash_val) noexcept {

    // Sector pointer
    word_t* sector_ptr = block_ptr + (word_cnt_per_sector * current_sector_idx);

    // Process remaining k's recursively, if any
    using sector_t =
      multiword_sector<key_t, word_t, word_cnt_per_sector, k_cnt_per_sector,
                       hasher, hash_value_t, hash_fn_idx, remaining_hash_bit_cnt,
                       k_cnt_per_sector>;
    sector_t::insert_atomic(sector_ptr, key, hash_val);
//    sector_t::insert(sector_ptr, key, hash_val);

    multisector_block<key_t, word_t, word_cnt, sector_cnt, k,
                      hasher, hash_value_t, sector_t::hash_fn_idx_end, sector_t::remaining_hash_bits,
                      remaining_sector_cnt - 1, early_out>
      ::insert(block_ptr, key, hash_val);
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains
  //===----------------------------------------------------------------------===//
  __forceinline__ __unroll_loops__ __host__ __device__
  static u1
  contains(const word_t* __restrict block_ptr, const key_t key) noexcept {

    hash_value_t hash_val = 0;

    // Call the recursive function
    static constexpr u32 remaining_hash_bits = 0;
    return multisector_block<key_t, word_t, word_cnt, sector_cnt, k,
                             hasher, hash_value_t, hash_fn_idx, remaining_hash_bits,
                             remaining_sector_cnt, early_out>
      ::contains(block_ptr, key, hash_val, true);
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains (Recursive)
  //===----------------------------------------------------------------------===//
  __forceinline__ __unroll_loops__ __host__ __device__
  static u1
  contains(const word_t* __restrict block_ptr, const key_t key, hash_value_t& hash_val, u1 is_contained_in_block) noexcept {

    // Sector pointer
    const word_t* sector_ptr = block_ptr + (word_cnt_per_sector * current_sector_idx);

    // Process the current sector
    using sector_t =
      multiword_sector<key_t, word_t, word_cnt_per_sector, k_cnt_per_sector,
                       hasher, hash_value_t, hash_fn_idx, remaining_hash_bit_cnt,
                       k_cnt_per_sector>;
    auto found_in_sector = sector_t::contains(sector_ptr, key, hash_val, true);

    // Early out
    if (early_out) {
      if (likely(!found_in_sector)) return false;
    }

    // Process remaining sectors recursively, if any
    return multisector_block<key_t, word_t, word_cnt, sector_cnt, k,
                      hasher, hash_value_t, sector_t::hash_fn_idx_end, sector_t::remaining_hash_bits,
                      remaining_sector_cnt - 1, early_out>
        ::contains(block_ptr, key, hash_val, found_in_sector & is_contained_in_block);
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains (SIMD)
  //===----------------------------------------------------------------------===//
  template<u64 n>
  __forceinline__ __unroll_loops__
  static auto
  contains(const vec<key_t,n>& keys,
           const word_t* __restrict bitvector_base_address,
           const vec<hash_value_t,n>& block_start_word_idxs) noexcept {

    vec<hash_value_t, n> hash_vals(0);
    const auto is_contained_in_block_mask = vec<word_t,n>::mask::make_all_mask(); // true

    // Call recursive function
    static constexpr u32 remaining_hash_bits = 0;
    return multisector_block<key_t, word_t, word_cnt, sector_cnt, k,
                             hasher, hash_value_t, hash_fn_idx, remaining_hash_bits,
                             remaining_sector_cnt, early_out>
      ::contains(keys, hash_vals, bitvector_base_address, block_start_word_idxs, is_contained_in_block_mask);

  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains (SIMD, Recursive)
  //===----------------------------------------------------------------------===//
  template<u64 n>
  __forceinline__ __unroll_loops__
  static auto
  contains(const vec<key_t,n>& keys,
           vec<hash_value_t,n>& hash_vals,
           const word_t* __restrict bitvector_base_address,
           const vec<hash_value_t,n>& block_start_word_idxs,
           const typename vec<word_t,n>::mask is_contained_in_block_mask) noexcept {

    // Sector pointers
    auto sector_start_word_idxs = block_start_word_idxs + (word_cnt_per_sector * current_sector_idx);

    // Process the current sector
    using sector_t =
            multiword_sector<key_t, word_t, word_cnt_per_sector, k_cnt_per_sector,
                             hasher, hash_value_t, hash_fn_idx, remaining_hash_bit_cnt,
                             k_cnt_per_sector>;
    auto found_in_sector_mask = sector_t::contains(keys, hash_vals, bitvector_base_address, sector_start_word_idxs, vec<word_t,n>::mask::make_all_mask());

    // Early out
    if (early_out) {
      if (likely(found_in_sector_mask.none())) return found_in_sector_mask;
    }

    // Process remaining sectors recursively, if any
    return multisector_block<key_t, word_t, word_cnt, sector_cnt, k,
                             hasher, hash_value_t, sector_t::hash_fn_idx_end, sector_t::remaining_hash_bits,
                             remaining_sector_cnt - 1, early_out>
      ::contains(keys, hash_vals, bitvector_base_address, block_start_word_idxs, found_in_sector_mask & is_contained_in_block_mask);
  }
  //===----------------------------------------------------------------------===//

};

//===----------------------------------------------------------------------===//
template<
    typename key_t,               // the key type
    typename word_t,              // the word type
    u32 word_cnt,                 // the number of words per block
    u32 s,                        // the numbers of sectors (must be a power of two)
    u32 k,                        // the number of bits to set/test
    template<typename Ty, u32 i> class hasher,      // the hash function family to use
    typename hash_value_t,        // the hash value type to use

    u32 hash_fn_idx,              // current hash function index (used for recursion)
    u32 remaining_hash_bit_cnt,   // the number of remaining hash bits (used for recursion)

    u1 early_out                  // allows for branching out during lookups (before the next sector is tested)
>
struct multisector_block<key_t, word_t, word_cnt, s, k, hasher, hash_value_t, hash_fn_idx, remaining_hash_bit_cnt,
                         0 /* no more remaining sectors */, early_out>  {

  //===----------------------------------------------------------------------===//
  // Insert
  //===----------------------------------------------------------------------===//
  __forceinline__
  static void
  insert(word_t* __restrict block_ptr, const key_t key, hash_value_t& hash_val) noexcept {
    // End of recursion.
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains
  //===----------------------------------------------------------------------===//
  __forceinline__ __host__ __device__
  static u1
  contains(const word_t* __restrict block_ptr, const key_t key, hash_value_t& hash_val, u1 is_contained_in_block) noexcept {
    // End of recursion.
    return is_contained_in_block;
  }
  //===----------------------------------------------------------------------===//

  //===----------------------------------------------------------------------===//
  // Contains (SIMD)
  //===----------------------------------------------------------------------===//
  template<u64 n>
  __forceinline__ __unroll_loops__
  static auto
  contains(const vec<key_t,n>& keys,
           vec<hash_value_t,n>& hash_vals,
           const word_t* __restrict bitvector_base_address,
           const vec<key_t,n>& block_start_word_idxs,
           const typename vec<word_t,n>::mask is_contained_in_block_mask) noexcept {
    // End of recursion.
    return is_contained_in_block_mask;
  }
  //===----------------------------------------------------------------------===//

};

} // namespace dtl