diff --git a/include/mata/utils/extendable-square-matrix.hh b/include/mata/utils/extendable-square-matrix.hh new file mode 100644 index 000000000..fc4c2c776 --- /dev/null +++ b/include/mata/utils/extendable-square-matrix.hh @@ -0,0 +1,919 @@ +/** @file extendable-square-matrix.hh + * @brief Definition of an extendable square matrix + * + * Description: + * + * An extendable square matrix is a n x n square matrix (where n <= capacity) + * which can be extended to the (n+1) x (n+1) matrix (if (n+1) <= capacity) + * whenever it is necessary. + * + * Such a matrix is able to represent a binary relation over a set, a matrix of + * counters etc. The data type of the matrix cells is templated. + * + * This file contains an abstract class ExtendableSquareMatrix which does + * not contain the exact inner representation of the matrix. + * + * The file also contains several concrete subclasses of the + * ExtendableSquareMatrix class, namely: + * - CascadeSquareMatrix + * - DynamicSquareMatrix + * - HashedSquareMatrix + * which implement the inner representation of the matrix on its own. The + * data cells will be accessible exclusively through the methods which are + * virtual in context of the abstract class ExtendableSquareMatrix and which + * are implemented within the subclasses. + * + * Using this class, it is possible to: + * - get a value of the matrix cell using two indices in O(1) + * - set a value of the matrix cell using two indices in O(1) + * - extend a n x n matrix to a (n+1) x (n+1) matrix in O(n) + * - implement and use custom inner representation of the matrix + * - choose a data type of the matrix cells + * + * @author Tomáš Kocourek + */ + +#ifndef EXTENDABLE_SQUARE_MATRIX_HH_ +#define EXTENDABLE_SQUARE_MATRIX_HH_ + +#include +#include +#include +#include +#include + +namespace mata::utils { + +/************************************************************************ +* +* +* EXTENDABLE SQUARE MATRIX +* +* +* (RELATIONS AND COUNTERS) +* +* +*************************************************************************/ + +/** + * ExtendableSquareMatrix + * + * @brief interface for extendable square matrix implementations + * + * Square matrix "n x n" which can be extended to "(n+1) x (n+1)" matrix + * if n is less than the maximal capacity. Such a class allows us to + * represent binary relations over carrier set with n elements and adjust + * it to n+1 elements whenever a new element of the carrier set is created + * (for example when we represent relation over partition and a block of + * partition is split in two) or matrices of counters etc. + * + * This abstract class declares methods for accessing elements of the + * matrix, assigning to the cells of matrix and extending the matrix by one row + * and one column (changing the size). + * It defines attributes for maximal capacity (which cannot be changed) and + * current size. + * It does not define the data structure for storing data. Each subclass + * which inherits from this abstract class should: + * - contain the storage for data of datatype T which represents n x n matrix + * - implement methods set, get and extend + * - implement a method clone which creates a deep copy of a matrix + * Then, the ExtendableSquareMatrix can be used independently of the inner + * representation of the matrix. Therefore, one can dynamically choose from + * various of implementations depending on the situation. If any new + * subclass is implemented, one should also modify the 'create' + * function and extend 'MatrixType' enumerator. + * + * Note that in context of an n x n matrix, this implementation uses the word + * 'size' to refer to the number n (number of rows or columns). The word + * 'capacity' corresponds to the maximal allowed size (maximal number + * of rows or columns). + * +**/ + +using MatrixType = enum MatrixType { None, Cascade, Dynamic, Hashed }; + +template +class ExtendableSquareMatrix { + protected: + + // number of rows (or columns) of the current square matrix + size_t size_{0}; + + // maximal allowed number of rows (or columns) of the square matrix + size_t capacity_{0}; + + // type of the matrix which will be chosen as soon as the + // child class is created + MatrixType m_type{MatrixType::None}; + + public: + + // + // GETTERS + // + + /** Returns a size of the matrix. In this context, + * the size of an n x n matrix corresponds to the value n. + * @brief returns the size of the matrix + * @return size of the matrix + */ + size_t size() const { return size_; } + + /** Returns a capacity of the matrix. In this context, + * the capacity of an n x n matrix corresponds to the value n_max such + * that if the matrix is extended to the n_max x n_max matrix, it cannot + * be extended anymore. + * @brief returns the capacity of the matrix + * @return capacity of the matrix + */ + size_t capacity() const { return capacity_; } + + /** Returns a type of the matrix. The type specifies the + * way the inner representation of the matrix is implemented. + * @brief returns the type of the matrix + * @return type of the matrix + */ + size_t type() const { return m_type; } + + // + // VIRTUAL METHODS + // + // These virtual methods will be implemented in the subclasses according + // to allow to the concrete representation of the matrix. These methods + // provide a way to access the contents of the matrix + // + + /** + * @brief Assigns a value to the cell of the matrix + * @param[in] i row of the matrix + * @param[in] j column of the matrix + * @param[in] value a value which will be assigned to the memory cell + */ + virtual void set(size_t i, size_t j, T value = T()) = 0; + + /** + * @brief Finds a value of the matrix memory cell + * @param[in] i row of the matrix + * @param[in] j column of the matrix + * @return a found value in the matrix + */ + virtual T get(size_t i, size_t j) const = 0; + + /** + * @brief changes the n x n matrix to the (n+1) x (n+1) matrix + * @param[in] placeholder value which will be assigned to the + * newly allocated memory cells + */ + virtual void extend(T placeholder = T()) = 0; + + /** + * Changes the n x n matrix to the (n+1) x (n+1) matrix by duplicating + * existing row and column. The row parameter is an index of the row + * which should be duplicated and added as a (n+1)th row, while the + * col parameter is an index of the column which should be duplicated + * and added as an (n+1)th row. If the row parameter equals n, then + * it will be initialized using default values of the type T. If the + * col parameter equals n, the new column will be also initialized + * with the default values of the type T. Using this approach, one is + * able to copy only a row or column and initialize the other one with + * default values. Calling extend_and_copy(n, n) has the same effect as + * calling extend(). + * The element at the position [n, n] will be always initialized using + * the default value of the type T. + * @brief changes the n x n matrix to the (n+1) x (n+1) matrix with + * copying of the existing data + * @param[in] placeholde value which will be assigned to the + * newly allocated memory cells + */ + virtual void extend_and_copy(size_t row, size_t col) = 0; + + // + // CLONING + // + + /** + * @brief creates a deep copy of the matrix + * @return deep copy of the matrix + */ + virtual std::unique_ptr> clone() const = 0; + + virtual ~ExtendableSquareMatrix() = default; + + // + // MATRIX PROPERTIES + // + + /** This function checks whether the matrix is reflexive. In this + * context, the matrix is reflexive iff none of the elements on the main + * diagonal is the zero element of the type T + * @brief checks whether the Extendable square matrix is reflexive + * @return true iff the matrix is reflexive + */ + bool is_reflexive() const { + size_t size = this->size(); + for(size_t i = 0; i < size; ++i) { + if(!get(i, i)) { return false; } + } + return true; + } + + /** This function checks whether the matrix is antisymmetric. In this + * context, the matrix is antisymmetric iff there are no indices i, j + * where i != j and both matrix[i][j], matrix[j][i] contain nonzero + * elements of the type T + * @brief checks whether the Extendable square matrix is antisymmetric + * @return true iff the matrix is antisymmetric + */ + bool is_antisymmetric() const { + size_t size = this->size(); + for(size_t i = 0; i < size; ++i) { + for(size_t j = 0; j < size; ++j) { + if(i == j) [[unlikely]] { continue; } + if(get(i, j) && get(j, i)) { return false; } + } + } + return true; + } + + /** This function checks whether the matrix is transitive. In this + * context, the matrix is transitive iff it holds that the input matrix + * casted to the matrix of booleans (false for zero values of type T, + * otherwise true) remains the same if it is multiplied by itself. + * @brief checks whether the Extendable square matrix is transitive + * @return true iff the matrix is transitive + */ + bool is_transitive() const { + size_t size = this->size(); + for(size_t i = 0; i < size; ++i) { + for(size_t j = 0; j < size; ++j) { + bool found = false; + for(size_t k = 0; k < size; ++k) { + if(get(i, k) && get(k, j)) { + found = true; + break; + } + } + if(!found == static_cast(get(i, j))) { return false; } + } + } + return true; + } + + +}; // ExtendableSquareMatrix + + + +/************************************* +* +* CASCADE SQUARE MATRIX +* +**************************************/ + +/** + * CascadeSquareMatrix + * + * @brief Linearized square matrix implemented using single vector of + * elements which stores data in some kind of "cascading" way + * + * This implementation tries to avoid + * - moving the whole matrix when it is extended + * - allocation of unnecessary data cells + * - violation of data locality + * + * The matrix is represented as a single vector of a type T. Initially, + * the maximal possible capacity is given to the constructor. It reserves + * 'capacity * capacity' data cells for the vector (in the constant time O(1)) + * without need to allocate anything. + * When the matrix is extended, additional (size * 2) + 1 elements of the + * vector are allocated. The matrix is traversed in some kind of "cascading" way + * as follows: + * + * Each number in the matrix corresponds to the order of accessing that element + * using this "cascading traversal". + * + * MATRIX: + * ----------------- + * | 0 | 3 | 8 | 15| + * ----------------- + * | 1 | 2 | 7 | 14| + * ----------------- + * | 4 | 5 | 6 | 13| + * ----------------- + * | 9 | 10| 11| 12| + * ----------------- + * + * VECTOR: + * ----------------------------------------------------------------------- + * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | + * ----------------------------------------------------------------------- + * + * The data cell matrix[i][j] could be accessed using the formula + * vector[i >= j ? i * i + j : j * j + 2 * j - i]. + * + * Using this approach, there is no need to allocate unnecessary data cells + * when extending n x n matrix to the (n+1) x (n+1) matrix (in contrast with + * using row or column traversal). + * Since 'capacity * capacity' data cells were reserved, the vector won't ever + * be moved in the memory due to the extending. However, it requieres to + * reserve a lot of memory space when the capacity is a huge number. + * The data locality won't be violated since all of the elements will be stored + * as a contiguous vector. + * +**/ +template +class CascadeSquareMatrix : public ExtendableSquareMatrix { + private: + + // data are stored in a single vector + std::vector data_{}; + + public: + + // + // CONSTRUCTORS + // + + /** + * @brief creates a Cascade square matrix + * @param[in] max_rows capacity of the square matrix + * @param[in] init_rows initial size of the square matrix + */ + CascadeSquareMatrix(size_t max_rows, size_t init_rows) { + assert(init_rows <= max_rows && + "Initial size of the matrix cannot be" + "bigger than the capacity"); + + this->m_type = MatrixType::Cascade; + this->capacity_ = max_rows; + data_.reserve(this->capacity_ * this->capacity_); + + // creating the initial size and filling the data cells with + // default values + for(size_t i = 0; i < init_rows; ++i) {extend();} + } + + /** This method provides a way to create a copy of a given + * CascadeSquareMatrix and preserves the reserved capacity of the vector + * 'data_'. This goal is achieved using the custom assignment operator. + * @brief copy constructor of a CascadeSquareMatrix + * @param other matrix which should be copied + */ + CascadeSquareMatrix(const CascadeSquareMatrix& other) { + *this = other; + } + + // + // IMPLEMENTED VIRTUAL METHODS + // + + /** + * @brief Assigns a value to the Cascade square matrix. + * @param[in] i row of the square matrix + * @param[in] j column of the square matrix + * @param[in] value value to be assigned to the square matrix data cell + */ + void set(size_t i, size_t j, T value) { + assert(i < this->size_ && "Nonexistent row cannot be accessed"); + assert(j < this->size_ && "Nonexistent column cannot be accessed"); + + // accessing the matrix in the cascading way + data_[i >= j ? i * i + j : j * j + 2 * j - i] = value; + } + + /** + * @brief returns a value of the Cascade square matrix + * @param[in] i row of the square matrix + * @param[in] j column of the square matrix + * @return value found in the square matrix data cell + */ + T get(size_t i, size_t j) const { + assert(i < this->size_ && "Nonexistent row cannot be accessed"); + assert(j < this->size_ && "Nonexistent column cannot be accessed"); + + // accessing the matrix in the cascading way + return data_[i >= j ? i * i + j : j * j + 2 * j - i]; + } + + /** + * @brief extends the Cascade square matrix by a new row and column + * @param[in] placeholder a value which will be assigned to all the new + * data cells (optional) + */ + void extend(T placeholder = T()) { + assert(this->size_ < this->capacity_ + && "The matrix cannot be extended anymore"); + + // allocation of 2 * size + 1 new data cells + data_.insert(data_.end(), 2 * this->size_ + 1, placeholder); + + // the size increases + ++this->size_; + } + + /** + * Extends the n x n cascade matrix to the (n+1) x (n+1) matrix by + * duplicating an existing row and column. The row parameter is an index + * of the row which should be duplicated and added as a (n+1)th row, + * while the col parameter is an index of the column which should be + * duplicated and added as an (n+1)th row. If the row parameter equals n, + * then it will be initialized using default values of the type T. If the + * col parameter equals n, the new column will be also initialized + * with the defaults values of the type T. Using this approach, one is + * able to copy only a row or column and initialize the other one with + * default values. Calling extend_and_copy(n, n) has the same effect as + * calling extend(). + * The element at the position [n, n] will be always initialized using + * the default value of the type T. + * @brief changes the n x n matrix to the (n+1) x (n+1) matrix with + * copying of the existing data + * @param[in] row index of the row which should be copied + * @param[in] col index of the column which should be copied + */ + void extend_and_copy(size_t row, size_t col) { + assert(this->size_ < this->capacity_ + && "The matrix cannot be extended anymore"); + std::cout << this->size_ << " " << row << std::endl; + assert(row <= this->size_ + && "Index of the copied row cannot be bigger than the size"); + assert(col <= this->size_ + && "Index of the copied col cannot be bigger than the size"); + + // if the row index corresponds to the index of the newly created + // row, it will be initialized using default values of the type T + if(row == this->size_) { + for(size_t i = 0; i < this->size_; ++i) { + data_.push_back(T()); + } + // otherwise, the row with the index 'row' will be duplicated + // to create a new row + } else { + for(size_t i = 0; i < this->size_; ++i) { + data_.push_back(get(row, i)); + } + } + + // element at the position [n, n] will always be initialized using + // the default value of the type T + data_.push_back(T()); + + // if the column index corresponds to the index of the newly created + // column, it will be initialized using default values of the type T + if(col == this->size_) { + for(size_t i = 0; i < this->size_; ++i) { + data_.push_back(T()); + } + // otherwise, the column with the index 'col' will be duplicated + // to create a new column + } else { + for(size_t i = 0; i < this->size_; ++i) { + data_.push_back(get(this->size_ - 1 - i, col)); + } + } + + // the size of the matrix increases after extending + ++this->size_; + } + + // + // CLONING + // + + std::unique_ptr> clone() const override { + return std::make_unique>(*this); + } + + // + // OPERATORS + // + + /** This method provides a way to assign a CascadeSquareMatrix + * to the variable. The method ensures us to keep the reserved + * capacity of the vector 'data_' since the default vector + * assignment does not preserve it. + * @brief assignment operator for the CascadeSquareMatrix class + * @param[in] other matrix which should be assigned + */ + CascadeSquareMatrix& operator=(const CascadeSquareMatrix& other) { + // initialization of the matrix + this->capacity_ = other.capacity(); + this->size_ = 0; + this->data_ = std::vector(); + this->data_.reserve(this->capacity_ * this->capacity_); + size_t other_size = other.size(); + for(size_t i = 0; i < other_size; ++i) {this->extend();} + + // copying memory cells + for(size_t i = 0; i < this->size_; ++i) { + for(size_t j = 0; j < this->size_; ++j) { + this->set(i, j, other.get(i, j)); + } + } + return *this; + } + +}; // CascadeSquareMatrix + +/************************************* +* +* DYNAMIC SQUARE MATRIX +* +**************************************/ + +/** + * DynamicSquareMatrix + * + * @brief Dynamic square matrix implemented using vector of vectors + * of the type T + * + * This implementation tries to avoid + * - allocation or reservation of data cells which won't ever be used + * + * The matrix is represented as a vector of vectors of the type T. It is + * extended dynamically without any need to allocate or reserve any unnecessary + * memory space before it is used. + * However, the data locality is not ensured. Moreover, if the matrix is + * extended, it could possibly be moved in the memory. +**/ +template +class DynamicSquareMatrix : public ExtendableSquareMatrix { + private: + + // data are stored in a vector of vectors + std::vector> data_{}; + + public: + + // + // CONSTRUCTORS + // + + /** + * @brief creates a Dynamic square matrix + * @param[in] max_rows capacity of the square matrix + * @param[in] init_rows initial size of the square matrix + */ + DynamicSquareMatrix(size_t max_rows, size_t init_rows) { + assert(init_rows <= max_rows && + "Initial size of the matrix cannot" + "be bigger than the capacity"); + + this->m_type = MatrixType::Dynamic; + this->capacity_ = max_rows; + + // creating the initial size and filling the data cells with + // default values + for(size_t i = 0; i < init_rows; ++i) {extend();} + } + + // + // IMPLEMENTED VIRTUAL METHODS + // + + /** + * @brief Assigns a value to the Dynamic square matrix. + * @param[in] i row of the square matrix + * @param[in] j column of the square matrix + * @param[in] value value to be assigned to the square matrix data cell + */ + T get(size_t i, size_t j) const { + assert(i < this->size_ && "Nonexistent row cannot be accessed"); + assert(j < this->size_ && "Nonexistent column cannot be accessed"); + + return data_[i][j]; + } + + /** + * @brief returns a value of the Dynamic square matrix + * @param[in] i row of the square matrix + * @param[in] j column of the square matrix + * @return value found in the square matrix data cell + */ + void set(size_t i, size_t j, T value) { + assert(i < this->size_ && "Nonexistent row cannot be accessed"); + assert(j < this->size_ && "Nonexistent column cannot be accessed"); + + data_[i][j] = value; + } + + /** + * @brief extends the Dynamic square matrix by a new row and column + * @param[in] placeholder a value which will be assigned to all the + * new data cells + */ + void extend(T placeholder = T()) { + assert(this->size_ < this->capacity_ + && "The matrix cannot be extended anymore"); + + // creating a new column + for(size_t i = 0; i < this->size_; ++i) { + data_[i].push_back(placeholder); + } + + // creating a new row + data_.emplace_back(); + ++this->size_; + data_.back().insert(data_.back().end(), this->size_, placeholder); + } + + /** + * Extends the n x n dynamic matrix to the (n+1) x (n+1) matrix by + * duplicating an existing row and column. The row parameter is an index + * of the row which should be duplicated and added as a (n+1)th row, + * while the col parameter is an index of the column which should be + * duplicated and added as an (n+1)th row. If the row parameter equals n, + * then it will be initialized using default values of the type T. If the + * col parameter equals n, the new column will be also initialized + * with the defaults values of the type T. Using this approach, one is + * able to copy only a row or column and initialize the other one with + * default values. Calling extend_and_copy(n, n) has the same effect as + * calling extend(). + * The element at the position [n, n] will be always initialized using + * the default value of the type T. + * @brief changes the n x n matrix to the (n+1) x (n+1) matrix with + * copying of the existing data + * @param[in] row index of the row which should be copied + * @param[in] col index of the column which should be copied + */ + void extend_and_copy(size_t row, size_t col) { + assert(this->size_ < this->capacity_ + && "The matrix cannot be extended anymore"); + assert(row <= this->size_ + && "Index of the copied row cannot be bigger than the size"); + assert(col <= this->size_ + && "Index of the copied col cannot be bigger than the size"); + + // if the row index corresponds to the index of the newly created + // row, it will be initialized using default values of the type T + if(row == this->size_) { + data_.emplace_back(); + for(size_t i = 0; i < this->size_; ++i) { + data_[this->size_].emplace_back(T()); + } + // otherwise, the row at the index 'row' will be duplicated + } else { + data_.push_back(data_[row]); + } + + // if the column index corresponds to the index of the newly created + // column, it will be initialized using default values of the type T + if(col == this->size_) { + for(size_t i = 0; i < this->size_; ++i) { + data_[i].emplace_back(T()); + } + // otherwise, the column at the index 'col' will be duplicated + } else { + for(size_t i = 0; i < this->size_; ++i) { + data_[i].push_back(data_[i][col]); + } + } + + // element at the position [n, n] will always be initialized using + // the default value of the type T + data_[this->size_].emplace_back(T()); + + // the size of the matrix increases after extending + ++this->size_; + } + + // + // CLONING + // + + std::unique_ptr> clone() const override { + return std::make_unique>(*this); + } + +}; // DynamicSquareMatrix + + + +/************************************* +* +* HASHED SQUARE MATRIX +* +**************************************/ + +/** + * HashedSquareMatrix + * + * @brief Hashed square matrix implemented using unordered hash map + * + * The matrix is represented as a unordered hash map of the type T indexed + * by the size_t type. It is referred as in context of row-traversal of the + * matrix. To access matrix[i][j], we use map[i * capacity + j]. +**/ +template +class HashedSquareMatrix : public ExtendableSquareMatrix { + private: + + // data are stored in a hashmap + mutable std::unordered_map data_{}; + + public: + + // + // CONSTRUCTORS + // + + /** + * @brief creates a Hashed square matrix + * @param[in] max_rows capacity of the square matrix + * @param[in] init_rows initial size of the square matrix + */ + HashedSquareMatrix(size_t max_rows, size_t init_rows) { + assert(init_rows <= max_rows && + "Initial size of the matrix cannot be" + "bigger than the capacity"); + + this->m_type = MatrixType::Hashed; + this->capacity_ = max_rows; + + // creating the initial size and filling the data cells with + // default values + for(size_t i = 0; i < init_rows; ++i) {extend();} + } + + // + // IMPLEMENTED VIRTUAL METHODS + // + + /** + * @brief Assigns a value to the Hashed square matrix. + * @param[in] i row of the square matrix + * @param[in] j column of the square matrix + * @param[in] value value to be assigned to the square matrix data cell + */ + void set(size_t i, size_t j, T value) { + assert(i < this->size_ && "Nonexistent row cannot be accessed"); + assert(j < this->size_ && "Nonexistent column cannot be accessed"); + + // accessing the hashmap using row matrix traversal + data_[i * this->capacity_ + j] = value; + } + + /** + * @brief returns a value of the Hashed square matrix + * @param[in] i row of the square matrix + * @param[in] j column of the square matrix + * @return value found in the square matrix data cell + */ + T get(size_t i, size_t j) const { + assert(i < this->size_ && "Nonexistent row cannot be accessed"); + assert(j < this->size_ && "Nonexistent column cannot be accessed"); + + // accessing the hashmap using row matrix traversal + return data_[i * this->capacity_ + j]; + } + + /** + * @brief extends the Hashed square matrix by a new row and column + * @param[in] placeholder a value which will be assigned to all the + * new data cells + */ + void extend(T placeholder = T()) { + assert(this->size_ < this->capacity_ + && "Matrix cannot be extended anymore"); + + // creating a new row and column + for(size_t i = 0; i < this->size_; ++i) { + data_[this->size_ * this->capacity_ + i] = placeholder; + data_[i * this->capacity_ + this->size_] = placeholder; + } + data_[this->size_ * this->capacity_ + this->size_] = placeholder; + + // increasing size + ++this->size_; + } + + /** + * Extends the n x n hashed matrix to the (n+1) x (n+1) matrix by + * duplicating an existing row and column. The row parameter is an index + * of the row which should be duplicated and added as a (n+1)th row, + * while the col parameter is an index of the column which should be + * duplicated and added as an (n+1)th row. If the row parameter equals n, + * then it will be initialized using default values of the type T. If the + * col parameter equals n, the new column will be also initialized + * with the defaults values of the type T. Using this approach, one is + * able to copy only a row or column and initialize the other one with + * default values. Calling extend_and_copy(n, n) has the same effect as + * calling extend(). + * The element at the position [n, n] will be always initialized using + * the default value of the type T. + * @brief changes the n x n matrix to the (n+1) x (n+1) matrix with + * copying of the existing data + * @param[in] row index of the row which should be copied + * @param[in] col index of the column which should be copied + */ + void extend_and_copy(size_t row, size_t col) { + assert(this->size_ < this->capacity_ + && "The matrix cannot be extended anymore"); + assert(row <= this->size_ + && "Index of the copied row cannot be bigger than the size"); + assert(col <= this->size_ + && "Index of the copied col cannot be bigger than the size"); + + // if the row index corresponds to the index of the newly created + // row, it will be initialized using default values of the type T + if(row == this->size_) { + for(size_t i = 0; i < this->size_; ++i) { + data_[this->size_ * this->capacity_ + i] = T(); + } + // otherwise, the row at the index 'row' will be duplicated + } else { + for(size_t i = 0; i < this->size_; ++i) { + data_[this->size_ * this->capacity_ + i] = + data_[row * this->capacity_ + i]; + } + } + + // if the col index corresponds to the index of the newly created + // column, it will be initialized using default values of the type T + if(col == this->size_) { + for(size_t i = 0; i < this->size_; ++i) { + data_[i * this->capacity_ + this->size_] = T(); + } + // otherwise, the column at the index 'col' will be duplicated + } else { + for(size_t i = 0; i < this->size_; ++i) { + data_[i * this->capacity_ + this->size_] = + data_[i * this->capacity_ + col]; + } + } + + // element at the position [n, n] will always be initialized using + // the default value of the type T + data_[this->size_ * this->capacity_ + this->size_] = T(); + + // the size of the matrix increases after extending + ++this->size_; + } + + // + // CLONING + // + + std::unique_ptr> clone(void) const override { + return std::make_unique>(*this); + } + +}; // HashedSquareMatrix + +/************************************* +* +* ADDITIONAL FUNCTIONS +* +**************************************/ + +/** +* @brief factory function which creates an ExtendableSquareMatrix of given type +* @param[in] type type of the new matrix +* @param[in] capacity maximal matrix capacity +* @param[in] size initial matrix size +* @return pointer to the newly created matrix +*/ +template +inline std::unique_ptr> create(MatrixType type, + size_t capacity, size_t size = 0) { + + switch(type) { + case MatrixType::Cascade: + return std::make_unique>(capacity, size); + case MatrixType::Dynamic: + return std::make_unique>(capacity, size); + case MatrixType::Hashed: + return std::make_unique>(capacity, size); + default: + return nullptr; + } +} + +/** +* @brief debugging function which allows us to print text representation of +* the Extendable square matrix +* @param[out] output stream +* @param[in] matrix which will be printed +* @return output stream +*/ +template +inline std::ostream& operator<<(std::ostream& os, + const ExtendableSquareMatrix& matrix) { + + size_t size = matrix.size(); + size_t capacity = matrix.capacity(); + std::string result = "\nSIZE: " + std::to_string(size) + "\n"; + result += "CAPACITY: " + std::to_string(capacity) + "\n"; + result += "MATRIX:\n"; + for(size_t i = 0; i < size; ++i) { + for(size_t j = 0; j < size; ++j) { + result += std::to_string( + static_cast(matrix.get(i, j))) + " "; + } + result += "\n"; + } + return os << result; +} + +} // namespace mata::utils + +#endif // EXTENDABLE_SQUARE_MATRIX_HH_ diff --git a/include/mata/utils/partition.hh b/include/mata/utils/partition.hh new file mode 100644 index 000000000..f97261f62 --- /dev/null +++ b/include/mata/utils/partition.hh @@ -0,0 +1,485 @@ +/** @file partition.hh + * @brief Definition of a partition + * + * In this context, we consider a carrier set S which contains all + * natural numbers from 0 to |S|-1 and nothing else. These numbers are called + * states. + * Then, partition over S is a set of blocks such that: + * - each block contains only states + * - each state is represented in exactly one block + * - blocks are disjoint + * - there is no state which is not represented in any block + * - no block is empty + * + * This file provides implementation of a partition P which allows us to: + * - find the block which contains a given state in O(1) + * - find a representative state of the given block in O(1) + * - test whether two states share the same block in O(1) + * - test whether all states in a vector A share the same block in O(|A|) + * - iterate through the block B in O(|B|) + * - iterate through the node N in O(|N|) + * - split the whole partition such that each block + * is split in two pieces or remains unchanged in O(|S|) + * - remember all ancestors of current blocks and access them if necessary + * so we can manipulate multiple generations of a partition (before and + * after it has been split) + * + * @author Tomáš Kocourek + */ + +#ifndef PARTITION_HH_ +#define PARTITION_HH_ + +#include +#include +#include +#include +#include "mata/utils/sparse-set.hh" + +namespace mata::utils { + +/************************************************************************ +* +* +* +* PARTITION +* +* +* +*************************************************************************/ + +using State = unsigned long; +using StateBlock = std::vector; +using StateBlocks = std::vector; + +/* +* @struct SplitPair +* @brief contains information about block which has been split +* +* The structure SplitPair is created as soon as a block of the partition +* is split. It contains: +* - index of the new block which keeps the identity of the split block +* (first_block_idx) +* - index of the new block which has been created (second_block_idx) +* - index of the node which had represented the former block before +* (node_idx) +* Using first_block_idx and second_block_idx, we can manipulate with the +* current generation of the partition. Using node_idx, we are able to work +* with the older generation of the partition. +*/ +using SplitPair = struct SplitPair { + + size_t first_block_idx; + size_t second_block_idx; + size_t node_idx; + + /** + * Initialization of the SplitPair + * + * @brief default constructor + * @param[in] first first part of the split block + * @param[in] second second part of the split block + * @param[in] node node corresponding to the split block + */ + SplitPair(size_t first, size_t second, size_t node) : + first_block_idx(first), second_block_idx(second), node_idx(node) {} +}; + +/** + * @class Partition + * @brief Partition of a set of states + * + * This data structure provides a partition of a set of states S. In this + * context, the term 'state' refers to any natural number from the + * interval <0, |S|-1>. + * + * This representation defines: + * - states - element from a consecutive interval of natural numbers + * - blocks - objects which represent the current generation of the partition. + * Each block refers to several states which belong to the block. + * The block could be possibly split. + * - nodes - objects which represent blocks either from the current generation + * of the partition or the block from the previous generations of + * the partition (block which had been split). Once a node is + * created, it is never changed. When a block is split, two new nodes + * are created. + * - block items + * - objects which serve as intermediate data structure between states + * and blocks. Each block item contains indices of both corresponding + * state and block. Block items are sorted in such way that one could + * iterate through each block B or each node N in the O(B) or O(N) + * time, respectively. + * + * Detailed explanation: + * + * STATES: + * States are represented by indices of the vector 'states_' with the + * unchangeable size |S|. Each element of the vector represents the state + * by its order in the 'states_' vector. The vector itself contains indices + * of corresponding block items. Index of the block item corresponding + * to the state 's' could be get in constant time using 'states_[s]'. + * + * BLOCK ITEMS: + * Block items are stored in the 'block_items_' vector of the unchangeable size + * |S|. The block item can by accessed by its index using + * block_items[block_item_idx]. Each block item contains its index (which is + * always the same as its order in the 'block_items_' vector but + * it is stored directly in the object to simplify manipulations with + * the partition). It also contains the index of its corresponding state + * which means that states and block items are bijectively mapped. + * In addition, each BlockItem includes an index of the corresponding partition + * block. + * The ordering of 'block_items_' vector satisfies the condition that the states + * of the same block (or node) should always form a contiguous subvector so one + * could iterate through states in each block (or node) efficiently using + * 'block_items' vector. + * + * BLOCKS: + * The blocks themselves are represented by the vector 'blocks_' with the + * size |P|, where P is a partition of states. Each block can be accessed by + * its index 0 <= i < |P|. The block can by accessed by its index using + * blocks_[block_idx]. The block contains an index of itself and an index of its + * corresponding node. The total number of blocks can be changed as soon as + * one block is split. However, the maximal number of blocks is equal to |S| + * (the case when each block contains only one state). When a block 'B' is + * split in two pieces 'B1' and 'B2', we create a brand new block 'B2' + * and modify the former block 'B' such that it will correspond to its + * subblock 'B1'. The former block 'B' is thus not represented + * in the 'blocks_' vector anymore since 'B1' takes over its identity. + * + * NODES: + * Each node represents a current block or a block which has been split before. + * The node can by accessed by its index using nodes_[node_idx]. If the given + * node represents an existing block, such block contains an index of that node. + * In context of nodes which represent former blocks, no block contains their + * indices. The total number of nodes can be changed as soon as + * one block is split. In such situation, two new nodes (which represent both + * new blocks) are created and the former node remains unchanged. Therefore, the + * maximal number of nodes is equal to 2 * |S| - 1 since once a node is created, + * it is never changed. Each node contains its own index and two indices of + * block items (namely 'first' and 'last') which could be used to access + * block items corresponding to the first and last block items which form a + * contiguous subvector of 'block_items_' vector included in such node. + * When a block is split, the corresponding block items are swapped + * in situ such that the indices 'first' and 'last' still surround the + * corresponding node and both new nodes also point to the contiguous subvector + * of its block items. + * + * EXAMPLE: + * In the example below, we represent a partition {{0, 1, 2, 3, 4}} + * which had been split to the partition {{0, 2}, {1, 3, 4}}. + * Thus, we have two blocks: 0 ({0, 2}) and 1 ({1, 3, 4}) which form the current + * generation of the partition. + * We also have three nodes: 0 ({0, 1, 2, 3, 4}), 1 ({0, 2}) and 2 ({1, 3, 4}) + * which represent both current generation of the partition and also block which + * does not exist anymore since it had been split. + * The block 0 corresponds to the node 1 and the block 1 corresponds to the + * node 2. + * The node 1 contains indices 0 (first) and 1 (last) which means that + * the blockItems 0 and 1 surround a contiguous subvector of elements in the + * node 1 (or in the block 0). + * Likewise, the node 2 contains indices 2 (first) and 4 (last) which means that + * the blockItems 2 and 4 surround a contiguous subvector of elements in the + * node 2 (or in the block 1). + * Moreover, we also represent the former block which does not exist anymore + * by the node 0 which contains indices 0 (first) and 4 (last) which means that + * the blockItems 0 and 4 surround a contiguous subvector of elements in the + * node 0. Thus, we know that there had been a block {0, 1, 2, 3, 4} before + * it has been split to obtain blocks {0, 2} and {1, 3, 4}. + * In the picture below, indices of vectors are depicted outside of the vectors. + * + * + * 0 1 2 3 4 + * ------- ------- ------- ------- ------- + * | 0 | 2 | 1 | 4 | 3 | states_ + * ------- ------- ------- ------- ------- + * ↑ ↑ ↑ ↑ ↑ + * | \ / \ / + * | X X + * | / \ / \ + * 0 ↓ 1 ↓ ↓ 2 3 ↓ ↓ 4 + * ------- ------- ------- ------- ------- + * | 0 | 2 | 1 | 4 | 3 | block_items_ + * --→→→|-------|-------|-------|-------|--------←←←------------ + * | | 0 | 0 | 1 | 1 | 1 | | + * | ------- ------- ------- ------- ------- | + * | | | | | | | + * | 0 ↓ ↓ 1 ↓ ↓ ↓ | + * | ---------------------------------------- | + * | | 1 | 2 | blocks_ | + * | ---------------------------------------- | + * | | | | + * | 0 1 ↓ 2 ↓ | + * | ------- ------- ------- | + * -----| 0 | 0 | 2 | nodes_ | + * |-------|-------|-------| | + * | 4 | 1 | 4 | | + * ------- ------- ------- | + * | | + * ---------------------------------------------------- + * + * Using this class, we can: + * - find the block which contains a given state in O(1) + * - find a representative state of the given block in O(1) + * - test whether two states share the same block in O(1) + * - test whether all states in a vector A share the same block in O(|A|) + * - iterate through the block B in O(|B|) + * - iterate through the node N in O(|N|) + * - split the whole partition such that each block + * is split in two pieces or remains unchanged in O(|S|) + * - remember all ancestors of current blocks and access them if necessary + * so we can manipulate multiple generations of a partition (before and + * after it has been split) + * + */ +class Partition { + + private: + + class BlockItem; + class Block; + class Node; + + using States = std::vector; + using BlockItems = std::vector; + using Blocks = std::vector; + using Nodes = std::vector; + + /**< vector of states referring to the block items */ + States states_{}; + + /**< vector of block items referring to the states and blocks */ + BlockItems block_items_{}; + + /**< vector of blocks referring to the nodes */ + Blocks blocks_{}; + + /**< vector of nodes referring to the first and last block item + of the node */ + Nodes nodes_{}; + + /** + * @class BlockItem + * @brief Intermediate between states and blocks + **/ + class BlockItem { + private: + + /**< index of itself */ + size_t idx_; + + /**< corresponding state */ + State state_; + + /**< index of the corresponding block */ + size_t block_idx_; + + /**< reference to the partition which works + with this block item */ + const Partition& partition_; + + // BlockItem class need to access private members + // of Partition class + friend class Partition; + + public: + + // constructors + BlockItem(size_t idx, size_t state, size_t block_idx, + const Partition& partition) : + + idx_(idx), state_(state), block_idx_(block_idx), + partition_(partition) {} + + // getters + size_t idx() const { return idx_; } + size_t state() const { return state_; } + + // methods which refer to the partition vectors + const Block& block() const { + return partition_.blocks_[block_idx_]; + } + const Node& node() const { return block().node(); } + const BlockItem& repr() const { return node().repr(); } + const BlockItem& first() const { return node().first(); } + const BlockItem& last() const { return node().last(); } + }; + + /** + * @class Block + * @brief Contains information about block from the current generation + * of the partition. + **/ + class Block { + private: + + /**< index of itself */ + size_t idx_; + + /**< index of the corresponding node */ + size_t node_idx_; + + /**< reference to the partition which works + with this block */ + const Partition& partition_; + + // Blocks need to access private members of Partition class + friend class Partition; + + public: + + // constructors + Block(size_t idx, size_t node_idx, const Partition& partition) : + idx_(idx), node_idx_(node_idx), partition_(partition) {} + + // getters + size_t idx() const { return idx_; } + + // methods which refer to the partition vectors + const Node& node() const { + return partition_.nodes_[node_idx_]; + } + const BlockItem& repr() const { return node().repr(); } + const BlockItem& first() const { return node().first(); } + const BlockItem& last() const { return node().last(); } + + // iterators + using const_iterator = typename BlockItems::const_iterator; + + const_iterator begin() const { + const_iterator it = partition_.block_items_.begin(); + std::advance(it, static_cast(node().first().idx())); + return it; + } + + const_iterator end() const { + const_iterator it = partition_.block_items_.begin(); + std::advance(it, static_cast(node().last().idx()) +1); + return it; + } + + // information about the block + size_t size() const { + return last().idx() - first().idx() + 1; + } + + }; + + /** + * @class Node + * @brief Contains information about block from the current generation + * of the partition or from the previous generation of the partition. + **/ + class Node { + private: + + /**< index of itself */ + size_t idx_; + + /**< index of the first block item in the node */ + size_t first_; + + /**< index of the last block item in the node */ + size_t last_; + + /**< reference to the partition which works + with this block */ + const Partition& partition_; + + // Blocks need to access private members of Partition class + friend class Partition; + + public: + + // constructors + Node(size_t idx, size_t first, size_t last, + const Partition& partition) : + + idx_(idx), first_(first), last_(last), partition_(partition) + {} + + // getters + size_t idx() const { return idx_; } + + // methods which refer to the partition vectors + const BlockItem& first() const { + return partition_.block_items_[first_]; + } + const BlockItem& last() const { + return partition_.block_items_[last_]; + } + const BlockItem& repr() const { + return partition_.block_items_[first_]; + } + + // iterators + using const_iterator = + typename BlockItems::const_iterator; + + const_iterator begin() const { + const_iterator it = partition_.block_items_.begin(); + std::advance(it, static_cast(first().idx())); + return it; + } + + const_iterator end() const { + const_iterator it = partition_.block_items_.begin(); + std::advance(it, static_cast(last().idx()) + 1); + return it; + } + + // information about the node + size_t size() const { + return last().idx() - first().idx() + 1; + } + + bool contains_block(size_t block_idx) const { + const Block& block = partition_.get_block(block_idx); + return first_ <= block.first().idx() && + last_ >= block.last().idx(); + } + }; + + public: + + // constructors + Partition() = default; + explicit Partition(size_t num_of_states, + const StateBlocks& partition = StateBlocks()); + Partition(const Partition& other); + + // sizes of the used vectors + size_t num_of_states() const { return states_.size(); } + size_t num_of_block_items() const { return block_items_.size(); } + size_t num_of_blocks() const { return blocks_.size(); } + size_t num_of_nodes() const { return nodes_.size(); } + + // blocks splitting + std::vector split_blocks(const SparseSet& marked); + + // basic information about the partition + size_t get_block_idx(State state) const; + bool in_same_block(State first, State second) const; + bool in_same_block(const std::vector& states) const; + std::vector states_in_same_block(State state) const; + + // accessing block items, blocks, nodes through indices + const BlockItem& get_block_item(size_t block_item_idx) const; + const Block& get_block(size_t block_idx) const; + const Node& get_node(size_t node_idx) const; + + // converts the partition to the vector of vectors of states + StateBlocks partition() const; + + // operators + Partition& operator=(const Partition& other); + friend std::ostream& operator<<(std::ostream& os, + const Partition& p); + + const BlockItem& operator[](State state) const; + +}; // Partition + +} + +#endif // PARTITION_HH_ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3b5bbea0..6660c7de0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(libmata STATIC mintermization.cc parser.cc re2parser.cc + partition.cc nfa/nfa.cc nfa/inclusion.cc nfa/universal.cc diff --git a/src/partition.cc b/src/partition.cc new file mode 100644 index 000000000..1508f124f --- /dev/null +++ b/src/partition.cc @@ -0,0 +1,531 @@ +/** @file partition.cc + * @brief Definition of a partition + * + * In this context, we consider a carrier set S which contains all + * natural numbers from 0 to |S|-1 and nothing else. These numbers are called + * states. + * Then, partition over S is a set of blocks such that: + * - each block contains only states + * - each state is represented in exactly one block + * - blocks are disjoint + * - there is no state which is not represented in any block + * - no block is empty + * + * This file provides implementation of a partition P which allows us to: + * - find the block which contains a given state in O(1) + * - find a representative state of the given block in O(1) + * - test whether two states share the same block in O(1) + * - test whether all states in a vector A share the same block in O(|A|) + * - iterate through the block B in O(|B|) + * - iterate through the node N in O(|N|) + * - split the whole partition such that each block + * is split in two pieces or remains unchanged in O(|S|) + * - remember all ancestors of current blocks and access them if necessary + * so we can manipulate multiple generations of a partition (before and + * after it has been split) + * + * @author Tomáš Kocourek + */ + +#include +#include +#include "mata/utils/partition.hh" +#include "mata/utils/sparse-set.hh" + +namespace mata::utils { + +/** Constructor of the partition object. This method reserves memory space +* for the vectors used to represent partition to ensure us that they won't +* ever be moved in the memory when extended. The maximal sizes of these vectors +* can be computed using the num_of_states parameter. +* The partition can be initialized in linear time (in respect to the carrier +* set of the partition) using initial partition represented as a vector of +* vectors of states. +* The constructor works as follows: +* - if there is any nonexistent state in the initial partition, the construction +* fails (state >= num_of_states) +* - if there are duplicates in the initial partition, the construction fails +* - if there is an empty partition class, the construction fails +* - if there are states which are not represented in the initial partition, +* they will be all part of the one additional block +* If there is no initial partition, all states will be assigned +* to the same block +* @brief constructs the partition +* @param[in] num_of_states cardinality of the carrier set +* @param[in] partition optional initial partition in the form of vectors of +* vectors of states +*/ +Partition::Partition(size_t num_of_states, const StateBlocks& partition) { + + // reserving memory space to avoid moving extended vectors + if(num_of_states) [[likely]] { + states_.reserve(num_of_states); + block_items_.reserve(num_of_states); + blocks_.reserve(num_of_states); + nodes_.reserve(2 * num_of_states - 1); + } + + // this vector says whether the given state has been already seen + // in the given initial partition to detect duplicates + // and to detect unused states + std::vector used; + if(num_of_states) [[likely]] { used.reserve(num_of_states); } + used.insert(used.end(), num_of_states, false); + + // initialization of the states_ vector + states_.insert(states_.end(), num_of_states, 0); + + // creating the partition using given initial vector of vectors + size_t num_of_blocks = partition.size(); + // iterating through initial partition blocks + for(size_t block_idx = 0; block_idx < num_of_blocks; ++block_idx) { + assert(!partition[block_idx].empty() && + "Partition class cannot be empty."); + + // iterating through one partition block + for(auto state_idx : partition[block_idx]) { + assert(state_idx < num_of_states && + "Invalid state name detected while creating" + "a partition relation pair."); + assert(!used[state_idx] && + "Partition could not be created." + "Duplicate occurrence of a state"); + + used[state_idx] = true; + + // creating a corresponding BlockItem + size_t block_item_idx = block_items_.size(); + states_[state_idx] = block_item_idx; + block_items_.emplace_back(block_item_idx, state_idx, + block_idx, *this); + + } + + // first and last states of the block will be used to create + // a corresponding node + size_t first = partition[block_idx].front(); + size_t last = partition[block_idx].back(); + + // creating a corresponding block and node + size_t node_idx = nodes_.size(); + nodes_.emplace_back(node_idx, states_[first], states_[last], *this); + blocks_.emplace_back(block_idx, block_idx, *this); + } + + // we need to detect whether there is a state which has not been used + // to create an additional partition block + bool all_states_used = true; + + // first and last unused states will surround a contiguous subvector + // of BlockItems + size_t first = 0; + size_t last = 0; + + // Iterating through the vector of flags saying which states have been seen. + // We need to create an additional block which will contain all states which + // have not been represented in the input partition if such states exist + for(size_t state_idx = 0; state_idx < num_of_states; ++state_idx) { + // if a state has been already seen and processed, + // there is no need to add it to the additional block + if(used[state_idx]) { + continue; + } + + // if there is at least one unused state, we need to + // create an additional block + // this branch will be executed only once (first time it is reached) + // and then it will never be executed again + if(all_states_used) [[unlikely]] { + all_states_used = false; + first = state_idx; + ++num_of_blocks; + } + + // creating the new BlockItem + size_t block_item_idx = block_items_.size(); + last = state_idx; + states_[state_idx] = block_item_idx; + block_items_.emplace_back(block_item_idx, state_idx, num_of_blocks-1, + *this); + } + + // creating a new block and node if there was an unused state + if(!all_states_used) { + nodes_.emplace_back(nodes_.size(), states_[first], states_[last], + *this); + blocks_.emplace_back(num_of_blocks-1, num_of_blocks-1, *this); + } +} + +/** +* Custom copy constructor which preserves reserved memory for the +* partition vectors. This method has to be implemented since the custom +* assignment operator is also implemented. The preservation of the reserved +* memory is provided by the custom assignment operator=. +* @brief copy constructor of the Partition +* @param[in] other partition which will be copied +*/ +Partition::Partition(const Partition& other) { + // using the custom assignment operator + *this = other; +} + +/** +* @brief returns a BlockItem corresponding to the given index +* @param[in] block_item_idx index of the BlockItem +* @return corresponding BlockItem +*/ +const Partition::BlockItem& Partition::get_block_item( + size_t block_item_idx) const { + + assert(block_item_idx < num_of_block_items() && + "Nonexistent block item index used."); + return block_items_[block_item_idx]; +} + +/** +* @brief returns a block corresponding to the given index +* @param[in] block_idx index of the block +* @return corresponding block +*/ +const Partition::Block& Partition::get_block(size_t block_idx) const { + assert(block_idx < num_of_blocks() && "Nonexistent block index used."); + return blocks_[block_idx]; +} + +/** +* @brief returns a node corresponding to the given index +* @param[in] node_idx index of the node +* @return corresponding node +*/ +const Partition::Node& Partition::get_node(size_t node_idx) const { + assert(node_idx < num_of_nodes() && "Nonexistent node index used."); + return nodes_[node_idx]; +} + +/** +* @brief returns a block index corresponding to the given state +* @param[in] state given state +* @return corresponding block index +*/ +size_t Partition::get_block_idx(State state) const { + assert(state < num_of_states() && "Nonexistent state used"); + return block_items_[states_[state]].block_idx_; +} + +/** +* @brief tests whether the two given states correspond +* to the same partition block +* @param[in] first first state to be checked +* @param[in] second second state to be checked +* @return true iff both given states belong to the same partition block +*/ +bool Partition::in_same_block(State first, State second) const { + assert(first < states_.size() && "The given state does not exist"); + assert(second < states_.size() && "The given state does not exist"); + return get_block_idx(first) == get_block_idx(second); +} + +/** +* @brief tests whether the given n states correspond to the same partition block +* @param[in] states vector of states to be checked +* @return true iff all of the given states belong to the same partition block +*/ +bool Partition::in_same_block(const std::vector& state_idxs) const { + if(state_idxs.empty()) [[unlikely]] { return true; } + size_t block_idx = get_block_idx(state_idxs.front()); + for(size_t state_idx : state_idxs) { + assert(state_idx < states_.size() && "The given state does not exist."); + if(get_block_idx(state_idx) != block_idx) { return false; } + } + return true; +} + +/** +* @brief find all of the states which share the block with the input state +* @param[in] state input state +* @return vector of all the states in the corresponding block +*/ +std::vector Partition::states_in_same_block(size_t state_idx) const { + assert(state_idx < num_of_states() && "The given state does not exist."); + std::vector result{}; + + // iterating through the corresponding block + for(const BlockItem& block_item : (*this)[state_idx].block()) { + result.push_back(block_item.idx()); + } + + return result; +} + +/** +* @brief transforms inner representation of the partition to the vector of +* vectors of states +* @return vector of vectors of states +*/ +StateBlocks Partition::partition() const { + StateBlocks result{}; + for(const Block& block : blocks_) { + result.emplace_back(); + for(const BlockItem& block_item : block) { + result.back().push_back(block_item.state()); + } + } + return result; +} + +/** Splitting the blocks of existing partition. According to the input +* sparse set of states 'marked', there will be two types of states - marked +* and unmarked ones. The partition will be split as follows: +* - if there is a block whose all elements are marked, the block remains +* unchanged +* - if there is a block whose all elements are unmarked, the block remains +* unchanged +* - if there is a block which contains both marked and unmarked states, it +* will be split in two blocks such that the first one contains marked states +* and the second one contains unmarked states of the original block +* - it means that each block is either unchanged or split in two parts +* - if a block contains states such that corresponding BlockItems form +* contiguous subvector on the interval of natural numbers , the split +* nodes will correspond to such BlockItems that they form contiguous subvectors +* on the intervals of natural numbers and , where a <= k < b. +* The BlockItems on the interval will be swapped such that the property +* above holds. The representant (first BlockItem on the interval) will always +* keep its position (the strategy of swapping will adapt to the fact whether +* the representant is marked or not). Thus, a representant of any node never +* changes its position. +* Moreover, the node corresponding to the ancestor of the two split blocks +* will still describe a valid contiguous interval of natural numbers . +* - if an nonexistent state is used, the function detects it and fails +* +* If a block is split, the function creates a structure SplitPair which +* contains: +* - index of the new block which keeps identity of the former block +* - index of the new block which is newly constructed +* - index of the node which is an ancestor of these two blocks +* The function returns a vector of such SplitPairs. +* +* @brief splits blocks of the partition +* @param[in] marked marked states which influence splitting +* @return vector of SplitPairs which contain information about split blocks +*/ +std::vector Partition::split_blocks(const SparseSet& marked) { + + // the vector which will be returned as the result + std::vector split{}; + + // if there is no marked state, no block could be split + if(marked.empty()) [[unlikely]] { return split; } + + // this vector contains information about blocks whose states have been + // marked and keep number of states of each block which has been marked + // to ease detecting whether the whole block has been marked + std::vector used_blocks{}; + if(!blocks_.empty()) [[likely]] { used_blocks.reserve(blocks_.size()); } + used_blocks.insert(used_blocks.end(), blocks_.size(), 0); + + // iterating through the marked states to fill used_blocks vector + for(size_t i : marked) { + assert(i < states_.size() && "The given state does not exist."); + ++used_blocks[get_block_idx(i)]; + } + + size_t old_blocks_size, new_block_idx; + old_blocks_size = new_block_idx = blocks_.size(); + + // iterating through the existing blocks + for(size_t i = 0; i < old_blocks_size; ++i) { + // if no state of the given block has been marked, it won't be split + if(!used_blocks[i]) { continue; } + + // looking for the subvector of BlockItems which forms processed + // block and computing its size + Node node = get_block(i).node(); + size_t iter_first = node.first().idx(); + size_t iter_last = node.last().idx(); + size_t block_size = node.size(); + + // if all states of the processed block have been marked, the block + // won't be split + if(used_blocks[i] >= block_size) { continue; } + + // choosing the strategy of swapping BlocksItems such that + // the representant of split block keeps its position + bool repr_marked = marked[node.repr().state()]; + + // We access the first and last element of the subvector of BlockItems + // which forms processed block. We look for the first unmarked element + // from left and first marked element from right (or vice versa since + // the exact strategy is chosen according to the fact whether the first + // element is marked or not). As soon as such elements are found, they + // are swapped. This procedure continues until these two indices used + // to iterate through the BlockItems meet somewhere in the middle + do { + + // we choose the swapping strategy using XOR operation + while(repr_marked ^ (!marked[get_block_item(iter_first).state()])) { + + // this visited state will be part of the former block + ++iter_first; + } + while(repr_marked ^ marked[get_block_item(iter_last).state()]) { + + // this visited state will be part of the new block + block_items_[iter_last].block_idx_ = new_block_idx; + --iter_last; + } + + // if the used indices meet, we finish swapping + if(iter_first > iter_last) { + break; + } + + // swapping BlockItems + State swapped_state = block_items_[iter_first].state(); + block_items_[iter_first].state_ = block_items_[iter_last].state_; + block_items_[iter_last].state_ = swapped_state; + + // since states_ and block_items_ vectors should be bijectively + // mapped, we need to update states_ after swapping two BlockItems + states_[block_items_[iter_first].state_] = iter_first; + states_[block_items_[iter_last].state_] = iter_last; + + // after the blockItems are swapped, one of them should + // be assigned to the new block + block_items_[iter_last].block_idx_ = new_block_idx; + + // after the blockItems are swapped, we continue to the + // next blockItems + ++iter_first; + --iter_last; + } while(iter_first <= iter_last); + + // creating new nodes + size_t first_idx = node.first().idx(); + size_t last_idx = node.last().idx(); + nodes_.emplace_back(nodes_.size(), first_idx, iter_last, *this); + nodes_.emplace_back(nodes_.size(), iter_first, last_idx, *this); + + // split blocks has to refer to the new nodes + blocks_[i].node_idx_ = nodes_.size() - 2; + blocks_.emplace_back(new_block_idx, nodes_.size() - 1, *this); + blocks_.back().idx_ = new_block_idx; + + // since a block has been split, we need to return information about + // indices of components of split block and about the node which + // correspond to the block which has been split + split.emplace_back(i, new_block_idx, node.idx()); + + // index of the following block which could be created + ++new_block_idx; + } + return split; +} + +/** +* Custom assignment operator which preserves reserved capacities for the +* partition vectors and assign a proper partition reference to the newly +* created block items, blocks and nodes +* @brief assignment of the partition +* @param[in] other partition which will be copied +* @return modified partition +*/ +Partition& Partition::operator=(const Partition& other) { + // since the default copying of the vectors do not preserve + // reserved capacity, we need to reserve it manually and + // then insert elements of the other partition to the reserved space + // if we want to keep the former capacity + states_.clear(); + block_items_.clear(); + blocks_.clear(); + nodes_.clear(); + + size_t states_num = other.num_of_states(); + if(states_num) [[likely]] { + states_.reserve(states_num); + block_items_.reserve(states_num); + blocks_.reserve(states_num); + nodes_.reserve(2 * states_num - 1); + } + + // copying vectors without losing information about reserved capacity + // and storing reference to the assigned partition when creating block + // items, blocks and nodes + for(size_t i = 0; i < states_num; ++i) { + states_.push_back(other.states_[i]); + const BlockItem& b = other.get_block_item(i); + block_items_.emplace_back(b.idx_, b.state_, b.block_idx_, *this); + } + size_t blocks_num = other.num_of_blocks(); + for(size_t i = 0; i < blocks_num; ++i) { + const Block& b = other.get_block(i); + blocks_.emplace_back(b.idx_, b.node_idx_, *this); + } + size_t nodes_num = other.num_of_nodes(); + for(size_t i = 0; i < nodes_num; ++i) { + const Node& n = other.get_node(i); + nodes_.emplace_back(n.idx_, n.first_, n.last_, *this); + } + return *this; +} + +/** +* @brief finding a block item corresponding to the given state +* @param[in] state state whose block item will be found +* @return corresponding block item +*/ +const Partition::BlockItem& Partition::operator[](State state) const { + assert(state < states_.size() && "The given state does not exist."); + return block_items_[states_[state]]; +} + +/** +* @brief debugging function which allows us to print text representation of +* the partition +* @param[out] output stream +* @param[in] partition which will be printed +* @return output stream +*/ +std::ostream& operator<<(std::ostream& os, const Partition& p) { + std::string result = std::string(); + result += "NUM OF STATES: " + std::to_string(p.num_of_states()) + "\n"; + result += "NUM OF BLOCKS: " + std::to_string(p.num_of_blocks()) + "\n"; + result += "NUM OF NODES: " + std::to_string(p.num_of_nodes()) + "\n"; + result += "\n"; + + result += "BLOCK ITEMS:\n"; + result += "idx -> state block_idx\n"; + for(const Partition::BlockItem& block_item : p.block_items_) { + result += std::to_string(block_item.idx()) + " -> " + + std::to_string(block_item.state()) + " " + + std::to_string(block_item.block().idx()) + "\n"; + + } + result += "\n"; + result += "BLOCKS:\n"; + result += "idx: states -> node_idx\n"; + for(const Partition::Block& block : p.blocks_) { + result += std::to_string(block.idx()) + ": "; + for(const Partition::BlockItem& block_item : block) { + result += std::to_string(block_item.state()) + " "; + } + result += "-> " + std::to_string(block.node().idx()) + "\n"; + } + result += "\n"; + + result += "NODES:\n"; + result += "idx: states -> first_idx last_idx\n"; + for(const Partition::Node& node : p.nodes_) { + result += std::to_string(node.idx()) + ": "; + for(const Partition::BlockItem& block_item : node) { + result += std::to_string(block_item.state()) + " "; + } + result += "-> " + std::to_string(node.first().idx()) + " " + + std::to_string(node.last().idx()) + "\n"; + } + result += "\n"; + + return os << result; +} + +} // namespace mata::utils diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c7a0a17e6..e0bcadc69 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,8 @@ add_executable(tests ord-vector.cc sparse-set.cc + partition.cc + extendable-square-matrix.cc synchronized-iterator.cc main.cc alphabet.cc diff --git a/tests/extendable-square-matrix.cc b/tests/extendable-square-matrix.cc new file mode 100644 index 000000000..f7f0f4d5a --- /dev/null +++ b/tests/extendable-square-matrix.cc @@ -0,0 +1,285 @@ +#include + +#include "mata/utils/extendable-square-matrix.hh" + +using namespace mata::utils; + +TEST_CASE("mata::utils::ExtendableSquareMatrix") { + + SECTION("CascadeSquareMatrix") { + + std::unique_ptr> e = + create(Cascade, 5, 2); + CHECK(e->size() == 2); + CHECK(e->capacity() == 5); + e->extend(); + CHECK(e->size() == 3); + CHECK(e->capacity() == 5); + e->extend(); + CHECK(e->size() == 4); + CHECK(e->capacity() == 5); + CHECK(e->get(0, 0) == 0); + CHECK(!e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(e->is_transitive()); + e->set(0, 0, 1); + CHECK(!e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(e->is_transitive()); + e->set(1, 1, 1); + e->set(2, 2, 1); + e->set(3, 3, 1); + CHECK(e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(e->is_transitive()); + e->set(3, 1, 1); + e->set(1, 2, 1); + CHECK(e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(!e->is_transitive()); + } + + SECTION("DynamicSquareMatrix") { + + std::unique_ptr> e = + create(Dynamic, 5, 2); + CHECK(e->size() == 2); + CHECK(e->capacity() == 5); + e->extend(); + CHECK(e->size() == 3); + CHECK(e->capacity() == 5); + e->extend(); + CHECK(e->size() == 4); + CHECK(e->capacity() == 5); + CHECK(e->get(0, 0) == 0); + CHECK(!e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(e->is_transitive()); + e->set(0, 0, 1); + CHECK(!e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(e->is_transitive()); + e->set(1, 1, 1); + e->set(2, 2, 1); + e->set(3, 3, 1); + CHECK(e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(e->is_transitive()); + e->set(3, 1, 1); + e->set(1, 2, 1); + CHECK(e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(!e->is_transitive()); + } + + SECTION("HashedSquareMatrix") { + + std::unique_ptr> e = + create(Hashed, 5, 2); + CHECK(e->size() == 2); + CHECK(e->capacity() == 5); + e->extend(); + CHECK(e->size() == 3); + CHECK(e->capacity() == 5); + e->extend(); + CHECK(e->size() == 4); + CHECK(e->capacity() == 5); + CHECK(e->get(0, 0) == 0); + CHECK(!e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(e->is_transitive()); + e->set(0, 0, 1); + CHECK(!e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(e->is_transitive()); + e->set(1, 1, 1); + e->set(2, 2, 1); + e->set(3, 3, 1); + CHECK(e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(e->is_transitive()); + e->set(3, 1, 1); + e->set(1, 2, 1); + CHECK(e->is_reflexive()); + CHECK(e->is_antisymmetric()); + CHECK(!e->is_transitive()); + } + + SECTION("Matrix of the None type") { + + std::unique_ptr> e = + create(None, 5, 2); + CHECK(e == nullptr); + + } + + SECTION("Empty matrices") { + std::unique_ptr> e1 = + create(Cascade, 5, 0); + std::unique_ptr> e2 = + create(Dynamic, 5, 0); + std::unique_ptr> e3 = + create(Hashed, 5, 0); + + CHECK(!e1->size()); + CHECK(!e2->size()); + CHECK(!e3->size()); + + CHECK(e1->capacity() == 5); + CHECK(e2->capacity() == 5); + CHECK(e3->capacity() == 5); + + std::unique_ptr> c1 = + e1->clone(); + std::unique_ptr> c2 = + e2->clone(); + std::unique_ptr> c3 = + e3->clone(); + + CHECK(!c1->size()); + CHECK(!c2->size()); + CHECK(!c3->size()); + + CHECK(c1->capacity() == 5); + CHECK(c2->capacity() == 5); + CHECK(c3->capacity() == 5); + + } + + SECTION("Matrices with only one element") { + std::unique_ptr> e1 = + create(Cascade, 5, 1); + std::unique_ptr> e2 = + create(Dynamic, 5, 1); + std::unique_ptr> e3 = + create(Hashed, 5, 1); + + + CHECK(e1->size() == 1); + CHECK(e2->size() == 1); + CHECK(e3->size() == 1); + + CHECK(e1->capacity() == 5); + CHECK(e2->capacity() == 5); + CHECK(e3->capacity() == 5); + + std::unique_ptr> c1 = + e1->clone(); + std::unique_ptr> c2 = + e2->clone(); + std::unique_ptr> c3 = + e3->clone(); + + CHECK(c1->size() == 1); + CHECK(c2->size() == 1); + CHECK(c3->size() == 1); + + CHECK(c1->capacity() == 5); + CHECK(c2->capacity() == 5); + CHECK(c3->capacity() == 5); + + } + + SECTION("Copying matrices") { + + std::unique_ptr> m1 = + create(Cascade, 1000, 2); + std::unique_ptr> m2 = + create(Dynamic, 5, 2); + std::unique_ptr> m3 = + create(Hashed, 5, 2); + + std::unique_ptr> c1 = m1->clone(); + std::unique_ptr> c2 = m2->clone(); + std::unique_ptr> c3 = m3->clone(); + + m1->set(0, 1, true); + m2->set(0, 1, true); + m3->set(0, 1, true); + + CHECK(m1->get(0, 1) != c1->get(0, 1)); + CHECK(m2->get(0, 1) != c2->get(0, 1)); + CHECK(m3->get(0, 1) != c3->get(0, 1)); + + CHECK(!c1->get(0, 1)); + CHECK(!c2->get(0, 1)); + CHECK(!c3->get(0, 1)); + + } + + SECTION("Extend and copy") { + + std::unique_ptr> m1 = + create(Cascade, 5, 3); + std::unique_ptr> m2 = + create(Dynamic, 5, 3); + std::unique_ptr> m3 = + create(Hashed, 5, 3); + + m1->set(0, 0, true); + m1->set(1, 0, true); + m1->set(1, 1, true); + m1->set(1, 2, true); + m2->set(0, 0, true); + m2->set(1, 0, true); + m2->set(1, 1, true); + m2->set(1, 2, true); + m3->set(0, 0, true); + m3->set(1, 0, true); + m3->set(1, 1, true); + m3->set(1, 2, true); + + CHECK(m1->size() == 3); + CHECK(m2->size() == 3); + CHECK(m3->size() == 3); + CHECK(m1->capacity() == 5); + CHECK(m2->capacity() == 5); + CHECK(m3->capacity() == 5); + + m1->extend_and_copy(3, 3); + m2->extend_and_copy(3, 3); + m3->extend_and_copy(3, 3); + m1->extend_and_copy(1, 0); + m2->extend_and_copy(1, 0); + m3->extend_and_copy(1, 0); + + CHECK(m1->size() == 5); + CHECK(m2->size() == 5); + CHECK(m3->size() == 5); + CHECK(m1->capacity() == 5); + CHECK(m2->capacity() == 5); + CHECK(m3->capacity() == 5); + + CHECK(m1->get(0, 4)); + CHECK(m1->get(1, 4)); + CHECK(!m1->get(2, 4)); + CHECK(!m1->get(3, 4)); + CHECK(m1->get(4, 0)); + CHECK(m1->get(4, 1)); + CHECK(m1->get(4, 2)); + CHECK(!m1->get(4, 3)); + CHECK(!m1->get(4, 4)); + + CHECK(m2->get(0, 4)); + CHECK(m2->get(1, 4)); + CHECK(!m2->get(2, 4)); + CHECK(!m2->get(3, 4)); + CHECK(m2->get(4, 0)); + CHECK(m2->get(4, 1)); + CHECK(m2->get(4, 2)); + CHECK(!m2->get(4, 3)); + CHECK(!m2->get(4, 4)); + + CHECK(m3->get(0, 4)); + CHECK(m3->get(1, 4)); + CHECK(!m3->get(2, 4)); + CHECK(!m3->get(3, 4)); + CHECK(m3->get(4, 0)); + CHECK(m3->get(4, 1)); + CHECK(m3->get(4, 2)); + CHECK(!m3->get(4, 3)); + CHECK(!m3->get(4, 4)); + + } + +} diff --git a/tests/partition.cc b/tests/partition.cc new file mode 100644 index 000000000..1f53b6f8e --- /dev/null +++ b/tests/partition.cc @@ -0,0 +1,665 @@ +#include + +#include "mata/utils/partition.hh" + +using namespace mata::utils; + +TEST_CASE("mata::utils::Partition") { + SECTION("Create simple partition with 1 block") { + Partition p{10}; + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 1); + CHECK(p.num_of_nodes() == 1); + CHECK(p.in_same_block({})); + CHECK(p.in_same_block({0})); + CHECK(p.in_same_block(0, 1)); + CHECK(p.in_same_block(1, 8)); + CHECK(p.in_same_block({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + for(size_t i = 0; i < 10; ++i) { + CHECK(p.get_block_item(i).state() == i); + CHECK(p.get_block_item(i).idx() == i); + CHECK(p.get_block_item(i).block().idx() == 0); + CHECK(p.get_block_idx(i) == 0); + CHECK(p.get_block_item(i).node().idx() == 0); + CHECK(p.get_block_item(i).repr().idx() == 0); + CHECK(p.get_block_item(i).first().idx() == 0); + CHECK(p.get_block_item(i).last().idx() == 9); + CHECK(p.get_block_item(i).node().first().idx() == 0); + CHECK(p.get_block_item(i).node().last().idx() == 9); + CHECK(p[i].idx() == i); + } + CHECK(p.get_block(0).idx() == 0); + CHECK(p.get_block(0).node().idx() == 0); + CHECK(p.get_block(0).repr().idx() == 0); + CHECK(p.get_block(0).first().idx() == 0); + CHECK(p.get_block(0).last().idx() == 9); + CHECK(p.get_block(0).size() == 10); + CHECK(p.get_node(0).idx() == 0); + CHECK(p.get_node(0).first().idx() == 0); + CHECK(p.get_node(0).last().idx() == 9); + CHECK(p.get_node(0).size() == 10); + CHECK(p.get_node(0).contains_block(0)); + for(auto& block_item : p.get_block(0)) { + CHECK(block_item.block().idx() == 0); + } + for(auto& block_item : p.get_node(0)) { + CHECK(block_item.node().idx() == 0); + } + CHECK(p.states_in_same_block(0).size() == 10); + CHECK(p.partition().size() == 1); + } + + SECTION("Create another simple partition with 1 block") { + Partition p = Partition(10, {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 1); + CHECK(p.num_of_nodes() == 1); + CHECK(p.in_same_block({})); + CHECK(p.in_same_block({0})); + CHECK(p.in_same_block(0, 1)); + CHECK(p.in_same_block(1, 8)); + CHECK(p.in_same_block({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + for(size_t i = 0; i < 10; ++i) { + CHECK(p.get_block_item(i).state() == i); + CHECK(p.get_block_item(i).idx() == i); + CHECK(p.get_block_item(i).block().idx() == 0); + CHECK(p.get_block_item(i).node().idx() == 0); + CHECK(p.get_block_item(i).repr().idx() == 0); + CHECK(p.get_block_item(i).first().idx() == 0); + CHECK(p.get_block_item(i).last().idx() == 9); + CHECK(p.get_block_item(i).node().first().idx() == 0); + CHECK(p.get_block_item(i).node().last().idx() == 9); + CHECK(p[i].idx() == i); + } + CHECK(p.get_block(0).idx() == 0); + CHECK(p.get_block(0).node().idx() == 0); + CHECK(p.get_block(0).repr().idx() == 0); + CHECK(p.get_block(0).first().idx() == 0); + CHECK(p.get_block(0).last().idx() == 9); + CHECK(p.get_block(0).size() == 10); + CHECK(p.get_node(0).idx() == 0); + CHECK(p.get_node(0).first().idx() == 0); + CHECK(p.get_node(0).last().idx() == 9); + CHECK(p.get_node(0).size() == 10); + CHECK(p.get_node(0).contains_block(0)); + for(auto& block_item : p.get_block(0)) { + CHECK(block_item.block().idx() == 0); + } + for(auto& block_item : p.get_node(0)) { + CHECK(block_item.node().idx() == 0); + } + CHECK(p.states_in_same_block(0).size() == 10); + CHECK(p.partition().size() == 1); + } + + SECTION("Create a simple partition with 2 blocks") { + Partition p{10, {{0, 5, 8}}}; + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 2); + CHECK(p.num_of_nodes() == 2); + CHECK(p.in_same_block({})); + CHECK(p.in_same_block({0})); + CHECK(p.in_same_block(0, 5)); + CHECK(p.in_same_block(5, 8)); + CHECK(!p.in_same_block(6, 5)); + CHECK(p.in_same_block({0, 5, 8})); + CHECK(p.in_same_block({1, 2, 3, 4, 6, 7, 9})); + CHECK(!p.in_same_block({1, 2, 3, 4, 5, 7, 9})); + CHECK(p[0].idx() == 0); + CHECK(p[0].state() == 0); + CHECK(p[0].block().idx() == 0); + CHECK(p[0].node().idx() == 0); + CHECK(p.get_block_item(0).node().idx() == 0); + CHECK(p.get_block_item(0).repr().idx() == 0); + CHECK(p.get_block_item(0).first().idx() == 0); + CHECK(p.get_block_item(0).last().idx() == 2); + CHECK(p[1].idx() == 3); + CHECK(p.get_block_item(3).state() == 1); + CHECK(p.get_block_item(3).repr().state() == 1); + CHECK(p.get_block_item(3).first().state() == 1); + CHECK(p.get_block_item(3).last().state() == 9); + CHECK(p[1].block().idx() == 1); + CHECK(p[1].node().idx() == 1); + CHECK(p.get_block_item(3).block().idx() == 1); + CHECK(p.get_block_item(3).node().idx() == 1); + CHECK(p.get_block(0).repr().state() == 0); + CHECK(p.get_block(1).repr().state() == 1); + CHECK(p.get_block(0).repr().block().idx() == 0); + CHECK(p.get_block(1).repr().block().idx() == 1); + CHECK(p.get_block(0).size() == 3); + CHECK(p.get_block(1).size() == 7); + CHECK(p.get_node(0).repr().state() == 0); + CHECK(p.get_node(0).repr().block().idx() == 0); + CHECK(p.get_node(0).contains_block(0)); + CHECK(!p.get_node(0).contains_block(1)); + CHECK(p.get_node(1).repr().state() == 1); + CHECK(p.get_node(1).repr().block().idx() == 1); + CHECK(p.get_node(0).first().idx() == 0); + CHECK(p.get_node(0).last().idx() == 2); + CHECK(p.get_node(1).first().idx() == 3); + CHECK(p.get_node(1).last().idx() == 9); + CHECK(!p.get_node(1).contains_block(0)); + CHECK(p.get_node(1).contains_block(1)); + CHECK(p.get_block(0).node().idx() == 0); + CHECK(p.get_block(1).node().idx() == 1); + CHECK(p.states_in_same_block(0).size() == 3); + CHECK(p.states_in_same_block(1).size() == 7); + CHECK(p.partition().size() == 2); + } + + SECTION("Create a simple partition with 3 blocks") { + Partition p{6, {{0}, {1, 2}}}; + CHECK(p.num_of_states() == 6); + CHECK(p.num_of_block_items() == 6); + CHECK(p.num_of_blocks() == 3); + CHECK(p.num_of_nodes() == 3); + CHECK(p.in_same_block({})); + CHECK(p.in_same_block({0})); + CHECK(p.in_same_block(3, 5)); + CHECK(p.in_same_block(1, 2)); + CHECK(!p.in_same_block(1, 4)); + CHECK(p.in_same_block({3, 4, 5})); + CHECK(!p.in_same_block({2, 3, 4, 5})); + for(size_t i = 0; i <= 5; ++i) { + CHECK(p[i].idx() == i); + CHECK(p[i].state() == i); + } + CHECK(p[0].block().idx() == 0); + CHECK(p[0].node().idx() == 0); + CHECK(p.get_block_item(0).block().idx() == 0); + CHECK(p.get_block_item(0).repr().idx() == 0); + CHECK(p.get_block_item(0).first().idx() == 0); + CHECK(p.get_block_item(0).last().idx() == 0); + CHECK(p.get_block(0).node().idx() == 0); + CHECK(p[1].block().idx() == 1); + CHECK(p[1].node().idx() == 1); + CHECK(p.get_block_item(1).block().idx() == 1); + CHECK(p.get_block_item(1).node().idx() == 1); + CHECK(p.get_block_item(1).repr().idx() == 1); + CHECK(p.get_block_item(1).first().idx() == 1); + CHECK(p.get_block_item(1).last().idx() == 2); + CHECK(p.get_block(0).repr().state() == 0); + CHECK(p.get_block(1).repr().state() == 1); + CHECK(p.get_block(2).repr().state() == 3); + CHECK(p.get_node(0).repr().state() == 0); + CHECK(p.get_node(1).repr().state() == 1); + CHECK(p.get_node(2).repr().state() == 3); + CHECK(p.get_node(0).first().idx() == 0); + CHECK(p.get_node(0).last().idx() == 0); + CHECK(p.get_node(1).first().idx() == 1); + CHECK(p.get_node(1).last().idx() == 2); + CHECK(p.get_node(2).first().idx() == 3); + CHECK(p.get_node(2).last().idx() == 5); + CHECK(p.get_node(0).contains_block(0)); + CHECK(!p.get_node(0).contains_block(1)); + CHECK(!p.get_node(0).contains_block(2)); + CHECK(!p.get_node(1).contains_block(0)); + CHECK(p.get_node(1).contains_block(1)); + CHECK(!p.get_node(1).contains_block(2)); + CHECK(!p.get_node(2).contains_block(0)); + CHECK(!p.get_node(2).contains_block(1)); + CHECK(p.get_node(2).contains_block(2)); + CHECK(p.get_block(0).node().idx() == 0); + CHECK(p.get_block(1).node().idx() == 1); + CHECK(p.get_block(2).node().idx() == 2); + CHECK(p.get_block(0).size() == 1); + CHECK(p.get_block(1).size() == 2); + CHECK(p.get_block(2).size() == 3); + CHECK(p.get_block_item(3).repr().idx() == 3); + CHECK(p.get_block_item(3).first().idx() == 3); + CHECK(p.get_block_item(3).last().idx() == 5); + CHECK(p.states_in_same_block(0).size() == 1); + CHECK(p.states_in_same_block(1).size() == 2); + CHECK(p.states_in_same_block(3).size() == 3); + CHECK(p.partition().size() == 3); + } + + SECTION("Splitting blocks") { + Partition p{10}; + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 1); + CHECK(p.num_of_nodes() == 1); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 0); + CHECK(p.in_same_block({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + CHECK(p.states_in_same_block(0).size() == 10); + CHECK(p.partition().size() == 1); + CHECK(p.get_block(0).size() == 10); + CHECK(p.get_node(0).contains_block(0)); + p.split_blocks({0, 1, 2, 3, 4}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 2); + CHECK(p.num_of_nodes() == 3); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 1); + CHECK(p.in_same_block({0, 1, 2, 3, 4})); + CHECK(p.in_same_block({5, 6, 7, 8, 9})); + CHECK(p.states_in_same_block(0).size() == 5); + CHECK(p.states_in_same_block(5).size() == 5); + CHECK(p.partition().size() == 2); + CHECK(p.get_block(0).size() == 5); + CHECK(p.get_block(1).size() == 5); + CHECK(p.get_node(0).contains_block(0)); + CHECK(p.get_node(0).contains_block(1)); + CHECK(p.get_node(1).contains_block(0)); + CHECK(!p.get_node(1).contains_block(1)); + CHECK(!p.get_node(2).contains_block(0)); + CHECK(p.get_node(2).contains_block(1)); + p.split_blocks({0, 1, 2, 5, 6, 7}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 4); + CHECK(p.num_of_nodes() == 7); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 3); + CHECK(p.in_same_block({0, 1, 2})); + CHECK(p.in_same_block({3, 4})); + CHECK(p.in_same_block({5, 6, 7})); + CHECK(p.in_same_block({8, 9})); + CHECK(p.states_in_same_block(0).size() == 3); + CHECK(p.states_in_same_block(3).size() == 2); + CHECK(p.states_in_same_block(5).size() == 3); + CHECK(p.states_in_same_block(8).size() == 2); + CHECK(p.partition().size() == 4); + CHECK(p.get_node(0).contains_block(0)); + CHECK(p.get_node(0).contains_block(1)); + CHECK(p.get_node(0).contains_block(2)); + CHECK(p.get_node(0).contains_block(3)); + CHECK(p.get_node(1).contains_block(0)); + CHECK(!p.get_node(1).contains_block(1)); + CHECK(p.get_node(1).contains_block(2)); + CHECK(!p.get_node(1).contains_block(3)); + CHECK(!p.get_node(2).contains_block(0)); + CHECK(p.get_node(2).contains_block(1)); + CHECK(!p.get_node(2).contains_block(2)); + CHECK(p.get_node(2).contains_block(3)); + CHECK(p.get_node(3).contains_block(0)); + CHECK(!p.get_node(3).contains_block(1)); + CHECK(!p.get_node(3).contains_block(2)); + CHECK(!p.get_node(3).contains_block(3)); + CHECK(!p.get_node(4).contains_block(0)); + CHECK(!p.get_node(4).contains_block(1)); + CHECK(p.get_node(4).contains_block(2)); + CHECK(!p.get_node(4).contains_block(3)); + CHECK(!p.get_node(5).contains_block(0)); + CHECK(p.get_node(5).contains_block(1)); + CHECK(!p.get_node(5).contains_block(2)); + CHECK(!p.get_node(5).contains_block(3)); + CHECK(!p.get_node(6).contains_block(0)); + CHECK(!p.get_node(6).contains_block(1)); + CHECK(!p.get_node(6).contains_block(2)); + CHECK(p.get_node(6).contains_block(3)); + p.split_blocks({0, 3, 5, 8}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 8); + CHECK(p.num_of_nodes() == 15); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 7); + CHECK(p.in_same_block({0})); + CHECK(p.in_same_block({1, 2})); + CHECK(p.in_same_block({3})); + CHECK(p.in_same_block({4})); + CHECK(p.in_same_block({5})); + CHECK(p.in_same_block({6, 7})); + CHECK(p.in_same_block({8})); + CHECK(p.in_same_block({9})); + CHECK(p.states_in_same_block(0).size() == 1); + CHECK(p.states_in_same_block(1).size() == 2); + CHECK(p.states_in_same_block(3).size() == 1); + CHECK(p.states_in_same_block(4).size() == 1); + CHECK(p.states_in_same_block(5).size() == 1); + CHECK(p.states_in_same_block(6).size() == 2); + CHECK(p.states_in_same_block(8).size() == 1); + CHECK(p.states_in_same_block(9).size() == 1); + CHECK(p.partition().size() == 8); + p.split_blocks({1, 6}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 10); + CHECK(p.num_of_nodes() == 19); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 7); + CHECK(p.states_in_same_block(0).size() == 1); + CHECK(p.states_in_same_block(1).size() == 1); + CHECK(p.states_in_same_block(2).size() == 1); + CHECK(p.states_in_same_block(3).size() == 1); + CHECK(p.states_in_same_block(4).size() == 1); + CHECK(p.states_in_same_block(5).size() == 1); + CHECK(p.states_in_same_block(6).size() == 1); + CHECK(p.states_in_same_block(7).size() == 1); + CHECK(p.states_in_same_block(8).size() == 1); + CHECK(p.states_in_same_block(9).size() == 1); + CHECK(p.partition().size() == 10); + p.split_blocks({0, 2, 4, 6, 8}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 10); + CHECK(p.num_of_nodes() == 19); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 7); + CHECK(p.states_in_same_block(0).size() == 1); + CHECK(p.states_in_same_block(1).size() == 1); + CHECK(p.states_in_same_block(2).size() == 1); + CHECK(p.states_in_same_block(3).size() == 1); + CHECK(p.states_in_same_block(4).size() == 1); + CHECK(p.states_in_same_block(5).size() == 1); + CHECK(p.states_in_same_block(6).size() == 1); + CHECK(p.states_in_same_block(7).size() == 1); + CHECK(p.states_in_same_block(8).size() == 1); + CHECK(p.states_in_same_block(9).size() == 1); + CHECK(p.partition().size() == 10); + } + + SECTION("Complicated blocks splitting with swapping") { + Partition p{10}; + p.split_blocks({0, 2, 4, 6, 8}); + CHECK(p.in_same_block(0, 2)); + CHECK(p.in_same_block(0, 4)); + CHECK(p.in_same_block(0, 6)); + CHECK(p.in_same_block(0, 8)); + CHECK(!p.in_same_block(0, 1)); + CHECK(!p.in_same_block(0, 3)); + CHECK(!p.in_same_block(0, 5)); + CHECK(!p.in_same_block(0, 7)); + CHECK(!p.in_same_block(0, 9)); + p.split_blocks({1, 9}); + CHECK(p.in_same_block(1, 9)); + CHECK(!p.in_same_block(1, 3)); + CHECK(!p.in_same_block(1, 5)); + CHECK(!p.in_same_block(1, 7)); + } + + SECTION("Custom copying and assigning with splitting") { + Partition p = Partition(5, {{2, 3}}); + p.split_blocks({0}); + + Partition q = p; + Partition r = Partition(p); + + CHECK(p.num_of_states() == q.num_of_states()); + CHECK(p.num_of_states() == r.num_of_states()); + CHECK(p.num_of_block_items() == q.num_of_block_items()); + CHECK(p.num_of_block_items() == r.num_of_block_items()); + CHECK(p.num_of_blocks() == q.num_of_blocks()); + CHECK(p.num_of_blocks() == r.num_of_blocks()); + CHECK(p.num_of_nodes() == q.num_of_nodes()); + CHECK(p.num_of_nodes() == r.num_of_nodes()); + + size_t statesNum = p.num_of_states(); + size_t blocksNum = p.num_of_blocks(); + size_t nodesNum = p.num_of_nodes(); + + for(size_t i = 0; i < statesNum; ++i) { + CHECK(p[i].idx() == q[i].idx()); + CHECK(p[i].idx() == r[i].idx()); + CHECK(p[i].state() == q[i].state()); + CHECK(p[i].state() == r[i].state()); + CHECK(p[i].block().idx() == q[i].block().idx()); + CHECK(p[i].block().idx() == r[i].block().idx()); + } + + for(size_t i = 0; i < blocksNum; ++i) { + CHECK(p[i].node().idx() == q[i].node().idx()); + CHECK(p[i].node().idx() == r[i].node().idx()); + } + + for(size_t i = 0; i < nodesNum; ++i) { + CHECK(p[i].node().first().idx() == q[i].node().first().idx()); + CHECK(p[i].node().first().idx() == r[i].node().first().idx()); + CHECK(p[i].node().last().idx() == q[i].node().last().idx()); + CHECK(p[i].node().last().idx() == r[i].node().last().idx()); + } + + std::cout << q; + r.split_blocks({1, 2}); + r.split_blocks({1, 2}); + std::cout << r; + } + + SECTION("Custom copying and assigning without splitting") { + Partition q{6, {{0}, {1, 2}}}; + Partition p = q; + CHECK(p.num_of_states() == 6); + CHECK(p.num_of_block_items() == 6); + CHECK(p.num_of_blocks() == 3); + CHECK(p.num_of_nodes() == 3); + CHECK(p.in_same_block({})); + CHECK(p.in_same_block({0})); + CHECK(p.in_same_block(3, 5)); + CHECK(p.in_same_block(1, 2)); + CHECK(!p.in_same_block(1, 4)); + CHECK(p.in_same_block({3, 4, 5})); + CHECK(!p.in_same_block({2, 3, 4, 5})); + for(size_t i = 0; i <= 5; ++i) { + CHECK(p[i].idx() == i); + CHECK(p[i].state() == i); + } + CHECK(p[0].block().idx() == 0); + CHECK(p[0].node().idx() == 0); + CHECK(p.get_block_item(0).block().idx() == 0); + CHECK(p.get_block_item(0).repr().idx() == 0); + CHECK(p.get_block_item(0).first().idx() == 0); + CHECK(p.get_block_item(0).last().idx() == 0); + CHECK(p.get_block(0).node().idx() == 0); + CHECK(p[1].block().idx() == 1); + CHECK(p[1].node().idx() == 1); + CHECK(p.get_block_item(1).block().idx() == 1); + CHECK(p.get_block_item(1).node().idx() == 1); + CHECK(p.get_block_item(1).repr().idx() == 1); + CHECK(p.get_block_item(1).first().idx() == 1); + CHECK(p.get_block_item(1).last().idx() == 2); + CHECK(p.get_block(0).repr().state() == 0); + CHECK(p.get_block(1).repr().state() == 1); + CHECK(p.get_block(2).repr().state() == 3); + CHECK(p.get_node(0).repr().state() == 0); + CHECK(p.get_node(1).repr().state() == 1); + CHECK(p.get_node(2).repr().state() == 3); + CHECK(p.get_node(0).first().idx() == 0); + CHECK(p.get_node(0).last().idx() == 0); + CHECK(p.get_node(1).first().idx() == 1); + CHECK(p.get_node(1).last().idx() == 2); + CHECK(p.get_node(2).first().idx() == 3); + CHECK(p.get_node(2).last().idx() == 5); + CHECK(p.get_block(0).node().idx() == 0); + CHECK(p.get_block(1).node().idx() == 1); + CHECK(p.get_block(2).node().idx() == 2); + CHECK(p.get_block_item(3).repr().idx() == 3); + CHECK(p.get_block_item(3).first().idx() == 3); + CHECK(p.get_block_item(3).last().idx() == 5); + CHECK(p.states_in_same_block(0).size() == 1); + CHECK(p.states_in_same_block(1).size() == 2); + CHECK(p.states_in_same_block(3).size() == 3); + CHECK(p.partition().size() == 3); + } + + SECTION("Another splitting blocks with partition copying") { + Partition q{10}; + Partition p = q; + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 1); + CHECK(p.num_of_nodes() == 1); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 0); + CHECK(p.in_same_block({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + CHECK(p.states_in_same_block(0).size() == 10); + CHECK(p.partition().size() == 1); + p.split_blocks({0, 1, 2, 3, 4}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 2); + CHECK(p.num_of_nodes() == 3); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 1); + CHECK(p.in_same_block({0, 1, 2, 3, 4})); + CHECK(p.in_same_block({5, 6, 7, 8, 9})); + CHECK(p.states_in_same_block(0).size() == 5); + CHECK(p.states_in_same_block(5).size() == 5); + CHECK(p.partition().size() == 2); + p.split_blocks({0, 1, 2, 5, 6, 7}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 4); + CHECK(p.num_of_nodes() == 7); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 3); + CHECK(p.in_same_block({0, 1, 2})); + CHECK(p.in_same_block({3, 4})); + CHECK(p.in_same_block({5, 6, 7})); + CHECK(p.in_same_block({8, 9})); + CHECK(p.states_in_same_block(0).size() == 3); + CHECK(p.states_in_same_block(3).size() == 2); + CHECK(p.states_in_same_block(5).size() == 3); + CHECK(p.states_in_same_block(8).size() == 2); + CHECK(p.partition().size() == 4); + p.split_blocks({0, 3, 5, 8}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 8); + CHECK(p.num_of_nodes() == 15); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 7); + CHECK(p.in_same_block({0})); + CHECK(p.in_same_block({1, 2})); + CHECK(p.in_same_block({3})); + CHECK(p.in_same_block({4})); + CHECK(p.in_same_block({5})); + CHECK(p.in_same_block({6, 7})); + CHECK(p.in_same_block({8})); + CHECK(p.in_same_block({9})); + CHECK(p.states_in_same_block(0).size() == 1); + CHECK(p.states_in_same_block(1).size() == 2); + CHECK(p.states_in_same_block(3).size() == 1); + CHECK(p.states_in_same_block(4).size() == 1); + CHECK(p.states_in_same_block(5).size() == 1); + CHECK(p.states_in_same_block(6).size() == 2); + CHECK(p.states_in_same_block(8).size() == 1); + CHECK(p.states_in_same_block(9).size() == 1); + CHECK(p.partition().size() == 8); + p.split_blocks({1, 6}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 10); + CHECK(p.num_of_nodes() == 19); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 7); + CHECK(p.states_in_same_block(0).size() == 1); + CHECK(p.states_in_same_block(1).size() == 1); + CHECK(p.states_in_same_block(2).size() == 1); + CHECK(p.states_in_same_block(3).size() == 1); + CHECK(p.states_in_same_block(4).size() == 1); + CHECK(p.states_in_same_block(5).size() == 1); + CHECK(p.states_in_same_block(6).size() == 1); + CHECK(p.states_in_same_block(7).size() == 1); + CHECK(p.states_in_same_block(8).size() == 1); + CHECK(p.states_in_same_block(9).size() == 1); + CHECK(p.partition().size() == 10); + p.split_blocks({0, 2, 4, 6, 8}); + CHECK(p.num_of_states() == 10); + CHECK(p.num_of_block_items() == 10); + CHECK(p.num_of_blocks() == 10); + CHECK(p.num_of_nodes() == 19); + CHECK(p[0].block().idx() == 0); + CHECK(p[9].block().idx() == 7); + CHECK(p.states_in_same_block(0).size() == 1); + CHECK(p.states_in_same_block(1).size() == 1); + CHECK(p.states_in_same_block(2).size() == 1); + CHECK(p.states_in_same_block(3).size() == 1); + CHECK(p.states_in_same_block(4).size() == 1); + CHECK(p.states_in_same_block(5).size() == 1); + CHECK(p.states_in_same_block(6).size() == 1); + CHECK(p.states_in_same_block(7).size() == 1); + CHECK(p.states_in_same_block(8).size() == 1); + CHECK(p.states_in_same_block(9).size() == 1); + CHECK(p.partition().size() == 10); + std::cout << p; + } + + SECTION("Another complicated blocks splitting with swapping and copying") { + Partition q{10}; + Partition p = q; + p.split_blocks({0, 2, 4, 6, 8}); + CHECK(p.in_same_block(0, 2)); + CHECK(p.in_same_block(0, 4)); + CHECK(p.in_same_block(0, 6)); + CHECK(p.in_same_block(0, 8)); + CHECK(!p.in_same_block(0, 1)); + CHECK(!p.in_same_block(0, 3)); + CHECK(!p.in_same_block(0, 5)); + CHECK(!p.in_same_block(0, 7)); + CHECK(!p.in_same_block(0, 9)); + p.split_blocks({1, 9}); + CHECK(p.in_same_block(1, 9)); + CHECK(!p.in_same_block(1, 3)); + CHECK(!p.in_same_block(1, 5)); + CHECK(!p.in_same_block(1, 7)); + std::cout << p; + } + + SECTION("Partition over an empty set") { + Partition q{0}; + Partition p = q; + p.split_blocks({}); + CHECK(!p.num_of_states()); + CHECK(!p.num_of_block_items()); + CHECK(!p.num_of_blocks()); + CHECK(!p.num_of_nodes()); + CHECK(!p.partition().size()); + std::cout << p; + } + + SECTION("Partition iterators") { + Partition p = Partition(8, {{0, 1}, {2, 3, 4, 5}}); + size_t index = 0; + for(auto& block_item : p.get_block(0)) { + CHECK(block_item.idx() == index); + CHECK(block_item.block().idx() == 0); + CHECK(block_item.node().idx() == 0); + CHECK(block_item.state() == index); + ++index; + } + for(auto& block_item : p.get_block(1)) { + CHECK(block_item.idx() == index); + CHECK(block_item.block().idx() == 1); + CHECK(block_item.node().idx() == 1); + CHECK(block_item.state() == index); + ++index; + } + for(auto& block_item : p.get_block(2)) { + CHECK(block_item.idx() == index); + CHECK(block_item.block().idx() == 2); + CHECK(block_item.node().idx() == 2); + CHECK(block_item.state() == index); + ++index; + } + index = 0; + for(auto& block_item : p.get_node(0)) { + CHECK(block_item.idx() == index); + CHECK(block_item.block().idx() == 0); + CHECK(block_item.node().idx() == 0); + CHECK(block_item.state() == index); + ++index; + } + for(auto& block_item : p.get_node(1)) { + CHECK(block_item.idx() == index); + CHECK(block_item.block().idx() == 1); + CHECK(block_item.node().idx() == 1); + CHECK(block_item.state() == index); + ++index; + } + for(auto& block_item : p.get_node(2)) { + CHECK(block_item.idx() == index); + CHECK(block_item.block().idx() == 2); + CHECK(block_item.node().idx() == 2); + CHECK(block_item.state() == index); + ++index; + } + } + +}