/// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public NFAEditScript GetNFAOptimalEdit(Automaton <BDD> nfa2)
        {
            NFAEditScript editScript = new NFAEditScript();

            //Start timer
            sw.Start();

            //Normalize NFAs
            var normNfaPair       = DFAUtilities.normalizeDFA(nfa2);
            var normNfa2          = normNfaPair.First;
            var stateNamesMapping = normNfaPair.Second;


            NFAEditScript bestScript = new NFAEditScript();

            bestScript.script = null;

            // increase depth up to maxMoves
            for (int depth = 1; true; depth++)
            {
                var editList = new List <NFAEdit>();
                if (GetNFAEditScriptTimeout(
                        depth, -1, normNfa2,
                        editScript.script, editScript.GetCost(), bestScript))
                {
                    // if hits timeout break and return null
                    break;
                }
                if (bestScript.script != null)
                {
                    bestScript.script.Reverse();
                    sw.Stop();
                    var mappedEditList = new List <NFAEdit>();

                    //fix states name because of normalization
                    foreach (var edit in bestScript.script)
                    {
                        NFAEdit mappedEdit = null;
                        if (edit is NFAEditState)
                        {
                            var castEdit = edit as NFAEditState;
                            mappedEdit = new NFAEditState(stateNamesMapping[castEdit.state], castEdit.makeFinal);
                        }
                        if (edit is NFAEditMove)
                        {
                            var castEdit = edit as NFAEditMove;
                            mappedEdit = new NFAEditMove(
                                stateNamesMapping[castEdit.sourceState],
                                stateNamesMapping[castEdit.newTargetState],
                                castEdit.ch);
                        }
                        mappedEditList.Add(mappedEdit);
                    }
                    return(bestScript);
                }
            }

            return(null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public NFAEditScript GetNFAOptimalEdit(Automaton<BDD> nfa2)
        {
            NFAEditScript editScript = new NFAEditScript();           

            //Start timer
            sw.Start();

            //Normalize NFAs
            var normNfaPair = DFAUtilities.normalizeDFA(nfa2);
            var normNfa2 = normNfaPair.First;
            var stateNamesMapping = normNfaPair.Second;


            NFAEditScript bestScript = new NFAEditScript();
            bestScript.script = null;

            // increase depth up to maxMoves
            for (int depth = 1; true; depth++)
            {
                var editList = new List<NFAEdit>();
                if(GetNFAEditScriptTimeout(
                    depth, -1, normNfa2,
                    editScript.script, editScript.GetCost(), bestScript))
                {
                    // if hits timeout break and return null
                    break;
                }
                if (bestScript.script != null)
                {
                    bestScript.script.Reverse();
                    sw.Stop();
                    var mappedEditList = new List<NFAEdit>();

                    //fix states name because of normalization
                    foreach (var edit in bestScript.script)
                    {   
                        NFAEdit mappedEdit = null;
                        if(edit is NFAEditState){
                            var castEdit = edit as NFAEditState;
                            mappedEdit = new NFAEditState(stateNamesMapping[castEdit.state], castEdit.makeFinal);
                        }
                        if(edit is NFAEditMove){
                            var castEdit = edit as NFAEditMove;
                            mappedEdit = new NFAEditMove(
                                stateNamesMapping[castEdit.sourceState], 
                                stateNamesMapping[castEdit.newTargetState],
                                castEdit.ch);
                        }
                        mappedEditList.Add(mappedEdit);
                    }
                    return bestScript;
                }
            }

            return null;
        }
 public NFANotMinimalFeedback(
     FeedbackLevel level, HashSet <char> alphabet,
     int stateDiff, int transitionDiff,
     NFAEditScript script,
     CharSetSolver solver)
     : base(level, alphabet, solver)
 {
     this.transitionDiff = transitionDiff;
     this.stateDiff      = stateDiff;
     this.script         = script;
 }
 public NFANotMinimalFeedback (
     FeedbackLevel level, HashSet<char> alphabet,
     int stateDiff, int transitionDiff,
     NFAEditScript script,
     CharSetSolver solver)
     : base(level, alphabet, solver)
 {
     this.transitionDiff = transitionDiff;
     this.stateDiff = stateDiff;
     this.script = script;
 }
        public NFAEDFeedback(Automaton <BDD> nfaGoal, Automaton <BDD> nfaAttempt,
                             FeedbackLevel level, HashSet <char> alphabet,
                             NFAEditScript script, CharSetSolver solver)
            : base(level, alphabet, solver)
        {
            //TODO might have to determinize to do this operations
            var positiveDifference = nfaGoal.Minus(nfaAttempt, solver).Determinize(solver).Minimize(solver);
            var negativeDifference = nfaAttempt.Minus(nfaGoal, solver).Determinize(solver).Minimize(solver);

            this.counterexample = DFAUtilities.GenerateShortTerm(positiveDifference.IsEmpty ? negativeDifference : positiveDifference, solver);
            this.script         = script;
        }
Beispiel #6
0
        /// <summary>
        /// Computes the grade for attempt using all the possible metrics
        /// </summary>
        /// <param name="solutionNFA">correct nfa</param>
        /// <param name="attemptNFA">nfa to be graded</param>
        /// <param name="alpahbet">input alphabet</param>
        /// <param name="solver">SMT solver for char set</param>
        /// <param name="timeout">timeout for the PDL enumeration (suggested > 1000)</param>
        /// <param name="maxGrade">Max grade for the homework</param>
        /// <param name="level">Feedback level</param>
        /// <returns>Grade for nfa2</returns>
        public static Pair <int, IEnumerable <NFAFeedback> > GetGrade(
            Automaton <BDD> solutionNFA, Automaton <BDD> attemptNFA, HashSet <char> alphabet,
            CharSetSolver solver, long timeout, int maxGrade, FeedbackLevel level)
        {
            var feedbacks          = new List <NFAFeedback>();
            int deadStateDeduction = 0;
            int tooBigDeduction    = 0;
            int incorrectDeduction = 0;

            // Remove at most a percentage of max grade when NFA is big
            double maxDeductionForTooBig     = ((double)maxGrade * 0.3);
            double maxDeductionForDeadStates = ((double)maxGrade * 0.1);


            double solutionStateCount      = solutionNFA.StateCount;
            double attemptStateCount       = attemptNFA.StateCount;
            double solutionTransCount      = solutionNFA.MoveCount;
            double attemptTransCount       = attemptNFA.MoveCount;
            NFAEditDistanceProvider nfaedp = new NFAEditDistanceProvider(solutionNFA, alphabet, solver, timeout);

            //Check if using epsilon and nondeterminism
            if (solutionNFA.IsEpsilonFree)
            {
                solutionNFA.CheckDeterminism(solver);
            }
            if (attemptNFA.IsEpsilonFree)
            {
                attemptNFA.CheckDeterminism(solver);
            }

            bool shouldUseEpsilon = !solutionNFA.IsEpsilonFree && attemptNFA.IsEpsilonFree;
            bool shouldUseNonDet  = !shouldUseEpsilon && !solutionNFA.isDeterministic && attemptNFA.isDeterministic;

            //Check if solution has dead states and remove if it does
            var statesBeforeDeadStatesElimination = attemptNFA.StateCount;

            attemptNFA.EliminateDeadStates();
            var solutionHasDeadStates = attemptNFA.StateCount < statesBeforeDeadStatesElimination;

            //Start checking equiv
            bool areEquivalent = solutionNFA.IsEquivalentWith(attemptNFA, solver);

            if (areEquivalent)
            {
                // prompt nfa is correct
                feedbacks.Insert(0, new NFAStringFeedback(level, alphabet, solver, "Your NFA accepts the CORRECT language."));

                #region Check number of states and decrease grade if too big
                int stateDiff = (int)(attemptStateCount - solutionStateCount);
                int transDiff = (int)(attemptTransCount - solutionTransCount);

                //If is not minimal apply deduction and compute edit
                if (stateDiff > 0 || transDiff > 0)
                {
                    #region Try to collapse for feedback
                    // Try to find a way to collaps states or remove states and transitions to make the NFA smaller
                    NFAEditScript collapseScript = null;
                    var           edit           = nfaedp.NFACollapseSearch(attemptNFA);
                    if (edit != null)
                    {
                        collapseScript = new NFAEditScript();
                        collapseScript.script.Insert(0, edit);
                    }
                    feedbacks.Add(new NFANotMinimalFeedback(level, alphabet, stateDiff, transDiff, collapseScript, solver));
                    #endregion

                    #region Compute tooBigDeduction
                    if (stateDiff > 0)
                    {
                        // ((att/sol)^2)-1
                        var stateRatio       = attemptStateCount / solutionStateCount;
                        var stateRatioSqM1   = Math.Pow(stateRatio, 2) - 1;
                        var sclaedStateRatio = stateRatioSqM1 * maxDeductionForTooBig / 2;

                        tooBigDeduction = (int)Math.Round(Math.Min(sclaedStateRatio, maxDeductionForTooBig));
                    }
                    else
                    {
                        if (transDiff > 0)
                        {
                            // ((att/sol)^2)-1
                            var transRatio       = attemptTransCount / solutionTransCount;
                            var transRatioSqM1   = Math.Pow(transRatio, 2) - 1;
                            var sclaedTransRatio = transRatioSqM1 * maxDeductionForTooBig / 2;

                            tooBigDeduction = (int)Math.Round(Math.Min(sclaedTransRatio, maxDeductionForTooBig));
                        }
                    }
                    //Make sure deduction is positive
                    tooBigDeduction = Math.Max(tooBigDeduction, 0);
                    #endregion
                }
                #endregion
            }
            else
            {
                // prompt nfa is incorrect
                feedbacks.Add(new NFAStringFeedback(level, alphabet, solver, "Your NFA does NOT accept the correct langauge."));

                //inequivalent, try using grading metrics and based on winning metric give feedback
                int remainingGrade = maxGrade - tooBigDeduction;

                #region metric computation
                //compute deterministic versions
                var solutionNFAdet = solutionNFA.RemoveEpsilons(solver.MkOr).Determinize(solver).MakeTotal(solver).Minimize(solver);
                var attemptNFAdet  = attemptNFA.RemoveEpsilons(solver.MkOr).Determinize(solver).MakeTotal(solver).Minimize(solver);
                solutionNFAdet.EliminateDeadStates();
                attemptNFAdet.EliminateDeadStates();

                //compute density
                double densityRatio = DFADensity.GetDFADifferenceRatio(solutionNFAdet, attemptNFAdet, alphabet, solver);

                //compute edit distance
                double nfaED      = 2;
                var    editScript = nfaedp.GetNFAOptimalEdit(attemptNFA);

                if (editScript != null)
                {
                    nfaED = ((double)(editScript.GetCost())) / ((double)((solutionNFA.StateCount + 1) * alphabet.Count));
                }
                #endregion

                #region metrics scaling
                var scalingSquareDensity = 1; var multv2 = 0.5;
                var scalingSquareDFAED = 1.03;

                var scaledDensityRatio = (scalingSquareDensity + (multv2 * densityRatio)) * (scalingSquareDensity + (multv2 * densityRatio)) - scalingSquareDensity * scalingSquareDensity;
                var scaledNfaED        = (scalingSquareDFAED + nfaED) * (scalingSquareDFAED + nfaED) - scalingSquareDFAED * scalingSquareDFAED;

                //If the edit script was not computed make sure it loses.
                if (editScript == null)
                {
                    scaledNfaED = Double.MaxValue;
                }

                //Select dominating Feedback based on grade
                double unscaledGrade = Math.Min(scaledDensityRatio, scaledNfaED);
                var    dfaedwins     = scaledNfaED <= scaledDensityRatio;
                var    densitywins   = scaledDensityRatio <= scaledNfaED;

                incorrectDeduction = (int)Math.Round(unscaledGrade * (double)(maxGrade));
                #endregion


                //If edit distance search works, provides feedback based upon result
                //Otherwise, gives counterexample feedback
                if (level != FeedbackLevel.Minimal)
                {
                    if (dfaedwins)
                    {
                        feedbacks.Add(new NFAEDFeedback(solutionNFA, attemptNFA, level, alphabet, editScript, solver));
                    }
                    else
                    {
                        feedbacks.Add(new NFACounterexampleFeedback(level, alphabet, solutionNFAdet, attemptNFAdet, solver));
                    }
                }
            }

            // Feedback related to nondeterminism and epislon
            if (shouldUseEpsilon)
            {
                feedbacks.Add(new NFAStringFeedback(level, alphabet, solver, "You should try using epsilon transitions."));
            }
            if (shouldUseNonDet)
            {
                feedbacks.Add(new NFAStringFeedback(level, alphabet, solver, "You should try using nondeterminism."));
            }

            // Deduct points and prompt feedback is solution has dead states
            if (solutionHasDeadStates)
            {
                deadStateDeduction = (int)maxDeductionForDeadStates;
                feedbacks.Add(new NFAStringFeedback(level, alphabet, solver, "Your NFA has some dead states."));
            }

            //Grade computation

            //changed to be binary, because we did not tell them of the deductions
            //int grade = Math.Max(maxGrade - deadStateDeduction - tooBigDeduction - incorrectDeduction, 0);
            //int grade = areEquivalent ? maxGrade : 0;

            // This requires further testing, but appears to grade ok, 0 points for empty automaton and full points for something in between
            int grade = Math.Max(maxGrade - incorrectDeduction, 0);

            return(new Pair <int, IEnumerable <NFAFeedback> >(grade, feedbacks));
        }
        // looks for an edit at depth "depth" 
        // returns false and null in bestScript if no edit is found at depth "depth"
        // returns false and not null in bestScript if found
        // returns true if timeout
        internal bool GetNFAEditScriptTimeout(
            int depth, long lastEditHash, 
            Automaton<BDD> currentNfa2,
            List<NFAEdit> editList, int scriptCost,
            NFAEditScript bestScript)
        {
            // if timeout return true
            if (sw.ElapsedMilliseconds > timeout)
                return true;


            //Stop if no more moves left
            if (depth == 0)
            {
                if (DFAUtilities.ApproximateMNEquivalent(tests, nfa1density, currentNfa2, al, solver) && currentNfa2.IsEquivalentWith(nfa1, solver))
                    //check if totalCost < finalScript cost and replace if needed
                    if (bestScript.script == null || scriptCost < bestScript.GetCost())
                        bestScript.script = ObjectCopier.Clone<List<NFAEdit>>(editList);
                return false;
            }

            NFAEdit edit = null;

            long thisEditHash = 0;

            #region Flip one state from fin to non fin
            foreach (var state in currentNfa2.States)
            {
                thisEditHash = state;
                if (CanAdd(thisEditHash, lastEditHash))
                {
                    //flip its final non final status

                    var newFinalStates = new HashSet<int>(currentNfa2.GetFinalStates());
                    Automaton<BDD> nfa2new = null;
                    if (currentNfa2.GetFinalStates().Contains(state))
                    {
                        edit = new NFAEditState(state, false);
                        editList.Insert(0, edit);
                        newFinalStates.Remove(state);
                        nfa2new = Automaton<BDD>.Create(currentNfa2.InitialState, newFinalStates, currentNfa2.GetMoves());
                    }
                    else
                    {
                        edit = new NFAEditState(state, true);
                        editList.Insert(0, edit);
                        newFinalStates.Add(state);
                        nfa2new = Automaton<BDD>.Create(currentNfa2.InitialState, newFinalStates, currentNfa2.GetMoves());
                    }

                    if (GetNFAEditScriptTimeout(depth - 1, thisEditHash, nfa2new, editList, scriptCost + edit.GetCost(), bestScript))
                        return true;

                    editList.RemoveAt(0);
                }
            }
            #endregion

            #region Change transition from source state
            currentNfa2 = NFAUtilities.normalizeMoves(currentNfa2, solver);
            foreach (var sourceState in currentNfa2.States)
            {
                HashSet<int> unreachedStates = new HashSet<int>(currentNfa2.States);
                foreach (var moveFromSource in currentNfa2.GetMovesFrom(sourceState))
                {
                    // take all chars in alphabet
                    foreach (var c in al)
                    {
                        long moveHash =  currentNfa2.StateCount + IntegerUtil.TripleToInt(sourceState, moveFromSource.TargetState, alphabetMap[c]);
                        thisEditHash = currentNfa2.StateCount + moveHash;
                        if (CanAdd(thisEditHash, lastEditHash))
                        {
                            BDD cCond = solver.False;
                            BDD newCond = solver.False;

                            //skip epsilon moves
                            if (moveFromSource.Label != null)
                            {
                                // if c in move, remove it and recursion
                                if (solver.Contains(moveFromSource.Label, c))
                                {
                                    cCond = solver.MkNot(solver.MkCharConstraint(false, c));
                                    newCond = solver.MkAnd(moveFromSource.Label, cCond);
                                }
                                else // if c not in move, add it and recursion
                                {
                                    cCond = solver.MkCharConstraint(false, c);
                                    newCond = solver.MkOr(moveFromSource.Label, cCond);
                                }

                                var newMoves = new List<Move<BDD>>(currentNfa2.GetMoves());
                                newMoves.Remove(moveFromSource);
                                newMoves.Add(new Move<BDD>(sourceState, moveFromSource.TargetState, newCond));
                                var nfa2new = Automaton<BDD>.Create(currentNfa2.InitialState, currentNfa2.GetFinalStates(), newMoves);

                                edit = new NFAEditMove(sourceState, moveFromSource.TargetState, c);
                                editList.Insert(0, edit);

                                if (GetNFAEditScriptTimeout(depth - 1, thisEditHash, nfa2new, editList, scriptCost + edit.GetCost(), bestScript))
                                    return true;

                                editList.RemoveAt(0);
                            }
                        }
                    }

                    unreachedStates.Remove(moveFromSource.TargetState);
                }

                foreach (var targetState in unreachedStates)
                {
                    //try adding a symbol not in transition
                    foreach (var c in al)
                    {
                        long moveHash = IntegerUtil.TripleToInt(sourceState, targetState, alphabetMap[c]);
                        thisEditHash = currentNfa2.StateCount + moveHash;

                        var moveCond = solver.MkCharConstraint(false, c);
                        var newMoves = new List<Move<BDD>>(currentNfa2.GetMoves()); 
                        newMoves.Add(new Move<BDD>(sourceState, targetState, moveCond));
                        var nfa2new = Automaton<BDD>.Create(currentNfa2.InitialState, currentNfa2.GetFinalStates(), newMoves);

                        edit = new NFAEditMove(sourceState, targetState, c);
                        editList.Insert(0, edit);

                        //TODO put correct hash
                        if (GetNFAEditScriptTimeout(depth - 1, thisEditHash, nfa2new, editList, scriptCost + edit.GetCost(), bestScript))
                            return true;

                        editList.RemoveAt(0);
                    }
                }
            }            
            #endregion

            return false;
        }
 public NFAEDFeedback(Automaton<BDD> nfaGoal, Automaton<BDD> nfaAttempt, 
     FeedbackLevel level, HashSet<char> alphabet, 
     NFAEditScript script, CharSetSolver solver)
     : base(level, alphabet, solver)
 {
     //TODO might have to determinize to do this operations
     var positiveDifference = nfaGoal.Minus(nfaAttempt, solver).Determinize(solver).Minimize(solver);
     var negativeDifference = nfaAttempt.Minus(nfaGoal, solver).Determinize(solver).Minimize(solver);
     this.counterexample = DFAUtilities.GenerateShortTerm(positiveDifference.IsEmpty ? negativeDifference : positiveDifference, solver);            
     this.script = script;
 }
        // looks for an edit at depth "depth"
        // returns false and null in bestScript if no edit is found at depth "depth"
        // returns false and not null in bestScript if found
        // returns true if timeout
        internal bool GetNFAEditScriptTimeout(
            int depth, long lastEditHash,
            Automaton <BDD> currentNfa2,
            List <NFAEdit> editList, int scriptCost,
            NFAEditScript bestScript)
        {
            // if timeout return true
            if (sw.ElapsedMilliseconds > timeout)
            {
                return(true);
            }


            //Stop if no more moves left
            if (depth == 0)
            {
                if (DFAUtilities.ApproximateMNEquivalent(tests, nfa1density, currentNfa2, al, solver) && currentNfa2.IsEquivalentWith(nfa1, solver))
                {
                    //check if totalCost < finalScript cost and replace if needed
                    if (bestScript.script == null || scriptCost < bestScript.GetCost())
                    {
                        bestScript.script = ObjectCopier.Clone <List <NFAEdit> >(editList);
                    }
                }
                return(false);
            }

            NFAEdit edit = null;

            long thisEditHash = 0;

            #region Flip one state from fin to non fin
            foreach (var state in currentNfa2.States)
            {
                thisEditHash = state;
                if (CanAdd(thisEditHash, lastEditHash))
                {
                    //flip its final non final status

                    var             newFinalStates = new HashSet <int>(currentNfa2.GetFinalStates());
                    Automaton <BDD> nfa2new        = null;
                    if (currentNfa2.GetFinalStates().Contains(state))
                    {
                        edit = new NFAEditState(state, false);
                        editList.Insert(0, edit);
                        newFinalStates.Remove(state);
                        nfa2new = Automaton <BDD> .Create(currentNfa2.InitialState, newFinalStates, currentNfa2.GetMoves());
                    }
                    else
                    {
                        edit = new NFAEditState(state, true);
                        editList.Insert(0, edit);
                        newFinalStates.Add(state);
                        nfa2new = Automaton <BDD> .Create(currentNfa2.InitialState, newFinalStates, currentNfa2.GetMoves());
                    }

                    if (GetNFAEditScriptTimeout(depth - 1, thisEditHash, nfa2new, editList, scriptCost + edit.GetCost(), bestScript))
                    {
                        return(true);
                    }

                    editList.RemoveAt(0);
                }
            }
            #endregion

            #region Change transition from source state
            currentNfa2 = NFAUtilities.normalizeMoves(currentNfa2, solver);
            foreach (var sourceState in currentNfa2.States)
            {
                HashSet <int> unreachedStates = new HashSet <int>(currentNfa2.States);
                foreach (var moveFromSource in currentNfa2.GetMovesFrom(sourceState))
                {
                    // take all chars in alphabet
                    foreach (var c in al)
                    {
                        long moveHash = currentNfa2.StateCount + IntegerUtil.TripleToInt(sourceState, moveFromSource.TargetState, alphabetMap[c]);
                        thisEditHash = currentNfa2.StateCount + moveHash;
                        if (CanAdd(thisEditHash, lastEditHash))
                        {
                            BDD cCond   = solver.False;
                            BDD newCond = solver.False;

                            //skip epsilon moves
                            if (moveFromSource.Label != null)
                            {
                                // if c in move, remove it and recursion
                                if (solver.Contains(moveFromSource.Label, c))
                                {
                                    cCond   = solver.MkNot(solver.MkCharConstraint(false, c));
                                    newCond = solver.MkAnd(moveFromSource.Label, cCond);
                                }
                                else // if c not in move, add it and recursion
                                {
                                    cCond   = solver.MkCharConstraint(false, c);
                                    newCond = solver.MkOr(moveFromSource.Label, cCond);
                                }

                                var newMoves = new List <Move <BDD> >(currentNfa2.GetMoves());
                                newMoves.Remove(moveFromSource);
                                newMoves.Add(new Move <BDD>(sourceState, moveFromSource.TargetState, newCond));
                                var nfa2new = Automaton <BDD> .Create(currentNfa2.InitialState, currentNfa2.GetFinalStates(), newMoves);

                                edit = new NFAEditMove(sourceState, moveFromSource.TargetState, c);
                                editList.Insert(0, edit);

                                if (GetNFAEditScriptTimeout(depth - 1, thisEditHash, nfa2new, editList, scriptCost + edit.GetCost(), bestScript))
                                {
                                    return(true);
                                }

                                editList.RemoveAt(0);
                            }
                        }
                    }

                    unreachedStates.Remove(moveFromSource.TargetState);
                }

                foreach (var targetState in unreachedStates)
                {
                    //try adding a symbol not in transition
                    foreach (var c in al)
                    {
                        long moveHash = IntegerUtil.TripleToInt(sourceState, targetState, alphabetMap[c]);
                        thisEditHash = currentNfa2.StateCount + moveHash;

                        var moveCond = solver.MkCharConstraint(false, c);
                        var newMoves = new List <Move <BDD> >(currentNfa2.GetMoves());
                        newMoves.Add(new Move <BDD>(sourceState, targetState, moveCond));
                        var nfa2new = Automaton <BDD> .Create(currentNfa2.InitialState, currentNfa2.GetFinalStates(), newMoves);

                        edit = new NFAEditMove(sourceState, targetState, c);
                        editList.Insert(0, edit);

                        //TODO put correct hash
                        if (GetNFAEditScriptTimeout(depth - 1, thisEditHash, nfa2new, editList, scriptCost + edit.GetCost(), bestScript))
                        {
                            return(true);
                        }

                        editList.RemoveAt(0);
                    }
                }
            }
            #endregion

            return(false);
        }
        /// <summary>
        /// Computes the grade for attempt using all the possible metrics
        /// </summary>
        /// <param name="solutionNFA">correct nfa</param>
        /// <param name="attemptNFA">nfa to be graded</param>
        /// <param name="alpahbet">input alphabet</param>
        /// <param name="solver">SMT solver for char set</param>
        /// <param name="timeout">timeout for the PDL enumeration (suggested > 1000)</param>
        /// <param name="maxGrade">Max grade for the homework</param>
        /// <param name="level">Feedback level</param>
        /// <returns>Grade for nfa2</returns>
        public static Pair<int, IEnumerable<NFAFeedback>> GetGrade(
            Automaton<BDD> solutionNFA, Automaton<BDD> attemptNFA, HashSet<char> alphabet,
            CharSetSolver solver, long timeout, int maxGrade, FeedbackLevel level)
        {
            var feedbacks = new List<NFAFeedback>();
            int deadStateDeduction = 0;
            int tooBigDeduction = 0;
            int incorrectDeduction = 0;

            // Remove at most a percentage of max grade when NFA is big
            double maxDeductionForTooBig = ((double)maxGrade * 0.3);
            double maxDeductionForDeadStates = ((double)maxGrade * 0.1);


            double solutionStateCount = solutionNFA.StateCount;
            double attemptStateCount = attemptNFA.StateCount;
            double solutionTransCount = solutionNFA.MoveCount;
            double attemptTransCount = attemptNFA.MoveCount;
            NFAEditDistanceProvider nfaedp = new NFAEditDistanceProvider(solutionNFA, alphabet, solver, timeout);

            //Check if using epsilon and nondeterminism
            if (solutionNFA.IsEpsilonFree)
                solutionNFA.CheckDeterminism(solver);
            if (attemptNFA.IsEpsilonFree)
                attemptNFA.CheckDeterminism(solver);

            bool shouldUseEpsilon = !solutionNFA.IsEpsilonFree && attemptNFA.IsEpsilonFree;
            bool shouldUseNonDet = !shouldUseEpsilon && !solutionNFA.isDeterministic && attemptNFA.isDeterministic;

            //Check if solution has dead states and remove if it does
            var statesBeforeDeadStatesElimination = attemptNFA.StateCount;
            attemptNFA.EliminateDeadStates();
            var solutionHasDeadStates = attemptNFA.StateCount < statesBeforeDeadStatesElimination;

            //Start checking equiv
            bool areEquivalent = solutionNFA.IsEquivalentWith(attemptNFA, solver);

            if (areEquivalent)
            {
                // prompt nfa is correct
                feedbacks.Insert(0, new NFAStringFeedback(level, alphabet, solver, "Your NFA accepts the CORRECT language."));

                #region Check number of states and decrease grade if too big
                int stateDiff = (int)(attemptStateCount - solutionStateCount);
                int transDiff = (int)(attemptTransCount - solutionTransCount);

                //If is not minimal apply deduction and compute edit
                if (stateDiff > 0 || transDiff > 0)
                {
                    #region Try to collapse for feedback
                    // Try to find a way to collaps states or remove states and transitions to make the NFA smaller  
                    NFAEditScript collapseScript = null;
                    var edit = nfaedp.NFACollapseSearch(attemptNFA);
                    if (edit != null)
                    {
                        collapseScript = new NFAEditScript();
                        collapseScript.script.Insert(0, edit);
                    }
                    feedbacks.Add(new NFANotMinimalFeedback(level, alphabet, stateDiff, transDiff, collapseScript, solver));
                    #endregion

                    #region Compute tooBigDeduction
                    if (stateDiff > 0)
                    {
                        // ((att/sol)^2)-1
                        var stateRatio = attemptStateCount / solutionStateCount;
                        var stateRatioSqM1 = Math.Pow(stateRatio, 2) - 1;
                        var sclaedStateRatio = stateRatioSqM1 * maxDeductionForTooBig / 2;

                        tooBigDeduction = (int)Math.Round(Math.Min(sclaedStateRatio, maxDeductionForTooBig));
                    }
                    else
                    {
                        if (transDiff > 0)
                        {
                            // ((att/sol)^2)-1
                            var transRatio = attemptTransCount / solutionTransCount;
                            var transRatioSqM1 = Math.Pow(transRatio, 2) - 1;
                            var sclaedTransRatio = transRatioSqM1 * maxDeductionForTooBig / 2;

                            tooBigDeduction = (int)Math.Round(Math.Min(sclaedTransRatio, maxDeductionForTooBig));
                        }
                    }
                    //Make sure deduction is positive
                    tooBigDeduction = Math.Max(tooBigDeduction, 0);
                    #endregion
                }
                #endregion

            }
            else
            {
                // prompt nfa is incorrect
                feedbacks.Add(new NFAStringFeedback(level, alphabet, solver, "Your NFA does NOT accept the correct langauge."));

                //inequivalent, try using grading metrics and based on winning metric give feedback
                int remainingGrade = maxGrade - tooBigDeduction;

                #region metric computation
                //compute deterministic versions
                var solutionNFAdet = solutionNFA.RemoveEpsilons(solver.MkOr).Determinize(solver).MakeTotal(solver).Minimize(solver);
                var attemptNFAdet = attemptNFA.RemoveEpsilons(solver.MkOr).Determinize(solver).MakeTotal(solver).Minimize(solver);
                solutionNFAdet.EliminateDeadStates();
                attemptNFAdet.EliminateDeadStates();

                //compute density
                double densityRatio = DFADensity.GetDFADifferenceRatio(solutionNFAdet, attemptNFAdet, alphabet, solver);

                //compute edit distance
                double nfaED = 2;
                var editScript = nfaedp.GetNFAOptimalEdit(attemptNFA);

                if (editScript != null)
                    nfaED = ((double)(editScript.GetCost())) / ((double)((solutionNFA.StateCount + 1) * alphabet.Count));
                #endregion

                #region metrics scaling
                var scalingSquareDensity = 1; var multv2 = 0.5;
                var scalingSquareDFAED = 1.03;

                var scaledDensityRatio = (scalingSquareDensity + (multv2 * densityRatio)) * (scalingSquareDensity + (multv2 * densityRatio)) - scalingSquareDensity * scalingSquareDensity;
                var scaledNfaED = (scalingSquareDFAED + nfaED) * (scalingSquareDFAED + nfaED) - scalingSquareDFAED * scalingSquareDFAED;

                //If the edit script was not computed make sure it loses.
                if (editScript == null)
                    scaledNfaED = Double.MaxValue;

                //Select dominating Feedback based on grade
                double unscaledGrade = Math.Min(scaledDensityRatio, scaledNfaED);
                var dfaedwins = scaledNfaED <= scaledDensityRatio;
                var densitywins = scaledDensityRatio <= scaledNfaED;

                incorrectDeduction = (int)Math.Round(unscaledGrade * (double)(maxGrade));
                #endregion


                //If edit distance search works, provides feedback based upon result
                //Otherwise, gives counterexample feedback
                if (level != FeedbackLevel.Minimal)
                {
                    if (dfaedwins)
                        feedbacks.Add(new NFAEDFeedback(solutionNFA, attemptNFA, level, alphabet, editScript, solver));
                    else
                        feedbacks.Add(new NFACounterexampleFeedback(level, alphabet, solutionNFAdet, attemptNFAdet, solver));
                }

            }

            // Feedback related to nondeterminism and epislon
            if (shouldUseEpsilon)
                feedbacks.Add(new NFAStringFeedback(level, alphabet, solver, "You should try using epsilon transitions."));
            if (shouldUseNonDet)
                feedbacks.Add(new NFAStringFeedback(level, alphabet, solver, "You should try using nondeterminism."));

            // Deduct points and prompt feedback is solution has dead states
            if (solutionHasDeadStates)
            {
                deadStateDeduction = (int)maxDeductionForDeadStates;
                feedbacks.Add(new NFAStringFeedback(level, alphabet, solver, "Your NFA has some dead states."));
            }

            //Grade computation
            int grade = Math.Max(maxGrade - deadStateDeduction - tooBigDeduction - incorrectDeduction, 0);

            return new Pair<int, IEnumerable<NFAFeedback>>(grade, feedbacks);
        }