kota
initial commit
e60e568
raw
history blame
31.6 kB
'''
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