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.

572 lines
22 KiB

  1. /*
  2. * PathBasedSubsystemGenerator.h
  3. *
  4. * Created on: 11.10.2013
  5. * Author: Manuel Sascha Weiand
  6. */
  7. #ifndef STORM_COUNTEREXAMPLES_PATHBASEDSUBSYSTEMGENERATOR_H_
  8. #define STORM_COUNTEREXAMPLES_PATHBASEDSUBSYSTEMGENERATOR_H_
  9. #include "src/models/Dtmc.h"
  10. #include "src/models/AbstractModel.h"
  11. #include "src/modelchecker/prctl/SparseDtmcPrctlModelChecker.h"
  12. #include "src/solver/GmmxxLinearEquationSolver.h"
  13. #include "src/storage/BitVector.h"
  14. #include "src/storage/SparseMatrix.h"
  15. #include "log4cplus/logger.h"
  16. #include "log4cplus/loggingmacros.h"
  17. extern log4cplus::Logger logger;
  18. namespace storm {
  19. namespace counterexamples {
  20. template <class T>
  21. class PathBasedSubsystemGenerator {
  22. private:
  23. template <class CompareType>
  24. class CompareStates {
  25. public:
  26. bool operator()(const std::pair<uint_fast64_t, CompareType>& s1, const std::pair<uint_fast64_t, CompareType>& s2) {
  27. return s1.second < s2.second;
  28. }
  29. };
  30. public:
  31. /*!
  32. *
  33. */
  34. static void computeShortestDistances(storm::storage::SparseMatrix<T> const& transMat, storm::storage::BitVector& subSysStates, storm::storage::BitVector& terminalStates, storm::storage::BitVector& allowedStates, std::vector<std::pair<uint_fast64_t, T>>& distances) {
  35. std::multiset<std::pair<uint_fast64_t, T>, CompareStates<T> > activeSet;
  36. //std::priority_queue<std::pair<uint_fast64_t, T*>, std::vector<std::pair<uint_fast64_t, T*>>, CompareStates<T> > queue;
  37. // resize and init distances
  38. const std::pair<uint_fast64_t, T> initDistances(0, (T) -1);
  39. distances.resize(transMat.getColumnCount(), initDistances);
  40. //since gcc 4.7.2 does not implement std::set::emplace(), insert is used.
  41. std::pair<uint_fast64_t, T> state;
  42. // First store all transitions from initial states
  43. // Also save all found initial states in array of discovered states.
  44. for(auto init : subSysStates) {
  45. //use init state only if it is allowed
  46. if(allowedStates.get(init)) {
  47. if(terminalStates.get(init)) {
  48. // it's an init -> target search
  49. // save target state as discovered and get it's outgoing transitions
  50. distances[init].first = init;
  51. distances[init].second = (T) 1;
  52. }
  53. for(auto const& trans : transMat.getRow(init)) {
  54. //save transition only if it's no 'virtual transition of prob 0 and it doesn't go from init state to init state.
  55. if(trans.getValue() != (T) 0 && !subSysStates.get(trans.getColumn())) {
  56. //new state?
  57. if(distances[trans.getColumn()].second == (T) -1) {
  58. distances[trans.getColumn()].first = init;
  59. distances[trans.getColumn()].second = trans.getValue();
  60. activeSet.insert(std::pair<uint_fast64_t, T>(trans.getColumn(), distances[trans.getColumn()].second));
  61. }
  62. else if(distances[trans.getColumn()].second < trans.getValue()){
  63. //This state has already been discovered
  64. //And the distance can be improved by using this transition.
  65. //find state in set, remove it, reenter it with new and correct values.
  66. auto range = activeSet.equal_range(std::pair<uint_fast64_t, T>(trans.getColumn(), distances[trans.getColumn()].second));
  67. for(;range.first != range.second; range.first++) {
  68. if(trans.getColumn() == range.first->first) {
  69. activeSet.erase(range.first);
  70. break;
  71. }
  72. }
  73. distances[trans.getColumn()].first = init;
  74. distances[trans.getColumn()].second = trans.getValue();
  75. activeSet.insert(std::pair<uint_fast64_t, T>(trans.getColumn(), trans.getValue()));
  76. }
  77. }
  78. }
  79. }
  80. }
  81. LOG4CPLUS_DEBUG(logger, "Initialized.");
  82. //Now find the shortest distances to all states
  83. while(!activeSet.empty()) {
  84. // copy here since using a reference leads to segfault
  85. std::pair<uint_fast64_t, T> activeState = *(--activeSet.end());
  86. activeSet.erase(--activeSet.end());
  87. // If this is an initial state, do not consider its outgoing transitions, since all relevant ones have already been considered
  88. // Same goes for forbidden states since they may not be used on a path, except as last node.
  89. if(!subSysStates.get(activeState.first) && allowedStates.get(activeState.first)) {
  90. // Look at all neighbors
  91. for(auto const& trans : transMat.getRow(activeState.first)) {
  92. // Only consider the transition if it's not virtual
  93. if(trans.getValue() != (T) 0) {
  94. T distance = activeState.second * trans.getValue();
  95. //not discovered or initial terminal state
  96. if(distances[trans.getColumn()].second == (T)-1) {
  97. //New state discovered -> save it
  98. distances[trans.getColumn()].first = activeState.first;
  99. distances[trans.getColumn()].second = distance;
  100. // push newly discovered state into activeSet
  101. activeSet.insert(std::pair<uint_fast64_t, T>(trans.getColumn(), distance));
  102. }
  103. else if(distances[trans.getColumn()].second < distance) {
  104. //This state has already been discovered
  105. //And the distance can be improved by using this transition.
  106. //find state in set, remove it, reenter it with new and correct values.
  107. auto range = activeSet.equal_range(std::pair<uint_fast64_t, T>(trans.getColumn(), distances[trans.getColumn()].second));
  108. for(;range.first != range.second; range.first++) {
  109. if(trans.getColumn() == range.first->first) {
  110. activeSet.erase(range.first);
  111. break;
  112. }
  113. }
  114. distances[trans.getColumn()].first = activeState.first;
  115. distances[trans.getColumn()].second = distance;
  116. activeSet.insert(std::pair<uint_fast64_t, T>(trans.getColumn(), distance));
  117. }
  118. }
  119. }
  120. }
  121. }
  122. LOG4CPLUS_DEBUG(logger, "Discovery done.");
  123. }
  124. /*!
  125. *
  126. */
  127. static void doDijkstraSearch(storm::storage::SparseMatrix<T> const& transMat, storm::storage::BitVector& subSysStates, storm::storage::BitVector& terminalStates, storm::storage::BitVector& allowedStates, std::vector<std::pair<uint_fast64_t, T>>& itDistances, std::vector<std::pair<uint_fast64_t, T>>& distances) {
  128. std::multiset<std::pair<uint_fast64_t, T>, CompareStates<T> > activeSet;
  129. // resize and init distances
  130. const std::pair<uint_fast64_t, T> initDistances(0, (T) -1);
  131. distances.resize(transMat.getColumnCount(), initDistances);
  132. //since gcc 4.7.2 does not implement std::set::emplace(), insert is used.
  133. std::pair<uint_fast64_t, T> state;
  134. // First store all transitions from initial states
  135. // Also save all found initial states in array of discovered states.
  136. for(auto init : subSysStates) {
  137. //use init state only if it is allowed
  138. if(allowedStates.get(init)) {
  139. if(terminalStates.get(init)) {
  140. // it's a subsys -> subsys search
  141. // ignore terminal state completely
  142. // (since any target state that is only reached by a path going through this state should not be reached)
  143. continue;
  144. }
  145. for(auto const& trans : transMat.getRow(init)) {
  146. //save transition only if it's no 'virtual transition of prob 0 and it doesn't go from init state to init state.
  147. if(trans.getValue() != (T) 0 && !subSysStates.get(trans.getColumn())) {
  148. //new state?
  149. if(distances[trans.getColumn()].second == (T) -1) {
  150. //for initialization of subsys -> subsys search use prob (init -> subsys state -> found state) instead of prob(subsys state -> found state)
  151. distances[trans.getColumn()].first = init;
  152. distances[trans.getColumn()].second = trans.getValue() * (itDistances[init].second == -1 ? 1 : itDistances[init].second);
  153. activeSet.insert(std::pair<uint_fast64_t, T>(trans.getColumn(), distances[trans.getColumn()].second));
  154. }
  155. else if(distances[trans.getColumn()].second < trans.getValue() * itDistances[init].second){
  156. //This state has already been discovered
  157. //And the distance can be improved by using this transition.
  158. //find state in set, remove it, reenter it with new and correct values.
  159. auto range = activeSet.equal_range(std::pair<uint_fast64_t, T>(trans.getColumn(), distances[trans.getColumn()].second));
  160. for(;range.first != range.second; range.first++) {
  161. if(trans.getColumn() == range.first->first) {
  162. activeSet.erase(range.first);
  163. break;
  164. }
  165. }
  166. //for initialization of subsys -> subsys search use prob (init -> subsys state -> found state) instead of prob(subsys state -> found state)
  167. distances[trans.getColumn()].first = init;
  168. distances[trans.getColumn()].second = trans.getValue() * (itDistances[init].second == -1 ? 1 : itDistances[init].second);
  169. activeSet.insert(std::pair<uint_fast64_t, T>(trans.getColumn(), trans.getValue()));
  170. }
  171. }
  172. }
  173. }
  174. }
  175. LOG4CPLUS_DEBUG(logger, "Initialized.");
  176. //Now find the shortest distances to all states
  177. while(!activeSet.empty()) {
  178. // copy here since using a reference leads to segfault
  179. std::pair<uint_fast64_t, T> activeState = *(--activeSet.end());
  180. activeSet.erase(--activeSet.end());
  181. // Always stop at first target/terminal state
  182. //if(terminalStates.get(activeState.getColumn()) || subSysStates.get(activeState.getColumn())) break;
  183. // If this is an initial state, do not consider its outgoing transitions, since all relevant ones have already been considered
  184. // Same goes for forbidden states since they may not be used on a path, except as last node.
  185. if(!subSysStates.get(activeState.first) && allowedStates.get(activeState.first)) {
  186. // Look at all neighbors
  187. for(auto const& trans : transMat.getRow(activeState.first)) {
  188. // Only consider the transition if it's not virtual
  189. if(trans.getValue() != (T) 0) {
  190. T distance = activeState.second * trans.getValue();
  191. //not discovered or initial terminal state
  192. if(distances[trans.getColumn()].second == (T)-1) {
  193. //New state discovered -> save it
  194. distances[trans.getColumn()].first = activeState.first;
  195. distances[trans.getColumn()].second = distance;
  196. // push newly discovered state into activeSet
  197. activeSet.insert(std::pair<uint_fast64_t, T>(trans.getColumn(), distance));
  198. }
  199. else if(distances[trans.getColumn()].second < distance) {
  200. //This state has already been discovered
  201. //And the distance can be improved by using this transition.
  202. //find state in set, remove it, reenter it with new and correct values.
  203. auto range = activeSet.equal_range(std::pair<uint_fast64_t, T>(trans.getColumn(), distances[trans.getColumn()].second));
  204. for(;range.first != range.second; range.first++) {
  205. if(trans.getColumn() == range.first->first) {
  206. activeSet.erase(range.first);
  207. break;
  208. }
  209. }
  210. distances[trans.getColumn()].first = activeState.first;
  211. distances[trans.getColumn()].second = distance;
  212. activeSet.insert(std::pair<uint_fast64_t, T>(trans.getColumn(), distance));
  213. }
  214. }
  215. }
  216. }
  217. }
  218. LOG4CPLUS_DEBUG(logger, "Discovery done.");
  219. }
  220. /*!
  221. *
  222. */
  223. static void findShortestPath(storm::storage::SparseMatrix<T> const& transMat, storm::storage::BitVector& subSysStates, storm::storage::BitVector& terminalStates, storm::storage::BitVector& allowedStates, std::vector<std::pair<uint_fast64_t, T>>& itDistances, std::vector<uint_fast64_t>& shortestPath, T& probability) {
  224. //Do Dijksta to find the shortest path from init states to all states
  225. std::vector<std::pair<uint_fast64_t, T>> distances;
  226. doDijkstraSearch(transMat, subSysStates, terminalStates, allowedStates, itDistances, distances);
  227. // Then get the shortest of them
  228. extractShortestPath(subSysStates, terminalStates, distances, itDistances, shortestPath, probability,false);
  229. }
  230. /*!
  231. * Only initialized distances vector!
  232. */
  233. static void extractShortestPath(storm::storage::BitVector& subSysStates, storm::storage::BitVector& terminalStates, std::vector<std::pair<uint_fast64_t, T>>& distances, std::vector<std::pair<uint_fast64_t, T>>& itDistances, std::vector<uint_fast64_t>& shortestPath, T& probability, bool stopAtFirstTarget = true, bool itSearch = false) {
  234. //Find terminal state of best distance
  235. uint_fast64_t bestIndex = 0;
  236. T bestValue = (T) 0;
  237. for(auto term : terminalStates) {
  238. //the terminal state might not have been found if it is in a system of forbidden states
  239. if(distances[term].second != -1 && distances[term].second > bestValue){
  240. bestIndex = term;
  241. bestValue = distances[term].second;
  242. //if set, stop since the first target that is not null was the only one found
  243. if(stopAtFirstTarget) break;
  244. }
  245. }
  246. if(!itSearch) {
  247. // it's a subSys->subSys search. So target states are terminals and subsys states
  248. for(auto term : subSysStates) {
  249. //the terminal state might not have been found if it is in a system of forbidden states
  250. if(distances[term].second != -1 && distances[term].second > bestValue){
  251. bestIndex = term;
  252. bestValue = distances[term].second;
  253. //if set, stop since the first target that is not null was the only one found
  254. if(stopAtFirstTarget) break;
  255. }
  256. }
  257. }
  258. //safety test: is the best terminal state viable?
  259. if(distances[bestIndex].second == (T) -1){
  260. shortestPath.push_back(bestIndex);
  261. probability = (T) 0;
  262. LOG4CPLUS_DEBUG(logger, "Terminal state not viable!");
  263. return;
  264. }
  265. // save the probability to reach the state via the shortest path
  266. probability = distances[bestIndex].second;
  267. //Reconstruct shortest path. Notice that the last state of the path might be an initState.
  268. //There might be a chain of terminal states with 1.0 transitions at the end of the path
  269. while(terminalStates.get(distances[bestIndex].first)) {
  270. bestIndex = distances[bestIndex].first;
  271. }
  272. LOG4CPLUS_DEBUG(logger, "Found best state: " << bestIndex);
  273. LOG4CPLUS_DEBUG(logger, "Value: " << bestValue);
  274. shortestPath.push_back(bestIndex);
  275. bestIndex = distances[bestIndex].first;
  276. while(!subSysStates.get(bestIndex)) {
  277. shortestPath.push_back(bestIndex);
  278. bestIndex = distances[bestIndex].first;
  279. }
  280. shortestPath.push_back(bestIndex);
  281. //At last compensate for the distance between init and source state
  282. probability = itSearch ? probability : probability / itDistances[bestIndex].first;
  283. }
  284. private:
  285. template <typename Type>
  286. struct transition {
  287. uint_fast64_t source;
  288. Type prob;
  289. uint_fast64_t target;
  290. };
  291. template <class CompareType>
  292. class CompareTransitions {
  293. public:
  294. bool operator()(transition<CompareType>& t1, transition<CompareType>& t2) {
  295. return t1.prob < t2.prob;
  296. }
  297. };
  298. public:
  299. /*!
  300. *
  301. */
  302. static storm::models::Dtmc<T> computeCriticalSubsystem(storm::models::Dtmc<T> & model, std::shared_ptr<storm::properties::prctl::AbstractStateFormula<T>> const & stateFormula) {
  303. //-------------------------------------------------------------
  304. // 1. Strip and handle formulas
  305. //-------------------------------------------------------------
  306. #ifdef BENCHMARK
  307. LOG4CPLUS_INFO(logger, "Formula: " << stateFormula.toString());
  308. #endif
  309. LOG4CPLUS_INFO(logger, "Start finding critical subsystem.");
  310. // make model checker
  311. // TODO: Implement and use generic Model Checker factory.
  312. storm::modelchecker::prctl::SparseDtmcPrctlModelChecker<T> modelCheck {model, new storm::solver::GmmxxLinearEquationSolver<T>()};
  313. // init bit vector to contain the subsystem
  314. storm::storage::BitVector subSys(model.getNumberOfStates());
  315. // Strip bound operator
  316. std::shared_ptr<storm::properties::prctl::ProbabilisticBoundOperator<T>> boundOperator = std::dynamic_pointer_cast<storm::properties::prctl::ProbabilisticBoundOperator<T>>(stateFormula);
  317. if(boundOperator == nullptr){
  318. LOG4CPLUS_ERROR(logger, "No path bound operator at formula root.");
  319. return model.getSubDtmc(subSys);
  320. }
  321. T bound = boundOperator->getBound();
  322. std::shared_ptr<storm::properties::prctl::AbstractPathFormula<T>> pathFormula = boundOperator->getChild();
  323. // get "init" labeled states
  324. storm::storage::BitVector initStates = model.getStates("init");
  325. //get real prob for formula
  326. logger.getAppender("mainFileAppender")->setThreshold(log4cplus::WARN_LOG_LEVEL);
  327. std::vector<T> trueProbs = pathFormula->check(modelCheck, false);
  328. logger.getAppender("mainFileAppender")->setThreshold(log4cplus::INFO_LOG_LEVEL);
  329. T trueProb = 0;
  330. for(auto index : initStates) {
  331. trueProb += trueProbs[index];
  332. }
  333. trueProb /= initStates.getNumberOfSetBits();
  334. //std::cout << "True Prob: " << trueProb << std::endl;
  335. // get allowed and target states
  336. storm::storage::BitVector allowedStates;
  337. storm::storage::BitVector targetStates;
  338. std::shared_ptr<storm::properties::prctl::Eventually<T>> eventually = std::dynamic_pointer_cast<storm::properties::prctl::Eventually<T>>(pathFormula);
  339. std::shared_ptr<storm::properties::prctl::Globally<T>> globally = std::dynamic_pointer_cast<storm::properties::prctl::Globally<T>>(pathFormula);
  340. std::shared_ptr<storm::properties::prctl::Until<T>> until = std::dynamic_pointer_cast<storm::properties::prctl::Until<T>>(pathFormula);
  341. if(eventually.get() != nullptr) {
  342. targetStates = eventually->getChild()->check(modelCheck);
  343. allowedStates = storm::storage::BitVector(targetStates.size(), true);
  344. }
  345. else if(globally.get() != nullptr){
  346. // eventually reaching a state without property visiting only states with property
  347. allowedStates = globally->getChild()->check(modelCheck);
  348. targetStates = storm::storage::BitVector(allowedStates);
  349. targetStates.complement();
  350. }
  351. else if(until.get() != nullptr) {
  352. allowedStates = until->getLeft()->check(modelCheck);
  353. targetStates = until->getRight()->check(modelCheck);
  354. }
  355. else {
  356. LOG4CPLUS_ERROR(logger, "Strange path formula. Can't decipher.");
  357. return model.getSubDtmc(subSys);
  358. }
  359. //-------------------------------------------------------------
  360. // 2. Precomputations for heuristics
  361. //-------------------------------------------------------------
  362. // estimate the path count using the models state count as well as the probability bound
  363. uint_fast8_t const minPrec = 10;
  364. uint_fast64_t const stateCount = model.getNumberOfStates();
  365. uint_fast64_t const stateEstimate = static_cast<uint_fast64_t>((static_cast<T>(stateCount)) * bound);
  366. // since this only has a good effect on big models -> use only if model has at least 10^5 states
  367. uint_fast64_t precision = stateEstimate > 100000 ? stateEstimate/1000 : minPrec;
  368. //-------------------------------------------------------------
  369. // 3. Subsystem generation
  370. //-------------------------------------------------------------
  371. // Search from init to target states until the shortest path for each target state is reached.
  372. std::vector<uint_fast64_t> shortestPath;
  373. std::vector<T> subSysProbs;
  374. T pathProb = 0;
  375. T subSysProb = 0;
  376. uint_fast64_t pathCount = 0;
  377. uint_fast64_t mcCount = 0;
  378. // First test if there are init states that are also target states.
  379. // If so the init state represents a subsystem with probability mass 1.
  380. // -> return it
  381. if((initStates & targetStates).getNumberOfSetBits() != 0) {
  382. subSys.set(*(initStates & targetStates).begin());
  383. LOG4CPLUS_INFO(logger, "Critical subsystem found.");
  384. LOG4CPLUS_INFO(logger, "Paths needed: " << pathCount);
  385. LOG4CPLUS_INFO(logger, "State count of critical subsystem: " << subSys.getNumberOfSetBits());
  386. LOG4CPLUS_INFO(logger, "Prob: " << 1);
  387. LOG4CPLUS_INFO(logger, "Model checks: " << mcCount);
  388. return model.getSubDtmc(subSys);
  389. }
  390. // Then compute the shortest paths from init states to all target states
  391. std::vector<std::pair<uint_fast64_t, T>> initTargetDistances;
  392. computeShortestDistances(model.getTransitionMatrix(), initStates, targetStates, allowedStates, initTargetDistances);
  393. pathProb = 0;
  394. extractShortestPath(initStates, targetStates, initTargetDistances, initTargetDistances, shortestPath, pathProb, false, true);
  395. // push states of found path into subsystem
  396. for(auto index : shortestPath) {
  397. subSys.set(index, true);
  398. }
  399. pathCount++;
  400. // Get estimate (upper bound) of new sub system probability
  401. // That is: prob(target) * cost(path) * (mean(prob(inits))/prob(source))
  402. subSysProb += trueProbs[shortestPath[0]] * pathProb * trueProb / trueProbs[shortestPath.back()];
  403. //find new nodes until the system becomes critical.
  404. while(true) {
  405. shortestPath.clear();
  406. pathProb = 0;
  407. findShortestPath(model.getTransitionMatrix(), subSys, targetStates, allowedStates, initTargetDistances, shortestPath, pathProb);
  408. //doBackwardsSearch(*model->getTransitionMatrix(), *initStates, subSys, targetStates, allowedStates, trueProbs, shortestPath, pathProb);
  409. pathCount++;
  410. // merge found states into subsystem
  411. for(uint_fast32_t i = 0; i < shortestPath.size(); i++) {
  412. subSys.set(shortestPath[i], true);
  413. }
  414. // Get estimate (upper bound) of new sub system probability
  415. // That is: prob(target) * cost(path) * (mean(prob(inits))/prob(source))
  416. //subSysProb += (trueProbs[shortestPath.back()] == 0 ? 0 : trueProbs[shortestPath[0]] * pathProb * trueProb / trueProbs[shortestPath.back()]);
  417. subSysProb += 1*(trueProbs[shortestPath.back()] == 0 ? 1 : trueProbs[shortestPath[0]] * pathProb == 0 ? 1 : pathProb );
  418. //std::cout << "Est. prob: " << subSysProb << std::endl;
  419. // Do we want to model check?
  420. if((pathCount % precision == 0) && subSysProb >= bound) {
  421. //get probabilities
  422. logger.getAppender("mainFileAppender")->setThreshold(log4cplus::WARN_LOG_LEVEL);
  423. subSysProbs = modelCheck.checkUntil(allowedStates & subSys, targetStates & subSys, false);
  424. logger.getAppender("mainFileAppender")->setThreshold(log4cplus::INFO_LOG_LEVEL);
  425. //T diff = subSysProb;
  426. //std::cout << "Est. prob: " << diff << std::endl;
  427. // reset sub system prob to correct value
  428. subSysProb = 0;
  429. for(auto index : initStates) {
  430. subSysProb += subSysProbs[index];
  431. }
  432. subSysProb /= initStates.getNumberOfSetBits();
  433. mcCount++;
  434. //diff -= subSysProb;
  435. //std::cout << "Real prob: " << subSysProb << std::endl;
  436. //std::cout << "Diff: " << diff << std::endl;
  437. //std::cout << "Path count: " << pathCount << std::endl;
  438. // Are we critical?
  439. if(subSysProb >= bound){
  440. break;
  441. } else if (stateEstimate > 100000){
  442. precision = static_cast<uint_fast64_t>((stateEstimate / 1000.0) - ((stateEstimate / 1000.0) - minPrec) * (subSysProb/bound));
  443. }
  444. }
  445. }
  446. LOG4CPLUS_INFO(logger, "Critical subsystem found.");
  447. LOG4CPLUS_INFO(logger, "Paths needed: " << pathCount);
  448. LOG4CPLUS_INFO(logger, "State count of critical subsystem: " << subSys.getNumberOfSetBits());
  449. LOG4CPLUS_INFO(logger, "Prob: " << subSysProb);
  450. LOG4CPLUS_INFO(logger, "Model checks: " << mcCount);
  451. return model.getSubDtmc(subSys);
  452. }
  453. };
  454. } // namespace counterexamples
  455. } // namespace storm
  456. #endif /* STORM_COUNTEREXAMPLES_PATHBASEDSUBSYSTEMGENERATOR_H_ */