Esempio n. 1
0
    def simplify(self, strformula, lst=False):
        if (strformula is None) or (strformula == ""):
            return [] if lst else None
        variables = re.sub('[~\&\|\(\)]', ' ', strformula)
        variables = re.sub(' +', ' ', variables.strip())
        variables = variables.split(" ")

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

        u = bdd.add_expr(strformula)
        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)
Esempio n. 2
0
 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 = {}
Esempio n. 3
0
 def __init__(self,
              total_vars=10,
              total_actions=10,
              max_value=10,
              with_bdd=False):
     self.total_vars = total_vars
     self.total_actions = total_actions
     self.aobs = AOBS()
     self.max_value = max_value
     self.bdd = BDD()
     self.bdd_expr = None
     self.bdd_var_names = [self.var_name(i) for i in range(self.total_vars)]
     self.with_bdd = with_bdd
     self.steps_made = 0
     self.belief_state_size_threshold = 1e6
     self.sizes_result = []
Esempio n. 4
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)
Esempio n. 5
0
 def __init__(self, numberOfClasses, numberOfNeurons, omittedNeuronIndices = {}):
 
     self.bdd = BDD()
     
     self.numberOfClasses = numberOfClasses
     self.numberOfNeurons = numberOfNeurons
     self.omittedNeuronIndices = omittedNeuronIndices 
     self.coveredPatterns = {}
     
     # For each classified class, create a BDD to be monitored
     for i in range(numberOfClasses):
         self.coveredPatterns[i] = self.bdd.false
Esempio n. 6
0
import pickle
import pdb
trace = pdb.set_trace
import z3
import kmaxtools.vcommon as CM
from dd.autoref import BDD

import kmaxtools.settings
mlog = CM.getLogger(__name__, kmaxtools.settings.logger_level)

# warning: this is not well encapsulated.  multiple runs in same process may not work properly
bdd_lib = BDD()


# todo: implement with dd package
def bdd_one():
    return bdd_lib.true


def bdd_zero():
    return bdd_lib.false


def bdd_ithvar(i):
    bdd_lib.add_var(i)
    return bdd_lib.var(i)


def bdd_init():
    pass
