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)
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 __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 = []
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)
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
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
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
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)
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