Commit c22861cf authored by Michele's avatar Michele

learning now works for 2 sets, still there is a problem in creating Hplus and...

learning now works for 2 sets, still there is a problem in creating Hplus and Hminus when not input complete
parent a9423172
......@@ -13,12 +13,6 @@ def bisimilar(system1, system2, startState1=0, startState2=0):
current = wait.pop()
past.add((current[0],current[1]))
system1Labels = system1.getInputs().union(system1.getOutputs())
system1Labels.add(system1.getQuiescence())
system2Labels = system2.getInputs().union(system2.getOutputs())
system2Labels.add(system2.getQuiescence())
enabledLabels_1 = set()
for state in current[0]:
enabledLabels_1 = enabledLabels_1.union(system1.outputs(state))
......
......@@ -56,7 +56,6 @@ class LearningAlgorithm:
# For all traces for which we did not observed all possible outputs
oTraces = self._table.getObservableTraces()
#prefixes = th.getPrefixes(oTraces)
trie = th.make_trie(oTraces)
# Until we tried K times with no results
......@@ -117,8 +116,7 @@ class LearningAlgorithm:
else:
# no input to process, ask an output
output = self._teacher.output()
#print("Ehi, we want an output for this trace " + str(currentTrace))
#print("Obtained output: "+str(output))
# we have an output for currentTrace, add it to observations
# this is the first output we observe for currentTrace
observations[currentTrace] = set([output])
......@@ -145,9 +143,7 @@ class LearningAlgorithm:
children = subtrie.keys()
inputChildren = [x for x in children \
if x in self._teacher.getInputAlphabet()]
#print("Current Trace: "+str(currentTrace))
#print("Children: " + str(children))
#print("Outputs of SUT (not processed some inputs?)"+ str(self._teacher._lts.outputs()))
if len(inputChildren) > 0:
# process this input, add it to consecutiveInputs
# and navigate subtrie
......@@ -172,7 +168,7 @@ class LearningAlgorithm:
else:
# no input to process, ask an output
output = self._teacher.output()
#print("Obtained output: "+str(output))
# we have an output for currentTrace,
# if currentTrace is in otraces add it to observations
if currentTrace in oTraces:
......@@ -184,7 +180,7 @@ class LearningAlgorithm:
# if that output is not a valid continuation
if output not in subtrie.keys():
#print("Not the one we wanted!")
# reset the process
currentTrace = ()
subtrie = trie
......@@ -224,10 +220,6 @@ class LearningAlgorithm:
if observation:
self._table.updateEntry(trace, observation=observation)
# # this update function uses teacher.process(trace)
# # in case a InputOutputTeacher is used, outputs in trace are forced to happen
# # this is not realistic, but still useful at the moment.
......@@ -269,7 +261,7 @@ class LearningAlgorithm:
output = self._teacher.oneOutput(consecutiveInputs)
if output == None:
# SUT did not accept an input.
self._logger.warning("SUT not input enabled")
self._logger.warning("SUT did not accept input in " + str(consecutiveInputs))
return None
return output
return self._teacher.output()
......@@ -551,4 +543,6 @@ class LearningAlgorithm2Sets(LearningAlgorithm):
self._table = DoubleSetTable(teacher.getInputAlphabet().copy(),
teacher.getOutputAlphabet().copy(),
teacher.getQuiescence(),
closeStrategy, logger=logger)
closeStrategy, logger=logger,
outputPurpose=outputPurpose,
inputPurpose=inputPurpose)
......@@ -263,7 +263,7 @@ class Table:
while wait:
current = wait.pop()
past.add((current[0],current[1]))
# TODO: input enabledness?
enabledRow2 = set()
if current[1] == "chaos_quiescence":
# If we are in chaos_quiescence only quiescence is enabled
......@@ -284,11 +284,7 @@ class Table:
# create set with current[2] and all its suffixes
for i in range(len(current[2])):
suffixes.add(current[2][i:])
# print("not reducible chaos = "+ str(chaos))
# print(str(row1)+" becomes "+str(current[0]))
# print(str(row2)+" becomes "+str(current[1]))
# print(suffixes)
# print(label)
return suffixes
else:
newRow1 = ""
......@@ -324,7 +320,7 @@ class Table:
# the first entry is (set(), false).
# In this case (it can only be an input) we
# send the input to chaotic_delta
if self._entries[current[1] + (label,)] == self._emptyEntry():
if self._entries[current[1] + (label,)] == self._emptyEntry(current[1] + (label,)):
newRow2 = "chaos_quiescence"
else:
self._logger.error("Quiescence reducibility check: cannot obtain new pair of states."
......@@ -427,15 +423,15 @@ class Table:
return changed
# get an empty nonfinal entry
def _emptyEntry(self):
def _emptyEntry(self, trace):
return (set(), False)
# get an empty final entry
def _emptyFinalEntry(self):
def _emptyFinalEntry(self, trace):
return (set(), True)
# get a final entry with given set of outputs
def _finalEntry(self, outputs):
def _finalEntry(self, trace, outputs):
return (outputs.copy(), True)
# is the entry final? If False, we can still observe some outputs
......@@ -458,7 +454,7 @@ class Table:
len(column) > 0 and column[0] in self._outputs):
# If row ends in delta and column starts with an output,
# then there are no outputs after row + column
self._entries[filteredEntry] = self._emptyFinalEntry()
self._entries[filteredEntry] = self._emptyFinalEntry(filteredEntry)
continue
allOutputs = self._outputs.union(set((self._quiescence,)))
if (len(column) > 0 and
......@@ -471,24 +467,24 @@ class Table:
# 'it could be impossible to obtain'
# if row + () is true, we will NEVER observe that
# output
self._entries[filteredEntry] = self._emptyFinalEntry()
self._entries[filteredEntry] = self._emptyFinalEntry(filteredEntry)
continue
# Otherwise add it to the dictionary
self._entries[filteredEntry] = self._emptyEntry()
self._entries[filteredEntry] = self._emptyEntry(filteredEntry)
# In some cases the entry can be filled without asking queries:
# If row + column ends with quiescence, then
# set the entry to ({delta},True)
if filteredEntry[-1] == self._quiescence:
quiescence = set()
quiescence.add(self._quiescence)
self._entries[filteredEntry] = self._finalEntry(quiescence)
self._entries[filteredEntry] = self._finalEntry(filteredEntry, quiescence)
# special case, row is a quiescence loop
if self._isDeltaLoop(row):
loopState = th.flatten(row[:-1] + column, self._quiescence)
if loopState in self._entries:
self._entries[filteredEntry] = self._entries[loopState]
else:
self._entries[filteredEntry] = self._emptyEntry()
self._entries[filteredEntry] = self._emptyEntry(filteredEntry)
# In a special case we are sure there is a quiescence loop
# If False is returned, then there is no certainty that there
......@@ -610,7 +606,7 @@ class DoubleSetTable(Table):
# _entries is a dictionary (tuple of actions) -> (set(outputs), set(outputs))
# start with the empty sequence of actions and one letter extensions
self._entries = {():(set(),self._possibleOutputs(()))}
for label in self._inputs:
for label in self._possibleInputs(()):
self._entries[tuple(label)] = (set(),self._possibleOutputs(tuple(label)))
# given a trace, query outputPurpose for the outputs that are possibly
......@@ -619,8 +615,7 @@ class DoubleSetTable(Table):
if self._outputPurpose == None:
return self._outputs.union(set((self._quiescence,)))
else:
#TODO
return self._outputs.union(set((self._quiescence,)))
return self._outputPurpose.getEnabled(trace)
# given a trace, query inputPurpose for the inputs that are
# enabled after it
......@@ -628,15 +623,26 @@ class DoubleSetTable(Table):
if self._inputPurpose == None:
return self._inputs
else:
#TODO
return self._inputs
return self._inputPurpose.getEnabled(trace)
def addOneLetterExtensions(self, rows):
modified = False
for row in rows:
for input in self._possibleInputs(row):
if self.addOneLetterExtension(row, input):
modified = True
outputs, observation = self._entries[row]
for output in outputs:
if self.addOneLetterExtension(row, output):
modified = True
return modified
# get an empty nonfinal entry
def _emptyEntry(self):
return (set(), self._outputs.union(set((self._quiescence,))))
def _emptyEntry(self, trace):
return (set(), self._possibleOutputs(trace))
# get an empty final entry
def _emptyFinalEntry(self):
def _emptyFinalEntry(self, trace):
return (set(), set())
# is the entry final? If False, we can still observe some outputs
......@@ -644,7 +650,7 @@ class DoubleSetTable(Table):
return self._entries[entry][0] == self._entries[entry][1]
# get a final entry with given set of outputs
def _finalEntry(self, outputs):
def _finalEntry(self, trace, outputs):
return (outputs.copy(), outputs.copy())
# Update an entry in the table. If it does not exist, create it
......@@ -663,8 +669,113 @@ class DoubleSetTable(Table):
if output != None:
outputs.add(output)
if observation == None or observation == False:
self._entries[trace] = self._emptyEntry()
self._entries[trace] = self._emptyEntry(trace)
observed = self._entries[trace][1]
else:
observed = outputs
self._entries[trace] = (outputs.copy(), observed.copy())
def _isNotReducible(self, chaos=False):
# function used for filtering rows, we want only those which
# have quiescence in their first column
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,
# I do not need to check it again for other loops. Thus past is outside
# the for loop.
past = set()
# filter rows
for row1 in filter(rowsWithQuiescence, self._rowsInS):
row1Extended = row1 + (self._quiescence,)
# filter for rows in top part of the table that are
# equivalent to row1Extended
equivalentRows = lambda x: lambda y: self._rowEquality(x, y, chaos)
row2 = next(filter(equivalentRows(row1Extended), self._rowsInS))
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
wait = set()
wait.add((row1, row2, ()))
while wait:
current = wait.pop()
past.add((current[0],current[1]))
enabledRow2 = set()
if current[1] == "chaos_quiescence":
# If we are in chaos_quiescence only quiescence is enabled
enabledRow2.add(self._quiescence)
else:
# input enabledness
enabledInputs = self._inputPurpose.getEnabled(current[1])
enabledRow2 = self._entries[current[1]][0].union(enabledInputs)
for label in enabledRow2:
enabledRow1 = set()
if current[0] == "chaos_quiescence":
# If we are in chaos_quiescence only quiescence is
# enabled
enabledRow1.add(self._quiescence)
else:
enabledInputs = self._inputPurpose.getEnabled(current[0])
enabledRow1 = self._entries[current[0]][0].union(enabledInputs)
if label not in enabledRow1:
suffixes = set()
suffixes.add(current[2])
# create set with current[2] and all its suffixes
for i in range(len(current[2])):
suffixes.add(current[2][i:])
return suffixes
else:
newRow1 = ""
if current[0] == "chaos_quiescence":
newRow1 = "chaos_quiescence"
else:
try:
newRow1 = next(filter(equivalentRows(current[0] + (label,)), self._rowsInS))
except StopIteration as error:
# current[0] + (label,) might not have
# corresponding row in S. This is the case when
# the first entry is emptyEntry.
# In this case (it can only be an input) we
# send the input to chaotic_delta
if self._entries[current[0] + (label,)] == self._emptyEntry:
newRow1 = "chaos_quiescence"
else:
self._logger.error("Quiescence reducibility check: cannot obtain new pair of states. Row1: "
+ str(current[0]) + " label: " + str(label))
self.printTable(prefix="_error")
self._logger.error("Table printed in ./tables/_error_table.csv")
self._logger.error(error)
raise
newRow2 = ""
if current[1] == "chaos_quiescence":
newRow2 = "chaos_quiescence"
else:
try:
newRow2 = next(filter(equivalentRows(current[1] + (label,)), self._rowsInS))
except StopIteration as error:
# current[1] + (label,) might not have
# corresponding row in S. This is the case when
# the first entry is (set(), false).
# In this case (it can only be an input) we
# send the input to chaotic_delta
if self._entries[current[1] + (label,)] == self._emptyEntry(current[1] + (label,)):
newRow2 = "chaos_quiescence"
else:
self._logger.error("Quiescence reducibility check: cannot obtain new pair of states."
+ " Row2: " + str(current[1]) + " label: " + str(label))
self.printTable(prefix="_error")
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,)))
return set()
......@@ -18,8 +18,8 @@ class TestLearningAlgorithm2:
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
inputs = set(['a','b'])
outputs = set(['x','y'])
inputs = set(['a','b', 'c', 'd'])
outputs = set(['x','y','z'])
quiescence = 'delta'
self.I1=InputOutputLTS(5, inputs, outputs, quiescence)
......@@ -46,23 +46,34 @@ class TestLearningAlgorithm2:
self.I1.addTransition(2,'a',3)
self.I1.addTransition(4,'y',0)
self.I1.makeInputEnabled()
#self.I1.makeInputEnabled()
self.T1 = InputOutputTeacher(self.I1)
self.O1 = InputOutputPowerOracle(self.I1)
self.tester = RandomTester(self.T1, 10000, 20)
self.tester = RandomTester(self.T1, 10000, 60)
outputExpert = OutputPurpose(outputs)
inputExpert = InputPurpose(inputs)
outputExpert = OutputPurpose(set(['x','y']))
inputExpert = InputPurpose(set(['a','b']))
currentdir = os.path.dirname(os.path.abspath(
inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
path = os.path.join(parentdir, "tests", "test_algo_2_sets")
self.L1 = LearningAlgorithm2Sets(self.T1, self.O1, self.tester,
currentdir = os.path.dirname(os.path.abspath(
inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
pathI1 = os.path.join(path, "I1")
gh.createDOTFile(self.I1, pathI1, "pdf")
self.L1 = LearningAlgorithm2Sets(self.T1, self.O1, self.tester, printPath=path,
logger=logger, tablePreciseness = 10000, outputPurpose=outputExpert,
inputPurpose=inputExpert)
def creation_test(self):
Hminus, Hplus = self.L1.run()
print(bi.bisimilar(self.I1,Hplus))
assert_equal(bi.bisimilar(self.I1,Hplus), True)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment