@Override
public LearnerGraph learnMachine()
{
final Configuration shallowCopy = tentativeAutomaton.config.copy();shallowCopy.setLearnerCloneGraph(false);
LearnerGraph ptaHardFacts = new LearnerGraph(shallowCopy);// this is now cloned to eliminate counter-examples added to ptaSoftFacts by Spin
LearnerGraph.copyGraphs(tentativeAutomaton, ptaHardFacts);
LearnerGraph ptaSoftFacts = tentativeAutomaton;
setChanged();tentativeAutomaton.setName(learntGraphName+"_init");
if (config.isUseConstraints())
{
LearnerGraph updatedTentativeAutomaton = new LearnerGraph(shallowCopy);
StringBuffer counterExampleHolder = new StringBuffer();
if (ifthenAutomata == null) ifthenAutomata = Transform.buildIfThenAutomata(ifthenAutomataAsText, ptaHardFacts, config).toArray(new LearnerGraph[0]);
if (!topLevelListener.AddConstraints(tentativeAutomaton,updatedTentativeAutomaton,counterExampleHolder))
throw new IllegalArgumentException(getHardFactsContradictionErrorMessage(ifthenAutomataAsText, counterExampleHolder.toString()));
tentativeAutomaton = updatedTentativeAutomaton;
}
if (tentativeAutomaton.config.getUseLTL() && tentativeAutomaton.config.getUseSpin() && !ifthenAutomataAsText.isEmpty()){
SpinResult sr = SpinUtil.check(ptaHardFacts, ifthenAutomataAsText);
if(!sr.isPass())
throw new IllegalArgumentException(getHardFactsContradictionErrorMessage(ifthenAutomataAsText, sr.getCounters()));
}
Stack<PairScore> possibleMerges = topLevelListener.ChooseStatePairs(tentativeAutomaton);
int iterations = 0, currentNonAmber = ptaHardFacts.getStateNumber()-ptaHardFacts.getAmberStateNumber();
JUConstants colourToAugmentWith = tentativeAutomaton.config.getUseAmber()? JUConstants.AMBER:null;
updateGraph(tentativeAutomaton);
while (!possibleMerges.isEmpty())
{
iterations++;
PairScore pair = possibleMerges.pop();
LearnerGraph temp = topLevelListener.MergeAndDeterminize(tentativeAutomaton, pair);
Collection<List<String>> questions = new LinkedList<List<String>>();
int score = pair.getScore();
RestartLearningEnum restartLearning = RestartLearningEnum.restartNONE;// whether we need to rebuild a PTA
// and restart learning.
//updateGraph(temp.paths.getGraph(learntGraphName+"_"+counterRestarted+"_"+iterations));
if (tentativeAutomaton.config.getUseLTL() && tentativeAutomaton.config.getUseSpin() && !ifthenAutomataAsText.isEmpty()){
Collection<List<String>> counterExamples = SpinUtil.check(temp, tentativeAutomaton, ifthenAutomataAsText).getCounters();
Iterator<List<String>> counterExampleIt = counterExamples.iterator();
while(counterExampleIt.hasNext())
{
List<String> 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))
{
tentativeAutomaton.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<String>> questionIt = null;
if (restartLearning == RestartLearningEnum.restartNONE && shouldAskQuestions(score))
{
temp.setName(learntGraphName+"_"+iterations);
//LearnerGraph updatedGraphActual = ComputeQuestions.constructGraphWithQuestions(pair, tentativeAutomaton, temp);
//updatedGraphActual.setName("questions "+iterations);setChanged();updateGraph(updatedGraphActual);
questions = topLevelListener.ComputeQuestions(pair, tentativeAutomaton, 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(tentativeAutomaton);pair.firstElem.setHighlight(false);pair.secondElem.setHighlight(false);
}
}
while (restartLearning == RestartLearningEnum.restartNONE && questionIt != null && questionIt.hasNext())
{
List<String> question = questionIt.next();
boolean accepted = pair.getQ().isAccept();
Pair<Integer,String> answer = null;
if (tentativeAutomaton.config.getUseLTL() && tentativeAutomaton.config.getUseSpin() && !ifthenAutomataAsText.isEmpty())
answer = new Pair<Integer,String>(checkWithSPIN(question),null);
CmpVertex tempVertex = temp.getVertex(question);
boolean answerFromSpin = false;
if(answer != null && answer.firstElem >= 0)
answerFromSpin = true;
else
{
if (Boolean.valueOf(GlobalConfiguration.getConfiguration().getProperty(GlobalConfiguration.G_PROPERTIES.ASSERT)))
if (ptaHardFacts.paths.tracePathPrefixClosed(question) == AbstractOracle.USER_ACCEPTED)
throw new IllegalArgumentException("question "+ question+ " has already been answered");
List<Boolean> acceptedElements = null;
acceptedElements = PathRoutines.mapPathToConfirmedElements(ptaHardFacts,question,ifthenAutomata);
answer = topLevelListener.CheckWithEndUser(tentativeAutomaton, question,
tempVertex.isAccept()?AbstractOracle.USER_ACCEPTED:question.size() - 1,
acceptedElements,
new Object[] { "LTL","IFTHEN","IGNORE QUESTION","MARK AS INCOMPATIBLE"});
}
if (answer.firstElem == AbstractOracle.USER_CANCELLED)
{
System.err.println("CANCELLED");
return null;
}
else
if (answer.firstElem == AbstractOracle.USER_IGNORED)
{// do nothing
restartLearning = RestartLearningEnum.restartNONE;
System.err.println("<ignore> "+question);
}
else
if (answer.firstElem == AbstractOracle.USER_INCOMPATIBLE)
{
tentativeAutomaton.addToCompatibility(pair.firstElem, pair.secondElem, PAIRCOMPATIBILITY.INCOMPATIBLE);
restartLearning = RestartLearningEnum.restartRECOMPUTEPAIRS;
}
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 (tentativeAutomaton.config.getUseLTL() && tentativeAutomaton.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<String> subAnswer = new LinkedList<String>();
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 (tentativeAutomaton.config.getUseLTL() && tentativeAutomaton.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<String>> counters = SpinUtil.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);
// if not obtained via auto, complain
System.out.println(errorMessage);
}
}
else{
LearnerGraph tmpIfthenAutomata[] = Transform.buildIfThenAutomata(tmpLtl, tentativeAutomaton, config).toArray(new LearnerGraph[0]);
LearnerGraph updatedTentativeAutomaton = new LearnerGraph(shallowCopy);
LearnerGraph.copyGraphs(tentativeAutomaton, updatedTentativeAutomaton);
try {
Transform.augmentFromIfThenAutomaton(updatedTentativeAutomaton, null, tmpIfthenAutomata, config.getHowManyStatesToAddFromIFTHEN());
} catch (AugmentFromIfThenAutomatonException e) {
// merge failed because the constraints disallowed it, hence return a failure
StringBuffer counterExampleHolder = new StringBuffer();
e.getFailureLocation(counterExampleHolder);
String errorMessage = getHardFactsContradictionErrorMessage(ifthenAutomataAsText, counterExampleHolder.toString());
if (obtainedLTLViaAuto) // cannot recover from autosetting, otherwise warn a user
throw new IllegalArgumentException(errorMessage);
// if not obtained via auto, complain
System.out.println(errorMessage);
}
}
// the current set of constraints does not contradict hard facts, update them and restart learning.
ifthenAutomataAsText.add(answerType+" "+addedConstraint);
// make sure constraints are rebuilt if in use
if (config.isUseConstraints())
ifthenAutomata = Transform.buildIfThenAutomata(ifthenAutomataAsText, ptaHardFacts, config).toArray(new LearnerGraph[0]);
restartLearning = RestartLearningEnum.restartHARD;
}
// no formula was entered, do not set the <em>questionAnswered</em> to answered, hence
// when we get to the top of the loop, we'll re-pop the previous question.
}
else
throw new IllegalArgumentException("unexpected user choice "+answer);
if ( (config.isUseConstraints() && restartLearning == RestartLearningEnum.restartNONE) || restartLearning == RestartLearningEnum.restartRECOMPUTEQUESTIONS)
{
questions = topLevelListener.RecomputeQuestions(pair, tentativeAutomaton, 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();restartLearning = RestartLearningEnum.restartNONE;
}
} // loop of questions
if (restartLearning == RestartLearningEnum.restartHARD ||
restartLearning == RestartLearningEnum.restartSOFT)
{// restart learning
if (restartLearning == RestartLearningEnum.restartHARD)
{
if (config.isSpeculativeQuestionAsking())
if (speculativeGraphUpdate(possibleMerges, ptaHardFacts))
return null;// this is the case when a user cancels the learning process when presented by "speculative" questions.
LearnerGraph.copyGraphs(ptaHardFacts,ptaSoftFacts);// this is cloned to eliminate counter-examples added to ptaSoftFacts by Spin
}
tentativeAutomaton = ptaSoftFacts;// no need to clone - this is the job of mergeAndDeterminize anyway
tentativeAutomaton.clearColoursButAmber();// this one will clear all colours if amber mode is not set.
setChanged();
}
else
if (restartLearning == RestartLearningEnum.restartNONE)
{
// At this point, tentativeAutomaton may have been modified because
// it may point to
// the original PTA which will be modified as a result of new
// sequences being added to it.
// temp is different too, hence there is no way for me to
// compute compatibility score here.
// This is hence computed inside the obtainPair method.
// keep going with the existing model
tentativeAutomaton = temp;
}
// if restartLearning == RestartLearningEnum.restartRECOMPUTEPAIRS, we do nothing, i.e. attempt to get state pairs again.
if (restartLearning != RestartLearningEnum.restartRECOMPUTEPAIRS && config.isUseConstraints())
{// Augmentation from IF-THEN does not use incompatibility constraints in a tentative automaton hence no point in re-augmenting (especially given that I do not have an automaton from before augmentation preserved).
LearnerGraph updatedTentativeAutomaton = new LearnerGraph(shallowCopy);
StringBuffer counterExampleHolder = new StringBuffer();
if (!topLevelListener.AddConstraints(tentativeAutomaton,updatedTentativeAutomaton,counterExampleHolder))
throw new IllegalArgumentException(getHardFactsContradictionErrorMessage(ifthenAutomataAsText, counterExampleHolder.toString()));
tentativeAutomaton = updatedTentativeAutomaton;
}