Spaces:
Running
Running
File size: 31,619 Bytes
e60e568 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 |
'''
This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
PM4Py is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PM4Py is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PM4Py. If not, see <https://www.gnu.org/licenses/>.
'''
from pm4py.util import exec_utils, nx_utils
from enum import Enum
from pm4py.objects.petri_net.utils import petri_utils
import copy
import numpy as np
# Importing for place invariants related stuff (s-components, uniform and weighted place invariants)
from pm4py.algo.analysis.woflan.place_invariants.place_invariants import compute_place_invariants
from pm4py.algo.analysis.woflan.place_invariants.utility import transform_basis
from pm4py.algo.analysis.woflan.place_invariants.s_component import compute_s_components
from pm4py.algo.analysis.woflan.place_invariants.s_component import compute_uncovered_places_in_component
from pm4py.algo.analysis.woflan.place_invariants.utility import \
compute_uncovered_places as compute_uncovered_place_in_invariants
# Importing to discover not-well handled pairs
from pm4py.algo.analysis.woflan.not_well_handled_pairs.not_well_handled_pairs import \
apply as compute_not_well_handled_pairs
# Minimal Coverability Graph
from pm4py.algo.analysis.woflan.graphs.minimal_coverability_graph.minimal_coverability_graph import \
apply as minimal_coverability_graph
from pm4py.algo.analysis.woflan.graphs.utility import check_for_dead_tasks
from pm4py.algo.analysis.woflan.graphs.utility import check_for_improper_conditions
from pm4py.algo.analysis.woflan.graphs.utility import check_for_substates
from pm4py.algo.analysis.woflan.graphs.utility import convert_marking
# Restricted coverability graph
from pm4py.algo.analysis.woflan.graphs.restricted_coverability_graph.restricted_coverability_graph import \
construct_tree as restricted_coverability_tree
# reachability Graph Creation
from pm4py.algo.analysis.woflan.graphs.reachability_graph.reachability_graph import apply as reachability_graph
from typing import Optional, Dict, Any, Union
from pm4py.objects.petri_net.obj import PetriNet, Marking
class Parameters(Enum):
RETURN_ASAP_WHEN_NOT_SOUND = "return_asap_when_not_sound"
PRINT_DIAGNOSTICS = "print_diagnostics"
RETURN_DIAGNOSTICS = "return_diagnostics"
class Outputs(Enum):
S_C_NET = "s_c_net"
PLACE_INVARIANTS = "place_invariants"
UNIFORM_PLACE_INVARIANTS = "uniform_place_invariants"
S_COMPONENTS = "s_components"
UNCOVERED_PLACES_S_COMPONENT = "uncovered_places_s_component"
NOT_WELL_HANDLED_PAIRS = "not_well_handled_pairs"
LEFT = "left"
UNCOVERED_PLACES_UNIFORM = "uncovered_places_uniform"
WEIGHTED_PLACE_INVARIANTS = "weighted_place_invariants"
UNCOVERED_PLACES_WEIGHTED = "uncovered_places_weighted"
MCG = "mcg"
DEAD_TASKS = "dead_tasks"
R_G_S_C = "r_g_s_c"
R_G = "r_g"
LOCKING_SCENARIOS = "locking_scenarios"
RESTRICTED_COVERABILITY_TREE = "restricted_coverability_tree"
DIAGNOSTIC_MESSAGES = "diagnostic_messages"
class woflan:
def __init__(self, net, initial_marking, final_marking, print_diagnostics=False):
self.net = net
self.initial_marking = initial_marking
self.final_marking = final_marking
self.print_diagnostics = print_diagnostics
self.s_c_net = None
self.place_invariants = None
self.uniform_place_invariants = None
self.s_components = None
self.uncovered_places_s_component = None
self.not_well_handled_pairs = None
self.left = None
self.uncovered_places_uniform = None
self.weighted_place_invariants = None
self.uncovered_places_weighted = None
self.mcg = None
self.dead_tasks = None
self.r_g_s_c = None
self.r_g = None
self.locking_scenarios = None
self.restricted_coverability_tree = None
self.diagnostic_messages = list()
def set_s_c_net(self, s_c_net):
self.s_c_net = s_c_net
def set_place_invariants(self, invariants):
self.place_invariants = invariants
def set_uniform_place_invariants(self, invariants):
self.uniform_place_invariants = invariants
def set_s_components(self, s_components):
self.s_components = s_components
def set_uncovered_places_s_component(self, uncovered_places):
self.uncovered_places_s_component = uncovered_places
def set_not_well_handled_pairs(self, not_well_handled_pairs):
self.not_well_handled_pairs = not_well_handled_pairs
def set_left(self, left):
self.left = left
def set_uncovered_places_uniform(self, places):
self.uncovered_places_uniform = places
def set_weighted_place_invariants(self, invariants):
self.weighted_place_invariants = invariants
def set_uncovered_places_weighted(self, places):
self.uncovered_places_weighted = places
def set_mcg(self, mcg):
self.mcg = mcg
def set_dead_tasks(self, dead_tasks):
self.dead_tasks = dead_tasks
def set_r_g_s_c(self, r_g):
self.r_g_s_c = r_g
def set_r_g(self, r_g):
self.r_g = r_g
def set_locking_scenarios(self, scenarios):
self.locking_scenarios = scenarios
def set_restricted_coverability_tree(self, graph):
self.restricted_coverability_tree = graph
def get_net(self):
return self.net
def get_initial_marking(self):
return self.initial_marking
def get_final_marking(self):
return self.final_marking
def get_s_c_net(self):
return self.s_c_net
def get_place_invariants(self):
return self.place_invariants
def get_uniform_place_invariants(self):
return self.uniform_place_invariants
def get_s_components(self):
return self.s_components
def get_uncovered_places_s_component(self):
return self.uncovered_places_s_component
def get_not_well_handled_pairs(self):
return self.not_well_handled_pairs
def get_left(self):
return self.left
def get_uncovered_places_uniform(self):
return self.uncovered_places_uniform
def get_weighted_place_invariants(self):
return self.weighted_place_invariants
def get_uncovered_places_weighted(self):
return self.uncovered_places_weighted
def get_mcg(self):
return self.mcg
def get_dead_tasks(self):
return self.dead_tasks
def get_r_g_s_c(self):
return self.r_g_s_c
def get_r_g(self):
return self.r_g
def get_locking_scenarios(self):
return self.locking_scenarios
def get_restricted_coverability_tree(self):
return self.restricted_coverability_tree
def get_output(self):
"""
Returns a dictionary representation of the
entities that are calculated during WOFLAN
"""
ret = {}
if self.s_c_net is not None:
ret[Outputs.S_C_NET.value] = self.s_c_net
if self.place_invariants is not None:
ret[Outputs.PLACE_INVARIANTS.value] = self.place_invariants
if self.uniform_place_invariants is not None:
ret[Outputs.UNIFORM_PLACE_INVARIANTS.value] = self.uniform_place_invariants
if self.s_components is not None:
ret[Outputs.S_COMPONENTS.value] = self.s_components
if self.uncovered_places_s_component is not None:
ret[Outputs.UNCOVERED_PLACES_S_COMPONENT.value] = self.uncovered_places_s_component
if self.not_well_handled_pairs is not None:
ret[Outputs.NOT_WELL_HANDLED_PAIRS.value] = self.not_well_handled_pairs
if self.left is not None:
ret[Outputs.LEFT.value] = self.left
if self.uncovered_places_uniform is not None:
ret[Outputs.UNCOVERED_PLACES_UNIFORM.value] = self.uncovered_places_uniform
if self.weighted_place_invariants is not None:
ret[Outputs.WEIGHTED_PLACE_INVARIANTS.value] = self.weighted_place_invariants
if self.uncovered_places_weighted is not None:
ret[Outputs.UNCOVERED_PLACES_WEIGHTED.value] = self.uncovered_places_weighted
if self.mcg is not None:
ret[Outputs.MCG.value] = self.mcg
if self.dead_tasks is not None:
ret[Outputs.DEAD_TASKS.value] = self.dead_tasks
if self.r_g_s_c is not None:
ret[Outputs.R_G_S_C.value] = self.r_g_s_c
if self.r_g is not None:
ret[Outputs.R_G] = self.r_g
if self.locking_scenarios is not None:
ret[Outputs.LOCKING_SCENARIOS] = self.locking_scenarios
if self.restricted_coverability_tree is not None:
ret[Outputs.RESTRICTED_COVERABILITY_TREE] = self.restricted_coverability_tree
ret[Outputs.DIAGNOSTIC_MESSAGES] = self.diagnostic_messages
return ret
def short_circuit_petri_net(net, print_diagnostics=False):
"""
Fist, sink and source place are identified. Then, a transition from source to sink is added to short-circuited
the given petri net. If there is no unique source and sink place, an error gets returned
:param net: Petri net that is going to be short circuited
:return:
"""
s_c_net = copy.deepcopy(net)
no_source_places = 0
no_sink_places = 0
sink = None
source = None
for place in s_c_net.places:
if len(place.in_arcs) == 0:
source = place
no_source_places += 1
if len(place.out_arcs) == 0:
sink = place
no_sink_places += 1
if (sink is not None) and (source is not None) and no_source_places == 1 and no_sink_places == 1:
# If there is one unique source and sink place, short circuit Petri Net is constructed
t_1 = PetriNet.Transition("short_circuited_transition", "short_circuited_transition")
s_c_net.transitions.add(t_1)
# add arcs in short-circuited net
petri_utils.add_arc_from_to(sink, t_1, s_c_net)
petri_utils.add_arc_from_to(t_1, source, s_c_net)
return s_c_net, []
else:
if sink is None:
if print_diagnostics:
print("There is no sink place.")
return None, ["There is no sink place."]
elif source is None:
if print_diagnostics:
print("There is no source place.")
return None, ["There is no source place."]
elif no_source_places > 1:
if print_diagnostics:
print("There is more than one source place.")
return None, ["There is more than one source place."]
elif no_sink_places > 1:
if print_diagnostics:
print("There is more than one sink place.")
return None, ["There is more than one sink place."]
def step_1(woflan_object, return_asap_when_unsound=False):
"""
In the first step, we check if the input is given correct. We check if net is an PM4Py Petri Net representation
and if the exist a correct entry for the initial and final marking.
:param woflan_object: Object that contains all necessary information
:return: Proceed with step 2 if ok; else False
"""
def check_if_marking_in_net(marking, net):
"""
Checks if the marked place exists in the Petri Net and if there is only one i_m and f_m
:param marking: Marking of Petri Net
:param net: PM4Py representation of Petri Net
:return: Boolean. True if marking can exists; False if not.
"""
for place in marking:
if place in net.places:
return True
return False
if isinstance(woflan_object.get_net(), PetriNet):
if len(woflan_object.get_initial_marking()) != 1 or len(woflan_object.get_final_marking()) != 1:
woflan_object.diagnostic_messages.append('There is more than one initial or final marking.')
if woflan_object.print_diagnostics:
print('There is more than one initial or final marking.')
return False
if check_if_marking_in_net(woflan_object.get_initial_marking(), woflan_object.get_net()):
if check_if_marking_in_net(woflan_object.get_final_marking(), woflan_object.get_net()):
woflan_object.diagnostic_messages.append('Input is ok.')
if woflan_object.print_diagnostics:
print('Input is ok.')
return step_2(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
woflan_object.diagnostic_messages.append('The Petri Net is not PM4Py Petri Net represenatation.')
if woflan_object.print_diagnostics:
print('The Petri Net is not PM4Py Petri Net represenatation.')
return False
def step_2(woflan_object, return_asap_when_unsound=False):
"""
This method checks if a given Petri net is a workflow net. First, the Petri Net gets short-circuited
(connect start and end place with a tau-transition. Second, the Petri Net gets converted into a networkx graph.
Finally, it is tested if the resulting graph is a strongly connected component.
:param woflan_object: Woflan objet containing all information
:return: Bool=True if net is a WF-Net
"""
def transform_petri_net_into_regular_graph(still_need_to_discover):
"""
Ths method transforms a list of places and transitions into a networkx graph
:param still_need_to_discover: set of places and transition that are not fully added to graph
:return:
"""
G = nx_utils.DiGraph()
while len(still_need_to_discover) > 0:
element = still_need_to_discover.pop()
G.add_node(element.name)
for in_arc in element.in_arcs:
G.add_node(in_arc.source.name)
G.add_edge(in_arc.source.name, element.name)
for out_arc in element.out_arcs:
G.add_node(out_arc.target.name)
G.add_edge(element.name, out_arc.target.name)
return G
s_c_net, diagnostic_messages = short_circuit_petri_net(woflan_object.get_net(),
print_diagnostics=woflan_object.print_diagnostics)
woflan_object.set_s_c_net(s_c_net)
woflan_object.diagnostic_messages += diagnostic_messages
if woflan_object.get_s_c_net() == None:
return False
to_discover = woflan_object.get_s_c_net().places | woflan_object.get_s_c_net().transitions
graph = transform_petri_net_into_regular_graph(to_discover)
if not nx_utils.is_strongly_connected(graph):
woflan_object.diagnostic_messages.append('Petri Net is a not a worflow net.')
if woflan_object.print_diagnostics:
print('Petri Net is a not a worflow net.')
return False
else:
woflan_object.diagnostic_messages.append('Petri Net is a workflow net.')
if woflan_object.print_diagnostics:
print('Petri Net is a workflow net.')
return step_3(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
def step_3(woflan_object, return_asap_when_unsound=False):
woflan_object.set_place_invariants(compute_place_invariants(woflan_object.get_s_c_net()))
woflan_object.set_uniform_place_invariants(transform_basis(woflan_object.get_place_invariants(), style='uniform'))
woflan_object.set_s_components(
compute_s_components(woflan_object.get_s_c_net(), woflan_object.get_uniform_place_invariants()))
woflan_object.set_uncovered_places_s_component(
compute_uncovered_places_in_component(woflan_object.get_s_components(), woflan_object.get_s_c_net()))
if len(woflan_object.get_uncovered_places_s_component()) == 0:
woflan_object.set_left(True)
woflan_object.diagnostic_messages.append('Every place is covered by s-components.')
if woflan_object.print_diagnostics:
print('Every place is covered by s-components.')
return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
else:
woflan_object.diagnostic_messages.append('The following places are not covered by an s-component: {}.'.format(
woflan_object.get_uncovered_places_s_component()))
if woflan_object.print_diagnostics:
print('The following places are not covered by an s-component: {}.'.format(
woflan_object.get_uncovered_places_s_component()))
if return_asap_when_unsound:
return False
return step_4(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
def step_4(woflan_object, return_asap_when_unsound=False):
woflan_object.set_not_well_handled_pairs(compute_not_well_handled_pairs(woflan_object.get_s_c_net()))
if len(woflan_object.get_not_well_handled_pairs()) == 0:
woflan_object.diagnostic_messages.append('Petri Net is unsound.')
if woflan_object.print_diagnostics:
print('Petri Net is unsound.')
woflan_object.set_left(False)
if return_asap_when_unsound:
return False
return step_5(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
else:
woflan_object.diagnostic_messages.append('Not well-handled pairs are: {}.'.format(
woflan_object.get_not_well_handled_pairs()))
if woflan_object.print_diagnostics:
print('Not well-handled pairs are: {}.'.format(woflan_object.get_not_well_handled_pairs()))
woflan_object.set_left(True)
return step_5(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
def step_5(woflan_object, return_asap_when_unsound=False):
woflan_object.set_uncovered_places_uniform(
compute_uncovered_place_in_invariants(woflan_object.get_uniform_place_invariants(),
woflan_object.get_s_c_net()))
if len(woflan_object.get_uncovered_places_uniform()) == 0:
woflan_object.diagnostic_messages.append('There are no uncovered places in uniform invariants.')
if woflan_object.print_diagnostics:
print('There are no uncovered places in uniform invariants.')
return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
else:
woflan_object.diagnostic_messages.append('The following places are uncovered in uniform invariants: {}'.format(
woflan_object.get_uncovered_places_uniform()))
if woflan_object.print_diagnostics:
print('The following places are uncovered in uniform invariants: {}'.format(
woflan_object.get_uncovered_places_uniform()))
return step_6(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
def step_6(woflan_object, return_asap_when_unsound=False):
woflan_object.set_weighted_place_invariants(transform_basis(woflan_object.get_place_invariants(), style='weighted'))
woflan_object.set_uncovered_places_weighted(
compute_uncovered_place_in_invariants(woflan_object.get_weighted_place_invariants(),
woflan_object.get_s_c_net()))
if len(woflan_object.get_uncovered_places_weighted()) == 0:
woflan_object.diagnostic_messages.append('There are no uncovered places in weighted invariants.')
if woflan_object.print_diagnostics:
print('There are no uncovered places in weighted invariants.')
return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
else:
woflan_object.diagnostic_messages.append('The following places are uncovered in weighted invariants: {}'.format(
woflan_object.get_uncovered_places_weighted()))
if woflan_object.print_diagnostics:
print('The following places are uncovered in weighted invariants: {}'.format(
woflan_object.get_uncovered_places_weighted()))
return step_7(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
def step_7(woflan_object, return_asap_when_unsound=False):
woflan_object.set_mcg(minimal_coverability_graph(woflan_object.get_s_c_net(), woflan_object.get_initial_marking(),
woflan_object.get_net()))
if len(check_for_improper_conditions(woflan_object.get_mcg())) == 0:
woflan_object.diagnostic_messages.append('No improper coditions.')
if woflan_object.print_diagnostics:
print('No improper conditions.')
if woflan_object.get_left == True:
return step_8(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
else:
return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
else:
woflan_object.diagnostic_messages.append('Improper WPD. The following are the improper conditions: {}.'.format(
check_for_improper_conditions(woflan_object.get_mcg())))
if woflan_object.print_diagnostics:
print('Improper WPD. The following are the improper conditions: {}.'.format(
check_for_improper_conditions(woflan_object.get_mcg())))
if return_asap_when_unsound:
return False
return step_9(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
def step_8(woflan_object, return_asap_when_unsound=False):
if check_for_substates(woflan_object.get_mcg()):
return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
else:
return step_10(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
def step_9(woflan_object, return_asap_when_unsound=False):
woflan_object.diagnostic_messages.append('The following sequences are unbounded: {}'.format(
compute_unbounded_sequences(woflan_object)))
if woflan_object.print_diagnostics:
print('The following sequences are unbounded: {}'.format(compute_unbounded_sequences(woflan_object)))
return False
def step_10(woflan_object, return_asap_when_unsound=False):
if woflan_object.get_mcg() == None:
woflan_object.set_mcg(
minimal_coverability_graph(woflan_object.get_s_c_net(), woflan_object.get_initial_marking(),
woflan_object.get_net()))
woflan_object.set_dead_tasks(check_for_dead_tasks(woflan_object.get_s_c_net(), woflan_object.get_mcg()))
if len(woflan_object.get_dead_tasks()) == 0:
woflan_object.diagnostic_messages.append('There are no dead tasks.')
if woflan_object.print_diagnostics:
print('There are no dead tasks.')
if woflan_object.get_left() == True:
return step_11(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
else:
if return_asap_when_unsound:
return False
return step_12(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
else:
woflan_object.diagnostic_messages.append('The following tasks are dead: {}'.format(
woflan_object.get_dead_tasks()))
if woflan_object.print_diagnostics:
print('The following tasks are dead: {}'.format(woflan_object.get_dead_tasks()))
return False
def step_11(woflan_object, return_asap_when_unsound=False):
woflan_object.set_r_g_s_c(
reachability_graph(woflan_object.get_s_c_net(), woflan_object.get_initial_marking(), woflan_object.get_net()))
if nx_utils.is_strongly_connected(woflan_object.get_r_g_s_c()):
woflan_object.diagnostic_messages.append('All tasks are live.')
if woflan_object.print_diagnostics:
print('All tasks are live.')
return True
else:
if return_asap_when_unsound:
return False
return step_13(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
def step_12(woflan_object, return_asap_when_unsound=False):
woflan_object.set_r_g_s_c(
reachability_graph(woflan_object.get_s_c_net(), woflan_object.get_initial_marking(), woflan_object.get_net()))
woflan_object.diagnostic_messages.append('There are non-live tasks.')
if woflan_object.print_diagnostics:
print('There are non-live tasks.')
if return_asap_when_unsound:
return False
return step_13(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
def step_13(woflan_object, return_asap_when_unsound=False):
woflan_object.set_locking_scenarios(compute_non_live_sequences(woflan_object))
woflan_object.diagnostic_messages.append('The following sequences lead to deadlocks: {}.'.format(
woflan_object.get_locking_scenarios()))
if woflan_object.print_diagnostics:
print('The following sequences lead to deadlocks: {}.'.format(woflan_object.get_locking_scenarios()))
return False
def apply(net: PetriNet, i_m: Marking, f_m: Marking, parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> Union[bool, Any]:
"""
Apply the Woflan Soundness check. Trough this process, different steps are executed.
:param net: Petri Net representation of PM4Py
:param i_m: initial marking of given Net. Marking object of PM4Py
:param f_m: final marking of given Net. Marking object of PM4Py
:return: True, if net is sound; False otherwise.
"""
if parameters is None:
parameters = {}
return_asap_when_unsound = exec_utils.get_param_value(Parameters.RETURN_ASAP_WHEN_NOT_SOUND, parameters, False)
print_diagnostics = exec_utils.get_param_value(Parameters.PRINT_DIAGNOSTICS, parameters, True)
return_diagnostics = exec_utils.get_param_value(Parameters.RETURN_DIAGNOSTICS, parameters, False)
woflan_object = woflan(net, i_m, f_m, print_diagnostics=print_diagnostics)
step_1_res = step_1(woflan_object, return_asap_when_unsound=return_asap_when_unsound)
if return_diagnostics:
return step_1_res, woflan_object.get_output()
return step_1_res
def compute_non_live_sequences(woflan_object):
"""
We want to compute the sequences of transitions which lead to deadlocks.
To do this, we first compute a reachbility graph (possible, since we know that the Petri Net is bounded) and then we
convert it to a spanning tree. Afterwards, we compute the paths which lead to nodes from which the final marking cannot
be reached. Note: We are searching for the shortest sequence. After the first red node, all successors are also red.
Therefore, we do not have to consider them.
:param woflan_object: Object that contains the necessary information
:return: List of sequence of transitions, each sequence is a list
"""
woflan_object.set_r_g(reachability_graph(woflan_object.get_net(), woflan_object.get_initial_marking()))
f_m = convert_marking(woflan_object.get_net(), woflan_object.get_final_marking())
sucessfull_terminate_state = None
for node in woflan_object.get_r_g().nodes:
if all(np.equal(woflan_object.get_r_g().nodes[node]['marking'], f_m)):
sucessfull_terminate_state = node
break
# red nodes are those from which the final marking is not reachable
red_nodes = []
for node in woflan_object.get_r_g().nodes:
if not nx_utils.has_path(woflan_object.get_r_g(), node, sucessfull_terminate_state):
red_nodes.append(node)
# Compute directed spanning tree
spanning_tree = nx_utils.Edmonds(woflan_object.get_r_g()).find_optimum()
queue = set()
paths = {}
# root node
queue.add(0)
paths[0] = []
processed_nodes = set()
red_paths = []
while len(queue) > 0:
v = queue.pop()
for node in spanning_tree.neighbors(v):
if node not in paths and node not in processed_nodes:
paths[node] = paths[v].copy()
# we can use directly 0 here, since we are working on a spanning tree and there should be no more edges to a node
paths[node].append(woflan_object.get_r_g().get_edge_data(v, node)[0]['transition'])
if node not in red_nodes:
queue.add(node)
else:
red_paths.append(paths[node])
processed_nodes.add(v)
return red_paths
def compute_unbounded_sequences(woflan_object):
"""
We compute the sequences which lead to an infinite amount of tokens. To do this, we compute a restricted coverability tree.
The tree works similar to the graph, despite we consider tree characteristics during the construction.
:param woflan_object: Woflan object that contains all needed information.
:return: List of unbounded sequences, each sequence is a list of transitions
"""
def check_for_markings_larger_than_final_marking(graph, f_m):
markings = []
for node in graph.nodes:
if all(np.greater_equal(graph.nodes[node]['marking'], f_m)):
markings.append(node)
return markings
woflan_object.set_restricted_coverability_tree(
restricted_coverability_tree(woflan_object.get_net(), woflan_object.get_initial_marking()))
f_m = convert_marking(woflan_object.get_net(), woflan_object.get_final_marking())
infinite_markings = []
for node in woflan_object.get_restricted_coverability_tree().nodes:
if np.inf in woflan_object.get_restricted_coverability_tree().nodes[node]['marking']:
infinite_markings.append(node)
larger_markings = check_for_markings_larger_than_final_marking(woflan_object.get_restricted_coverability_tree(),
f_m)
green_markings = []
for node in woflan_object.get_restricted_coverability_tree().nodes:
add_to_green = True
for marking in infinite_markings:
if nx_utils.has_path(woflan_object.get_restricted_coverability_tree(), node, marking):
add_to_green = False
for marking in larger_markings:
if nx_utils.has_path(woflan_object.get_restricted_coverability_tree(), node, marking):
add_to_green = False
if add_to_green:
green_markings.append(node)
red_markings = []
for node in woflan_object.get_restricted_coverability_tree().nodes:
add_to_red = True
for node_green in green_markings:
if nx_utils.has_path(woflan_object.get_restricted_coverability_tree(), node, node_green):
add_to_red = False
break
if add_to_red:
red_markings.append(node)
# Make the path as short as possible. If we reach a red state, we stop and do not go further in the "red zone".
queue = set()
queue.add(0)
paths = {}
paths[0] = []
paths_to_red = []
while len(queue) > 0:
v = queue.pop()
successors = woflan_object.get_restricted_coverability_tree().successors(v)
for suc in successors:
paths[suc] = paths[v].copy()
paths[suc].append(woflan_object.get_restricted_coverability_tree().get_edge_data(v, suc)['transition'])
if suc in red_markings:
paths_to_red.append(paths[suc])
else:
queue.add(suc)
return paths_to_red
|