You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

566 lines
32 KiB

#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 ValueType>
typename DFTModelChecker<ValueType>::dft_results DFTModelChecker<ValueType>::check(storm::storage::DFT<ValueType> const& origDft,
std::vector<std::shared_ptr<const storm::logic::Formula>> const& properties, bool symred,
bool allowModularisation, std::set<size_t> 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<ValueType> 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<storm::models::sparse::Model<ValueType>> model = buildModelViaComposition(dft, properties, symred, true, relevantEvents);
// Model checking
std::vector<ValueType> 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 ValueType>
typename DFTModelChecker<ValueType>::dft_results DFTModelChecker<ValueType>::checkHelper(storm::storage::DFT<ValueType> const& dft, property_vector const& properties,
bool symred, bool allowModularisation, std::set<size_t> const& relevantEvents,
bool allowDCForRelevantEvents, double approximationError,
storm::builder::ApproximationHeuristic approximationHeuristic,
bool eliminateChains, storm::transformer::EliminationLabelBehavior labelBehavior) {
STORM_LOG_TRACE("Check helper called");
std::vector<storm::storage::DFT<ValueType>> 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<storm::storage::DFTVot<ValueType> 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<ValueType> 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<ValueType>(ftResults[0]));
}
// Combine modularisation results
STORM_LOG_TRACE("Combining all results... K=" << nrK << "; M=" << nrM << "; invResults="
<< (invResults ? "On" : "Off"));
ValueType result = storm::utility::zero<ValueType>();
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<uint64_t>(cK));
do {
STORM_LOG_TRACE("Permutation=" << permutation);
ValueType permResult = storm::utility::one<ValueType>();
for (size_t i = 0; i < res.size(); ++i) {
if (permutation & (1ul << i)) {
permResult *= res[i];
} else {
permResult *= storm::utility::one<ValueType>() - 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<ValueType>() - result;
}
results.push_back(result);
}
}
return results;
} else {
// No modularisation was possible
return checkDFT(dft, properties, symred, relevantEvents, allowDCForRelevantEvents, approximationError,
approximationHeuristic, eliminateChains, labelBehavior);
}
}
template<typename ValueType>
std::shared_ptr<storm::models::sparse::Ctmc<ValueType>>
DFTModelChecker<ValueType>::buildModelViaComposition(storm::storage::DFT<ValueType> const &dft,
property_vector const &properties, bool symred,
bool allowModularisation,
std::set<size_t> const &relevantEvents,
bool allowDCForRelevantEvents) {
// TODO: use approximation?
STORM_LOG_TRACE("Build model via composition");
std::vector<storm::storage::DFT<ValueType>> 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<storm::models::sparse::Ctmc<ValueType>> composedModel;
for (auto const ft : dfts) {
STORM_LOG_DEBUG("Building Model via parallel composition...");
explorationTimer.start();
ft.setRelevantEvents(relevantEvents, allowDCForRelevantEvents);
// Find symmetries
std::map<size_t, std::vector<std::vector<size_t>>> 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<ValueType> builder(ft, symmetries);
builder.buildModel(0, 0.0);
std::shared_ptr<storm::models::sparse::Model<ValueType>> 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<storm::models::sparse::Ctmc<ValueType>> ctmc = model->template as<storm::models::sparse::Ctmc<ValueType>>();
// Apply bisimulation to new CTMC
bisimulationTimer.start();
ctmc = storm::api::performDeterministicSparseBisimulationMinimization<storm::models::sparse::Ctmc<ValueType>>(
ctmc, properties,
storm::storage::BisimulationType::Weak)->template as<storm::models::sparse::Ctmc<ValueType>>();
bisimulationTimer.stop();
if (firstTime) {
composedModel = ctmc;
firstTime = false;
} else {
composedModel = storm::builder::ParallelCompositionBuilder<ValueType>::compose(composedModel,
ctmc, isAnd);
}
// Apply bisimulation to parallel composition
bisimulationTimer.start();
composedModel = storm::api::performDeterministicSparseBisimulationMinimization<storm::models::sparse::Ctmc<ValueType>>(
composedModel, properties,
storm::storage::BisimulationType::Weak)->template as<storm::models::sparse::Ctmc<ValueType>>();
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<size_t, std::vector<std::vector<size_t>>> 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<ValueType> builder(dft, symmetries);
builder.buildModel(0, 0.0);
std::shared_ptr<storm::models::sparse::Model<ValueType>> 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<storm::models::sparse::Ctmc<ValueType>>();
}
}
template<typename ValueType>
typename DFTModelChecker<ValueType>::dft_results
DFTModelChecker<ValueType>::checkDFT(storm::storage::DFT<ValueType> const &dft,
property_vector const &properties, bool symred,
std::set<size_t> const &relevantEvents, bool allowDCForRelevantEvents,
double approximationError,
storm::builder::ApproximationHeuristic approximationHeuristic,
bool eliminateChains, storm::transformer::EliminationLabelBehavior labelBehavior) {
explorationTimer.start();
auto ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
auto dftIOSettings = storm::settings::getModule<storm::settings::modules::DftIOSettings>();
dft.setRelevantEvents(relevantEvents, allowDCForRelevantEvents);
// Find symmetries
std::map<size_t, std::vector<std::vector<size_t>>> 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<ValueType> comparator;
// Build approximate Markov Automata for lower and upper bound
approximation_result approxResult = std::make_pair(storm::utility::zero<ValueType>(),
storm::utility::zero<ValueType>());
std::shared_ptr<storm::models::sparse::Model<ValueType>> model;
std::vector<ValueType> newResult;
storm::builder::ExplicitDFTModelBuilder<ValueType> builder(dft, symmetries);
// TODO: compute approximation for all properties simultaneously?
std::shared_ptr<const storm::logic::Formula> 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<std::string> 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<ValueType>(approxResult.first) &&
!storm::utility::isInfinity<ValueType>(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::settings::modules::IOSettings>();
STORM_LOG_DEBUG("Building Model...");
storm::builder::ExplicitDFTModelBuilder<ValueType> builder(dft, symmetries);
builder.buildModel(0, 0.0);
std::shared_ptr<storm::models::sparse::Model<ValueType>> model = builder.getModel();
if (eliminateChains && model->isOfType(storm::models::ModelType::MarkovAutomaton)) {
auto ma = std::static_pointer_cast<storm::models::sparse::MarkovAutomaton<ValueType>>(model);
model = storm::transformer::NonMarkovianChainTransformer<ValueType>::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<std::string> 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<ValueType> resultsValue = checkModel(model, properties);
dft_results results;
for (ValueType result : resultsValue) {
results.push_back(result);
}
return results;
}
}
template<typename ValueType>
std::vector<ValueType>
DFTModelChecker<ValueType>::checkModel(std::shared_ptr<storm::models::sparse::Model<ValueType>> &model,
property_vector const &properties) {
// Bisimulation
if (model->isOfType(storm::models::ModelType::Ctmc) &&
storm::settings::getModule<storm::settings::modules::GeneralSettings>().isBisimulationSet()) {
bisimulationTimer.start();
STORM_LOG_DEBUG("Bisimulation...");
model = storm::api::performDeterministicSparseBisimulationMinimization<storm::models::sparse::Ctmc<ValueType>>(
model->template as<storm::models::sparse::Ctmc<ValueType>>(), properties,
storm::storage::BisimulationType::Weak)->template as<storm::models::sparse::Ctmc<ValueType>>();
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<ValueType> 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<storm::modelchecker::CheckResult> result(
storm::api::verifyWithSparseEngine<ValueType>(model, storm::api::createTask<ValueType>(property,true)));
if (result) {
result->filter(storm::modelchecker::ExplicitQualitativeCheckResult(model->getInitialStates()));
ValueType resultValue = result->asExplicitQuantitativeCheckResult<ValueType>().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<ValueType>());
}
//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<typename ValueType>
bool DFTModelChecker<ValueType>::isApproximationSufficient(ValueType, ValueType, double, bool) {
STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Approximation works only for double.");
}
template<>
bool DFTModelChecker<double>::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<typename ValueType>
void DFTModelChecker<ValueType>::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<typename ValueType>
void DFTModelChecker<ValueType>::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<std::ostream&> stream(os);
boost::apply_visitor(ResultOutputVisitor(), result, stream);
}
os << "]" << std::endl;
}
template
class DFTModelChecker<double>;
#ifdef STORM_HAVE_CARL
template
class DFTModelChecker<storm::RationalFunction>;
#endif
}
}