@Override
  public InstanceMetaData<FSM> getInstanceMetaData(FSM fsm) {
    numberOfFitnessEvaluations++;

    FSM labelled = labelFSM(fsm, useScenarios);
    Set<Transition> visitedTransitions =
        new HashSet<Transition>(fsm.getNumberOfStates() * fsm.getNumberOfEvents());
    double f1 = 0;
    int numberOfSuccesses = 0;

    Set<Node>[] visits = new Set[fsm.getNumberOfStates()];
    for (int state = 0; state < visits.length; state++) {
      visits[state] = new HashSet<Node>();
    }

    for (AutomatonTest test : tests) {
      Node node = scenarioTree.getRoot();
      int currentState = fsm.getInitialState();

      List<String> answers = new ArrayList<String>();
      for (int i = 0; i < test.getInput().length; i++) {
        String efPair = test.getInput()[i];
        int eventIndex = events.indexOf(efPair);
        String answer = labelled.transitions[currentState][eventIndex].getAction();
        int nextState = labelled.transitions[currentState][eventIndex].getEndState();
        if (nextState == -1) {
          break;
        }
        visits[currentState].add(node);
        String event = efPairToEvent.get(efPair);
        MyBooleanExpression guard = efPairToBooleanExpression.get(efPair);

        visitedTransitions.add(new Transition(currentState, nextState, efPair, answer));
        currentState = nextState;
        answers.add(answer);

        if (node.hasTransition(event, guard)) {
          node = node.getTransition(event, guard).getDst();
        }
      }
      String answerArray[] = answers.toArray(new String[0]);

      double f =
          Math.max(test.getOutput().length, answerArray.length) == 0
              ? 1.0
              : test.getLevenshteinDistance(answerArray)
                  / Math.max(test.getOutput().length, answerArray.length);

      if (f < 1e-5) {
        numberOfSuccesses++;
      }
      f1 += 1.0 - f;
    }

    fsm.markUsedTransitions(visitedTransitions);

    double testsFF = testsCost;
    if (tests.length > 0) {
      testsFF = (numberOfSuccesses == tests.length) ? testsCost : testsCost * (f1 / tests.length);
    }

    FST fst = FstFactory.createFST(labelled, actions);

    if (fst.getUsedTransitionsCount() == 0) {
      return new FsmMetaData(fsm, visitedTransitions, 0);
    }

    double ltlFF = ltlCost;
    if (formulas.size() > 0) {
      verifier.configureStateMachine(fst);
      int verificationResult[] = verifier.verify();
      ltlFF = ltlCost * verificationResult[0] / formulas.size() / fst.getUsedTransitionsCount();
      if ((ltlFF > ltlCost) || (ltlFF < 0)) {
        throw new RuntimeException(String.valueOf(ltlFF));
      }
    }

    boolean[][] transitionUsedInCounterexample =
        new boolean[fsm.getNumberOfStates()][fsm.getNumberOfEvents()];
    for (int i = 0; i < transitionUsedInCounterexample.length; i++) {
      Arrays.fill(transitionUsedInCounterexample[i], false);
    }

    for (int state = 0; state < fsm.getNumberOfStates(); state++) {
      for (int event = 0; event < fsm.getNumberOfEvents(); event++) {
        ru.ifmo.ctddev.genetic.transducer.algorithm.Transition fstTransition =
            fst.getTransition(state, FSM.EVENTS.get(event));
        if (fstTransition == null) {
          continue;
        }
        if (fstTransition.isUsedByVerifier()) {
          transitionUsedInCounterexample[state][event] = true;
        }
      }
    }

    fsm.setTransitionsInCounterexample(transitionUsedInCounterexample);

    double consistencyFF = 1.0;
    if (testsFF < 1.0) {
      consistencyFF = 0;
      for (int state = 0; state < visits.length; state++) {
        if (visits[state].isEmpty()) {
          continue;
        }
        Set<NodePair> bad = new HashSet<NodePair>();
        Set<Node> set = visits[state];

        for (Node n1 : set) {
          if (!hasInconsistency[n1.getNumber()]) {
            continue;
          }
          for (Node n2 : set) {
            if (n2 == n1) {
              continue;
            }
            if (consistencyGraphBoolean[n1.getNumber()][n2.getNumber()]) {
              NodePair badPair = new NodePair(n1, n2);
              if (!bad.contains(badPair.getComplimentaryPair())) {
                bad.add(badPair);
              }
            }
          }
        }

        consistencyFF += bad.size() / Math.pow((double) visits[state].size(), 2);
      }
      consistencyFF /= fsm.getNumberOfStates();
      if (consistencyFF > 1.0 || consistencyFF < 0) {
        throw new RuntimeException("Consistency metric out of range: " + consistencyFF);
      }
      consistencyFF = 1.0 - consistencyFF;
    }

    double fitness = testsFF + ltlFF + consistencyFF;

    double transitionsFF = 0.0001 * (100 - fsm.getNumberOfTransitions()) / (double) tests.length;
    ;
    if (fitness < 3) {
      fitness = 0.75 * fitness + transitionsFF;
    } else {
      fitness += transitionsFF;
    }

    return new FsmMetaData(fsm, visitedTransitions, fitness);
  }