text: '''
      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.objects.petri_net.utils.networkx_graph import create_networkx_undirected_graph
  from pm4py.objects.petri_net.utils import explore_path
  from pm4py.objects.petri_net import obj
  
  
  def check_source_and_sink_reachability(net, unique_source, unique_sink):
      """
      Checks reachability of the source and the sink place from all simulation nodes (places/transitions)
      of the Petri net
  
      Parameters
      -------------
      net
          Petri net
      unique_source
          Unique source place of the Petri net
      unique_sink
          Unique sink place of the Petri net
  
      Returns
      -------------
      boolean
          Boolean value that is true if each node is in a path from the source place to the sink place
      """
      import networkx as nx
  
      graph, unique_source_corr, unique_sink_corr, inv_dictionary = create_networkx_undirected_graph(net, unique_source,
                                                                                                     unique_sink)
      if unique_source_corr is not None and unique_sink_corr is not None:
          nodes_list = list(graph.nodes())
          finish_to_sink = list(nx.ancestors(graph, unique_sink_corr))
          connected_to_source = list(nx.descendants(graph, unique_source_corr))
          if len(finish_to_sink) == len(nodes_list) - 1 and len(connected_to_source) == len(nodes_list) - 1:
              return True
      return False
  
  
  def check_source_place_presence(net):
      """
      Check if there is a unique source place with empty connections
  
      Parameters
      -------------
      net
          Petri net
  
      Returns
      -------------
      place
          Unique source place (or None otherwise)
      """
      count_empty_input = 0
      unique_source = None
      for place in net.places:
          if len(place.in_arcs) == 0:
              count_empty_input = count_empty_input + 1
              unique_source = place
      if count_empty_input == 1:
          return unique_source
      return None
  
  
  def check_sink_place_presence(net):
      """
      Check if there is a unique sink place with empty connections
  
      Parameters
      -------------
      net
          Petri net
  
      Returns
      -------------
      place
          Unique source place (or None otherwise)
      """
      count_empty_output = 0
      unique_sink = None
      for place in net.places:
          if len(place.out_arcs) == 0:
              count_empty_output = count_empty_output + 1
              unique_sink = place
      if count_empty_output == 1:
          return unique_sink
      return None
  
  
  def check_wfnet(net):
      """
      Check if the Petri net is a workflow net
  
      Parameters
      ------------
      net
          Petri net
  
      Returns
      ------------
      boolean
          Boolean value that is true when the Petri net is a workflow net
      """
      unique_source_place = check_source_place_presence(net)
      unique_sink_place = check_sink_place_presence(net)
      source_sink_reachability = check_source_and_sink_reachability(net, unique_source_place, unique_sink_place)
  
      return (unique_source_place is not None) and (unique_sink_place is not None) and source_sink_reachability
  
  
  def check_source_sink_place_conditions(net):
      """
      Check some conditions on the source/sink place important
      for a sound workflow net
  
      Parameters
      --------------
      net
          Petri net
  
      Returns
      --------------
      boolean
          Boolean value (True is good)
      """
      # check also that the transitions connected to the source/sink place have unique arcs
      unique_source_place = check_source_place_presence(net)
      unique_sink_place = check_sink_place_presence(net)
      if unique_source_place is not None:
          for arc in unique_source_place.out_arcs:
              trans = arc.target
              if len(trans.in_arcs) > 1:
                  return False
      if unique_sink_place is not None:
          for arc in unique_sink_place.in_arcs:
              trans = arc.source
              if len(trans.out_arcs) > 1:
                  return False
      return True
  
  
  def check_easy_soundness_net_in_fin_marking(net, ini, fin):
      """
      Checks the easy soundness of a Petri net having the initial and the final marking
  
      Parameters
      -------------
      net
          Petri net
      ini
          Initial marking
      fin
          Final marking
  
      Returns
      -------------
      boolean
          Boolean value
      """
      try:
          alignment = explore_path.__search(net, ini, fin)
          if alignment is not None:
              return True
          return False
      except:
          return False
  
  
  def check_easy_soundness_of_wfnet(net):
      """
      Checks the easy soundness of a workflow net
  
      Parameters
      -------------
      net
          Petri net
  
      Returns
      -------------
      boolean
          Boolean value
      """
      source = list(x for x in net.places if len(x.in_arcs) == 0)[0]
      sink = list(x for x in net.places if len(x.out_arcs) == 0)[0]
  
      ini = obj.Marking({source: 1})
      fin = obj.Marking({sink: 1})
  
      return check_easy_soundness_net_in_fin_marking(net, ini, fin)
  