diff --git a/learning/learning.py b/learning/learning.py index 7dc735c3a1347b562086dc0b592586eb89662e0b..cb0c9831330090afbeacf6c85570a0387a296a4e 100644 --- a/learning/learning.py +++ b/learning/learning.py @@ -81,19 +81,16 @@ class LearningAlgorithm: # cannot force it to happen. def updateTable(self): # First, try to avoid impossible traces: ask observation query - print("update") for trace in self._table.getObservableTraces(): observedOutputs = self._table.getOutputs(trace) observation = self._oracle.observation(trace, observedOutputs) if observation: self._table.updateEntry(trace, observation=observation) - print("done observable") # For all traces for which we did not observed all possible outputs oTraces = self._table.getObservableTraces() trie = th.make_trie(oTraces) - print("asking "+str(len(oTraces))+" membership queries") # Until we tried K times with no results, where K is the number of # observable traces times the number of outputs (including quiescence) K = len(oTraces) * 35 # (len(self._teacher.getOutputAlphabet()) + 1) # TODO comment from * @@ -264,11 +261,15 @@ class LearningAlgorithm: def stabilizeTable(self): - # While nothing changes, keep closing and consistent the table - print("Searching closing") + # While nothing changes, keep closing and making the table consistent closingRows = self._table.isNotGloballyClosed() + + if not closingRows: + if self._makeConsistent(): + self.updateTable() + closingRows = self._table.isNotGloballyClosed() + while closingRows: - print("Not closed") self._logger.debug("Table is not closed") self._logger.debug(closingRows) self._table.promote(closingRows) @@ -281,49 +282,23 @@ class LearningAlgorithm: closingRows = self._table.isNotGloballyClosed() if not closingRows: - print("Searching consistent") - consistentCheck = self._table.isNotGloballyConsistent() - # Table is closed, check for consistency - if consistentCheck: - print("Not consistent") - self._logger.debug("Table is not consistent") - self._logger.debug(consistentCheck) - self._table.addColumn(consistentCheck, force=True) - if self._logger.isEnabledFor(logging.DEBUG): - self._table.printTable(prefix="_i_") + if self._makeConsistent(): self.updateTable() closingRows = self._table.isNotGloballyClosed() - # - # while closingRows or consistentCheck: - # while closingRows: - # print("Not closed") - # self._logger.debug("Table is not closed") - # self._logger.debug(closingRows) - # self._table.promote(closingRows) - # # After promoting one should check if some one letter - # # extensions should also be added - # self._table.addOneLetterExtensions(closingRows) - # if self._logger.isEnabledFor(logging.DEBUG): - # self._table.printTable(prefix="_c_") - # self.updateTable() - # closingRows = self._table.isNotGloballyClosed() - # consistentCheck = self._table.isNotGloballyConsistent() - # # Table is closed, check for consistency - # if consistentCheck: - # print("Not consistent") - # self._logger.debug("Table is not consistent") - # self._logger.debug(consistentCheck) - # self._table.addColumn(consistentCheck, force=True) - # if self._logger.isEnabledFor(logging.DEBUG): - # self._table.printTable(prefix="_i_") - # # TODO: is an update needed here? in theory, when I encounter - # # an inconsistency, by adding a column, the interesting row - # # will immediately make the table not closed, no need of - # # update, right? - # #self.updateTable() - # closingRows = self._table.isNotGloballyClosed() - # consistentCheck = self._table.isNotGloballyConsistent() + def _makeConsistent(self): + consistentCheck = self._table.isNotGloballyConsistent() + # Table is closed, check for consistency + if consistentCheck: + self._logger.debug("Table is not consistent") + self._logger.debug(consistentCheck) + self._table.addColumn(consistentCheck, force=True) + if self._logger.isEnabledFor(logging.DEBUG): + self._table.printTable(prefix="_i_") + return True + else: + return False + def getHypothesis(self, chaos=False): # If table is not closed, ERROR @@ -339,16 +314,26 @@ class LearningAlgorithm: chaos) # assign to each equivalence class a state number # start with equivalence class of empty trace to 0 - assignments = {():0} + # TODO: is () always the representative of an equivalence class? + # NO + rowForEmpty = () + # search for the representative for () + for row in rows: + # TODO: the method of table called at next line is + # private. Change to public, or add a public version + if self._table._moreSpecificRow(row, rowForEmpty, chaos): + rowForEmpty = row + break + + assignments = {rowForEmpty:0} count = 1 for row in rows: - if row != (): + if row != rowForEmpty: assignments[row] = count count = count + 1 # add transitions for row in rows: - # TODO reduce allLabels set, use outputExpert enabledOuptuts = self._table.getOutputs(row) allLabels = self._getAllLabels(row, enabledOuptuts) @@ -356,16 +341,15 @@ class LearningAlgorithm: # create row and search it in the table extension = row + (label,) if self._table.isInRows(extension): + found = False for target in rows: - found = False # TODO: the method of table called at next line is # private. Change to public, or add a public version - if self._table._moreSpecificRow(extension, target, chaos): + if self._table._moreSpecificRow(target, extension, chaos): hyp.addTransition(assignments[row], label, assignments[target]) - found = True - break + # do not break here, for hPlus we add nondeterminism here if not found: self._logger.warning("Chaotic behaviour") # Either the table is not closed, or @@ -377,7 +361,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()) @@ -439,37 +423,27 @@ class LearningAlgorithm: self._currentLoop = self._currentLoop + 1 self._logger.info("Learning loop number " + str(self._currentLoop)) # Fill the table and make it closed and consistent - print("Updating") - self.updateTable() # TODO learning sometimes stops here! - print("Stabilizing") + self.updateTable() self.stabilizeTable() - print("Stabilized") # Is the table quiescence reducible? If not make it so and # then fill it again, make it closed and consitent - print("Reducing") newSuffixes = self._table.isNotQuiescenceReducible() while (newSuffixes or not self._table.isStable()): - print("Something to reduce") if newSuffixes: self._logger.debug("Table is not quiescence reducible") self._logger.debug(newSuffixes) if self._table.addColumn(newSuffixes, force=True): if self._logger.isEnabledFor(logging.DEBUG): self._table.printTable(prefix="_Q_") - print("Update in reduce") self.updateTable() - print("Stabilize after reducing") self.stabilizeTable() - print("Reducing again") newSuffixes = self._table.isNotQuiescenceReducible() - print("Done reducing") self._hMinus = self.getHypothesis() # If there are no observable traces, hMinus == hPlus if self._table.getObservableTraces() != set(): self._hPlus = self.getHypothesis(chaos=True) else: self._hPlus = self._hMinus - print("Hyp created") if self._printPath != None: self.generateDOT(path=self._printPath) diff --git a/learning/observationtable.py b/learning/observationtable.py index 1ae9a34b03b47b77ad60c003233d93d53b6cc834..429de12a9170219c5f46708e86e07f4b546ae9d2 100644 --- a/learning/observationtable.py +++ b/learning/observationtable.py @@ -675,11 +675,10 @@ class Table: return outputs # Return the set of outputs that can be observed after trace - # TODO: not clear the purpose of this method. rename? - @d.deprecated + # Return the second set. def getPossibleOutputs(self, trace): - if trace in self._entries and self._entries[trace][1]: - return self._entries[trace][0] + if trace in self._entries: + return self._entries[trace][1] else: return self._possibleOutputs(trace, None)