From 90600f0b2e565c1c52f93a7609b69d8b3a089454 Mon Sep 17 00:00:00 2001 From: Michele Date: Tue, 24 Nov 2015 18:12:40 +0100 Subject: [PATCH] working on existing methods to match new definitions --- learning/learning.py | 1 + learning/observationtable.py | 179 +++++++++++++++++++---------------- 2 files changed, 101 insertions(+), 79 deletions(-) diff --git a/learning/learning.py b/learning/learning.py index 72b1ff4..0e489ea 100644 --- a/learning/learning.py +++ b/learning/learning.py @@ -382,6 +382,7 @@ class LearningAlgorithm: if label in self._teacher.getInputAlphabet(): hyp.addTransition(assignments[row], label, hyp.getChaosDelta()) + # TODO: getPossibleOutputs is deprecated elif label in self._table.getPossibleOutputs(row): hyp.addTransition(assignments[row], label, hyp.getChaos()) diff --git a/learning/observationtable.py b/learning/observationtable.py index 0e2e97d..2fc9db6 100644 --- a/learning/observationtable.py +++ b/learning/observationtable.py @@ -20,7 +20,7 @@ import csv import os, inspect -from itertools import permutations, product +from itertools import permutations, product, combinations import logging import helpers.traces as th import helpers.deprecator as d @@ -285,15 +285,12 @@ class Table: return modified def isNotQuiescenceReducible(self): - newSuffixes = set() - hMinusSuffixes = self._isNotReducible() - newSuffixes = newSuffixes.union(hMinusSuffixes) - if newSuffixes != set(): + newSuffixes = self._isNotReducible() + if newSuffixes: # priority to hMinus suffixes return newSuffixes else: - hPlusSuffixes = self._isNotReducible(chaos=True) - return newSuffixes.union(hPlusSuffixes) + return self._isNotReducible(chaos=True) def _isNotReducible(self, chaos=False): # function used for filtering rows, we want only those which @@ -301,7 +298,7 @@ class Table: rowsWithQuiescence = lambda x: self._quiescence in self._entries[x][0] # past contains the pair of rows already visited. - # If I visited already a pair for one loop of the following for loop, + # If I visited already a pair for one loop, # I do not need to check it again for other loops. Thus past is outside # the for loop. past = set() @@ -313,21 +310,25 @@ class Table: moreSpecific = lambda x: lambda y: self._moreSpecificRow(y, x, chaos) listOfRows = list(filter(moreSpecific(row1Extended), self._rowsInS)) - # I want the most specific one - row2 = listOfRows.pop() - for row in listOfRows: - if self._moreSpecificRow(row, row2, chaos): - row2 = row - if row1 == row2: - # same row, simulation is trivial - continue - - if (row1, row2) in past: - # I already visited this pair. Go to next state enabling quiescence - continue + rowsToCheck = set() + # get representative of each equivalence class + eqClasses = self.getEquivalenceClasses(chaos) + for row in eqClasses: + if row in listOfRows: + rowsToCheck.add(row) wait = set() - wait.add((row1, row2, ())) + for row in rowsToCheck: + if row1 == row: + # same row, simulation is trivial + continue + + if (row1, row) in past: + # I already visited this pair. Go to next state enabling quiescence + continue + + wait.add((row1, row, ())) + while wait: current = wait.pop() past.add((current[0],current[1])) @@ -337,10 +338,9 @@ class Table: # If we are in chaos_quiescence only quiescence is enabled enabledRow2.add(self._quiescence) else: - # input enabledness - outputs = self._entries[current[1]][0] + outputs = self.getOutputs(current[1]) enabledInputs = self._possibleInputs(current[1], outputs) - enabledRow2 = self._entries[current[1]][0].union(enabledInputs) + enabledRow2 = outputs.union(enabledInputs) for label in enabledRow2: enabledRow1 = set() if current[0] == "chaos_quiescence": @@ -348,9 +348,9 @@ class Table: # enabled enabledRow1.add(self._quiescence) else: - outputs = self._entries[current[0]][0] + outputs = self.getOutputs(current[0]) enabledInputs = self._possibleInputs(current[0], outputs) - enabledRow1 = self._entries[current[0]][0].union(enabledInputs) + enabledRow1 = outputs.union(enabledInputs) if label not in enabledRow1: suffixes = set() suffixes.add(current[2]) @@ -365,13 +365,15 @@ class Table: newRow1 = "chaos_quiescence" else: try: - # the table is closed, there should be a row + # the table is closed, there should be some row # more specific than current[0] + (label,) in S listOfRows = list(filter(moreSpecific(current[0] + (label,)), self._rowsInS)) - newRow1 = listOfRows.pop() - for row in listOfRows: - if self._moreSpecificRow(row, newRow1, chaos): - newRow1 = row + newRow1 = set() + # get representative of each equivalence class + eqClasses = self.getEquivalenceClasses(chaos) + for row in eqClasses: + if row in listOfRows: + newRow1.add(row) except IndexError as error: # current[0] + (label,) might not have # corresponding row in S. This is the case when @@ -393,10 +395,12 @@ class Table: else: try: listOfRows = list(filter(moreSpecific(current[1] + (label,)), self._rowsInS)) - newRow2 = listOfRows.pop() - for row in listOfRows: - if self._moreSpecificRow(row, newRow2, chaos): - newRow2 = row + newRow2 = set() + # get representative of each equivalence class + eqClasses = self.getEquivalenceClasses(chaos) + for row in eqClasses: + if row in listOfRows: + newRow2.add(row) except IndexError as error: # current[1] + (label,) might not have # corresponding row in S. This is the case when @@ -412,40 +416,39 @@ class Table: self._logger.error("Table printed in ./tables/_error_table.csv") self._logger.error(error) raise - if (newRow1,newRow2) not in past and newRow1 != newRow2: - wait.add((newRow1, newRow2, current[2] + (label,))) + for (newSingle1,newSingle2) in product(newRow1, newRow2): + if (newSingle1,newSingle2) not in past and newSingle1 != newSingle2: + wait.add((newSingle1, newSingle2, current[2] + (label,))) return set() def isNotGloballyConsistent(self): - newSuffixes = set() - hMinusSuffixes = self._isNotConsistent() - newSuffixes.union(hMinusSuffixes) + newSuffixes = self._isNotConsistent() if newSuffixes: # priority to hMinus suffixes return newSuffixes else: - hPlusSuffixes = self._isNotConsistent(chaos=True) - return newSuffixes.union(hPlusSuffixes) + return self._isNotConsistent(chaos=True) def _isNotConsistent(self, chaos=False): - # TODO refactor entire method? - # get all combinations of rows in S, with repetitions - combi = permutations(self._rowsInS, 2) + # get all combinations of rows in S, with repetitions for hPlus + combi = combinations(self._rowsInS, 2) + if not chaos: + combi = permutations(self._rowsInS, 2) newColumns = set() for row1, row2 in combi: # If the rows are in the same equivalence class if self._moreSpecificRow(row1, row2, chaos): #row2 is in the equivalence class defined by row1 - # get all possible (enabled) labels + # get all possible (enabled) labels of row1 inputs = self._possibleInputs(row1) - outputs = self._entries[row1][0] - + outputs = self.getOutputs(row1) labels = inputs.union(outputs) + for label in labels: rowExt1 = th.flatten(row1 + (label,),self._quiescence) - rowExt2 = rowExt1 = th.flatten(row2 + (label,),self._quiescence) + rowExt2 = th.flatten(row2 + (label,),self._quiescence) # rowExt1 must be in Rows, because label is either an # enabled input after row, or an observed output. if rowExt1 not in self._rows: @@ -455,7 +458,6 @@ class Table: # of _moreSpecificRow() if chaos == False: # if we are checking Hminus, then there is an error - # TODO: might be that I did not add a one letter extension when needed? self._logger.error("Error while checking consistency for Hminus: the prefix "+str(rowExt1)+" should be a row, but it is not.") self.printTable(prefix="_error") self._logger.error("Table printed in ./tables/_error_table.csv") @@ -463,7 +465,7 @@ class Table: else: # if we are checking Hplus (and label is an output) # then there is a transition to a chaotic state - if label in self._possibleOutputs(row1): + if label in outputs: if label == self._quiescence: # rowExt1 is chaos_quiescence rowExt1 = "chaos_quiescence" @@ -471,22 +473,30 @@ class Table: # rowExt1 is chaos rowExt1 = "chaos" else: + # label is an input, error. self._logger.error("Error while checking consistency for Hplus: the prefix "+str(rowExt1)+" should be a row, but it is not.") self.printTable(prefix="_error") self._logger.error("Table printed in ./tables/_error_table.csv") if not rowExt2 in self._rows: - # rowExt2 is not in rows. If label is an input then - # row2 does not enable it: row1 should not be more - # specific than row2 - if label in inputs: - self._logger.error("Error while checking consistency: the prefix "+str(rowExt2)+" should be a row, but it is not.") + if chaos == False: + # if we are checking Hminus, then there is an error + self._logger.error("Error while checking consistency for Hminus: the prefix "+str(rowExt2)+" should be a row, but it is not.") self.printTable(prefix="_error") self._logger.error("Table printed in ./tables/_error_table.csv") raise - # If label is an output, then ok (being less specific, - # this is allowed) else: - continue + # rowExt2 is not in rows. If label is an input then + # row2 does not enable it: row1 should not be more + # specific than row2 + if label in inputs: + self._logger.error("Error while checking consistency: the prefix "+str(rowExt2)+" should be a row, but it is not.") + self.printTable(prefix="_error") + self._logger.error("Table printed in ./tables/_error_table.csv") + raise + # If label is an output, then ok (being less specific, + # this is allowed) + else: + continue # rowExt1 in self._rows and rowExt2 in self._rows # I could call self._moreSpecificRow() but then @@ -498,9 +508,15 @@ class Table: entry2 = th.flatten(rowExt2 + column, self._quiescence) if (rowExt1 == "chaos_quiescence" or rowExt1 == "chaos"): - #TODO: check if entry2+column has chaotic behaviour - print(erfefr) - continue + # We are checking hPlus + # Does entry2 has chaotic behaviour? + extendedOutputs = self._outputs.union((self._quiescence,)) + if self._entries[entry2] != extendedOutputs: + self._logger.warning("Checking chaotic behaviour of entry2: not chaotic") + newColumns.add((label,) + column) + break + else: + continue entry1 = th.flatten(rowExt1 + column, self._quiescence) if (entry1 not in self._entries and @@ -518,11 +534,11 @@ class Table: newColumns.add((label,) + column) break - if newColumns: - # TODO: only one suffix per round, ok? - break - if newColumns: - break + # if newColumns: + # # Only one suffix per round, ok? + # break + # if newColumns: + # break return newColumns @@ -565,7 +581,9 @@ class Table: # is the entry final? If False, we can still observe some outputs def _isFinal(self, entry): - # TODO: check if entries contains entry + if entry not in self._entries.keys(): + self._logger.warning("Checking if entry is final: entry is not in the table.") + return False return self._entries[entry][0] == self._entries[entry][1] # get a final entry with given set of outputs @@ -575,7 +593,6 @@ class Table: # Private method for creating entries from rows and columns def _createEntries(self): for row in self._rows: - # Row should be flattened. for column in self._columns: newEntry = row + column # If there are multiple quiescence in sequence, reduce to @@ -584,6 +601,7 @@ class Table: # Check if the concatenation of the two tuples is # already in _entries: if filteredEntry not in self._entries.keys(): + # Special cases: if (len(row) > 0 and row[-1] == self._quiescence and len(column) > 0 and column[0] in self._outputs): # If row ends in delta and column starts with an output, @@ -599,7 +617,7 @@ class Table: # if columns starts with output x (or quiescence) and # x not in entry[row+()] then mark row+column as # 'it could be impossible to obtain' - # if row + () is true, we will NEVER observe that + # if row + () is final, we will NEVER observe that # output self._entries[filteredEntry] = self._emptyFinalEntry(filteredEntry) continue @@ -627,10 +645,12 @@ class Table: # quiescence, then the entry for row is the same as the # entry for the proper prefix [VT15] if len(row) > 0 and row[-1] == self._quiescence: - # TODO: check that entries contains row[:-1] - (outputs, observed) = self._entries[row[:-1]] - if self._isFinal(row[:-1]) and outputs == set((self._quiescence,)): - return True + if row[:-1] not in self._entries.keys(): + self._logger.warning("Checking delta loops: shorter trace is not in the table.") + else: + (outputs, observed) = self._entries[row[:-1]] + if self._isFinal(row[:-1]) and outputs == set((self._quiescence,)): + return True return False def getDeltaTraces(self, trace): @@ -659,6 +679,7 @@ class Table: # Return the set of outputs that can be observed after trace # TODO: not clear the purpose of this method. rename? + @d.deprecated def getPossibleOutputs(self, trace): if trace in self._entries and self._entries[trace][1]: return self._entries[trace][0] @@ -666,6 +687,7 @@ class Table: return self._possibleOutputs(trace, None) # Update an entry in the table. If it does not exist, create it + # observation is the answer of the observation orale def updateEntry(self, trace, output=None, observation=None): trace = th.flatten(trace, self._quiescence) if trace in self._entries: @@ -682,10 +704,8 @@ class Table: outputs.add(output) if observation == None or observation == False: self._entries[trace] = self._emptyEntry(trace) - observed = self._entries[trace][1] else: - observed = outputs - self._entries[trace] = (outputs.copy(), observed.copy()) + self._entries[trace] = self._finalEntry(trace, outputs) # given a trace, query outputPurpose for the outputs that are possibly # enabled after it @@ -706,7 +726,8 @@ class Table: # Given two entries, check if the former is more specific than the latter # entry1 ⊑ entry2 => entry2.first is subset of entry1.first and entry1.second is subset of entry2.second def _moreSpecificEntry(self, entry1, entry2, plus): - # TODO: check if entry1 and entry2 are in entries + if (entry1 not in self._entries.values() or entry2 not in self._entries.values()): + self._logger.warning("Checking relation between entries: one or both are not in the table.") if not plus: return entry1[0] == entry2[0] # equality #return entry2[0].issubset(entry1[0]) # inclusion @@ -717,7 +738,7 @@ class Table: # row1 ⊑ row2 => for each column, row1[column] ⊑ row2[column] def _moreSpecificRow(self, row1, row2, plus=False): # First: if they do not enable the same inputs, they are not in such - # a relation + # a relation TODO: should this check be in moreSpecificEntry instead? outputs1 = self._entries[th.flatten(row1,self._quiescence)][0] outputs2 = self._entries[th.flatten(row2,self._quiescence)][0] inputs1 = self._possibleInputs(th.flatten(row1,self._quiescence), outputs1) -- GitLab