@Override
public LearnerGraph learnMachine()
{
final Configuration shallowCopy = getTentativeAutomaton().config.copy();shallowCopy.setLearnerCloneGraph(false);
ptaHardFacts = new LearnerGraph(shallowCopy);// this is now cloned to eliminate counter-examples added to ptaSoftFacts by Spin
SpinUtil spin = null;
LearnerGraph.copyGraphs(getTentativeAutomaton(), ptaHardFacts);
LearnerGraph ptaSoftFacts = getTentativeAutomaton();
setChanged();getTentativeAutomaton().setName(getGraphName()+"_init");
final List<List<Label>> extraTracesPlus = new LinkedList<List<Label>>(), extraTracesMinus = new LinkedList<List<Label>>();
if (config.isUseConstraints())
{
LearnerGraph updatedTentativeAutomaton = new LearnerGraph(shallowCopy);
StringBuffer counterExampleHolder = new StringBuffer();
if (ifthenAutomata == null)
ifthenAutomata = Transform.buildIfThenAutomata(ifthenAutomataAsText, alphabetUsedForIfThen, ptaHardFacts, config, topLevelListener.getLabelConverter()).toArray(new LearnerGraph[0]);
if (!topLevelListener.AddConstraints(getTentativeAutomaton(),updatedTentativeAutomaton,counterExampleHolder))
throw new IllegalArgumentException(getHardFactsContradictionErrorMessage(ifthenAutomataAsText, counterExampleHolder.toString()));
setTentativeAutomaton(updatedTentativeAutomaton);
}
if (getTentativeAutomaton().config.getUseLTL() && getTentativeAutomaton().config.getUseSpin() && !ifthenAutomataAsText.isEmpty()){
spin = new SpinUtil(config,getLabelConverter());
SpinResult sr = spin.check(ptaHardFacts, ifthenAutomataAsText);
if(!sr.isPass())
throw new IllegalArgumentException(getHardFactsContradictionErrorMessage(ifthenAutomataAsText, sr.getCounters()));
}
Stack<PairScore> possibleMerges = topLevelListener.ChooseStatePairs(getTentativeAutomaton());
int iterations = 0, currentNonAmber = ptaHardFacts.getStateNumber()-ptaHardFacts.getAmberStateNumber();
JUConstants colourToAugmentWith = getTentativeAutomaton().config.getUseAmber()? JUConstants.AMBER:null;
updateGraph(getTentativeAutomaton(),ptaHardFacts);
while (!possibleMerges.isEmpty())
{
iterations++;
PairScore pair = possibleMerges.pop();
final LearnerGraph temp = topLevelListener.MergeAndDeterminize(getTentativeAutomaton(), pair);
Collection<List<Label>> questions = new LinkedList<List<Label>>();
long score = pair.getScore();
RestartLearningEnum restartLearning = RestartLearningEnum.restartNONE;// whether we need to rebuild a PTA and restart learning.
if (getTentativeAutomaton().config.getUseLTL() && getTentativeAutomaton().config.getUseSpin() && !ifthenAutomataAsText.isEmpty()){
Collection<List<Label>> counterExamples = spin.check(temp, getTentativeAutomaton(), ifthenAutomataAsText).getCounters();
Iterator<List<Label>> counterExampleIt = counterExamples.iterator();
while(counterExampleIt.hasNext())
{
List<Label> counterExample = counterExampleIt.next();
topLevelListener.AugmentPTA(ptaSoftFacts, RestartLearningEnum.restartSOFT, counterExample, false,colourToAugmentWith);
System.out.println("<temp> "+counterExample);
}
if(counterExamples.size()>0)
restartLearning = RestartLearningEnum.restartSOFT;
}
if (config.isUseConstraints())
{
LearnerGraph updatedTentativeAutomaton = new LearnerGraph(shallowCopy);
StringBuffer counterExampleHolder = new StringBuffer();
if (!topLevelListener.AddConstraints(temp,updatedTentativeAutomaton,counterExampleHolder))
{
getTentativeAutomaton().addToCompatibility(pair.firstElem, pair.secondElem, PAIRCOMPATIBILITY.INCOMPATIBLE);
restartLearning = RestartLearningEnum.restartRECOMPUTEPAIRS;
System.out.println("<info> pair "+pair+" contradicts constraints, hence recorded as incompatible");
}
// since we still need the outcome of merging to ask questions,
// we delay actually performing augmentation until the time we are
// finished with questions.
}
Iterator<List<Label>> questionIt = null;
if (restartLearning == RestartLearningEnum.restartNONE)
{
Map<CmpVertex,JUConstants.PAIRCOMPATIBILITY> compatibilityMap = ptaHardFacts.pairCompatibility.compatibility.get(pair.getQ());
if (pair.getScore() > config.getScoreForAutomergeUponRestart() && compatibilityMap != null && compatibilityMap.get(pair.getR()) == JUConstants.PAIRCOMPATIBILITY.MERGED)
System.out.println("<automerge of previously merged pair> "+pair);
else
{
// ask questions if needed
if (shouldAskQuestions(score))
{
temp.setName(getGraphName()+"_"+iterations);
//LearnerGraph updatedGraphActual = ComputeQuestions.constructGraphWithQuestions(pair, tentativeAutomaton, temp);
//updatedGraphActual.setName(getGraphName(config)+"questions "+iterations);setChanged();updateGraph(updatedGraphActual,ptaHardFacts);
questions = topLevelListener.ComputeQuestions(pair, getTentativeAutomaton(), temp);// all answers are considered "hard", hence we have to ask questions based on hard facts in order to avoid prefixes which are not valid in hard facts
questionIt = questions.iterator();
if (questionIt.hasNext())
{
pair.firstElem.setHighlight(true);
pair.secondElem.setHighlight(true);
updateGraph(getTentativeAutomaton(),ptaHardFacts);
pair.firstElem.setHighlight(false);
pair.secondElem.setHighlight(false);
}
}
}
}
while (restartLearning == RestartLearningEnum.restartNONE && questionIt != null && questionIt.hasNext())
{
List<Label> question = questionIt.next();
boolean accepted = pair.getQ().isAccept();
Pair<Integer,String> answer = null;
if (getTentativeAutomaton().config.getUseLTL() && getTentativeAutomaton().config.getUseSpin() && !ifthenAutomataAsText.isEmpty())
answer = new Pair<Integer,String>(spin.check(question, ifthenAutomataAsText),null);
CmpVertex tempVertex = temp.getVertex(question);
boolean answerFromSpin = false;
if(answer != null && answer.firstElem >= 0)
answerFromSpin = true;
else
{
if (GlobalConfiguration.getConfiguration().isAssertEnabled())
if (ptaHardFacts.paths.tracePathPrefixClosed(question) == AbstractOracle.USER_ACCEPTED) {
throw new IllegalArgumentException("question "+ question+ " has already been answered");
}
List<Boolean> acceptedElements = PathRoutines.mapPathToConfirmedElements(ptaHardFacts,question,ifthenAutomata);
answer = topLevelListener.CheckWithEndUser(getTentativeAutomaton(), question,
tempVertex.isAccept()?AbstractOracle.USER_ACCEPTED:question.size() - 1,
acceptedElements, pair,
new Object[] { "LTL","IFTHEN","IGNORE QUESTION","MARK AS INCOMPATIBLE","Add trace"});
}
if (answer.firstElem == AbstractOracle.USER_CANCELLED)
{
System.out.println("CANCELLED");
return null;
}
else
if (answer.firstElem == AbstractOracle.USER_IGNORED)
{// do nothing
restartLearning = RestartLearningEnum.restartNONE;
}
else
if (answer.firstElem == AbstractOracle.USER_INCOMPATIBLE)
{
/* When autoanswers says that a particular pair is incompatible, <em>StoredAnswers</em> the answer refers to the current
* question, but we have to return a pair which is incompatible. Although strange, it is not really surprising - the whole
* AutoAnswers framework aims to automate debugging hence we are expected to go through the same sequence of questions
* over and over again - it is enough to validate that we do not deviate and hence all that is necessary is to check that
* the current pair to be merged is the same as the one recorded as incompatible when an answer was recorded.
*/
getTentativeAutomaton().addToCompatibility(pair.firstElem, pair.secondElem, PAIRCOMPATIBILITY.INCOMPATIBLE);
restartLearning = RestartLearningEnum.restartRECOMPUTEPAIRS;
}
else
if (answer.firstElem == AbstractOracle.USER_NEWTRACE)
{
String traceDescr = answer.secondElem;
boolean obtainedViaAuto = answer.secondElem != null;
if (traceDescr == null) traceDescr = JOptionPane.showInputDialog("New trace :");
if(traceDescr != null && traceDescr.length() != 0)
{
final JUConstants colour = colourToAugmentWith;
final AtomicBoolean whetherToRestart = new AtomicBoolean(false);
QSMTool.parseSequenceOfTraces(traceDescr, config, new TraceAdder() {
@Override
public void addTrace(List<Label> trace, boolean positive) {
if (positive) extraTracesPlus.add(trace);else extraTracesMinus.add(trace);
CmpVertex tailVertex = temp.getVertex(trace);
if (tailVertex != null && tailVertex.isAccept() != positive)
whetherToRestart.set(true);
}
}, getLabelConverter());
if (!obtainedViaAuto) System.out.println(RPNILearner.QUESTION_USER+" "+question.toString()+" "+RPNILearner.QUESTION_NEWTRACE+" "+traceDescr);
// At this point, we attempt to augment the current automaton with the supplied traces,
// which may be successful or not (if we did some erroneous mergers earlier), in which case we restart.
for(List<Label> positive:extraTracesPlus)
AugumentPTA_and_QuestionPTA(ptaHardFacts,RestartLearningEnum.restartHARD,positive, true,colour);
for(List<Label> negative:extraTracesMinus)
AugumentPTA_and_QuestionPTA(ptaHardFacts,RestartLearningEnum.restartHARD,negative, false,colour);
if (whetherToRestart.get())
restartLearning = RestartLearningEnum.restartHARD;// we've seen at least one trace which contradicts the new tentative PTA.
else
restartLearning = RestartLearningEnum.restartRECOMPUTEQUESTIONS;// the set of questions will be rebuilt because we possibly modified the "nonexistent" PTA containing questions.
}
}
else
if (answer.firstElem == AbstractOracle.USER_ACCEPTED)
{
if(!answerFromSpin) // only add to hard facts when obtained directly from a user or from autofile
AugumentPTA_and_QuestionPTA(ptaHardFacts,RestartLearningEnum.restartHARD,question, true,colourToAugmentWith);
if (getTentativeAutomaton().config.getUseLTL() && getTentativeAutomaton().config.getUseSpin()) topLevelListener.AugmentPTA(ptaSoftFacts,RestartLearningEnum.restartSOFT,question, true,colourToAugmentWith);
if (!tempVertex.isAccept())
{// contradiction with the result of merging
if(!answerFromSpin)
restartLearning = RestartLearningEnum.restartHARD;
else
restartLearning = RestartLearningEnum.restartSOFT;
}
}
else
if (answer.firstElem >= 0)
{// The sequence has been rejected by a user
assert answer.firstElem < question.size();
LinkedList<Label> subAnswer = new LinkedList<Label>();
subAnswer.addAll(question.subList(0, answer.firstElem + 1));
if(!answerFromSpin) // only add to hard facts when obtained directly from a user or from autofile
AugumentPTA_and_QuestionPTA(ptaHardFacts, RestartLearningEnum.restartHARD,subAnswer, false,colourToAugmentWith);
if (getTentativeAutomaton().config.getUseLTL() && getTentativeAutomaton().config.getUseSpin()) topLevelListener.AugmentPTA(ptaSoftFacts,RestartLearningEnum.restartSOFT,subAnswer, false,colourToAugmentWith);
// important: since vertex IDs is
// only unique for each instance of ComputeStateScores, only
// one instance should ever receive calls to augmentPTA
if ((answer.firstElem < question.size() - 1) || tempVertex.isAccept())
{// contradiction with the result of merging
assert accepted == true;
if(!answerFromSpin)
restartLearning = RestartLearningEnum.restartHARD;
else
restartLearning = RestartLearningEnum.restartSOFT;
}
}
else
if(answer.firstElem == AbstractOracle.USER_LTL || answer.firstElem == AbstractOracle.USER_IFTHEN)
{
String answerType = null;
if (answer.firstElem == AbstractOracle.USER_LTL)
answerType = QSMTool.cmdLTL;
else
if (answer.firstElem == AbstractOracle.USER_IFTHEN)
answerType = QSMTool.cmdIFTHENAUTOMATON;
else
throw new IllegalArgumentException("unexpected user choice kind "+answer.firstElem);
restartLearning = RestartLearningEnum.restartRECOMPUTEQUESTIONS;
String addedConstraint = answer.secondElem;
boolean obtainedLTLViaAuto = addedConstraint != null;
if (addedConstraint == null) addedConstraint = JOptionPane.showInputDialog("New "+answerType+" formula:");
if(addedConstraint != null && addedConstraint.length() != 0)
{
if (!obtainedLTLViaAuto) System.out.println(QUESTION_USER+" "+question.toString()+ " <"+answerType+"> "+addedConstraint);
Set<String> tmpLtl = new HashSet<String>();tmpLtl.addAll(ifthenAutomataAsText);tmpLtl.add(answerType+" "+addedConstraint);
if(!config.isUseConstraints())
{
Collection<List<Label>> counters = spin.check(ptaHardFacts, tmpLtl).getCounters();
if (counters.size()>0)
{
String errorMessage = getHardFactsContradictionErrorMessage(tmpLtl, counters);
if (obtainedLTLViaAuto) // cannot recover from autosetting, otherwise warn a user
throw new IllegalArgumentException(errorMessage);