#include "src/modelchecker/csl/HybridCtmcCslModelChecker.h" #include "src/modelchecker/csl/SparseCtmcCslModelChecker.h" #include "src/modelchecker/prctl/HybridDtmcPrctlModelChecker.h" #include "src/storage/dd/CuddOdd.h" #include "src/utility/macros.h" #include "src/utility/graph.h" #include "src/modelchecker/results/SymbolicQualitativeCheckResult.h" #include "src/modelchecker/results/SymbolicQuantitativeCheckResult.h" #include "src/modelchecker/results/HybridQuantitativeCheckResult.h" #include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "src/exceptions/InvalidStateException.h" #include "src/exceptions/InvalidPropertyException.h" namespace storm { namespace modelchecker { template HybridCtmcCslModelChecker::HybridCtmcCslModelChecker(storm::models::symbolic::Ctmc const& model) : SymbolicPropositionalModelChecker(model), linearEquationSolverFactory(new storm::utility::solver::LinearEquationSolverFactory()) { // Intentionally left empty. } template HybridCtmcCslModelChecker::HybridCtmcCslModelChecker(storm::models::symbolic::Ctmc const& model, std::unique_ptr>&& linearEquationSolverFactory) : SymbolicPropositionalModelChecker(model), linearEquationSolverFactory(std::move(linearEquationSolverFactory)) { // Intentionally left empty. } template bool HybridCtmcCslModelChecker::canHandle(storm::logic::Formula const& formula) const { return formula.isCslStateFormula() || formula.isCslPathFormula() || formula.isRewardPathFormula(); } template storm::dd::Add HybridCtmcCslModelChecker::computeProbabilityMatrix(storm::models::symbolic::Model const& model, storm::dd::Add const& rateMatrix, storm::dd::Add const& exitRateVector) { return rateMatrix / exitRateVector; } template std::unique_ptr HybridCtmcCslModelChecker::computeUntilProbabilities(storm::logic::UntilFormula const& pathFormula, bool qualitative, boost::optional const& optimalityType) { std::unique_ptr leftResultPointer = this->check(pathFormula.getLeftSubformula()); std::unique_ptr rightResultPointer = this->check(pathFormula.getRightSubformula()); SymbolicQualitativeCheckResult const& leftResult = leftResultPointer->asSymbolicQualitativeCheckResult(); SymbolicQualitativeCheckResult const& rightResult = rightResultPointer->asSymbolicQualitativeCheckResult(); return HybridDtmcPrctlModelChecker::computeUntilProbabilitiesHelper(this->getModel(), this->computeProbabilityMatrix(this->getModel(), this->getModel().getTransitionMatrix(), this->getModel().getExitRateVector()), leftResult.getTruthValuesVector(), rightResult.getTruthValuesVector(), qualitative, *this->linearEquationSolverFactory); } template std::unique_ptr HybridCtmcCslModelChecker::computeNextProbabilities(storm::logic::NextFormula const& pathFormula, bool qualitative, boost::optional const& optimalityType) { std::unique_ptr subResultPointer = this->check(pathFormula.getSubformula()); SymbolicQualitativeCheckResult const& subResult = subResultPointer->asSymbolicQualitativeCheckResult(); return std::unique_ptr(new SymbolicQuantitativeCheckResult(this->getModel().getReachableStates(), HybridDtmcPrctlModelChecker::computeNextProbabilitiesHelper(this->getModel(), this->computeProbabilityMatrix(this->getModel(), this->getModel().getTransitionMatrix(), this->getModel().getExitRateVector()), subResult.getTruthValuesVector()))); } template storm::models::symbolic::Ctmc const& HybridCtmcCslModelChecker::getModel() const { return this->template getModelAs>(); } template storm::dd::Add HybridCtmcCslModelChecker::computeUniformizedMatrix(storm::models::symbolic::Model const& model, storm::dd::Add const& transitionMatrix, storm::dd::Add const& exitRateVector, storm::dd::Bdd const& maybeStates, ValueType uniformizationRate) { STORM_LOG_DEBUG("Computing uniformized matrix using uniformization rate " << uniformizationRate << "."); STORM_LOG_DEBUG("Keeping " << maybeStates.getNonZeroCount() << " rows."); // Cut all non-maybe rows/columns from the transition matrix. storm::dd::Add uniformizedMatrix = transitionMatrix * maybeStates.toAdd() * maybeStates.swapVariables(model.getRowColumnMetaVariablePairs()).toAdd(); // Now perform the uniformization. uniformizedMatrix = uniformizedMatrix / model.getManager().getConstant(uniformizationRate); storm::dd::Add diagonal = model.getRowColumnIdentity() * maybeStates.toAdd(); storm::dd::Add diagonalOffset = diagonal; diagonalOffset -= diagonal * (exitRateVector / model.getManager().getConstant(uniformizationRate)); uniformizedMatrix += diagonalOffset; return uniformizedMatrix; } template std::unique_ptr HybridCtmcCslModelChecker::computeReachabilityRewards(storm::logic::ReachabilityRewardFormula const& rewardPathFormula, bool qualitative, boost::optional const& optimalityType) { std::unique_ptr subResultPointer = this->check(rewardPathFormula.getSubformula()); SymbolicQualitativeCheckResult const& subResult = subResultPointer->asSymbolicQualitativeCheckResult(); boost::optional> modifiedStateRewardVector; if (this->getModel().hasStateRewards()) { modifiedStateRewardVector = this->getModel().getStateRewardVector() / this->getModel().getExitRateVector(); } return HybridDtmcPrctlModelChecker::computeReachabilityRewardsHelper(this->getModel(), this->computeProbabilityMatrix(this->getModel(), this->getModel().getTransitionMatrix(), this->getModel().getExitRateVector()), modifiedStateRewardVector, this->getModel().getOptionalTransitionRewardMatrix(), subResult.getTruthValuesVector(), *linearEquationSolverFactory, qualitative); } template std::unique_ptr HybridCtmcCslModelChecker::computeBoundedUntilProbabilities(storm::logic::BoundedUntilFormula const& pathFormula, bool qualitative, boost::optional const& optimalityType) { std::unique_ptr leftResultPointer = this->check(pathFormula.getLeftSubformula()); std::unique_ptr rightResultPointer = this->check(pathFormula.getRightSubformula()); SymbolicQualitativeCheckResult const& leftResult = leftResultPointer->asSymbolicQualitativeCheckResult(); SymbolicQualitativeCheckResult const& rightResult = rightResultPointer->asSymbolicQualitativeCheckResult(); double lowerBound = 0; double upperBound = 0; if (!pathFormula.hasDiscreteTimeBound()) { std::pair const& intervalBounds = pathFormula.getIntervalBounds(); lowerBound = intervalBounds.first; upperBound = intervalBounds.second; } else { upperBound = pathFormula.getDiscreteTimeBound(); } return this->computeBoundedUntilProbabilitiesHelper(leftResult.getTruthValuesVector(), rightResult.getTruthValuesVector(), this->getModel().getExitRateVector(), qualitative, lowerBound, upperBound); } template std::unique_ptr HybridCtmcCslModelChecker::computeBoundedUntilProbabilitiesHelper(storm::dd::Bdd const& phiStates, storm::dd::Bdd const& psiStates, storm::dd::Add const& exitRates, bool qualitative, double lowerBound, double upperBound) const { // If the time bounds are [0, inf], we rather call untimed reachability. storm::utility::ConstantsComparator comparator; if (comparator.isZero(lowerBound) && comparator.isInfinity(upperBound)) { return HybridDtmcPrctlModelChecker::computeUntilProbabilitiesHelper(this->getModel(), this->computeProbabilityMatrix(this->getModel(), this->getModel().getTransitionMatrix(), this->getModel().getExitRateVector()), phiStates, psiStates, qualitative, *this->linearEquationSolverFactory); } // From this point on, we know that we have to solve a more complicated problem [t, t'] with either t != 0 // or t' != inf. // If we identify the states that have probability 0 of reaching the target states, we can exclude them from the // further computations. storm::dd::Bdd statesWithProbabilityGreater0 = storm::utility::graph::performProbGreater0(this->getModel(), this->getModel().getTransitionMatrix().notZero(), phiStates, psiStates); STORM_LOG_INFO("Found " << statesWithProbabilityGreater0.getNonZeroCount() << " states with probability greater 0."); storm::dd::Bdd statesWithProbabilityGreater0NonPsi = statesWithProbabilityGreater0 && !psiStates; STORM_LOG_INFO("Found " << statesWithProbabilityGreater0NonPsi.getNonZeroCount() << " 'maybe' states."); if (!statesWithProbabilityGreater0NonPsi.isZero()) { if (comparator.isZero(upperBound)) { // In this case, the interval is of the form [0, 0]. return std::unique_ptr(new SymbolicQuantitativeCheckResult(this->getModel().getReachableStates(), psiStates.toAdd())); } else { if (comparator.isZero(lowerBound)) { // In this case, the interval is of the form [0, t]. // Note that this excludes [0, inf] since this is untimed reachability and we considered this case earlier. // Find the maximal rate of all 'maybe' states to take it as the uniformization rate. ValueType uniformizationRate = 1.02 * (statesWithProbabilityGreater0NonPsi.toAdd() * exitRates).getMax(); STORM_LOG_THROW(uniformizationRate > 0, storm::exceptions::InvalidStateException, "The uniformization rate must be positive."); // Compute the uniformized matrix. storm::dd::Add uniformizedMatrix = this->computeUniformizedMatrix(this->getModel(), this->getModel().getTransitionMatrix(), exitRates, statesWithProbabilityGreater0NonPsi, uniformizationRate); // Compute the vector that is to be added as a compensation for removing the absorbing states. storm::dd::Add b = (statesWithProbabilityGreater0NonPsi.toAdd() * this->getModel().getTransitionMatrix() * psiStates.swapVariables(this->getModel().getRowColumnMetaVariablePairs()).toAdd()).sumAbstract(this->getModel().getColumnVariables()) / this->getModel().getManager().getConstant(uniformizationRate); // Create an ODD for the translation to an explicit representation. storm::dd::Odd odd(statesWithProbabilityGreater0NonPsi); // Convert the symbolic parts to their explicit representation. storm::storage::SparseMatrix explicitUniformizedMatrix = uniformizedMatrix.toMatrix(odd, odd); std::vector explicitB = b.template toVector(odd); // Finally compute the transient probabilities. std::vector values(statesWithProbabilityGreater0NonPsi.getNonZeroCount(), storm::utility::zero()); std::vector subresult = SparseCtmcCslModelChecker::computeTransientProbabilities(explicitUniformizedMatrix, &explicitB, upperBound, uniformizationRate, values, *this->linearEquationSolverFactory); return std::unique_ptr(new HybridQuantitativeCheckResult(this->getModel().getReachableStates(), (psiStates || !statesWithProbabilityGreater0) && this->getModel().getReachableStates(), psiStates.toAdd(), statesWithProbabilityGreater0NonPsi, odd, subresult)); } else if (comparator.isInfinity(upperBound)) { // In this case, the interval is of the form [t, inf] with t != 0. // Start by computing the (unbounded) reachability probabilities of reaching psi states while // staying in phi states. std::unique_ptr unboundedResult = HybridDtmcPrctlModelChecker::computeUntilProbabilitiesHelper(this->getModel(), this->computeProbabilityMatrix(this->getModel(), this->getModel().getTransitionMatrix(), this->getModel().getExitRateVector()), phiStates, psiStates, qualitative, *this->linearEquationSolverFactory); // Compute the set of relevant states. storm::dd::Bdd relevantStates = statesWithProbabilityGreater0 && phiStates; // Filter the unbounded result such that it only contains values for the relevant states. unboundedResult->filter(SymbolicQualitativeCheckResult(this->getModel().getReachableStates(), relevantStates)); // Build an ODD for the relevant states. storm::dd::Odd odd(relevantStates); std::vector result; if (unboundedResult->isHybridQuantitativeCheckResult()) { std::unique_ptr explicitUnboundedResult = unboundedResult->asHybridQuantitativeCheckResult().toExplicitQuantitativeCheckResult(); result = std::move(explicitUnboundedResult->asExplicitQuantitativeCheckResult().getValueVector()); } else { STORM_LOG_THROW(unboundedResult->isSymbolicQuantitativeCheckResult(), storm::exceptions::InvalidStateException, "Expected check result of different type."); result = unboundedResult->asSymbolicQuantitativeCheckResult().getValueVector().template toVector(odd); } // Determine the uniformization rate for the transient probability computation. ValueType uniformizationRate = 1.02 * (relevantStates.toAdd() * exitRates).getMax(); // Compute the uniformized matrix. storm::dd::Add uniformizedMatrix = this->computeUniformizedMatrix(this->getModel(), this->getModel().getTransitionMatrix(), exitRates, relevantStates, uniformizationRate); storm::storage::SparseMatrix explicitUniformizedMatrix = uniformizedMatrix.toMatrix(odd, odd); // Compute the transient probabilities. result = SparseCtmcCslModelChecker::computeTransientProbabilities(explicitUniformizedMatrix, nullptr, lowerBound, uniformizationRate, result, *this->linearEquationSolverFactory); return std::unique_ptr(new HybridQuantitativeCheckResult(this->getModel().getReachableStates(), !relevantStates && this->getModel().getReachableStates(), this->getModel().getManager().getAddZero(), relevantStates, odd, result)); } else { // In this case, the interval is of the form [t, t'] with t != 0 and t' != inf. if (lowerBound != upperBound) { // In this case, the interval is of the form [t, t'] with t != 0, t' != inf and t != t'. // Find the maximal rate of all 'maybe' states to take it as the uniformization rate. ValueType uniformizationRate = 1.02 * (statesWithProbabilityGreater0NonPsi.toAdd() * exitRates).getMax(); STORM_LOG_THROW(uniformizationRate > 0, storm::exceptions::InvalidStateException, "The uniformization rate must be positive."); // Compute the (first) uniformized matrix. storm::dd::Add uniformizedMatrix = this->computeUniformizedMatrix(this->getModel(), this->getModel().getTransitionMatrix(), exitRates, statesWithProbabilityGreater0NonPsi, uniformizationRate); // Create the one-step vector. storm::dd::Add b = (statesWithProbabilityGreater0NonPsi.toAdd() * this->getModel().getTransitionMatrix() * psiStates.swapVariables(this->getModel().getRowColumnMetaVariablePairs()).toAdd()).sumAbstract(this->getModel().getColumnVariables()) / this->getModel().getManager().getConstant(uniformizationRate); // Build an ODD for the relevant states and translate the symbolic parts to their explicit representation. storm::dd::Odd odd = storm::dd::Odd(statesWithProbabilityGreater0NonPsi); storm::storage::SparseMatrix explicitUniformizedMatrix = uniformizedMatrix.toMatrix(odd, odd); std::vector explicitB = b.template toVector(odd); // Compute the transient probabilities. std::vector values(statesWithProbabilityGreater0NonPsi.getNonZeroCount(), storm::utility::zero()); std::vector subResult = SparseCtmcCslModelChecker::computeTransientProbabilities(explicitUniformizedMatrix, &explicitB, upperBound - lowerBound, uniformizationRate, values, *this->linearEquationSolverFactory); // Transform the explicit result to a hybrid check result, so we can easily convert it to // a symbolic qualitative format. HybridQuantitativeCheckResult hybridResult(this->getModel().getReachableStates(), psiStates || (!statesWithProbabilityGreater0 && this->getModel().getReachableStates()), psiStates.toAdd(), statesWithProbabilityGreater0NonPsi, odd, subResult); // Compute the set of relevant states. storm::dd::Bdd relevantStates = statesWithProbabilityGreater0 && phiStates; // Filter the unbounded result such that it only contains values for the relevant states. hybridResult.filter(SymbolicQualitativeCheckResult(this->getModel().getReachableStates(), relevantStates)); // Build an ODD for the relevant states. odd = storm::dd::Odd(relevantStates); std::unique_ptr explicitResult = hybridResult.toExplicitQuantitativeCheckResult(); std::vector newSubresult = std::move(explicitResult->asExplicitQuantitativeCheckResult().getValueVector()); // Then compute the transient probabilities of being in such a state after t time units. For this, // we must re-uniformize the CTMC, so we need to compute the second uniformized matrix. uniformizationRate = 1.02 * (relevantStates.toAdd() * exitRates).getMax(); STORM_LOG_THROW(uniformizationRate > 0, storm::exceptions::InvalidStateException, "The uniformization rate must be positive."); // If the lower and upper bounds coincide, we have only determined the relevant states at this // point, but we still need to construct the starting vector. if (lowerBound == upperBound) { odd = storm::dd::Odd(relevantStates); newSubresult = psiStates.toAdd().template toVector(odd); } // Finally, we compute the second set of transient probabilities. uniformizedMatrix = this->computeUniformizedMatrix(this->getModel(), this->getModel().getTransitionMatrix(), exitRates, relevantStates, uniformizationRate); explicitUniformizedMatrix = uniformizedMatrix.toMatrix(odd, odd); newSubresult = SparseCtmcCslModelChecker::computeTransientProbabilities(explicitUniformizedMatrix, nullptr, lowerBound, uniformizationRate, newSubresult, *this->linearEquationSolverFactory); return std::unique_ptr(new HybridQuantitativeCheckResult(this->getModel().getReachableStates(), !relevantStates && this->getModel().getReachableStates(), this->getModel().getManager().getAddZero(), relevantStates, odd, newSubresult)); } else { // In this case, the interval is of the form [t, t] with t != 0, t != inf. // Build an ODD for the relevant states. storm::dd::Odd odd = storm::dd::Odd(statesWithProbabilityGreater0); std::vector newSubresult = psiStates.toAdd().template toVector(odd); // Then compute the transient probabilities of being in such a state after t time units. For this, // we must re-uniformize the CTMC, so we need to compute the second uniformized matrix. ValueType uniformizationRate = 1.02 * (statesWithProbabilityGreater0.toAdd() * exitRates).getMax(); STORM_LOG_THROW(uniformizationRate > 0, storm::exceptions::InvalidStateException, "The uniformization rate must be positive."); // Finally, we compute the second set of transient probabilities. storm::dd::Add uniformizedMatrix = this->computeUniformizedMatrix(this->getModel(), this->getModel().getTransitionMatrix(), exitRates, statesWithProbabilityGreater0, uniformizationRate); storm::storage::SparseMatrix explicitUniformizedMatrix = uniformizedMatrix.toMatrix(odd, odd); newSubresult = SparseCtmcCslModelChecker::computeTransientProbabilities(explicitUniformizedMatrix, nullptr, lowerBound, uniformizationRate, newSubresult, *this->linearEquationSolverFactory); return std::unique_ptr(new HybridQuantitativeCheckResult(this->getModel().getReachableStates(), !statesWithProbabilityGreater0 && this->getModel().getReachableStates(), this->getModel().getManager().getAddZero(), statesWithProbabilityGreater0, odd, newSubresult)); } } } } else { return std::unique_ptr(new SymbolicQuantitativeCheckResult(this->getModel().getReachableStates(), psiStates.toAdd())); } } template std::unique_ptr HybridCtmcCslModelChecker::computeInstantaneousRewards(storm::logic::InstantaneousRewardFormula const& rewardPathFormula, bool qualitative, boost::optional const& optimalityType) { return this->computeInstantaneousRewardsHelper(rewardPathFormula.getContinuousTimeBound()); } template std::unique_ptr HybridCtmcCslModelChecker::computeInstantaneousRewardsHelper(double timeBound) const { // Only compute the result if the model has a state-based reward this->getModel(). STORM_LOG_THROW(this->getModel().hasStateRewards(), storm::exceptions::InvalidPropertyException, "Missing reward model for formula. Skipping formula."); // Create ODD for the translation. storm::dd::Odd odd(this->getModel().getReachableStates()); // Initialize result to state rewards of the this->getModel(). std::vector result = this->getModel().getStateRewardVector().template toVector(odd); // If the time-bound is not zero, we need to perform a transient analysis. if (timeBound > 0) { ValueType uniformizationRate = 1.02 * this->getModel().getExitRateVector().getMax(); STORM_LOG_THROW(uniformizationRate > 0, storm::exceptions::InvalidStateException, "The uniformization rate must be positive."); storm::dd::Add uniformizedMatrix = this->computeUniformizedMatrix(this->getModel(), this->getModel().getTransitionMatrix(), this->getModel().getExitRateVector(), this->getModel().getReachableStates(), uniformizationRate); storm::storage::SparseMatrix explicitUniformizedMatrix = uniformizedMatrix.toMatrix(odd, odd); result = SparseCtmcCslModelChecker::computeTransientProbabilities(explicitUniformizedMatrix, nullptr, timeBound, uniformizationRate, result, *this->linearEquationSolverFactory); } return std::unique_ptr(new HybridQuantitativeCheckResult(this->getModel().getReachableStates(), this->getModel().getManager().getBddZero(), this->getModel().getManager().getAddZero(), this->getModel().getReachableStates(), odd, result)); } template std::unique_ptr HybridCtmcCslModelChecker::computeCumulativeRewards(storm::logic::CumulativeRewardFormula const& rewardPathFormula, bool qualitative, boost::optional const& optimalityType) { return this->computeCumulativeRewardsHelper(rewardPathFormula.getContinuousTimeBound()); } template std::unique_ptr HybridCtmcCslModelChecker::computeCumulativeRewardsHelper(double timeBound) const { // Only compute the result if the model has a state-based reward this->getModel(). STORM_LOG_THROW(this->getModel().hasStateRewards() || this->getModel().hasTransitionRewards(), storm::exceptions::InvalidPropertyException, "Missing reward model for formula. Skipping formula."); // If the time bound is zero, the result is the constant zero vector. if (timeBound == 0) { return std::unique_ptr(new SymbolicQuantitativeCheckResult(this->getModel().getReachableStates(), this->getModel().getManager().getAddZero())); } // Otherwise, we need to perform some computations. // Start with the uniformization. ValueType uniformizationRate = 1.02 * this->getModel().getExitRateVector().getMax(); STORM_LOG_THROW(uniformizationRate > 0, storm::exceptions::InvalidStateException, "The uniformization rate must be positive."); // Create ODD for the translation. storm::dd::Odd odd(this->getModel().getReachableStates()); // Compute the uniformized matrix. storm::dd::Add uniformizedMatrix = this->computeUniformizedMatrix(this->getModel(), this->getModel().getTransitionMatrix(), this->getModel().getExitRateVector(), this->getModel().getReachableStates(), uniformizationRate); storm::storage::SparseMatrix explicitUniformizedMatrix = uniformizedMatrix.toMatrix(odd, odd); // Then compute the state reward vector to use in the computation. storm::dd::Add totalRewardVector = this->getModel().hasStateRewards() ? this->getModel().getStateRewardVector() : this->getModel().getManager().getAddZero(); if (this->getModel().hasTransitionRewards()) { totalRewardVector += (this->getModel().getTransitionMatrix() * this->getModel().getTransitionRewardMatrix()).sumAbstract(this->getModel().getColumnVariables()); } std::vector explicitTotalRewardVector = totalRewardVector.template toVector(odd); // Finally, compute the transient probabilities. std::vector result = SparseCtmcCslModelChecker::template computeTransientProbabilities(explicitUniformizedMatrix, nullptr, timeBound, uniformizationRate, explicitTotalRewardVector, *this->linearEquationSolverFactory); return std::unique_ptr(new HybridQuantitativeCheckResult(this->getModel().getReachableStates(), this->getModel().getManager().getBddZero(), this->getModel().getManager().getAddZero(), this->getModel().getReachableStates(), std::move(odd), std::move(result))); } template std::unique_ptr HybridCtmcCslModelChecker::computeLongRunAverage(storm::logic::StateFormula const& stateFormula, bool qualitative, boost::optional const& optimalityType) { std::unique_ptr subResultPointer = this->check(stateFormula); SymbolicQualitativeCheckResult const& subResult = subResultPointer->asSymbolicQualitativeCheckResult(); storm::dd::Add probabilityMatrix = this->computeProbabilityMatrix(this->getModel(), this->getModel().getTransitionMatrix(), this->getModel().getExitRateVector()); // Create ODD for the translation. storm::dd::Odd odd(this->getModel().getReachableStates()); storm::storage::SparseMatrix explicitProbabilityMatrix = probabilityMatrix.toMatrix(odd, odd); std::vector explicitExitRateVector = this->getModel().getExitRateVector().template toVector(odd); std::vector result = SparseCtmcCslModelChecker::computeLongRunAverageHelper(explicitProbabilityMatrix, subResult.getTruthValuesVector().toVector(odd), &explicitExitRateVector, qualitative, *this->linearEquationSolverFactory); return std::unique_ptr(new HybridQuantitativeCheckResult(this->getModel().getReachableStates(), this->getModel().getManager().getBddZero(), this->getModel().getManager().getAddZero(), this->getModel().getReachableStates(), std::move(odd), std::move(result))); } // Explicitly instantiate the model checker. template class HybridCtmcCslModelChecker; } // namespace modelchecker } // namespace storm