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.

534 lines
23 KiB

2 months ago
  1. import sys
  2. if sys.version_info[0] == 2:
  3. raise ImportError('Python 2.x is not supported for stormpy.')
  4. from . import core
  5. from .core import *
  6. from . import storage
  7. from .storage import *
  8. from ._config import *
  9. from .logic import *
  10. from .exceptions import *
  11. from pycarl import Variable # needed for building parametric models
  12. __version__ = "unknown"
  13. try:
  14. from ._version import __version__
  15. except ImportError:
  16. # We're running in a tree that doesn't have a _version.py, so we don't know what our version is.
  17. pass
  18. core._set_up("")
  19. def _convert_sparse_model(model, parametric=False):
  20. """
  21. Convert (parametric) model in sparse representation into model corresponding to exact model type.
  22. :param model: Sparse model.
  23. :param parametric: Flag indicating if the model is parametric.
  24. :return: Model corresponding to exact model type.
  25. """
  26. if parametric:
  27. assert model.supports_parameters
  28. if model.model_type == ModelType.DTMC:
  29. return model._as_sparse_pdtmc()
  30. elif model.model_type == ModelType.MDP:
  31. return model._as_sparse_pmdp()
  32. elif model.model_type == ModelType.POMDP:
  33. return model._as_sparse_ppomdp()
  34. elif model.model_type == ModelType.CTMC:
  35. return model._as_sparse_pctmc()
  36. elif model.model_type == ModelType.MA:
  37. return model._as_sparse_pma()
  38. else:
  39. raise StormError("Not supported parametric model constructed")
  40. else:
  41. assert not model.supports_parameters
  42. if model.model_type == ModelType.DTMC:
  43. return model._as_sparse_dtmc()
  44. elif model.model_type == ModelType.MDP:
  45. return model._as_sparse_mdp()
  46. elif model.model_type == ModelType.POMDP:
  47. return model._as_sparse_pomdp()
  48. elif model.model_type == ModelType.CTMC:
  49. return model._as_sparse_ctmc()
  50. elif model.model_type == ModelType.MA:
  51. return model._as_sparse_ma()
  52. else:
  53. raise StormError("Not supported non-parametric model constructed")
  54. def _convert_symbolic_model(model, parametric=False):
  55. """
  56. Convert (parametric) model in symbolic representation into model corresponding to exact model type.
  57. :param model: Symbolic model.
  58. :param parametric: Flag indicating if the model is parametric.
  59. :return: Model corresponding to exact model type.
  60. """
  61. if parametric:
  62. assert model.supports_parameters
  63. if model.model_type == ModelType.DTMC:
  64. return model._as_symbolic_pdtmc()
  65. elif model.model_type == ModelType.MDP:
  66. return model._as_symbolic_pmdp()
  67. elif model.model_type == ModelType.CTMC:
  68. return model._as_symbolic_pctmc()
  69. elif model.model_type == ModelType.MA:
  70. return model._as_symbolic_pma()
  71. else:
  72. raise StormError("Not supported parametric model constructed")
  73. else:
  74. assert not model.supports_parameters
  75. if model.model_type == ModelType.DTMC:
  76. return model._as_symbolic_dtmc()
  77. elif model.model_type == ModelType.MDP:
  78. return model._as_symbolic_mdp()
  79. elif model.model_type == ModelType.CTMC:
  80. return model._as_symbolic_ctmc()
  81. elif model.model_type == ModelType.MA:
  82. return model._as_symbolic_ma()
  83. else:
  84. raise StormError("Not supported non-parametric model constructed")
  85. def build_model(symbolic_description, properties=None):
  86. """
  87. Build a model in sparse representation from a symbolic description.
  88. :param symbolic_description: Symbolic model description to translate into a model.
  89. :param List[Property] properties: List of properties that should be preserved during the translation. If None, then all properties are preserved.
  90. :return: Model in sparse representation.
  91. """
  92. return build_sparse_model(symbolic_description, properties=properties)
  93. def build_parametric_model(symbolic_description, properties=None):
  94. """
  95. Build a parametric model in sparse representation from a symbolic description.
  96. :param symbolic_description: Symbolic model description to translate into a model.
  97. :param List[Property] properties: List of properties that should be preserved during the translation. If None, then all properties are preserved.
  98. :return: Parametric model in sparse representation.
  99. """
  100. return build_sparse_parametric_model(symbolic_description, properties=properties)
  101. def build_sparse_model(symbolic_description, properties=None):
  102. """
  103. Build a model in sparse representation from a symbolic description.
  104. :param symbolic_description: Symbolic model description to translate into a model.
  105. :param List[Property] properties: List of properties that should be preserved during the translation. If None, then all properties are preserved.
  106. :return: Model in sparse representation.
  107. """
  108. if not symbolic_description.undefined_constants_are_graph_preserving:
  109. raise StormError("Program still contains undefined constants")
  110. if properties:
  111. formulae = [(prop.raw_formula if isinstance(prop, Property) else prop) for prop in properties]
  112. intermediate = core._build_sparse_model_from_symbolic_description(symbolic_description, formulae)
  113. else:
  114. intermediate = core._build_sparse_model_from_symbolic_description(symbolic_description)
  115. return _convert_sparse_model(intermediate, parametric=False)
  116. def build_sparse_parametric_model(symbolic_description, properties=None):
  117. """
  118. Build a parametric model in sparse representation from a symbolic description.
  119. :param symbolic_description: Symbolic model description to translate into a model.
  120. :param List[Property] properties: List of properties that should be preserved during the translation. If None, then all properties are preserved.
  121. :return: Parametric model in sparse representation.
  122. """
  123. if not symbolic_description.undefined_constants_are_graph_preserving:
  124. raise StormError("Program still contains undefined constants")
  125. if properties:
  126. formulae = [(prop.raw_formula if isinstance(prop, Property) else prop) for prop in properties]
  127. intermediate = core._build_sparse_parametric_model_from_symbolic_description(symbolic_description, formulae)
  128. else:
  129. intermediate = core._build_sparse_parametric_model_from_symbolic_description(symbolic_description)
  130. return _convert_sparse_model(intermediate, parametric=True)
  131. def build_symbolic_model(symbolic_description, properties=None):
  132. """
  133. Build a model in symbolic representation from a symbolic description.
  134. :param symbolic_description: Symbolic model description to translate into a model.
  135. :param List[Property] properties: List of properties that should be preserved during the translation. If None, then all properties are preserved.
  136. :return: Model in symbolic representation.
  137. """
  138. if not symbolic_description.undefined_constants_are_graph_preserving:
  139. raise StormError("Program still contains undefined constants")
  140. if properties:
  141. formulae = [(prop.raw_formula if isinstance(prop, Property) else prop) for prop in properties]
  142. intermediate = core._build_symbolic_model_from_symbolic_description(symbolic_description, formulae)
  143. else:
  144. intermediate = core._build_symbolic_model_from_symbolic_description(symbolic_description)
  145. return _convert_symbolic_model(intermediate, parametric=False)
  146. def build_symbolic_parametric_model(symbolic_description, properties=None):
  147. """
  148. Build a parametric model in symbolic representation from a symbolic description.
  149. :param symbolic_description: Symbolic model description to translate into a model.
  150. :param List[Property] properties: List of properties that should be preserved during the translation. If None, then all properties are preserved.
  151. :return: Parametric model in symbolic representation.
  152. """
  153. if not symbolic_description.undefined_constants_are_graph_preserving:
  154. raise StormError("Program still contains undefined constants")
  155. if properties:
  156. formulae = [(prop.raw_formula if isinstance(prop, Property) else prop) for prop in properties]
  157. intermediate = core._build_symbolic_parametric_model_from_symbolic_description(symbolic_description, formulae)
  158. else:
  159. intermediate = core._build_symbolic_parametric_model_from_symbolic_description(symbolic_description)
  160. return _convert_symbolic_model(intermediate, parametric=True)
  161. def build_model_from_drn(file, options = DirectEncodingParserOptions()):
  162. """
  163. Build a model in sparse representation from the explicit DRN representation.
  164. :param String file: DRN file containing the model.
  165. :param DirectEncodingParserOptions: Options for the parser.
  166. :return: Model in sparse representation.
  167. """
  168. intermediate = core._build_sparse_model_from_drn(file, options)
  169. return _convert_sparse_model(intermediate, parametric=False)
  170. def build_parametric_model_from_drn(file, options = DirectEncodingParserOptions()):
  171. """
  172. Build a parametric model in sparse representation from the explicit DRN representation.
  173. :param String file: DRN file containing the model.
  174. :param DirectEncodingParserOptions: Options for the parser.
  175. :return: Parametric model in sparse representation.
  176. """
  177. intermediate = core._build_sparse_parametric_model_from_drn(file, options)
  178. return _convert_sparse_model(intermediate, parametric=True)
  179. def perform_bisimulation(model, properties, bisimulation_type):
  180. """
  181. Perform bisimulation on model.
  182. :param model: Model.
  183. :param properties: Properties to preserve during bisimulation.
  184. :param bisimulation_type: Type of bisimulation (weak or strong).
  185. :return: Model after bisimulation.
  186. """
  187. return perform_sparse_bisimulation(model, properties, bisimulation_type)
  188. def perform_sparse_bisimulation(model, properties, bisimulation_type):
  189. """
  190. Perform bisimulation on model in sparse representation.
  191. :param model: Model.
  192. :param properties: Properties to preserve during bisimulation.
  193. :param bisimulation_type: Type of bisimulation (weak or strong).
  194. :return: Model after bisimulation.
  195. """
  196. formulae = [(prop.raw_formula if isinstance(prop, Property) else prop) for prop in properties]
  197. if model.supports_parameters:
  198. return core._perform_parametric_bisimulation(model, formulae, bisimulation_type)
  199. else:
  200. return core._perform_bisimulation(model, formulae, bisimulation_type)
  201. def perform_symbolic_bisimulation(model, properties):
  202. """
  203. Perform bisimulation on model in symbolic representation.
  204. :param model: Model.
  205. :param properties: Properties to preserve during bisimulation.
  206. :return: Model after bisimulation.
  207. """
  208. formulae = [(prop.raw_formula if isinstance(prop, Property) else prop) for prop in properties]
  209. bisimulation_type = BisimulationType.STRONG
  210. if model.supports_parameters:
  211. return core._perform_symbolic_parametric_bisimulation(model, formulae, bisimulation_type)
  212. else:
  213. return core._perform_symbolic_bisimulation(model, formulae, bisimulation_type)
  214. def model_checking(model, property, only_initial_states=False, extract_scheduler=False, force_fully_observable=False, environment=Environment(), shield_expression=None):
  215. """
  216. Perform model checking on model for property.
  217. :param model: Model.
  218. :param property: Property to check for.
  219. :param only_initial_states: If True, only results for initial states are computed, otherwise for all states.
  220. :param extract_scheduler: If True, try to extract a scheduler
  221. :return: Model checking result.
  222. :rtype: CheckResult
  223. """
  224. if model.is_sparse_model:
  225. return check_model_sparse(model, property, only_initial_states=only_initial_states,
  226. extract_scheduler=extract_scheduler, force_fully_observable=force_fully_observable, environment=environment, shield_expression=shield_expression)
  227. else:
  228. assert (model.is_symbolic_model)
  229. if extract_scheduler:
  230. raise StormError("Model checking based on dd engine does not support extracting schedulers right now.")
  231. return check_model_dd(model, property, only_initial_states=only_initial_states,
  232. environment=environment)
  233. def check_model_sparse(model, property, only_initial_states=False, extract_scheduler=False, force_fully_observable=False, environment=Environment(), shield_expression=None):
  234. """
  235. Perform model checking on model for property.
  236. :param model: Model.
  237. :param property: Property to check for.
  238. :param only_initial_states: If True, only results for initial states are computed, otherwise for all states.
  239. :param extract_scheduler: If True, try to extract a scheduler
  240. :param force_fully_observable: If True, treat a POMDP as an MDP
  241. :return: Model checking result.
  242. :rtype: CheckResult
  243. """
  244. if isinstance(property, Property):
  245. formula = property.raw_formula
  246. else:
  247. formula = property
  248. if force_fully_observable:
  249. if model.is_partially_observable:
  250. # Note that casting a model to a fully observable model wont work with python/pybind, so we actually have other access points
  251. if model.supports_parameters:
  252. raise NotImplementedError("")
  253. elif model.is_exact:
  254. task = core.ExactCheckTask(formula, only_initial_states)
  255. task.set_produce_schedulers(extract_scheduler)
  256. return core._exact_model_checking_fully_observable(model, task, environment=environment)
  257. else:
  258. task = core.CheckTask(formula, only_initial_states)
  259. task.set_produce_schedulers(extract_scheduler)
  260. return core._model_checking_fully_observable(model, task, environment=environment)
  261. else:
  262. raise RuntimeError("Forcing models that are fully observable is not possible")
  263. if model.supports_parameters:
  264. task = core.ParametricCheckTask(formula, only_initial_states)
  265. task.set_produce_schedulers(extract_scheduler)
  266. return core._parametric_model_checking_sparse_engine(model, task, environment=environment)
  267. else:
  268. if model.is_exact:
  269. task = core.ExactCheckTask(formula, only_initial_states)
  270. task.set_produce_schedulers(extract_scheduler)
  271. return core._exact_model_checking_sparse_engine(model, task, environment=environment)
  272. else:
  273. task = core.CheckTask(formula, only_initial_states)
  274. task.set_produce_schedulers(extract_scheduler)
  275. if shield_expression is not None:
  276. task.set_shielding_expression(shield_expression)
  277. return core._model_checking_sparse_engine(model, task, environment=environment)
  278. def check_model_dd(model, property, only_initial_states=False, environment=Environment()):
  279. """
  280. Perform model checking using dd engine.
  281. :param model: Model.
  282. :param property: Property to check for.
  283. :param only_initial_states: If True, only results for initial states are computed, otherwise for all states.
  284. :return: Model checking result.
  285. :rtype: CheckResult
  286. """
  287. if isinstance(property, Property):
  288. formula = property.raw_formula
  289. else:
  290. formula = property
  291. if model.supports_parameters:
  292. task = core.ParametricCheckTask(formula, only_initial_states)
  293. return core._parametric_model_checking_dd_engine(model, task, environment=environment)
  294. else:
  295. task = core.CheckTask(formula, only_initial_states)
  296. return core._model_checking_dd_engine(model, task, environment=environment)
  297. def check_model_hybrid(model, property, only_initial_states=False, environment=Environment()):
  298. """
  299. Perform model checking using hybrid engine.
  300. :param model: Model.
  301. :param property: Property to check for.
  302. :param only_initial_states: If True, only results for initial states are computed, otherwise for all states.
  303. :return: Model checking result.
  304. :rtype: CheckResult
  305. """
  306. if isinstance(property, Property):
  307. formula = property.raw_formula
  308. else:
  309. formula = property
  310. if model.supports_parameters:
  311. task = core.ParametricCheckTask(formula, only_initial_states)
  312. return core._parametric_model_checking_hybrid_engine(model, task, environment=environment)
  313. else:
  314. task = core.CheckTask(formula, only_initial_states)
  315. return core._model_checking_hybrid_engine(model, task, environment=environment)
  316. def transform_to_sparse_model(model):
  317. """
  318. Transform model in symbolic representation into model in sparse representation.
  319. :param model: Symbolic model.
  320. :return: Sparse model.
  321. """
  322. if model.supports_parameters:
  323. return core._transform_to_sparse_parametric_model(model)
  324. else:
  325. return core._transform_to_sparse_model(model)
  326. def transform_to_discrete_time_model(model, properties):
  327. """
  328. Transform continuous-time model to discrete time model.
  329. :param model: Continuous-time model.
  330. :param properties: List of properties to transform as well.
  331. :return: Tuple (Discrete-time model, converted properties).
  332. """
  333. formulae = [(prop.raw_formula if isinstance(prop, Property) else prop) for prop in properties]
  334. if model.supports_parameters:
  335. return core._transform_to_discrete_time_parametric_model(model, formulae)
  336. else:
  337. return core._transform_to_discrete_time_model(model, formulae)
  338. def eliminate_non_markovian_chains(ma, properties, label_behavior):
  339. """
  340. Eliminate chains of non-Markovian states if possible.
  341. :param ma: Markov automaton.
  342. :param properties: List of properties to transform as well.
  343. :param label_behavior: Behavior of labels while elimination.
  344. :return: Tuple (converted MA, converted properties).
  345. """
  346. formulae = [(prop.raw_formula if isinstance(prop, Property) else prop) for prop in properties]
  347. if ma.supports_parameters:
  348. return core._eliminate_non_markovian_chains_parametric(ma, formulae, label_behavior)
  349. else:
  350. return core._eliminate_non_markovian_chains(ma, formulae, label_behavior)
  351. def prob01min_states(model, eventually_formula):
  352. assert type(eventually_formula) == logic.EventuallyFormula
  353. labelform = eventually_formula.subformula
  354. labelprop = core.Property("label-prop", labelform)
  355. phiStates = BitVector(model.nr_states, True)
  356. psiStates = model_checking(model, labelprop).get_truth_values()
  357. return compute_prob01min_states(model, phiStates, psiStates)
  358. def prob01max_states(model, eventually_formula):
  359. assert type(eventually_formula) == logic.EventuallyFormula
  360. labelform = eventually_formula.subformula
  361. labelprop = core.Property("label-prop", labelform)
  362. phiStates = BitVector(model.nr_states, True)
  363. psiStates = model_checking(model, labelprop).get_truth_values()
  364. return compute_prob01min_states(model, phiStates, psiStates)
  365. def compute_prob01_states(model, phi_states, psi_states):
  366. """
  367. Compute prob01 states for properties of the form phi_states until psi_states
  368. :param SparseDTMC model:
  369. :param BitVector phi_states:
  370. :param BitVector psi_states: Target states
  371. """
  372. if model.model_type != ModelType.DTMC:
  373. raise StormError("Prob 01 is only defined for DTMCs -- model must be a DTMC")
  374. if model.supports_parameters:
  375. return core._compute_prob01states_rationalfunc(model, phi_states, psi_states)
  376. else:
  377. return core._compute_prob01states_double(model, phi_states, psi_states)
  378. def compute_prob01min_states(model, phi_states, psi_states):
  379. if model.model_type == ModelType.DTMC:
  380. return compute_prob01_states(model, phi_states, psi_states)
  381. if model.supports_parameters:
  382. return core._compute_prob01states_min_rationalfunc(model, phi_states, psi_states)
  383. else:
  384. return core._compute_prob01states_min_double(model, phi_states, psi_states)
  385. def compute_prob01max_states(model, phi_states, psi_states):
  386. if model.model_type == ModelType.DTMC:
  387. return compute_prob01_states(model, phi_states, psi_states)
  388. if model.supports_parameters:
  389. return core._compute_prob01states_max_rationalfunc(model, phi_states, psi_states)
  390. else:
  391. return core._compute_prob01states_max_double(model, phi_states, psi_states)
  392. def topological_sort(model, forward=True, initial=[]):
  393. """
  394. :param model:
  395. :param forward:
  396. :return:
  397. """
  398. matrix = model.transition_matrix if forward else model.backward_transition_matrix
  399. if isinstance(model, storage._SparseParametricModel):
  400. return storage._topological_sort_rf(matrix, initial)
  401. elif isinstance(model, storage._SparseModel):
  402. return storage._topological_sort_double(matrix, initial)
  403. else:
  404. raise StormError("Unknown kind of model.")
  405. def construct_submodel(model, states, actions, keep_unreachable_states=True, options=SubsystemBuilderOptions()):
  406. """
  407. :param model: The model
  408. :param states: Which states should be preserved
  409. :param actions: Which actions should be preserved
  410. :param keep_unreachable_states: If False, run a reachability analysis.
  411. :return: A model with fewer states/actions
  412. """
  413. if isinstance(model, storage._SparseModel):
  414. return core._construct_subsystem_double(model, states, actions, keep_unreachable_states, options)
  415. else:
  416. raise NotImplementedError()
  417. def parse_properties(properties, context=None, filters=None):
  418. """
  419. :param properties: A string with the pctl properties
  420. :param context: A symbolic model that gives meaning to variables and constants.
  421. :param filters: filters, if applicable.
  422. :return: A list of properties
  423. """
  424. if context is None:
  425. return core.parse_properties_without_context(properties, filters)
  426. elif type(context) == core.SymbolicModelDescription:
  427. if context.is_prism_program():
  428. return core.parse_properties_for_prism_program(properties, context.as_prism_program(), filters)
  429. else:
  430. core.parse_properties_for_jani_program(properties, context.as_jani_model(), filters)
  431. elif type(context) == storage.PrismProgram:
  432. return core.parse_properties_for_prism_program(properties, context, filters)
  433. elif type(context) == storage.JaniModel:
  434. core.parse_properties_for_jani_model(properties, context, filters)
  435. else:
  436. raise StormError("Unclear context. Please pass a symbolic model description")
  437. def export_to_drn(model, file, options=DirectEncodingOptions()):
  438. """
  439. Export a model to DRN format
  440. :param model: The model
  441. :param file: A path
  442. :param options: DirectEncodingOptions
  443. :return:
  444. """
  445. if model.supports_parameters:
  446. return core._export_parametric_to_drn(model, file, options)
  447. if model.is_exact:
  448. return core._export_exact_to_drn(model, file, options)
  449. return core._export_to_drn(model, file, options)