Exemple #1
0
    def support_exist(self, expr1, expr2, lst=False):
        if ((expr1 is None) or (expr1 == "")) or ((expr2 is None) or
                                                  (expr2 == "")):
            return [] if lst else None

        variables = re.sub('[~\&\|\(\)]', ' ', expr1 + expr2)
        variables = re.sub(' +', ' ', variables.strip())
        variables = variables.split(" ")

        bdd = BDD()
        for var in variables:
            bdd.add_var(var)

        expr1 = bdd.add_expr(expr1)
        expr2 = bdd.add_expr(expr2)
        u = bdd.exist(bdd.support(expr1), expr2)

        dnf = self.__get_dnf(bdd, u)

        conj = []
        for el in dnf:
            if el[0] is True:
                el = el[1:]
                el.reverse()
                conj.append("(%s)" % " & ".join(el))

        if lst:
            return conj

        return " | ".join(conj)
Exemple #2
0
class UVW:
    def __init__(self,ddMgr = None):
        """Initializes the UVW. There is always a fail state with no. 0"""
        self.stateNames = ["reject"] # Init with reject stat
        self.rejecting = [True]
        if ddMgr is None:
            self.ddMgr = BDD()
        else:
            self.ddMgr = ddMgr
        self.transitions = [[(0,self.ddMgr.true)]]
        self.initialStates = []
        self.propositions = []
        self.labelToStateAndActivatingMapper = {}

    def inGameSolverFormat(self, singleAction = False):

        resultLines = []

        if singleAction:
            allowedActions = self.ddMgr.true

            # At most one variable is true
            for varName in self.ddMgr.vars:
                for varNameOther in self.ddMgr.vars:
                    if varNameOther!=varName:
                        allowedActions = allowedActions & ( ~(self.ddMgr.var(varNameOther)) |  ~(self.ddMgr.var(varName)))

            # One of them is True
            thisOne = self.ddMgr.false
            for varName in self.ddMgr.vars:
                thisOne = thisOne | self.ddMgr.var(varName)

            # allowedActions = allowedActions & thisOne   <--- There could be actions not in the spec

        #Define STATES
        for i,a in enumerate(self.stateNames):
            flags = []
            if i in self.initialStates:
                flags.append("initial")
            if self.rejecting[i]:
                flags.append("reject")
            flags = " ".join(flags)
            if len(flags)>0:
                flags = " "+flags
            resultLines.append("State **TYPE** "+"q"+str(i)+flags)


        # Define transitions
        for i,a in enumerate(self.stateNames):
            for (j,b) in self.transitions[i]:

                while b!=self.ddMgr.false:
                    # Generate minterm for transition
                    rest = b
                    conditionParts = []
                    if singleAction:
                        if b==allowedActions:
                            conditionParts = ["TRUE"]
                            rest = self.ddMgr.true
                        else:
                            for p in self.propositions:
                                if ~(self.ddMgr.var(p)) & b == ~(self.ddMgr.var(p)) & allowedActions:
                                    conditionParts = ["! "+p]
                                    rest = self.ddMgr.true
                            if conditionParts==[]:
                                for p in self.propositions:
                                    if not (self.ddMgr.var(p) & b == self.ddMgr.false):
                                        conditionParts = [p]
                                        rest = self.ddMgr.var(p)
                            if conditionParts==[]:
                                # All other remaining case - Is there any?
                                print("C3",file=sys.stderr)
                                for p in self.propositions:
                                    if self.ddMgr.forall([p],(rest & self.ddMgr.var(p)))!=rest:
                                        if (rest & self.ddMgr.var(p))==self.ddMgr.false:
                                            conditionParts.append("! "+p)
                                            rest = rest & ~(self.ddMgr.var(p))
                                        else:
                                            conditionParts.append(p)
                                            rest = rest & (self.ddMgr.var(p))
                    else:
                        for p in self.propositions:
                            if self.ddMgr.forall([p],(rest & self.ddMgr.var(p)))!=rest:
                                if (rest & self.ddMgr.var(p))==self.ddMgr.false:
                                    conditionParts.append("! "+p)
                                    rest = rest & ~(self.ddMgr.var(p))
                                else:
                                    conditionParts.append(p)
                                    rest = rest & (self.ddMgr.var(p))
                    b = b & ~rest
                    if len(conditionParts)>0:
                        # We know that there can only be one event at a time -- Remove negated ones if possible
                        label = "& "*(len(conditionParts)-1)+" ".join(conditionParts)
                        resultLines.append("Transition **TYPE** q"+str(i)+" "+label+" q"+str(j))
                    else:
                        resultLines.append("Transition **TYPE** q"+str(i)+" TRUE q"+str(j))
        return "\n".join(resultLines)




    def toNeverClaim(self):
        # Never claim header
        resultLines = ["never { /* Subformulas covered by this Never Claim:"]
        for i in self.initialStates:
            if len(self.stateNames[i])>100:
                resultLines.append("  - "+self.stateNames[i][0:100]+"...")
            else:
                resultLines.append("  - "+self.stateNames[i])
        resultLines.append(" */")

        # Assign state names
        neverClaimStateNames = ["T"+str(i) for i in range(0,len(self.transitions))]
        assert self.rejecting[0]
        neverClaimStateNames[0] = "all"
        for i in self.initialStates:
            neverClaimStateNames[i] = neverClaimStateNames[i]+"_init"
        for i in range(0,len(self.transitions)):
            if self.rejecting[i]:
                neverClaimStateNames[i] = "accept_"+neverClaimStateNames[i]

        # Other states
        for i in range(1,len(self.transitions)):
            resultLines.append(neverClaimStateNames[i]+":")
            resultLines.append("  // "+str(self.stateNames[i]))
            resultLines.append("  if")
            for (a,b) in self.transitions[i]:
                while b!=self.ddMgr.false:
                    # Generate minterm for transition
                    rest = b
                    conditionParts = []
                    for p in self.propositions:
                        if self.ddMgr.exist([p],(rest & self.ddMgr.var(p)))!=rest:
                            if (rest & self.ddMgr.var(p))==self.ddMgr.false:
                                conditionParts.append("! "+p)
                                rest = rest & ~(self.ddMgr.var(p))
                            else:
                                conditionParts.append(p)
                                rest = rest & (self.ddMgr.var(p))
                    b = b & ~rest
                    if len(conditionParts)>0:
                        resultLines.append("  :: ("+" && ".join(conditionParts)+") -> goto "+neverClaimStateNames[a])
                    else:
                        resultLines.append("  :: (1) -> goto "+neverClaimStateNames[a])
            resultLines.append("  fi")

        # First state is special case
        resultLines.append(neverClaimStateNames[0]+":\n  skip")

        resultLines.append("}")
        return "\n".join(resultLines)

    @staticmethod
    def isBisimulationEquivalent(uvw1Pre,uvw2):
        # Make a copy as we are going to modify it - but the BDD manager must be the same
        uvw1 = UVW(uvw1Pre.ddMgr)
        uvw1.stateNames = copy.copy(uvw1Pre.stateNames)
        uvw1.rejecting = copy.copy(uvw1Pre.rejecting)
        uvw1.transitions = copy.copy(uvw1Pre.transitions)
        uvw1.initialStates = copy.copy(uvw1Pre.initialStates)
        uvw1.propositions = None # Not needed in the following - this is a temporary copy

        # Merge the new states in / the initial states stay unmerged
        oldNofStates = len(uvw1.rejecting)
        uvw1.rejecting = uvw1.rejecting + uvw2.rejecting
        uvw1.stateNames = uvw1.stateNames + uvw2.stateNames
        for i in range(0,len(uvw2.transitions)):
            uvw1.transitions.append([(a+oldNofStates,b) for (a,b) in uvw2.transitions[i]])

        # Get simulation relation
        (bottomUpOrder,reverseBottomUpOrder) = uvw1.getBottomUpAndReverseBottomUpOrder()
        backwardSimulation = uvw1.computeSimulationRelation(bottomUpOrder,reverseBottomUpOrder)

        # Check now that for each initial state in A there is one simulating one in B
        otherInitialStates = [a+oldNofStates for a in uvw2.initialStates]
        for (reference,duplicates) in [(uvw1.initialStates,otherInitialStates),(otherInitialStates,uvw1.initialStates)]:
            for a in reference:
                found = False
                for b in duplicates:
                    if backwardSimulation[reverseBottomUpOrder[a]][reverseBottomUpOrder[b]]:
                        found = True
                if not found:
                    return False

        # Found no counter-example ---> equivalent
        return True


    @staticmethod
    def parseFromUVWDescription(ddMgr,description):
        """Parses a UVW from a textual description consisting of the transitions of the UVW, where
           state names from which a transition originate can be labeled by whether they are rejecting and initial."""
        uvw = UVW(ddMgr)
        transitions = description.split(",")
        if transitions==[""]: # Special case: Nothing to parse ---> No transition
            transitions = []
        for transition in transitions:
            transition = transition.strip()
            (fromState,rest) = tuple(transition.split("-["))
            (condition,targetState) = tuple(rest.split("]->"))
            fromStateAttributes=""
            if "(" in fromState:
                (fromState,fromStateAttributes) = tuple(fromState.split("("))
            fromStateNo = int(fromState)
            toStateNo = int(targetState)

            # Make new state
            while len(uvw.rejecting)<=max(fromStateNo,toStateNo):
                newStateNr = len(uvw.rejecting)
                uvw.stateNames.append(str(newStateNr))
                uvw.rejecting.append(False)
                uvw.transitions.append([])

            # Add from state attributes
            for a in fromStateAttributes:
                if a==")" or a==",":
                    pass
                elif a=="i":
                    if not fromStateNo in uvw.initialStates:
                        uvw.initialStates.append(fromStateNo)
                elif a=="r":
                    uvw.rejecting[fromStateNo] = True
                else:
                    raise Exception("Unknown state attribute:"+str(a))

            # Parse transition
            uvw.transitions[fromStateNo].append((toStateNo,uvw.ddMgr.add_expr(condition)))
        return uvw


    def __str__(self):
        assert len(self.rejecting) == len(self.stateNames)
        assert len(self.transitions) == len(self.stateNames)
        result = "UVW with " + str(len(self.rejecting))+" states and initial states "+str(self.initialStates)+":\n"
        for i in range(0,len(self.rejecting)):
            result = result + " - State No. "+str(i)+" with name "+self.stateNames[i]
            if self.rejecting[i]:
                result = result + " (rej.)"
            result = result + ":\n"
            for a in self.transitions[i]:
                result = result + "   - t.t.s. "+str(a[0])+" for "+a[1].to_expr()+"\n"
        return result

    def __del__(self):
        # First delete the transitions, as only then, all BDD nodes are freed.
        del self.transitions
        self.ddMgr = None

    def addStateButNotNewListOfTransitionsForTheNewState(self,stateName,rejecting=False):
        self.stateNames.append(stateName)
        self.rejecting.append(rejecting)
        return len(self.stateNames)-1

    def makeTransientStatesNonRejecting(self):
        for i in range(0,len(self.stateNames)):
            if self.rejecting[i]:
                foundLoop = False
                for (a,b) in self.transitions[i]:
                    if a==i:
                        foundLoop = True
                if not foundLoop:
                    self.rejecting[i] = False

    def restrictToTheCaseThatThereCanOnlyBeOneActionAtATime(self):
        # Build BDD for the allowed character combinations
        allowedActions = self.ddMgr.true

        # At most one variable is true
        for varName in self.ddMgr.vars:
            for varNameOther in self.ddMgr.vars:
                if varNameOther!=varName:
                    allowedActions = allowedActions & ( ~(self.ddMgr.var(varNameOther)) |  ~(self.ddMgr.var(varName)))

        # One of them is True
        thisOne = self.ddMgr.false
        for varName in self.ddMgr.vars:
            thisOne = thisOne | self.ddMgr.var(varName)

        # allowedActions = allowedActions & thisOne <---- There could be actions not mentioned in the spec

        # Clean up the transitions
        oldTransitions = self.transitions
        for fromStateNo in range(0,len(oldTransitions)):
            t = []
            for (toStateNo,expr) in oldTransitions[fromStateNo]:
                expr = expr & allowedActions
                if expr != self.ddMgr.false:
                    t.append((toStateNo,expr))
            uvw.transitions[fromStateNo] = t


    def removeUnreachableStates(self):
        # Obtain list of reachable states
        # Assumes that there are no transitions labeled by FALSE
        reachable = [False for i in self.rejecting]
        reachable[0] = True
        todo = copy.copy(self.initialStates)
        for a in self.initialStates:
            reachable[a] = True
        while len(todo)>0:
            thisOne = todo[0]
            todo = todo[1:]
            for (a,b) in self.transitions[thisOne]:
                assert b!=self.ddMgr.false
                if not reachable[a]:
                    reachable[a] = True
                    todo.append(a)

        # Get renumeration list
        stateMapper = []
        stateMapperSoFar = 0
        for i in range(0,len(reachable)):
            if reachable[i]:
                stateMapper.append(stateMapperSoFar)
                stateMapperSoFar += 1
            else:
                stateMapper.append(None)

        # Move state numbers
        self.labelToStateAndActivatingMapper = {} # Purge as this modfies the automaton

        newStateNames = [self.stateNames[i] for i in range(0,len(self.stateNames)) if reachable[i]]
        self.stateNames = newStateNames

        # 2. Rejecting
        newRejecting = [self.rejecting[i] for i in range(0,len(self.rejecting)) if reachable[i]]
        self.rejecting = newRejecting

        # 3. Rejecting
        newTransitions = []
        for i in range(0,len(self.transitions)):
            if reachable[i]:
                newTransitions.append([])
                for (a,b) in self.transitions[i]:
                    newTransitions[-1].append((stateMapper[a],b))
        self.transitions = newTransitions

        # 4. Initial states
        self.initialStates = [stateMapper[a] for a in self.initialStates]


    def getBottomUpAndReverseBottomUpOrder(self):
        # 1. Compute a bottom-up ordering of all states
        bottomUpOrder = []
        while len(bottomUpOrder)!=len(self.stateNames):
            for i in range(0,len(self.stateNames)):
                allDone = reduce(lambda x,y: x and y, [a in bottomUpOrder or a==i for (a,b) in self.transitions[i]])
                if not i in bottomUpOrder and allDone:
                    bottomUpOrder.append(i)

        # 1b. Reverse bottom up order
        reverseBottomUpOrder = copy.copy(bottomUpOrder)
        for i in range(0,len(bottomUpOrder)):
            reverseBottomUpOrder[bottomUpOrder[i]] = i

        return (bottomUpOrder,reverseBottomUpOrder)


    def computeSimulationRelation(self,bottomUpOrder,reverseBottomUpOrder):
        # 2. Compute simulation relation
        simulationRelation = []
        for orderPosA in range(0,len(bottomUpOrder)):
            simulationForA = []
            simulationRelation.append(simulationForA)
            stateA = bottomUpOrder[orderPosA]
            for orderPosB in range(0,len(bottomUpOrder)):
                stateB = bottomUpOrder[orderPosB]

                # Check whether state A accepts at least as much as state B does
                # This means that for every action in automaton A, there must be one in B to a state that is at least as restrictive.
                # Double-Self-loops are fine if we do not have that state A is rejecting but state B is not
                simIsFine = True
                for (dest,destCond) in self.transitions[stateA]:
                    # Try to discharge all elements of destCond
                    restCond = destCond
                    for (destB,destCondB) in self.transitions[stateB]:
                        if dest==stateA and destB==stateB:
                            if (not self.rejecting[dest]) or self.rejecting[destB]:
                                restCond = restCond & ~(destCond & destCondB)
                        else:
                            posA = reverseBottomUpOrder[dest]
                            posB = reverseBottomUpOrder[destB]
                            if simulationRelation[posA][posB]:
                                restCond = restCond & ~(destCond & destCondB)
                    simIsFine = simIsFine and (restCond == self.ddMgr.false)

                simulationForA.append(simIsFine)
        return simulationRelation


    def findReachabilityImplyingStates(self,bottomUpOrder: typing.List[int]) -> typing.List[typing.List[bool]]:
        """Computes a list a such that a[i][j] is True whenever being in state i implies also being in state j."""
        reachabilityImplications = [[False for i in range(0,len(bottomUpOrder))] for j in range(0,len(bottomUpOrder))]

        # Compute inverse transitions
        inverseTransitions = [[] for i in bottomUpOrder]
        for i in range(0,len(self.transitions)):
            for (a,b) in self.transitions[i]:
                inverseTransitions[a].append((i,b))

        # Compute reachability implying states
        for aIndex in range(len(bottomUpOrder)-1,-1,-1):
            for bIndex in range(len(bottomUpOrder)-1,-1,-1):
                aState = bottomUpOrder[aIndex]
                bState = bottomUpOrder[bIndex]

                # Check if for every incoming transition to state "a", there is
                # a corresponding on the state "b"
                foundUnimplyingCase = False

                # Check initial
                if (aState in self.initialStates) and not (bState in self.initialStates):
                    foundUnimplyingCase = True

                # Check the rest
                for (a,b) in inverseTransitions[aState]:
                    rest = b
                    for (c,d) in inverseTransitions[bState]:

                        # Self-loops must be contained
                        if (a==aState) and (c==bState):
                            rest = rest & ~d
                        elif reachabilityImplications[a][c]:
                            rest = rest & ~d
                    if rest!=self.ddMgr.false:
                        foundUnimplyingCase = True

                # Store results
                reachabilityImplications[aState][bState] = not foundUnimplyingCase

        return reachabilityImplications

    def computeTransitiveClosureOfTransitionRelation(self):
        result = set([])

        for a in range(0,len(self.transitions)):
            result.add((a,a))
        for i,a in enumerate(self.transitions):
            for (b,c) in a:
                result.add((i,b))

        lastResult = set([])
        while len(result)!=len(lastResult):
            lastResult = result
            result = set([])
            for (a,b) in lastResult:
                result.add((a,b))
                for (c,d) in lastResult:
                    if c==b:
                        result.add((a,d))
        return result

    def mergeEquivalentlyReachableStates(self):

        # 1. Compute a bottom-up ordering of all states
        (bottomUpOrder,reverseBottomUpOrder) = self.getBottomUpAndReverseBottomUpOrder()

        # 2. Compute simulation relation
        forwardSimulation = self.findReachabilityImplyingStates(bottomUpOrder)

        # 2b. Compute transitive closure of transition relation
        transitiveClosureOfTransitionRelation = self.computeTransitiveClosureOfTransitionRelation()

        # 3. Print forward simulation
        # for i in range(0,len(self.transitions)):
        #    for j in range(0,len(self.transitions)):
        #        if forwardSimulation[i][j]:
        #            print("REachImply: "+str(bottomUpOrder[i])+","+str(bottomUpOrder[j]))
        #print("Bottom Up Order:",bottomUpOrder)
        #print("Closure:",transitiveClosureOfTransitionRelation)

        # 4. Forward bisimulation merge State Mapper Computation

        stateMapper = {}
        for j in range(len(bottomUpOrder)-1,-1,-1):
            for i in range(0,j):
                if forwardSimulation[bottomUpOrder[i]][bottomUpOrder[j]] and forwardSimulation[bottomUpOrder[j]][bottomUpOrder[i]]:
                    if not bottomUpOrder[i] in stateMapper:
                        if not (bottomUpOrder[j],bottomUpOrder[i]) in transitiveClosureOfTransitionRelation:
                            stateMapper[bottomUpOrder[i]] = bottomUpOrder[j]

        # print("StateMapper: ",stateMapper)
        # print("This means: ",[(bottomUpOrder[a],bottomUpOrder[b]) for (a,b) in stateMapper.items()])

        # 5. Redirect
        for i in range(0,len(bottomUpOrder)):
            if i in stateMapper:
                self.stateNames[stateMapper[i]] = "& "+ self.stateNames[stateMapper[i]] + " " + self.stateNames[i]
                for (a,b) in self.transitions[i]:
                    self.transitions[stateMapper[i]].append((a,b))
                self.transitions[i] = []


        # 6. Clean up
        self.removeStatesWithoutOutgoingTransitions()
        self.mergeTransitionsBetweenTheSameStates()
        self.removeUnreachableStates()

    def removeForwardReachableBackwardSimulatingStates(self):

        # 1. Compute a bottom-up ordering of all states
        (bottomUpOrder,reverseBottomUpOrder) = self.getBottomUpAndReverseBottomUpOrder()

        # 2. Compute simulation relation --> Indices are over the state numbers!
        forwardSimulation = self.findReachabilityImplyingStates(bottomUpOrder)

        # 2. Compute backward simulation relation --> Indices are over the order in the bottom up order
        backwardSimulation = self.computeSimulationRelation(bottomUpOrder,reverseBottomUpOrder)

        # 3. Find out which state is reachable from which other states
        reachabilityRelation = []
        for i in range(0,len(bottomUpOrder)):
            reachable = set([i])
            todo = set([i])
            while len(todo)>0:
                thisOne = todo.pop()
                for (a,b) in self.transitions[thisOne]:
                    if not a in reachable:
                        assert b!=self.ddMgr.false
                        reachable.add(a)
                        todo.add(a)
            thisList = []
            for a in range(0,len(bottomUpOrder)):
                thisList.append(a in reachable)
            reachabilityRelation.append(thisList)

        # Print reachability relation
        # print("Reachability Relation:")
        # for a in range(0,len(bottomUpOrder)):
        #     for b in range(0,len(bottomUpOrder)):
        #         if reachabilityRelation[a][b]:
        #             print ("Reach:",a,b)

        # 3. Print forward simulation
        # for i in range(0,len(self.transitions)):
        #     for j in range(0,len(self.transitions)):
        #         if forwardSimulation[i][j]:
        #             print("REachImply: "+str(i)+","+str(j))

        # print("Simulation:")
        # for orderPosA in range(0,len(bottomUpOrder)):
        #     for orderPosB in range(0,len(bottomUpOrder)):
        #         if (backwardSimulation[orderPosA][orderPosB]):
        #             print("States "+str(bottomUpOrder[orderPosA])+" "+str(bottomUpOrder[orderPosB])+" sim\n")


        # 4. Remove states that are removable
        for backwardSimulationPositionA in range(0,len(backwardSimulation)):
            for backwardSimulationPositionB in range(0,len(backwardSimulation)):
                if backwardSimulationPositionA!=backwardSimulationPositionB:
                    if backwardSimulation[backwardSimulationPositionA][backwardSimulationPositionB]:

                        # We know that A accepts at least as much as B
                        stateA = bottomUpOrder[backwardSimulationPositionA]
                        stateB = bottomUpOrder[backwardSimulationPositionB]

                        sys.stdout.flush()
                        if forwardSimulation[stateA][stateB]:
                            # We know that B is reached at least as easily as state A
                            # print("Merging candidates",stateA,"and",stateB,file=sys.stderr)
                            # old:
                            # if not reachabilityRelation[stateA][stateB] and not reachabilityRelation[stateB][stateA]:
                            if not reachabilityRelation[stateB][stateA]:
                                # Get rid of A
                                # print("Removing state",stateA,"because of state ",stateB,file=sys.stderr)
                                self.transitions[stateA] = []
                                # Reroute all transitions to State A to State B
                                for i in range(0,len(self.transitions)):
                                    self.transitions[i] = [(a if a!=stateA else stateB,b) for (a,b) in self.transitions[i]]

                            elif not reachabilityRelation[stateA][stateB] and self.rejecting[stateA] == self.rejecting[stateB]:
                                # print("Merger:",self.rejecting,file=sys.stderr)
                                # print("old uvw:",self,file=sys.stderr)
                                self.transitions[stateB].extend([(stateB,cond) for (a,cond) in self.transitions[stateA] if a==stateA])
                                self.transitions[stateB].extend([(a,cond) for (a,cond) in self.transitions[stateA] if a!=stateA])
                                self.transitions[stateA] = []
                                for i in range(0,len(self.transitions)):
                                    self.transitions[i] = [(a if a!=stateA else stateB,b) for (a,b) in self.transitions[i]]


        # Some states may have no outgoing transitions now. Remove them!
        self.removeStatesWithoutOutgoingTransitions()
        self.mergeTransitionsBetweenTheSameStates() # State merging could have caused a disbalance here.
        self.removeUnreachableStates() # State merging could have caused a disbalance here.


    def removeStatesWithoutOutgoingTransitions(self):
        """Removes all states without outgoing transitions. Assumes that all FALSE-transitions have already been removed,
           otherwise some states may remain"""
        assert len(self.rejecting)==len(self.transitions)
        assert len(self.rejecting)==len(self.stateNames)
        emptyStates = [len(self.transitions[i])==0 for i in range(0,len(self.transitions))]
        stateMapper = {}
        self.labelToStateAndActivatingMapper = {} # Purge as this modfies the automaton
        nofStatesSoFar = 0
        for i in range(0,len(self.transitions)):
            if emptyStates[i]:
                stateMapper[i] = None
            else:
                stateMapper[i] = nofStatesSoFar
                nofStatesSoFar += 1
        self.transitions = [[(stateMapper[a],b) for (a,b) in self.transitions[i] if not emptyStates[a]] for i in range(0,len(self.transitions)) if not emptyStates[i]]
        self.stateNames = [self.stateNames[i] for i in range(0,len(self.stateNames)) if not emptyStates[i]]
        self.rejecting = [self.rejecting[i] for i in range(0,len(self.rejecting)) if not emptyStates[i]]
        self.initialStates = [stateMapper[a] for a in self.initialStates if not emptyStates[a]]
        assert len(self.rejecting)==len(self.transitions)
        assert len(self.rejecting)==len(self.stateNames)

    def mergeTransitionsBetweenTheSameStates(self):
        for i in range(0,len(self.transitions)):
            allTrans = {}
            for (a,b) in self.transitions[i]:
                if a in allTrans:
                    allTrans[a] = allTrans[a] | b
                else:
                    if b!=self.ddMgr.false:
                        allTrans[a] = b
            self.transitions[i] = [(a,allTrans[a]) for a in allTrans]

    def simulationBasedMinimization(self):
        """Perform simulation-based minimization. States that are bisimilar are always made equivalent"""

        # 1. Compute a bottom-up ordering of all states
        (bottomUpOrder,reverseBottomUpOrder) = self.getBottomUpAndReverseBottomUpOrder()

        # 2. Compute simulation relation
        simulationRelation = self.computeSimulationRelation(bottomUpOrder,reverseBottomUpOrder)

        # 3. Print Simulation relation
        # print("Simulation:")
        # for orderPosA in range(0,len(bottomUpOrder)):
        #     for orderPosB in range(0,len(bottomUpOrder)):
        #         if (simulationRelation[orderPosA][orderPosB]):
        #             print("States "+str(bottomUpOrder[orderPosA])+" "+str(bottomUpOrder[orderPosB])+" sim\n")

        # 4. Minimize using bisimulation
        #    -> prepare state mapper
        stateMapper = {}
        for j in range(0,len(bottomUpOrder)):
            found = False
            for i in range(0,j):
                if simulationRelation[i][j] and simulationRelation[j][i]:
                    if not found:
                        stateMapper[bottomUpOrder[j]] = bottomUpOrder[i]
                        found = True
            if not found:
                stateMapper[bottomUpOrder[j]] = bottomUpOrder[j]

        # Renumber according to state wrapper
        for i in range(0,len(bottomUpOrder)):
            newTransitions = []
            for (a,b) in self.transitions[i]:
                newTransitions.append((stateMapper[a],b))
            self.transitions[i] = newTransitions
        self.initialStates = [stateMapper[a] for a in self.initialStates]


    def decomposeIntoSimpleChains(self):
        self.mergeTransitionsBetweenTheSameStates()
        result = []

        def recurse(result,pathSoFar):
            # print("Rec:",pathSoFar)
            lastState = pathSoFar[-1]
            foundSucc = False
            foundSelfLoop = False
            for (a,b) in self.transitions[lastState]:
                if a==lastState:
                    foundSelfLoop=True
                    pathSoFar.append(b)
                    # print("MK:",a,b.to_expr())
            if not foundSelfLoop:
                pathSoFar.append(None)
            for (a,b) in self.transitions[lastState]:
                if a!=lastState:
                    foundSucc = True
                    recurse(result,pathSoFar+[b,a])
            if not foundSucc:
                # final!
                novo = UVW(self.ddMgr)
                novo.propositions = copy.copy(self.propositions)
                for i in range(0,(len(pathSoFar)+1)//3):
                    currentState = pathSoFar[i*3]
                    # print("CS:",currentState,pathSoFar[i*3+1])
                    if currentState!=0:
                        novo.addStateButNotNewListOfTransitionsForTheNewState(self.stateNames[currentState],self.rejecting[currentState])
                        novo.transitions.append([])
                        stateNo = len(novo.transitions)-1
                    else:
                        stateNo = 0
                    if (i==0):
                        novo.initialStates.append(stateNo)
                    if not pathSoFar[i*3+1] is None:
                        novo.transitions[stateNo].append((stateNo,pathSoFar[i*3+1]))
                        # print("Reg:",pathSoFar[i*3+1].to_expr(),stateNo,i)
                    if len(pathSoFar)>i*3+2:
                        if pathSoFar[i*3+3]==0:
                            nextState = 0
                        else:
                            nextState = stateNo+1
                        novo.transitions[stateNo].append((nextState,pathSoFar[i*3+2]))
                        # print("TRANS:",stateNo,nextState,pathSoFar[i*3+2].to_expr())
                novo.mergeTransitionsBetweenTheSameStates()
                result.append(novo)

        for i in self.initialStates:
            recurse(result,[i])
        return result