#include "src/storage/dd/cudd/InternalCuddBdd.h" #include #include "src/storage/dd/cudd/InternalCuddDdManager.h" #include "src/storage/dd/Odd.h" #include "src/storage/BitVector.h" namespace storm { namespace dd { InternalBdd::InternalBdd(InternalDdManager const* ddManager, cudd::BDD cuddBdd) : ddManager(ddManager), cuddBdd(cuddBdd) { // Intentionally left empty. } template InternalBdd InternalBdd::fromVector(InternalDdManager const* ddManager, std::vector const& values, Odd const& odd, std::vector const& sortedDdVariableIndices, std::function const& filter) { uint_fast64_t offset = 0; return InternalBdd(ddManager, cudd::BDD(ddManager->getCuddManager(), fromVectorRec(ddManager->getCuddManager().getManager(), offset, 0, sortedDdVariableIndices.size(), values, odd, sortedDdVariableIndices, filter))); } bool InternalBdd::operator==(InternalBdd const& other) const { return this->getCuddBdd() == other.getCuddBdd(); } bool InternalBdd::operator!=(InternalBdd const& other) const { return !(*this == other); } InternalBdd InternalBdd::ite(InternalBdd const& thenDd, InternalBdd const& elseDd) const { return InternalBdd(ddManager, this->getCuddBdd().Ite(thenDd.getCuddBdd(), elseDd.getCuddBdd())); } InternalBdd InternalBdd::operator||(InternalBdd const& other) const { InternalBdd result(*this); result |= other; return result; } InternalBdd& InternalBdd::operator|=(InternalBdd const& other) { this->cuddBdd = this->getCuddBdd() | other.getCuddBdd(); return *this; } InternalBdd InternalBdd::operator&&(InternalBdd const& other) const { InternalBdd result(*this); result &= other; return result; } InternalBdd& InternalBdd::operator&=(InternalBdd const& other) { this->cuddBdd = this->getCuddBdd() & other.getCuddBdd(); return *this; } InternalBdd InternalBdd::iff(InternalBdd const& other) const { return InternalBdd(ddManager, this->getCuddBdd().Xnor(other.getCuddBdd())); } InternalBdd InternalBdd::exclusiveOr(InternalBdd const& other) const { return InternalBdd(ddManager, this->getCuddBdd().Xor(other.getCuddBdd())); } InternalBdd InternalBdd::implies(InternalBdd const& other) const { return InternalBdd(ddManager, this->getCuddBdd().Ite(other.getCuddBdd(), ddManager->getBddOne().getCuddBdd())); } InternalBdd InternalBdd::operator!() const { InternalBdd result(*this); result.complement(); return result; } InternalBdd& InternalBdd::complement() { this->cuddBdd = ~this->getCuddBdd(); return *this; } InternalBdd InternalBdd::existsAbstract(InternalBdd const& cube) const { return InternalBdd(ddManager, this->getCuddBdd().ExistAbstract(cube.getCuddBdd())); } InternalBdd InternalBdd::universalAbstract(InternalBdd const& cube) const { return InternalBdd(ddManager, this->getCuddBdd().UnivAbstract(cube.getCuddBdd())); } InternalBdd InternalBdd::andExists(InternalBdd const& other, InternalBdd const& cube) const { return InternalBdd(ddManager, this->getCuddBdd().AndAbstract(other.getCuddBdd(), cube.getCuddBdd())); } InternalBdd InternalBdd::constrain(InternalBdd const& constraint) const { return InternalBdd(ddManager, this->getCuddBdd().Constrain(constraint.getCuddBdd())); } InternalBdd InternalBdd::restrict(InternalBdd const& constraint) const { return InternalBdd(ddManager, this->getCuddBdd().Restrict(constraint.getCuddBdd())); } InternalBdd InternalBdd::swapVariables(std::vector> const& from, std::vector> const& to) const { std::vector fromBdd; std::vector toBdd; for (auto it1 = from.begin(), ite1 = from.end(), it2 = to.begin(); it1 != ite1; ++it1, ++it2) { fromBdd.push_back(it1->getCuddBdd()); toBdd.push_back(it2->getCuddBdd()); } return InternalBdd(ddManager, this->getCuddBdd().SwapVariables(fromBdd, toBdd)); } InternalBdd InternalBdd::getSupport() const { return InternalBdd(ddManager, this->getCuddBdd().Support()); } uint_fast64_t InternalBdd::getNonZeroCount(uint_fast64_t numberOfDdVariables) const { return static_cast(this->getCuddBdd().CountMinterm(static_cast(numberOfDdVariables))); } uint_fast64_t InternalBdd::getLeafCount() const { return static_cast(this->getCuddBdd().CountLeaves()); } uint_fast64_t InternalBdd::getNodeCount() const { return static_cast(this->getCuddBdd().nodeCount()); } bool InternalBdd::isOne() const { return this->getCuddBdd().IsOne(); } bool InternalBdd::isZero() const { return this->getCuddBdd().IsZero(); } uint_fast64_t InternalBdd::getIndex() const { return static_cast(this->getCuddBdd().NodeReadIndex()); } void InternalBdd::exportToDot(std::string const& filename, std::vector const& ddVariableNamesAsStrings) const { // Build the name input of the DD. std::vector ddNames; std::string ddName("f"); ddNames.push_back(new char[ddName.size() + 1]); std::copy(ddName.c_str(), ddName.c_str() + 2, ddNames.back()); // Now build the variables names. std::vector ddVariableNames; for (auto const& element : ddVariableNamesAsStrings) { ddVariableNames.push_back(new char[element.size() + 1]); std::copy(element.c_str(), element.c_str() + element.size() + 1, ddVariableNames.back()); } // Open the file, dump the DD and close it again. FILE* filePointer = fopen(filename.c_str() , "w"); std::vector cuddBddVector = { this->getCuddBdd() }; ddManager->getCuddManager().DumpDot(cuddBddVector, &ddVariableNames[0], &ddNames[0], filePointer); fclose(filePointer); // Finally, delete the names. for (char* element : ddNames) { delete element; } for (char* element : ddVariableNames) { delete element; } } cudd::BDD InternalBdd::getCuddBdd() const { return this->cuddBdd; } DdNode* InternalBdd::getCuddDdNode() const { return this->getCuddBdd().getNode(); } template InternalAdd InternalBdd::toAdd() const { return InternalAdd(ddManager, this->getCuddBdd().Add()); } template DdNode* InternalBdd::fromVectorRec(::DdManager* manager, uint_fast64_t& currentOffset, uint_fast64_t currentLevel, uint_fast64_t maxLevel, std::vector const& values, Odd const& odd, std::vector const& ddVariableIndices, std::function const& filter) { if (currentLevel == maxLevel) { // If we are in a terminal node of the ODD, we need to check whether the then-offset of the ODD is one // (meaning the encoding is a valid one) or zero (meaning the encoding is not valid). Consequently, we // need to copy the next value of the vector iff the then-offset is greater than zero. if (odd.getThenOffset() > 0) { if (filter(values[currentOffset++])) { return Cudd_ReadOne(manager); } else { return Cudd_ReadLogicZero(manager); } } else { return Cudd_ReadZero(manager); } } else { // If the total offset is zero, we can just return the constant zero DD. if (odd.getThenOffset() + odd.getElseOffset() == 0) { return Cudd_ReadZero(manager); } // Determine the new else-successor. DdNode* elseSuccessor = nullptr; if (odd.getElseOffset() > 0) { elseSuccessor = fromVectorRec(manager, currentOffset, currentLevel + 1, maxLevel, values, odd.getElseSuccessor(), ddVariableIndices, filter); } else { elseSuccessor = Cudd_ReadLogicZero(manager); } Cudd_Ref(elseSuccessor); // Determine the new then-successor. DdNode* thenSuccessor = nullptr; if (odd.getThenOffset() > 0) { thenSuccessor = fromVectorRec(manager, currentOffset, currentLevel + 1, maxLevel, values, odd.getThenSuccessor(), ddVariableIndices, filter); } else { thenSuccessor = Cudd_ReadLogicZero(manager); } Cudd_Ref(thenSuccessor); // Create a node representing ITE(currentVar, thenSuccessor, elseSuccessor); DdNode* result = Cudd_bddIthVar(manager, static_cast(ddVariableIndices[currentLevel])); Cudd_Ref(result); DdNode* newResult = Cudd_bddIte(manager, result, thenSuccessor, elseSuccessor); Cudd_Ref(newResult); // Dispose of the intermediate results Cudd_RecursiveDeref(manager, result); Cudd_RecursiveDeref(manager, thenSuccessor); Cudd_RecursiveDeref(manager, elseSuccessor); // Before returning, we remove the protection imposed by the previous call to Cudd_Ref. Cudd_Deref(newResult); return newResult; } } storm::storage::BitVector InternalBdd::toVector(storm::dd::Odd const& rowOdd, std::vector const& ddVariableIndices) const { storm::storage::BitVector result(rowOdd.getTotalOffset()); this->toVectorRec(this->getCuddDdNode(), ddManager->getCuddManager(), result, rowOdd, Cudd_IsComplement(this->getCuddDdNode()), 0, ddVariableIndices.size(), 0, ddVariableIndices); return result; } void InternalBdd::toVectorRec(DdNode const* dd, cudd::Cudd const& manager, storm::storage::BitVector& result, Odd const& rowOdd, bool complement, uint_fast64_t currentRowLevel, uint_fast64_t maxLevel, uint_fast64_t currentRowOffset, std::vector const& ddRowVariableIndices) const { // If there are no more values to select, we can directly return. if (dd == Cudd_ReadLogicZero(manager.getManager()) && !complement) { return; } else if (dd == Cudd_ReadOne(manager.getManager()) && complement) { return; } // If we are at the maximal level, the value to be set is stored as a constant in the DD. if (currentRowLevel == maxLevel) { result.set(currentRowOffset, true); } else if (ddRowVariableIndices[currentRowLevel] < dd->index) { toVectorRec(dd, manager, result, rowOdd.getElseSuccessor(), complement, currentRowLevel + 1, maxLevel, currentRowOffset, ddRowVariableIndices); toVectorRec(dd, manager, result, rowOdd.getThenSuccessor(), complement, currentRowLevel + 1, maxLevel, currentRowOffset + rowOdd.getElseOffset(), ddRowVariableIndices); } else { // Otherwise, we compute the ODDs for both the then- and else successors. DdNode* elseDdNode = Cudd_E(dd); DdNode* thenDdNode = Cudd_T(dd); // Determine whether we have to evaluate the successors as if they were complemented. bool elseComplemented = Cudd_IsComplement(elseDdNode) ^ complement; bool thenComplemented = Cudd_IsComplement(thenDdNode) ^ complement; toVectorRec(Cudd_Regular(elseDdNode), manager, result, rowOdd.getElseSuccessor(), elseComplemented, currentRowLevel + 1, maxLevel, currentRowOffset, ddRowVariableIndices); toVectorRec(Cudd_Regular(thenDdNode), manager, result, rowOdd.getThenSuccessor(), thenComplemented, currentRowLevel + 1, maxLevel, currentRowOffset + rowOdd.getElseOffset(), ddRowVariableIndices); } } Odd InternalBdd::createOdd(std::vector const& ddVariableIndices) const { // Prepare a unique table for each level that keeps the constructed ODD nodes unique. std::vector, std::shared_ptr, HashFunctor>> uniqueTableForLevels(ddVariableIndices.size() + 1); // Now construct the ODD structure from the BDD. std::shared_ptr rootOdd = createOddRec(Cudd_Regular(this->getCuddDdNode()), ddManager->getCuddManager(), 0, Cudd_IsComplement(this->getCuddDdNode()), ddVariableIndices.size(), ddVariableIndices, uniqueTableForLevels); // Return a copy of the root node to remove the shared_ptr encapsulation. return Odd(*rootOdd); } std::size_t InternalBdd::HashFunctor::operator()(std::pair const& key) const { std::size_t result = 0; boost::hash_combine(result, key.first); boost::hash_combine(result, key.second); return result; } std::shared_ptr InternalBdd::createOddRec(DdNode* dd, cudd::Cudd const& manager, uint_fast64_t currentLevel, bool complement, uint_fast64_t maxLevel, std::vector const& ddVariableIndices, std::vector, std::shared_ptr, HashFunctor>>& uniqueTableForLevels) { // Check whether the ODD for this node has already been computed (for this level) and if so, return this instead. auto const& iterator = uniqueTableForLevels[currentLevel].find(std::make_pair(dd, complement)); if (iterator != uniqueTableForLevels[currentLevel].end()) { return iterator->second; } else { // Otherwise, we need to recursively compute the ODD. // If we are already past the maximal level that is to be considered, we can simply create an Odd without // successors if (currentLevel == maxLevel) { uint_fast64_t elseOffset = 0; uint_fast64_t thenOffset = 0; // If the DD is not the zero leaf, then the then-offset is 1. if (dd != Cudd_ReadZero(manager.getManager())) { thenOffset = 1; } // If we need to complement the 'terminal' node, we need to negate its offset. if (complement) { thenOffset = 1 - thenOffset; } return std::make_shared(nullptr, elseOffset, nullptr, thenOffset); } else if (ddVariableIndices[currentLevel] < static_cast(dd->index)) { // If we skipped the level in the DD, we compute the ODD just for the else-successor and use the same // node for the then-successor as well. std::shared_ptr elseNode = createOddRec(dd, manager, currentLevel + 1, complement, maxLevel, ddVariableIndices, uniqueTableForLevels); std::shared_ptr thenNode = elseNode; uint_fast64_t totalOffset = elseNode->getElseOffset() + elseNode->getThenOffset(); return std::make_shared(elseNode, totalOffset, thenNode, totalOffset); } else { // Otherwise, we compute the ODDs for both the then- and else successors. DdNode* thenDdNode = Cudd_T(dd); DdNode* elseDdNode = Cudd_E(dd); // Determine whether we have to evaluate the successors as if they were complemented. bool elseComplemented = Cudd_IsComplement(elseDdNode) ^ complement; bool thenComplemented = Cudd_IsComplement(thenDdNode) ^ complement; std::shared_ptr elseNode = createOddRec(Cudd_Regular(elseDdNode), manager, currentLevel + 1, elseComplemented, maxLevel, ddVariableIndices, uniqueTableForLevels); std::shared_ptr thenNode = createOddRec(Cudd_Regular(thenDdNode), manager, currentLevel + 1, thenComplemented, maxLevel, ddVariableIndices, uniqueTableForLevels); return std::make_shared(elseNode, elseNode->getElseOffset() + elseNode->getThenOffset(), thenNode, thenNode->getElseOffset() + thenNode->getThenOffset()); } } } template void InternalBdd::filterExplicitVector(Odd const& odd, std::vector const& ddVariableIndices, std::vector const& sourceValues, std::vector& targetValues) const { uint_fast64_t currentIndex = 0; filterExplicitVectorRec(this->getCuddDdNode(), ddManager->getCuddManager(), 0, Cudd_IsComplement(this->getCuddDdNode()), ddVariableIndices.size(), ddVariableIndices, 0, odd, targetValues, currentIndex, sourceValues); } template void InternalBdd::filterExplicitVectorRec(DdNode* dd, cudd::Cudd const& manager, uint_fast64_t currentLevel, bool complement, uint_fast64_t maxLevel, std::vector const& ddVariableIndices, uint_fast64_t currentOffset, storm::dd::Odd const& odd, std::vector& result, uint_fast64_t& currentIndex, std::vector const& values) { // If there are no more values to select, we can directly return. if (dd == Cudd_ReadLogicZero(manager.getManager()) && !complement) { return; } else if (dd == Cudd_ReadOne(manager.getManager()) && complement) { return; } if (currentLevel == maxLevel) { // If the DD is not the zero leaf, then the then-offset is 1. bool selected = false; if (dd != Cudd_ReadLogicZero(manager.getManager())) { selected = !complement; } if (selected) { result[currentIndex++] = values[currentOffset]; } } else if (ddVariableIndices[currentLevel] < dd->index) { // If we skipped a level, we need to enumerate the explicit entries for the case in which the bit is set // and for the one in which it is not set. filterExplicitVectorRec(dd, manager, currentLevel + 1, complement, maxLevel, ddVariableIndices, currentOffset, odd.getElseSuccessor(), result, currentIndex, values); filterExplicitVectorRec(dd, manager, currentLevel + 1, complement, maxLevel, ddVariableIndices, currentOffset + odd.getElseOffset(), odd.getThenSuccessor(), result, currentIndex, values); } else { // Otherwise, we compute the ODDs for both the then- and else successors. DdNode* thenDdNode = Cudd_T(dd); DdNode* elseDdNode = Cudd_E(dd); // Determine whether we have to evaluate the successors as if they were complemented. bool elseComplemented = Cudd_IsComplement(elseDdNode) ^ complement; bool thenComplemented = Cudd_IsComplement(thenDdNode) ^ complement; filterExplicitVectorRec(Cudd_Regular(elseDdNode), manager, currentLevel + 1, elseComplemented, maxLevel, ddVariableIndices, currentOffset, odd.getElseSuccessor(), result, currentIndex, values); filterExplicitVectorRec(Cudd_Regular(thenDdNode), manager, currentLevel + 1, thenComplemented, maxLevel, ddVariableIndices, currentOffset + odd.getElseOffset(), odd.getThenSuccessor(), result, currentIndex, values); } } template InternalBdd InternalBdd::fromVector(InternalDdManager const* ddManager, std::vector const& values, Odd const& odd, std::vector const& sortedDdVariableIndices, std::function const& filter); template InternalBdd InternalBdd::fromVector(InternalDdManager const* ddManager, std::vector const& values, Odd const& odd, std::vector const& sortedDdVariableIndices, std::function const& filter); template InternalAdd InternalBdd::toAdd() const; template InternalAdd InternalBdd::toAdd() const; template void InternalBdd::filterExplicitVectorRec(DdNode* dd, cudd::Cudd const& manager, uint_fast64_t currentLevel, bool complement, uint_fast64_t maxLevel, std::vector const& ddVariableIndices, uint_fast64_t currentOffset, storm::dd::Odd const& odd, std::vector& result, uint_fast64_t& currentIndex, std::vector const& values); template void InternalBdd::filterExplicitVectorRec(DdNode* dd, cudd::Cudd const& manager, uint_fast64_t currentLevel, bool complement, uint_fast64_t maxLevel, std::vector const& ddVariableIndices, uint_fast64_t currentOffset, storm::dd::Odd const& odd, std::vector& result, uint_fast64_t& currentIndex, std::vector const& values); template void InternalBdd::filterExplicitVector(Odd const& odd, std::vector const& ddVariableIndices, std::vector const& sourceValues, std::vector& targetValues) const; template void InternalBdd::filterExplicitVector(Odd const& odd, std::vector const& ddVariableIndices, std::vector const& sourceValues, std::vector& targetValues) const; } }