All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ing.baker.petrinet.api.PetriNetAnalysis.scala Maven / Gradle / Ivy

The newest version!
package com.ing.baker.petrinet.api

import com.ing.baker.il.petrinet.{Place, Transition}

object PetriNetAnalysis {

  // indicates an unbounded token count in a place
  val W: Int = -1

  implicit class WMarkingOps[P](marking: MultiSet[P]) {
    // this checks if marking m 'covers' another
    def >=(other: MultiSet[P]): Boolean = other.forall {
      case (p, `W`) => marking.get(p).contains(W)
      case (p, n) => marking.get(p) match {
        case Some(m) => m >= n || m == W
        case _       => false
      }
    }
  }

  implicit class PetriNetOps(petriNet: PetriNet) {
    def removeTransitions(transitions: Iterable[Transition]): PetriNet = {
      val graph = transitions.foldLeft(petriNet.innerGraph) {
        case (acc, t) => acc.-(Right(t))
      }
      new PetriNet(graph)
    }
  }

  /**
   * A node in the coverability tree
   */
  class Node(
              var marking: MultiSet[Place],
              var isNew: Boolean,
              var children: Map[Transition, Node]) {

    // returns the path to a new node
    def newNode: Option[List[Node]] =
      if (isNew)
        Some(List(this))
      else
        children.values.view.map(_.newNode.map(nodes => this :: nodes)).find(_.isDefined).map(_.get)

    override def toString: String = {
      val markingString = marking.iterator.map { case (p, n) => s"$p -> ${if (n == W) 'W' else n}" }.mkString(",")
      s"marking: $markingString, children: $children"
    }

    def isCoverable(target: MultiSet[Place]): Boolean = {
      if (marking >= target)
        true
      else
        children.values.exists(_.isCoverable(target))
    }

    def maxNrTokens: Int = (marking.values ++ children.values.map(_.maxNrTokens)).max
  }

  def optimize(petrinet: PetriNet, m0: MultiSet[Place]): (PetriNet, MultiSet[Place]) = {

    val unboundedTransitions = unboundedEnabled(petrinet, m0)

    if (unboundedTransitions.isEmpty)
      (petrinet, m0)
    else {
      // remove the cold transitions to simplify things
      val updatedPetriNet = petrinet.removeTransitions(unboundedTransitions)

      val unboundedOut = unboundedTransitions.foldLeft(MultiSet.empty[Place]) {
        case (acc, t) => acc.multisetSum(petrinet.outMarking(t))
      }.map {
        case (p, _) => p -> W
      }

      optimize(updatedPetriNet, m0 ++ unboundedOut)
    }
  }

  def unboundedEnabled(petrinet: PetriNet, m0: MultiSet[Place]): Iterable[Transition] = {

    val coldTransitions = petrinet.transitions.filter(t => petrinet.incomingPlaces(t).isEmpty)
    val unboundedMarking = m0.filter { case (_, n) => n == W }
    val enabled = unboundedMarking.keys.map(petrinet.outgoingTransitions).reduceOption(_ ++ _).getOrElse(Set.empty).
      filter(t => unboundedMarking >= petrinet.inMarking(t))

    coldTransitions ++ enabled
  }

  /**
   * Implements page 47 of http://cpntools.org/_media/book/covgraph.pdf
   */
  def calculateCoverabilityTree(petrinet: PetriNet, m0: MultiSet[Place]): Node = {

    val (pn, initialMarking) = optimize(petrinet, m0)

    val coldTransitions = petrinet.transitions.filter(t => petrinet.incomingPlaces(t).isEmpty)
    val inMarking = pn.transitions.map(t => t -> petrinet.inMarking(t)).toMap
    val outMarking = pn.transitions.map(t => t -> petrinet.outMarking(t)).toMap

    def fire(m0: MultiSet[Place], t: Transition): MultiSet[Place] = {
      // unbounded places stay unchanged
      val (unbounded, bounded) = m0.partition { case (_, n) => n == W }

      bounded
        .multisetDifference(inMarking(t))
        .multisetSum(outMarking(t)) ++ unbounded
    }

    def enabledTransitions(m0: MultiSet[Place]): Iterator[Transition] = {

      val outAdjacent = m0.keys.map(pn.outgoingTransitions).reduceOption(_ ++ _).getOrElse(Set.empty).
        filter(t => m0 >= inMarking(t))

      outAdjacent.iterator
    }

    // 1. Label the initial marking M0 as the root and tag it 'new'
    val root = new Node(initialMarking, true, Map.empty)

    var newNode = root.newNode

    // while 'new' markings exist
    while (newNode.isDefined) {
      newNode.foreach { pathToM =>

        // the new node
        val node = pathToM.last

        // remove the 'new' tag
        node.isNew = false

        val M = node.marking

        // there exists no marking equal to m on the path from root to m
        if (!pathToM.dropRight(1).exists(_.marking == M)) {

          enabledTransitions(M).foreach { t =>

            // i. obtain the marking that results from firing t at M
            val postT: MultiSet[Place] = fire(M, t)

            // ii. if on the path to m there exists a marking that is covered by M1
            val coverableM: Option[MultiSet[Place]] =
              pathToM.map(_.marking).find(M11 => postT >= M11 && postT != M11)

            val M1: MultiSet[Place] = coverableM.map { M11 =>
              postT.map {
                case (p, n) if n > M11.getOrElse(p, 0) => p -> W
                case (p, n)                            => p -> n
              }
            }.getOrElse(postT)

            // iii. introduce M1 as a node
            val newNode: Node = new Node(M1, true, Map.empty)
            node.children += t -> newNode
          }
        }
      }

      newNode = root.newNode
    }

    root
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy