#ifndef STORM_STORAGE_BITVECTOR_H_ #define STORM_STORAGE_BITVECTOR_H_ #include #include #include #include #include "src/storage/VectorSet.h" #include "src/exceptions/InvalidStateException.h" #include "src/exceptions/InvalidArgumentException.h" #include "src/exceptions/OutOfRangeException.h" #include "src/utility/OsDetection.h" #include "src/utility/Hash.h" #include "log4cplus/logger.h" #include "log4cplus/loggingmacros.h" extern log4cplus::Logger logger; #include namespace storm { namespace storage { /*! * A bit vector that is internally represented by an array of 64-bit values. */ class BitVector { public: /*! * A class that enables iterating over the indices of the bit vector whose * bits are set to true. Note that this is a const iterator, which cannot * alter the bit vector. */ class constIndexIterator { friend class BitVector; public: /*! * Constructs a const iterator over the indices of the set bits in the * given bit vector, starting and stopping, respectively, at the given * indices. * @param bitVector The bit vector to iterate over. * @param startIndex The index where to begin looking for set bits. * @param setOnFirstBit If set, upon construction, the current index is * set to the first set bit in the bit vector. * @param endIndex The number of elements to iterate over. */ constIndexIterator(const BitVector& bitVector, uint_fast64_t startIndex, uint_fast64_t endIndex, bool setOnFirstBit = true) : bitVector(bitVector), endIndex(endIndex) { if (setOnFirstBit) { currentIndex = bitVector.getNextSetIndex(startIndex, endIndex); } else { currentIndex = startIndex; } } /*! * Increases the position of the iterator to the position of the next bit that * is set to true. * @return A reference to this iterator. */ constIndexIterator& operator++() { currentIndex = bitVector.getNextSetIndex(++currentIndex, endIndex); return *this; } /*! * Returns the index of the current bit that is set to true. * @return The index of the current bit that is set to true. */ uint_fast64_t operator*() const { return currentIndex; } /*! * Compares the iterator with another iterator to determine whether * the iteration process has reached the end. */ bool operator!=(const constIndexIterator& rhs) const { return currentIndex != rhs.currentIndex; } private: /*! The bit vector to search for set bits. */ const BitVector& bitVector; /*! The index of the most recently discovered set bit. */ uint_fast64_t currentIndex; /*! * The index of the element past the end. Used for properly terminating * the search for set bits. */ uint_fast64_t endIndex; }; /* * Standard constructor. Constructs an empty bit vector of length 0. */ BitVector() : bucketCount(0), bitCount(0), bucketArray(nullptr), endIterator(*this, 0, 0, false), truncateMask(0) { // Intentionally left empty. } /*! * Constructs a bit vector which can hold the given number of bits and * initializes all bits to the provided truth value. * @param length The number of bits the bit vector should be able to hold. * @param initTrue The initial value of the first |length| bits. */ BitVector(uint_fast64_t length, bool initTrue = false) : bitCount(length), endIterator(*this, length, length, false), truncateMask((1ll << (bitCount & mod64mask)) - 1ll) { // Check whether the given length is valid. if (length == 0) { LOG4CPLUS_ERROR(logger, "Cannot create bit vector of size 0."); throw storm::exceptions::InvalidArgumentException() << "Cannot create bit vector of size 0."; } // Compute the correct number of buckets needed to store the given number of bits bucketCount = length >> 6; if ((length & mod64mask) != 0) { ++bucketCount; } if (initTrue) { // Finally, create the full bucket array. this->bucketArray = new uint64_t[bucketCount]; // Now initialize the values. for (uint_fast64_t i = 0; i < bucketCount; ++i) { this->bucketArray[i] = -1ll; } truncateLastBucket(); } else { // Finally, create the full bucket array. this->bucketArray = new uint64_t[bucketCount](); } } /*! * Creates a bit vector that has exactly those bits set that are defined by the given set. * * @param length The length of the bit vector to create. * @param bitsToSet A set of indices whose bits to set in the bit vector. * @param initTrue The initial value of the first |length| bits. */ BitVector(uint_fast64_t length, storm::storage::VectorSet const& bitsToSet, bool initTrue = false) : BitVector(length, initTrue) { for (auto bit : bitsToSet) { this->set(bit, true); } } /*! * Creates a bit vector that has exactly the bits set that are given by the provided iterator range. * * @param The length of the bit vector. * @param begin The begin of the iterator range. * @param end The end of the iterator range. */ template BitVector(uint_fast64_t length, InputIterator begin, InputIterator end) : BitVector(length) { for (InputIterator it = begin; it != end; ++it) { this->set(*it, true); } } /*! * Copy Constructor. Performs a deep copy of the given bit vector. * @param bv A reference to the bit vector to be copied. */ BitVector(const BitVector & bv) : bucketCount(bv.bucketCount), bitCount(bv.bitCount), endIterator(*this, bitCount, bitCount, false), truncateMask((1ll << (bitCount & mod64mask)) - 1ll) { LOG4CPLUS_DEBUG(logger, "Invoking copy constructor."); bucketArray = new uint64_t[bucketCount]; std::copy(bv.bucketArray, bv.bucketArray + this->bucketCount, this->bucketArray); } /*! * Copy Constructor. Performs a deep copy of the bits in the given bit vector that are given by the filter. * @param bv A reference to the bit vector to be copied. * @param filter The filter to apply for copying. */ BitVector(BitVector const& other, BitVector const& filter) : bitCount(filter.getNumberOfSetBits()), endIterator(*this, bitCount, bitCount, false), truncateMask((1ll << (bitCount & mod64mask)) - 1ll) { // Determine the number of buckets we need for the given bit count and create bucket array. bucketCount = bitCount >> 6; if ((bitCount & mod64mask) != 0) { ++bucketCount; } this->bucketArray = new uint64_t[bucketCount](); // Now copy over all bits given by the filter. uint_fast64_t nextPosition = 0; for (auto position : filter) { this->set(nextPosition, other.get(position)); nextPosition++; } } /*! * Move constructor. Move constructs the bit vector from the given bit vector. * */ BitVector(BitVector&& bv) : bucketCount(bv.bucketCount), bitCount(bv.bitCount), bucketArray(bv.bucketArray), endIterator(*this, bitCount, bitCount, false), truncateMask((1ll << (bitCount & mod64mask)) - 1ll) { LOG4CPLUS_DEBUG(logger, "Invoking move constructor."); bv.bucketArray = nullptr; } /*! * Destructor. Frees the underlying bucket array. */ ~BitVector() { if (this->bucketArray != nullptr) { delete[] this->bucketArray; } } /*! * Compares the given bit vector with the current one. */ bool operator==(BitVector const& bv) { // If the lengths of the vectors do not match, they are considered unequal. if (this->bitCount != bv.bitCount) return false; // If the lengths match, we compare the buckets one by one. for (uint_fast64_t index = 0; index < this->bucketCount; ++index) { if (this->bucketArray[index] != bv.bucketArray[index]) { return false; } } // All buckets were equal, so the bit vectors are equal. return true; } /*! * Assigns the given bit vector to the current bit vector by a deep copy. * @param bv The bit vector to assign to the current bit vector. * @return A reference to this bit vector after it has been assigned the * given bit vector by means of a deep copy. */ BitVector& operator=(BitVector const& bv) { LOG4CPLUS_DEBUG(logger, "Performing copy assignment."); // Check if we need to dispose of our current storage. if (this->bucketArray != nullptr) { delete[] this->bucketArray; } // Copy the values from the other bit vector. bucketCount = bv.bucketCount; bitCount = bv.bitCount; bucketArray = new uint64_t[bucketCount]; std::copy(bv.bucketArray, bv.bucketArray + bucketCount, bucketArray); updateSizeChange(); return *this; } /*! * Move assigns the given bit vector to the current bit vector. * * @param bv The bit vector whose content is moved to the current bit vector. * @return A reference to this bit vector after the contents of the given bit vector * have been moved into it. */ BitVector& operator=(BitVector&& bv) { LOG4CPLUS_DEBUG(logger, "Performing move assignment."); // Only perform the assignment if the source and target are not identical. if (this != &bv) { // Check if we need to dispose of our current storage. if (this->bucketArray != nullptr) { delete[] this->bucketArray; } // Copy the values from the other bit vector, but directly steal its storage. bucketCount = bv.bucketCount; bitCount = bv.bitCount; bucketArray = bv.bucketArray; updateSizeChange(); // Now alter the other bit vector such that it does not dispose of our stolen storage. bv.bucketArray = nullptr; } return *this; } /*! * Resizes the bit vector to hold the given new number of bits. * @param newLength The new number of bits the bit vector can hold. */ void resize(uint_fast64_t newLength) { bitCount = newLength; uint_fast64_t newBucketCount = newLength >> 6; if ((newLength & mod64mask) != 0) { ++newBucketCount; } // Reserve a temporary array for copying. uint64_t* tempArray = new uint64_t[newBucketCount]; // Copy over the elements from the old bit vector. uint_fast64_t copySize = (newBucketCount <= bucketCount) ? newBucketCount : bucketCount; std::copy(this->bucketArray, this->bucketArray + copySize, tempArray); // Initialize missing values in the new bit vector. for (uint_fast64_t i = copySize; i < newBucketCount; ++i) { tempArray[i] = 0; } updateSizeChange(); // Dispose of the old bit vector and set the new one. delete[] this->bucketArray; this->bucketArray = tempArray; this->bucketCount = newBucketCount; } /*! * Sets the given truth value at the given index. * @param index The index where to set the truth value. * @param value The truth value to set. */ void set(const uint_fast64_t index, bool value = true) { uint_fast64_t bucket = index >> 6; if (bucket >= this->bucketCount) throw storm::exceptions::OutOfRangeException() << "Written index " << index << " out of bounds."; uint_fast64_t mask = static_cast(1) << (index & mod64mask); if (value) { this->bucketArray[bucket] |= mask; } else { this->bucketArray[bucket] &= ~mask; } if (bucket == this->bucketCount - 1) { truncateLastBucket(); } } /*! * Sets all bits in the given iterator range. * * @param begin The begin of the iterator range. * @param end The element past the last element of the iterator range. */ template void set(InputIterator begin, InputIterator end) { for (InputIterator it = begin; it != end; ++it) { this->set(*it); } } /*! * Retrieves the truth value at the given index. * @param index The index from which to retrieve the truth value. */ bool get(const uint_fast64_t index) const { uint_fast64_t bucket = index >> 6; if (bucket >= this->bucketCount) throw storm::exceptions::OutOfRangeException() << "Read index " << index << " out of bounds."; uint_fast64_t mask = static_cast(1) << (index & mod64mask); return ((this->bucketArray[bucket] & mask) == mask); } /*! * Performs a logical "and" with the given bit vector. In case the sizes of the bit vectors * do not match, only the matching portion is considered and the overlapping bits * are set to 0. * @param bv A reference to the bit vector to use for the operation. * @return A bit vector corresponding to the logical "and" of the two bit vectors. */ BitVector operator&(BitVector const& bv) const { uint_fast64_t minSize = (bv.bitCount < this->bitCount) ? bv.bitCount : this->bitCount; // Create resulting bit vector and perform the operation on the individual elements. BitVector result(minSize); for (uint_fast64_t i = 0; i < result.bucketCount; ++i) { result.bucketArray[i] = this->bucketArray[i] & bv.bucketArray[i]; } return result; } /*! * Performs a logical "and" with the given bit vector and assigns the result to the * current bit vector. In case the sizes of the bit vectors do not match, * only the matching portion is considered and the overlapping bits are set to 0. * @param bv A reference to the bit vector to use for the operation. * @return A reference to the current bit vector corresponding to the logical "and" * of the two bit vectors. */ BitVector& operator&=(BitVector const& bv) { uint_fast64_t minSize = (bv.bucketCount < this->bucketCount) ? bv.bucketCount : this->bucketCount; for (uint_fast64_t i = 0; i < minSize; ++i) { this->bucketArray[i] &= bv.bucketArray[i]; } return *this; } /*! * Performs a logical "or" with the given bit vector. In case the sizes of the bit vectors * do not match, only the matching portion is considered and the overlapping bits * are set to 0. * @param bv A reference to the bit vector to use for the operation. * @return A bit vector corresponding to the logical "or" of the two bit vectors. */ BitVector operator|(BitVector const& bv) const { uint_fast64_t minSize = (bv.bitCount < this->bitCount) ? bv.bitCount : this->bitCount; // Create resulting bit vector and perform the operation on the individual elements. BitVector result(minSize); for (uint_fast64_t i = 0; i < result.bucketCount; ++i) { result.bucketArray[i] = this->bucketArray[i] | bv.bucketArray[i]; } result.truncateLastBucket(); return result; } /*! * Performs a logical "or" with the given bit vector and assigns the result to the * current bit vector. In case the sizes of the bit vectors do not match, * only the matching portion is considered and the overlapping bits are set to 0. * @param bv A reference to the bit vector to use for the operation. * @return A reference to the current bit vector corresponding to the logical "or" * of the two bit vectors. */ BitVector& operator|=(BitVector const& bv) { uint_fast64_t minSize = (bv.bucketCount < this->bucketCount) ? bv.bucketCount : this->bucketCount; for (uint_fast64_t i = 0; i < minSize; ++i) { this->bucketArray[i] |= bv.bucketArray[i]; } truncateLastBucket(); return *this; } /*! * Performs a logical "xor" with the given bit vector. In case the sizes of the bit vectors * do not match, only the matching portion is considered and the overlapping bits * are set to 0. * @param bv A reference to the bit vector to use for the operation. * @return A bit vector corresponding to the logical "xor" of the two bit vectors. */ BitVector operator^(BitVector const& bv) const { uint_fast64_t minSize = (bv.bitCount < this->bitCount) ? bv.bitCount : this->bitCount; // Create resulting bit vector and perform the operation on the individual elements. BitVector result(minSize); for (uint_fast64_t i = 0; i < result.bucketCount; ++i) { result.bucketArray[i] = this->bucketArray[i] ^ bv.bucketArray[i]; } result.truncateLastBucket(); return result; } /*! * Performs a logical "not" on the bit vector. * @return A bit vector corresponding to the logical "not" of the bit vector. */ BitVector operator~() const { // Create resulting bit vector and perform the operation on the individual elements. BitVector result(this->bitCount); for (uint_fast64_t i = 0; i < this->bucketCount; ++i) { result.bucketArray[i] = ~this->bucketArray[i]; } result.truncateLastBucket(); return result; } /*! * Negates all bits in the bit vector. */ void complement() { for (uint_fast64_t i = 0; i < this->bucketCount; ++i) { this->bucketArray[i] = ~this->bucketArray[i]; } truncateLastBucket(); } /*! * Performs a logical "implies" with the given bit vector. In case the sizes of the bit vectors * do not match, only the matching portion is considered and the overlapping bits * are set to 0. * * @param bv A reference to the bit vector to use for the operation. * @return A bit vector corresponding to the logical "implies" of the two bit vectors. */ BitVector implies(BitVector const& bv) const { uint_fast64_t minSize = (bv.bitCount < this->bitCount) ? bv.bitCount : this->bitCount; // Create resulting bit vector and perform the operation on the individual elements. BitVector result(minSize); for (uint_fast64_t i = 0; i < result.bucketCount - 1; ++i) { result.bucketArray[i] = ~this->bucketArray[i] | bv.bucketArray[i]; } result.truncateLastBucket(); return result; } /*! * Checks whether all bits that are set in the current bit vector are also set in the given bit * vector. * * @param bv A reference to the bit vector whose bits are (possibly) a superset of the bits of * the current bit vector. * @return True iff all bits that are set in the current bit vector are also set in the given bit * vector. */ bool isSubsetOf(BitVector const& bv) const { for (uint_fast64_t i = 0; i < this->bucketCount; ++i) { if ((this->bucketArray[i] & bv.bucketArray[i]) != this->bucketArray[i]) { return false; } } return true; } /*! * Checks whether none of the bits that are set in the current bit vector are also set in the * given bit vector. * * @param bv A reference to the bit vector whose bits are (possibly) disjoint from the bits in * the current bit vector. * @returns True iff none of the bits that are set in the current bit vector are also set in the * given bit vector. */ bool isDisjointFrom(BitVector const& bv) const { for (uint_fast64_t i = 0; i < this->bucketCount; ++i) { if ((this->bucketArray[i] & bv.bucketArray[i]) != 0) { return false; } } return true; } /*! * Computes a bit vector such that bit i is set iff the i-th set bit of the current bit vector is also contained * in the given bit vector. * * @param bv A reference the bit vector to be used. * @return A bit vector whose i-th bit is set iff the i-th set bit of the current bit vector is also contained * in the given bit vector. */ BitVector operator%(BitVector const& bv) const { // Create resulting bit vector. BitVector result(this->getNumberOfSetBits()); // If the current bit vector has not too many elements compared to the given bit vector we prefer iterating // over its elements. if (this->getNumberOfSetBits() / 10 < bv.getNumberOfSetBits()) { uint_fast64_t position = 0; for (auto bit : *this) { if (bv.get(bit)) { result.set(position, true); } ++position; } } else { // If the given bit vector had much less elements, we iterate over its elements and accept calling the more // costly operation getNumberOfSetBitsBeforeIndex on the current bit vector. for (auto bit : bv) { if (this->get(bit)) { result.set(this->getNumberOfSetBitsBeforeIndex(bit), true); } } } return result; } /*! * Retrieves whether there is at least one bit set in the vector. * * @return True if there is at least one bit set in this vector. */ bool empty() const { for (uint_fast64_t i = 0; i < this->bucketCount; ++i) { if (this->bucketArray[i] != 0) { return false; } } return true; } /*! * Removes all set bits from the bit vector. */ void clear() { for (uint_fast64_t i = 0; i < this->bucketCount; ++i) { this->bucketArray[i] = 0; } } /*! * Returns a list containing all indices such that the bits at these indices are set to true * in the bit vector. * * @return A vector of indices of set bits in the bit vector. */ std::vector getSetIndicesList() const { std::vector result; result.reserve(this->getNumberOfSetBits()); for (auto index : *this) { result.push_back(index); } return result; } /*! * Adds all indices of bits set to one to the given list. * * @param list The list to which to append the indices. */ void addSetIndicesToVector(std::vector& vector) const { for (auto index : *this) { vector.push_back(index); } } /*! * Returns the number of bits that are set (to one) in this bit vector. * @return The number of bits that are set (to one) in this bit vector. */ uint_fast64_t getNumberOfSetBits() const { return getNumberOfSetBitsBeforeIndex(bucketCount << 6); } uint_fast64_t getNumberOfSetBitsBeforeIndex(uint_fast64_t index) const { uint_fast64_t result = 0; // First, count all full buckets. uint_fast64_t bucket = index >> 6; for (uint_fast64_t i = 0; i < bucket; ++i) { // Check if we are using g++ or clang++ and, if so, use the built-in function #if (defined (__GNUG__) || defined(__clang__)) result += __builtin_popcountll(this->bucketArray[i]); #elif defined WINDOWS #include // if the target machine does not support SSE4, this will fail. result += _mm_popcnt_u64(this->bucketArray[i]); #else uint_fast32_t cnt; uint_fast64_t bitset = this->bucketArray[i]; for (cnt = 0; bitset; cnt++) { bitset &= bitset - 1; } result += cnt; #endif } // Now check if we have to count part of a bucket. uint64_t tmp = index & mod64mask; if (tmp != 0) { tmp = ((1ll << (tmp & mod64mask)) - 1ll); tmp &= bucketArray[bucket]; // Check if we are using g++ or clang++ and, if so, use the built-in function #if (defined (__GNUG__) || defined(__clang__)) result += __builtin_popcountll(tmp); #else uint_fast32_t cnt; uint64_t bitset = tmp; for (cnt = 0; bitset; cnt++) { bitset &= bitset - 1; } result += cnt; #endif } return result; } /*! * Retrieves the number of bits this bit vector can store. */ uint_fast64_t getSize() const { return bitCount; } /*! * Returns the size of the bit vector in memory measured in bytes. * @return The size of the bit vector in memory measured in bytes. */ uint_fast64_t getSizeInMemory() const { return sizeof(*this) + sizeof(uint_fast64_t) * bucketCount; } /*! * Returns an iterator to the indices of the set bits in the bit vector. */ constIndexIterator begin() const { return constIndexIterator(*this, 0, bitCount); } /*! * Returns an iterator pointing at the element past the bit vector. */ const constIndexIterator& end() const { return endIterator; } /*! * Returns a string representation of the bit vector. */ std::string toString() const { std::stringstream result; result << "bit vector(" << this->getNumberOfSetBits() << "/" << bitCount << ") ["; for (auto index : *this) { result << index << " "; } result << "]"; return result.str(); } /*! * Calculates a hash over all values contained in this Sparse Matrix. * @return size_t A Hash Value */ std::size_t getHash() const { std::size_t result = 0; boost::hash_combine(result, bucketCount); boost::hash_combine(result, bitCount); for (uint_fast64_t i = 0; i < bucketCount; ++i) { boost::hash_combine(result, bucketArray[i]); } return result; } private: /*! * Retrieves the index of the bit that is set after the given starting index, * but before the given end index in the given bit vector. * @param bitVector The bit vector to search. * @param startingIndex The index where to start the search. * @param endIndex The end index at which to stop the search. * @return The index of the bit that is set after the given starting index, * but before the given end index in the given bit vector or endIndex in case * the end index was reached. */ uint_fast64_t getNextSetIndex(uint_fast64_t startingIndex, uint_fast64_t endIndex) const { uint_fast8_t currentBitInByte = startingIndex & mod64mask; startingIndex >>= 6; uint64_t* bucketPtr = this->bucketArray + startingIndex; while ((startingIndex << 6) < endIndex) { // Compute the remaining bucket content by a right shift // to the current bit. uint_fast64_t remainingInBucket = *bucketPtr >> currentBitInByte; // Check if there is at least one bit in the remainder of the bucket // that is set to true. if (remainingInBucket != 0) { // Find that bit. while ((remainingInBucket & 1) == 0) { remainingInBucket >>= 1; ++currentBitInByte; } // Only return the index of the set bit if we are still in the // valid range. if ((startingIndex << 6) + currentBitInByte < endIndex) { return (startingIndex << 6) + currentBitInByte; } else { return endIndex; } } // Advance to the next bucket. ++startingIndex; ++bucketPtr; currentBitInByte = 0; } return endIndex; } /*! * Truncate the last bucket so that no bits are set in the range starting * from (bitCount + 1). */ void truncateLastBucket() { if ((bitCount & mod64mask) != 0) { bucketArray[bucketCount - 1] = bucketArray[bucketCount - 1] & truncateMask; } } /*! * Updates internal structures in case the size of the bit vector changed. Needs to be called * after the size of the bit vector changed. */ void updateSizeChange() { endIterator.currentIndex = bitCount; truncateMask = (1ll << (bitCount & mod64mask)) - 1ll; } /*! The number of 64-bit buckets we use as internal storage. */ uint_fast64_t bucketCount; /*! The number of bits that have to be stored */ uint_fast64_t bitCount; /*! Array of 64-bit buckets to store the bits. */ uint64_t* bucketArray; /*! An iterator marking the end of the bit vector. */ constIndexIterator endIterator; /*! A bit mask that can be used to reduce a modulo operation to a logical "and". */ static const uint_fast64_t mod64mask = (1 << 6) - 1; uint64_t truncateMask; }; } // namespace storage } // namespace storm #endif // STORM_STORAGE_BITVECTOR_H_