#include "DFTModelChecker.h" #include "storm/settings/modules/IOSettings.h" #include "storm/settings/modules/GeneralSettings.h" #include "storm/builder/ParallelCompositionBuilder.h" #include "storm/exceptions/UnmetRequirementException.h" #include "storm/utility/bitoperations.h" #include "storm/io/DirectEncodingExporter.h" #include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "storm/modelchecker/results/ExplicitQualitativeCheckResult.h" #include "storm/models/ModelType.h" #include "storm-dft/api/storm-dft.h" #include "storm-dft/builder/ExplicitDFTModelBuilder.h" #include "storm-dft/storage/dft/DFTIsomorphism.h" #include "storm-dft/settings/modules/DftIOSettings.h" #include "storm-dft/settings/modules/FaultTreeSettings.h" namespace storm { namespace modelchecker { template typename DFTModelChecker::dft_results DFTModelChecker::check(storm::storage::DFT const& origDft, std::vector> const& properties, bool symred, bool allowModularisation, std::set const& relevantEvents, bool allowDCForRelevantEvents, double approximationError, storm::builder::ApproximationHeuristic approximationHeuristic, bool eliminateChains, storm::transformer::EliminationLabelBehavior labelBehavior) { totalTimer.start(); dft_results results; // Check well-formedness of DFT auto wellFormedResult = storm::api::isWellFormed(origDft, true); STORM_LOG_THROW(wellFormedResult.first, storm::exceptions::UnmetRequirementException, "DFT is not well-formed for analysis: " << wellFormedResult.second); // Optimizing DFT storm::storage::DFT dft = origDft.optimize(); // TODO: check that all paths reach the target state for approximation // Checking DFT // TODO: distinguish for all properties, not only for first one if (properties[0]->isTimeOperatorFormula() && allowModularisation) { // Use parallel composition as modularisation approach for expected time std::shared_ptr> model = buildModelViaComposition(dft, properties, symred, true, relevantEvents); // Model checking std::vector resultsValue = checkModel(model, properties); for (ValueType result : resultsValue) { results.push_back(result); } } else { results = checkHelper(dft, properties, symred, allowModularisation, relevantEvents, allowDCForRelevantEvents, approximationError, approximationHeuristic, eliminateChains, labelBehavior); } totalTimer.stop(); return results; } template typename DFTModelChecker::dft_results DFTModelChecker::checkHelper(storm::storage::DFT const& dft, property_vector const& properties, bool symred, bool allowModularisation, std::set const& relevantEvents, bool allowDCForRelevantEvents, double approximationError, storm::builder::ApproximationHeuristic approximationHeuristic, bool eliminateChains, storm::transformer::EliminationLabelBehavior labelBehavior) { STORM_LOG_TRACE("Check helper called"); std::vector> dfts; bool invResults = false; size_t nrK = 0; // K out of M size_t nrM = 0; // K out of M // Try modularisation if (allowModularisation) { switch (dft.topLevelType()) { case storm::storage::DFTElementType::AND: STORM_LOG_TRACE("top modularisation called AND"); dfts = dft.topModularisation(); STORM_LOG_TRACE("Modularisation into " << dfts.size() << " submodules."); nrK = dfts.size(); nrM = dfts.size(); break; case storm::storage::DFTElementType::OR: STORM_LOG_TRACE("top modularisation called OR"); dfts = dft.topModularisation(); STORM_LOG_TRACE("Modularisation into " << dfts.size() << " submodules."); nrK = 0; nrM = dfts.size(); invResults = true; break; case storm::storage::DFTElementType::VOT: STORM_LOG_TRACE("top modularisation called VOT"); dfts = dft.topModularisation(); STORM_LOG_TRACE("Modularisation into " << dfts.size() << " submodules."); nrK = std::static_pointer_cast const>( dft.getTopLevelGate())->threshold(); nrM = dfts.size(); if (nrK <= nrM / 2) { nrK -= 1; invResults = true; } break; default: // No static gate -> no modularisation applicable break; } } // Perform modularisation if (dfts.size() > 1) { STORM_LOG_TRACE("Recursive CHECK Call"); // TODO: compute simultaneously dft_results results; for (auto property : properties) { if (!property->isProbabilityOperatorFormula()) { STORM_LOG_WARN("Could not check property: " << *property); } else { // Recursively call model checking std::vector res; for (auto const ft : dfts) { // TODO: allow approximation in modularisation dft_results ftResults = checkHelper(ft, {property}, symred, true, relevantEvents, allowDCForRelevantEvents, 0.0); STORM_LOG_ASSERT(ftResults.size() == 1, "Wrong number of results"); res.push_back(boost::get(ftResults[0])); } // Combine modularisation results STORM_LOG_TRACE("Combining all results... K=" << nrK << "; M=" << nrM << "; invResults=" << (invResults ? "On" : "Off")); ValueType result = storm::utility::zero(); int limK = invResults ? -1 : nrM + 1; int chK = invResults ? -1 : 1; for (int cK = nrK; cK != limK; cK += chK) { STORM_LOG_ASSERT(cK >= 0, "ck negative."); uint64_t permutation = smallestIntWithNBitsSet(static_cast(cK)); do { STORM_LOG_TRACE("Permutation=" << permutation); ValueType permResult = storm::utility::one(); for (size_t i = 0; i < res.size(); ++i) { if (permutation & (1ul << i)) { permResult *= res[i]; } else { permResult *= storm::utility::one() - res[i]; } } STORM_LOG_TRACE("Result for permutation:" << permResult); permutation = nextBitPermutation(permutation); result += permResult; } while (permutation < (1ul << nrM) && permutation != 0); } if (invResults) { result = storm::utility::one() - result; } results.push_back(result); } } return results; } else { // No modularisation was possible return checkDFT(dft, properties, symred, relevantEvents, allowDCForRelevantEvents, approximationError, approximationHeuristic, eliminateChains, labelBehavior); } } template std::shared_ptr> DFTModelChecker::buildModelViaComposition(storm::storage::DFT const &dft, property_vector const &properties, bool symred, bool allowModularisation, std::set const &relevantEvents, bool allowDCForRelevantEvents) { // TODO: use approximation? STORM_LOG_TRACE("Build model via composition"); std::vector> dfts; bool isAnd = true; // Try modularisation if (allowModularisation) { switch (dft.topLevelType()) { case storm::storage::DFTElementType::AND: STORM_LOG_TRACE("top modularisation called AND"); dfts = dft.topModularisation(); STORM_LOG_TRACE("Modularisation into " << dfts.size() << " submodules."); isAnd = true; break; case storm::storage::DFTElementType::OR: STORM_LOG_TRACE("top modularisation called OR"); dfts = dft.topModularisation(); STORM_LOG_TRACE("Modularsation into " << dfts.size() << " submodules."); isAnd = false; break; case storm::storage::DFTElementType::VOT: // TODO enable modularisation for voting gate break; default: // No static gate -> no modularisation applicable break; } } // Perform modularisation via parallel composition if (dfts.size() > 1) { STORM_LOG_TRACE("Recursive CHECK Call"); bool firstTime = true; std::shared_ptr> composedModel; for (auto const ft : dfts) { STORM_LOG_DEBUG("Building Model via parallel composition..."); explorationTimer.start(); ft.setRelevantEvents(relevantEvents, allowDCForRelevantEvents); // Find symmetries std::map>> emptySymmetry; storm::storage::DFTIndependentSymmetries symmetries(emptySymmetry); if (symred) { auto colouring = ft.colourDFT(); symmetries = ft.findSymmetries(colouring); STORM_LOG_DEBUG("Found " << symmetries.groups.size() << " symmetries."); STORM_LOG_TRACE("Symmetries: " << std::endl << symmetries); } // Build a single CTMC STORM_LOG_DEBUG("Building Model from DFT with top level element " << ft.getElement(ft.getTopLevelIndex())->toString() << " ..."); storm::builder::ExplicitDFTModelBuilder builder(ft, symmetries); builder.buildModel(0, 0.0); std::shared_ptr> model = builder.getModel(); explorationTimer.stop(); STORM_LOG_THROW(model->isOfType(storm::models::ModelType::Ctmc), storm::exceptions::NotSupportedException, "Parallel composition only applicable for CTMCs"); std::shared_ptr> ctmc = model->template as>(); // Apply bisimulation to new CTMC bisimulationTimer.start(); ctmc = storm::api::performDeterministicSparseBisimulationMinimization>( ctmc, properties, storm::storage::BisimulationType::Weak)->template as>(); bisimulationTimer.stop(); if (firstTime) { composedModel = ctmc; firstTime = false; } else { composedModel = storm::builder::ParallelCompositionBuilder::compose(composedModel, ctmc, isAnd); } // Apply bisimulation to parallel composition bisimulationTimer.start(); composedModel = storm::api::performDeterministicSparseBisimulationMinimization>( composedModel, properties, storm::storage::BisimulationType::Weak)->template as>(); bisimulationTimer.stop(); STORM_LOG_DEBUG("No. states (Composed): " << composedModel->getNumberOfStates()); STORM_LOG_DEBUG("No. transitions (Composed): " << composedModel->getNumberOfTransitions()); if (composedModel->getNumberOfStates() <= 15) { STORM_LOG_TRACE("Transition matrix: " << std::endl << composedModel->getTransitionMatrix()); } else { STORM_LOG_TRACE("Transition matrix: too big to print"); } } if (printInfo) { composedModel->printModelInformationToStream(std::cout); } return composedModel; } else { // No composition was possible explorationTimer.start(); dft.setRelevantEvents(relevantEvents, allowDCForRelevantEvents); // Find symmetries std::map>> emptySymmetry; storm::storage::DFTIndependentSymmetries symmetries(emptySymmetry); if (symred) { auto colouring = dft.colourDFT(); symmetries = dft.findSymmetries(colouring); STORM_LOG_DEBUG("Found " << symmetries.groups.size() << " symmetries."); STORM_LOG_TRACE("Symmetries: " << std::endl << symmetries); } // Build a single CTMC STORM_LOG_DEBUG("Building Model..."); storm::builder::ExplicitDFTModelBuilder builder(dft, symmetries); builder.buildModel(0, 0.0); std::shared_ptr> model = builder.getModel(); if (printInfo) { model->printModelInformationToStream(std::cout); } explorationTimer.stop(); STORM_LOG_THROW(model->isOfType(storm::models::ModelType::Ctmc), storm::exceptions::NotSupportedException, "Parallel composition only applicable for CTMCs"); return model->template as>(); } } template typename DFTModelChecker::dft_results DFTModelChecker::checkDFT(storm::storage::DFT const &dft, property_vector const &properties, bool symred, std::set const &relevantEvents, bool allowDCForRelevantEvents, double approximationError, storm::builder::ApproximationHeuristic approximationHeuristic, bool eliminateChains, storm::transformer::EliminationLabelBehavior labelBehavior) { explorationTimer.start(); auto ioSettings = storm::settings::getModule(); auto dftIOSettings = storm::settings::getModule(); dft.setRelevantEvents(relevantEvents, allowDCForRelevantEvents); // Find symmetries std::map>> emptySymmetry; storm::storage::DFTIndependentSymmetries symmetries(emptySymmetry); if (symred) { auto colouring = dft.colourDFT(); symmetries = dft.findSymmetries(colouring); STORM_LOG_DEBUG("Found " << symmetries.groups.size() << " symmetries."); STORM_LOG_TRACE("Symmetries: " << std::endl << symmetries); } if (approximationError > 0.0) { // Comparator for checking the error of the approximation storm::utility::ConstantsComparator comparator; // Build approximate Markov Automata for lower and upper bound approximation_result approxResult = std::make_pair(storm::utility::zero(), storm::utility::zero()); std::shared_ptr> model; std::vector newResult; storm::builder::ExplicitDFTModelBuilder builder(dft, symmetries); // TODO: compute approximation for all properties simultaneously? std::shared_ptr property = properties[0]; if (properties.size() > 1) { STORM_LOG_WARN("Computing approximation only for first property: " << *property); } bool probabilityFormula = property->isProbabilityOperatorFormula(); STORM_LOG_ASSERT((property->isTimeOperatorFormula() && !probabilityFormula) || (!property->isTimeOperatorFormula() && probabilityFormula), "Probability formula not initialized correctly"); size_t iteration = 0; do { // Iteratively build finer models if (iteration > 0) { explorationTimer.start(); } STORM_LOG_DEBUG("Building model..."); // TODO refine model using existing model and MC results builder.buildModel(iteration, approximationError, approximationHeuristic); explorationTimer.stop(); buildingTimer.start(); // TODO: possible to do bisimulation on approximated model and not on concrete one? // Build model for lower bound STORM_LOG_DEBUG("Getting model for lower bound..."); model = builder.getModelApproximation(true, !probabilityFormula); // We only output the info from the lower bound as the info for the upper bound is the same if (printInfo && dftIOSettings.isShowDftStatisticsSet()) { std::cout << "Model in iteration " << (iteration + 1) << ":" << std::endl; model->printModelInformationToStream(std::cout); } buildingTimer.stop(); if (ioSettings.isExportExplicitSet()) { std::vector parameterNames; // TODO fill parameter names storm::api::exportSparseModelAsDrn(model, ioSettings.getExportExplicitFilename(), parameterNames, !ioSettings.isExplicitExportPlaceholdersDisabled()); } // Check lower bounds newResult = checkModel(model, {property}); STORM_LOG_ASSERT(newResult.size() == 1, "Wrong size for result vector."); STORM_LOG_ASSERT(iteration == 0 || !comparator.isLess(newResult[0], approxResult.first), "New under-approximation " << newResult[0] << " is smaller than old result " << approxResult.first); approxResult.first = newResult[0]; // Build model for upper bound STORM_LOG_DEBUG("Getting model for upper bound..."); buildingTimer.start(); model = builder.getModelApproximation(false, !probabilityFormula); buildingTimer.stop(); // Check upper bound newResult = checkModel(model, {property}); STORM_LOG_ASSERT(newResult.size() == 1, "Wrong size for result vector."); STORM_LOG_ASSERT(iteration == 0 || !comparator.isLess(approxResult.second, newResult[0]), "New over-approximation " << newResult[0] << " is greater than old result " << approxResult.second); approxResult.second = newResult[0]; STORM_LOG_ASSERT(comparator.isLess(approxResult.first, approxResult.second) || comparator.isEqual(approxResult.first, approxResult.second), "Under-approximation " << approxResult.first << " is greater than over-approximation " << approxResult.second); totalTimer.stop(); if (printInfo && dftIOSettings.isShowDftStatisticsSet()) { std::cout << "Result after iteration " << (iteration + 1) << ": (" << std::setprecision(10) << approxResult.first << ", " << approxResult.second << ")" << std::endl; printTimings(); std::cout << std::endl; } else { STORM_LOG_DEBUG("Result after iteration " << (iteration + 1) << ": (" << std::setprecision(10) << approxResult.first << ", " << approxResult.second << ")"); } totalTimer.start(); STORM_LOG_THROW(!storm::utility::isInfinity(approxResult.first) && !storm::utility::isInfinity(approxResult.second), storm::exceptions::NotSupportedException, "Approximation does not work if result might be infinity."); ++iteration; } while (!isApproximationSufficient(approxResult.first, approxResult.second, approximationError, probabilityFormula)); //STORM_LOG_INFO("Finished approximation after " << iteration << " iteration" << (iteration > 1 ? "s." : ".")); if (printInfo) { model->printModelInformationToStream(std::cout); } dft_results results; results.push_back(approxResult); return results; } else { // Build a single Markov Automaton auto ioSettings = storm::settings::getModule(); STORM_LOG_DEBUG("Building Model..."); storm::builder::ExplicitDFTModelBuilder builder(dft, symmetries); builder.buildModel(0, 0.0); std::shared_ptr> model = builder.getModel(); if (eliminateChains && model->isOfType(storm::models::ModelType::MarkovAutomaton)) { auto ma = std::static_pointer_cast>(model); model = storm::transformer::NonMarkovianChainTransformer::eliminateNonmarkovianStates(ma, labelBehavior); } explorationTimer.stop(); // Print model information if (printInfo) { model->printModelInformationToStream(std::cout); } // Export the model if required // TODO move this outside of the model checker? if (ioSettings.isExportExplicitSet()) { std::vector parameterNames; // TODO fill parameter names storm::api::exportSparseModelAsDrn(model, ioSettings.getExportExplicitFilename(), parameterNames, !ioSettings.isExplicitExportPlaceholdersDisabled()); } if (ioSettings.isExportDotSet()) { storm::api::exportSparseModelAsDot(model, ioSettings.getExportDotFilename(), ioSettings.getExportDotMaxWidth()); } // Model checking std::vector resultsValue = checkModel(model, properties); dft_results results; for (ValueType result : resultsValue) { results.push_back(result); } return results; } } template std::vector DFTModelChecker::checkModel(std::shared_ptr> &model, property_vector const &properties) { // Bisimulation if (model->isOfType(storm::models::ModelType::Ctmc) && storm::settings::getModule().isBisimulationSet()) { bisimulationTimer.start(); STORM_LOG_DEBUG("Bisimulation..."); model = storm::api::performDeterministicSparseBisimulationMinimization>( model->template as>(), properties, storm::storage::BisimulationType::Weak)->template as>(); STORM_LOG_DEBUG("No. states (Bisimulation): " << model->getNumberOfStates()); STORM_LOG_DEBUG("No. transitions (Bisimulation): " << model->getNumberOfTransitions()); bisimulationTimer.stop(); } // Check the model STORM_LOG_DEBUG("Model checking..."); modelCheckingTimer.start(); std::vector results; // Check each property storm::utility::Stopwatch singleModelCheckingTimer; for (auto property : properties) { singleModelCheckingTimer.reset(); singleModelCheckingTimer.start(); //STORM_PRINT_AND_LOG("Model checking property " << *property << " ..." << std::endl); std::unique_ptr result( storm::api::verifyWithSparseEngine(model, storm::api::createTask(property,true))); if (result) { result->filter(storm::modelchecker::ExplicitQualitativeCheckResult(model->getInitialStates())); ValueType resultValue = result->asExplicitQuantitativeCheckResult().getValueMap().begin()->second; results.push_back(resultValue); } else { STORM_LOG_WARN("The property '" << *property << "' could not be checked with the current settings."); results.push_back(-storm::utility::one()); } //STORM_PRINT_AND_LOG("Result (initial states): " << resultValue << std::endl); singleModelCheckingTimer.stop(); //STORM_PRINT_AND_LOG("Time for model checking: " << singleModelCheckingTimer << "." << std::endl); } modelCheckingTimer.stop(); STORM_LOG_DEBUG("Model checking done."); return results; } template bool DFTModelChecker::isApproximationSufficient(ValueType, ValueType, double, bool) { STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Approximation works only for double."); } template<> bool DFTModelChecker::isApproximationSufficient(double lowerBound, double upperBound, double approximationError, bool relative) { STORM_LOG_THROW(!std::isnan(lowerBound) && !std::isnan(upperBound), storm::exceptions::NotSupportedException, "Approximation does not work if result is NaN."); if (relative) { return upperBound - lowerBound <= approximationError; } else { return upperBound - lowerBound <= approximationError * (lowerBound + upperBound) / 2; } } template void DFTModelChecker::printTimings(std::ostream &os) { os << "Times:" << std::endl; os << "Exploration:\t" << explorationTimer << std::endl; os << "Building:\t" << buildingTimer << std::endl; os << "Bisimulation:\t" << bisimulationTimer << std::endl; os << "Modelchecking:\t" << modelCheckingTimer << std::endl; os << "Total:\t\t" << totalTimer << std::endl; } template void DFTModelChecker::printResults(dft_results const &results, std::ostream &os) { bool first = true; os << "Result: ["; for (auto result : results) { if (first) { first = false; } else { os << ", "; } boost::variant stream(os); boost::apply_visitor(ResultOutputVisitor(), result, stream); } os << "]" << std::endl; } template class DFTModelChecker; #ifdef STORM_HAVE_CARL template class DFTModelChecker; #endif } }