Esempio n. 7
0
class RandomExploration:
    def __init__(self,
                 total_vars=10,
                 total_actions=10,
                 max_value=10,
                 with_bdd=False):
        self.total_vars = total_vars
        self.total_actions = total_actions
        self.aobs = AOBS()
        self.max_value = max_value
        self.bdd = BDD()
        self.bdd_expr = None
        self.bdd_var_names = [self.var_name(i) for i in range(self.total_vars)]
        self.with_bdd = with_bdd
        self.steps_made = 0
        self.belief_state_size_threshold = 1e6
        self.sizes_result = []
        # TODO: 0 <= values < self.max_value !

    @staticmethod
    def var_name(i):
        if i < 26:
            return chr(ord('a') + i)
        else:
            return RandomExploration.var_name(
                i // 26) + RandomExploration.var_name(i % 26)

    def initialize_aobs(self, physical_state):
        self.aobs.root = self.aobs.hash_recursive(
            ['a'] + [[i, v] for i, v in enumerate(physical_state)])

    def act_bdd(self, preconditions, action, bdd=None):
        bdd_precs = self.assignment_bdd(*zip(*preconditions))
        # print(bdd_precs.to_expr())
        # poss, negs = self.assignment_bdd_(*zip(*preconditions))
        # print("only poss or negs: ", (self.bdd_expr & poss).dag_size, (self.bdd_expr & negs).dag_size)
        # print("before prec size: ", self.bdd_expr.dag_size)
        # print("after prec size: ", (self.bdd_expr & bdd_precs).dag_size)
        if bdd is None:
            self.bdd_expr = (self.bdd_expr & ~bdd_precs) | self.act_on_bdd(
                self.bdd_expr & bdd_precs, action)
        else:
            return self.act_on_bdd(bdd & bdd_precs, action)

    def act_on_bdd(self, bdd, action):
        variables = list(zip(*action[1][1][1:]))[0]
        bdd_effects = (self.assignment_bdd(*zip(*effect[1:]))
                       for _, effect in action[1:])
        bdd_action = functools.reduce(operator.or_, bdd_effects)
        # print(variables)
        # print("size before: ", bdd.dag_size)
        bdd_cleaned = self.clean_vars_bdd(bdd, variables)
        # print("cleaned size: ", bdd_cleaned.dag_size)
        # print("after action size: ", (bdd_cleaned & bdd_action).dag_size)
        return bdd_cleaned & bdd_action

    def clean_vars_bdd(self, bdd, variables):
        # print("to clean: ", variables)
        bvars = (self.bdd_var_names[var] + str(val)
                 for var, val in itertools.product(variables,
                                                   range(self.max_value)))
        for bv in bvars:
            bdd = self.bdd.let({bv: True}, bdd) | self.bdd.let({bv: False},
                                                               bdd)
        return bdd

    def assignment_bdd(self, vars, vals):
        kvs = {v: u for v, u in zip(vars, vals)}
        # print("vars: ", vars)
        # print("vals: ", vals)

        bvars = list(self.bdd_var_names[var] + str(val)
                     for var, val in zip(vars, vals))
        # print(bvars)
        positives = functools.reduce(operator.and_, (self.bdd.add_expr(bvar)
                                                     for bvar in bvars))
        negatives_bvars = list(
            self.bdd_var_names[var] + str(val)
            for var, val in itertools.product(vars, range(self.max_value))
            if kvs[var] != val)
        # print(negatives_bvars)
        negatives = functools.reduce(operator.and_,
                                     (~self.bdd.add_expr(bvar)
                                      for bvar in negatives_bvars))
        return positives & negatives

    def assignment_bdd_(self, vars, vals):
        kvs = {v: u for v, u in zip(vars, vals)}
        # print("vars: ", vars)
        # print("vals: ", vals)

        bvars = list(self.bdd_var_names[var] + str(val)
                     for var, val in zip(vars, vals))
        # print(bvars)
        positives = functools.reduce(operator.and_, (self.bdd.add_expr(bvar)
                                                     for bvar in bvars))
        negatives_bvars = list(
            self.bdd_var_names[var] + str(val)
            for var, val in itertools.product(vars, range(self.max_value))
            if kvs[var] != val)
        # print(negatives_bvars)
        negatives = functools.reduce(operator.and_,
                                     (~self.bdd.add_expr(bvar)
                                      for bvar in negatives_bvars))
        return positives, negatives

    def initialize_bdd(self, physical_state):
        for var, val in itertools.product(self.bdd_var_names,
                                          range(self.max_value)):
            self.bdd.add_var(var + str(val))
        self.bdd_expr = self.assignment_bdd(*zip(*enumerate(physical_state)))

    def random_init(self):
        ps = [
            random.randint(0, self.max_value - 1)
            for _ in range(self.total_vars)
        ]
        self.initialize_aobs(ps)
        if self.with_bdd:
            self.initialize_bdd(ps)
        return ps

    def generate_random_action(self, precs, total, each):
        preconds = {}
        while len(preconds) < precs:
            preconds[random.randint(0, self.total_vars - 1)] = random.randint(
                0, self.max_value - 1)
        preconditions = [(var, lambda v, val=val: v == val)
                         for var, val in preconds.items()]
        preconditions_bdd = list(preconds.items())
        eff_vars = set()
        while len(eff_vars) < each:
            eff_vars.add(random.randint(0, self.total_vars - 1))
        probs = [random.random() for _ in range(total)]
        norm = sum(probs)
        probs = [p / norm for p in probs]
        action = ['o'] + [
            (p, ['a'] + [[v, random.randint(0, self.max_value - 1)]
                         for v in eff_vars]) for p in probs
        ]
        return preconditions, action, preconditions_bdd

    def random_exploration(self,
                           steps=20,
                           precs=3,
                           effects=3,
                           each=2,
                           each_step=False):
        initial_state = self.random_init()
        actions_applied = []
        j = 0
        self.steps_made = 0
        while len(actions_applied) < steps:
            preconditions, action, preconditions_bdd = self.generate_random_action(
                precs, effects, each)
            if self.aobs.prob(preconditions) > 0:
                # print("aobs nodes before: ", self.aobs.dag_size())
                self.aobs.act(preconditions, action)
                # print("aobs nodes after: ", self.aobs.dag_size())
                # print("aobs states: ", self.aobs.count_physical_states_real())
                if self.with_bdd:
                    self.act_bdd(preconditions_bdd, action)
                actions_applied.append((preconditions_bdd, action))
                j = 0
                self.steps_made = len(actions_applied)
                if each_step:
                    self.sizes_result.append(
                        dict(total=self.total_vars *
                             self.aobs.count_physical_states_real(),
                             aobs=self.aobs.dag_size(),
                             aobs_greedy=self.aobs.size_after_greedy(
                                 include_prob=True),
                             total_naive_dup=self.aobs.
                             estimate_number_of_states() * self.total_vars,
                             bdd=self.bdd_expr.dag_size))
                if self.aobs.estimate_number_of_states(
                ) * self.total_vars > self.belief_state_size_threshold:
                    return True
                # print(len(actions_applied))
            else:
                j += 1
                if j > 10000:
                    return False
        return True
Esempio n. 8
0
from pyeda.inter import *
from dd.autoref import BDD

bdd = BDD()
bdd.declare('x', 'y', 'z', 'w')

f = expr("a & b | a & c | b & c")
f = expr2bdd(f)

print(f)
Esempio n. 9
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