Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TTT: Index 1 out of bounds in first counter example #140

Open
Jojeker opened this issue Jan 16, 2025 · 3 comments
Open

TTT: Index 1 out of bounds in first counter example #140

Jojeker opened this issue Jan 16, 2025 · 3 comments

Comments

@Jojeker
Copy link

Jojeker commented Jan 16, 2025

Describe the bug

Hello,

I am trying to run the TTT algorithm on learnlib 0.17 and run into some issues when setting it up with a "TraceLevelOracle", i.e. I have no SUL as such, but only the Oracle that provides an Output Trace for a corresponding input trace.

My example is fairly short, and oriented at the examples that were given in examples/. Regarding the "TraceLevelOracle", I followed the advice from #130.

To Reproduce

I have the following main:

package uelearner;

import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.learnlib.acex.AcexAnalyzers;
import de.learnlib.algorithm.LearningAlgorithm.MealyLearner;
import de.learnlib.algorithm.ttt.mealy.TTTLearnerMealyBuilder;
import de.learnlib.filter.statistic.Counter;
import de.learnlib.oracle.EquivalenceOracle;
import de.learnlib.oracle.equivalence.MealyRandomWordsEQOracle;
import de.learnlib.query.DefaultQuery;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.alphabet.ArrayAlphabet;
import net.automatalib.automaton.transducer.MealyMachine;
import net.automatalib.word.Word;

public final class App {

  private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

  // prevent instantiation
  private App() {
  }

  final static String[] INPUT_ALPHABET = { "A" };

  public static void main(String[] args) throws IOException {

    // Setup the Oracle and alphabet
    TraceLevelOracle oracle = new TraceLevelOracle();
    Alphabet<String> inputAlphabet = new ArrayAlphabet<>(INPUT_ALPHABET);

    // This learner crashes in the first counter example
    MealyLearner<String, String> learner = new TTTLearnerMealyBuilder<String, String>()
        .withAlphabet(inputAlphabet)
        .withOracle(oracle)
        .withAnalyzer(AcexAnalyzers.BINARY_SEARCH_BWD)
        .create();

    // This learner runs into an infinite loop
    // MealyLearner<String, String> learner = new
    // ExtensibleLStarMealyBuilder<String, String>()
    // .withAlphabet(inputAlphabet)
    // .withOracle(oracle)
    // .withCexHandler(ObservationTableCEXHandlers.RIVEST_SCHAPIRE)
    // .create();

    // This is the easy way for finding counter examples
    EquivalenceOracle.MealyEquivalenceOracle<String, String> eqOracle = new MealyRandomWordsEQOracle<String, String>(
        oracle, 2, 2, 20);

    boolean learning = true;

    Counter rounds = new Counter("Rounds", "TTTLearner");

    LOGGER.info("====== Learning started =======");

    // Start learning step and get first model
    learner.startLearning();
    MealyMachine<?, String, ?, String> hypothesis = learner.getHypothesisModel();

    while (learning) {
      // For every round, output the dot model
      rounds.increment();

      LOGGER.info("Trying to find counter example...");
      DefaultQuery<String, Word<String>> counterExample = eqOracle.findCounterExample(hypothesis, inputAlphabet);

      // Learning finished
      if (counterExample == null) {
        learning = false;
        LOGGER.info("Learning complete!");

      } else {
        // Counter example has to be included
        LOGGER.info("Counter example found: {}", counterExample.toString());

        // Refine the hypothesis
        if (learner.refineHypothesis(counterExample)) {
          LOGGER.info("Refinement successful!");
        } else {
          LOGGER.error("Refinement failed");
          System.exit(-1);
        }

        // Get a new hypothesis after refining it with the
        // counter example
        hypothesis = learner.getHypothesisModel();
      }
    }
  }

}

And my TraceLevelOracle looks as follows:

package uelearner;

import java.util.ArrayList;
import java.util.Collection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle;
import de.learnlib.query.Query;
import net.automatalib.word.Word;

public class TraceLevelOracle implements MealyMembershipOracle<String, String> {

  private final Logger LOGGER = LoggerFactory.getLogger(TraceLevelOracle.class);

  TraceLevelOracle() {
  }


  @Override
  public void processQueries(Collection<? extends Query<String, Word<String>>> queries) {
    // Use batch processor in python
    ArrayList<ArrayList<String>> inputs = new ArrayList<>();

    // Convert input to list of lists
    queries.forEach(q -> {
      ArrayList<String> inputList = new ArrayList<>(q.getInput().asList());
      inputs.add(inputList);
    });

    LOGGER.info("Processing queries: " + inputs.size());

    ArrayList<ArrayList<String>> res_batch = new ArrayList<>();
    for (ArrayList<String> trace : inputs) {
      ArrayList<String> res = new ArrayList<>();
      for (int in = 0; in < trace.size(); in++) {
        // Only first symbol is 1
        if (in == 0) {
          res.add("1");
        } else {
          res.add("0");
        }
      }
      res_batch.add(res);
    }


    // Convert the output to the right format
    queries.forEach(q -> {
      ArrayList<String> outputList = res_batch.remove(0);
      Word<String> outputWord = Word.fromList(outputList);
      q.answer(outputWord);
    });
  }

  @Override
  public void processBatch(Collection<? extends Query<String, Word<String>>> queries) {
    processQueries(queries);
  }

}

The processQueries performs a transformation to nested ArrayLists, as they are usually passed to another program, where the batch is performed and retrieved. To simplify, I used a simple loop and an if to abstract this behavior.

The output I get is the following:

18:05:53.929 [main] INFO  uelearner.App -- ====== Learning started =======
18:05:53.941 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
18:05:53.943 [main] INFO  uelearner.App -- Trying to find counter example...
18:05:53.945 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
18:05:53.945 [main] INFO  uelearner.App -- Counter example found: Query[ε|A A / 1 0]
18:05:53.950 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
18:05:53.951 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
18:05:53.951 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
        at net.automatalib.word.SharedWord.getSymbol(SharedWord.java:101)
        at de.learnlib.util.mealy.MealyUtil.findMismatch(MealyUtil.java:50)
        at de.learnlib.algorithm.ttt.mealy.TTTLearnerMealy.findOutputInconsistency(TTTLearnerMealy.java:103)
        at de.learnlib.algorithm.ttt.base.AbstractTTTLearner.refineHypothesisSingle(AbstractTTTLearner.java:219)
        at de.learnlib.algorithm.ttt.mealy.TTTLearnerMealy.refineHypothesisSingle(TTTLearnerMealy.java:67)
        at de.learnlib.algorithm.ttt.base.AbstractTTTLearner.refineHypothesis(AbstractTTTLearner.java:163)
        at uelearner.App.main(App.java:82)

Oddly enough, using the ExtensibleLStarMealy Learner yields an execution which runs in an infinite loop.
Is there some detail I overlooked with the abstraction? I would expect both algorithms to provide as output the two state mealy machine [0] - (Sigma) -> [1] (Loop (Sigma)).

Finally I have one question about the implementation of batching for TTT and for LStarMealy: When executing ExtensibleLStarMealy with a larger alphabet, I get batch sizes, that are greater then 1, while with TTT I don't. Is it correct, that TTT always performs sequential queries to the oracle and does not batch them?

Thank you for the great project and the work you put into it!

@mtf90
Copy link
Member

mtf90 commented Jan 16, 2025

Hi,

Is there some detail I overlooked with the abstraction?

Yes. Queries consist of a prefix (e.g., an access sequence to a state) and a suffix (e.g., the future behavior of a reached state). Queries should be answered with the system's response to the suffix only. In your transformation, you construct lists with the full getInput() and call answer() with the full output. Your example can be fixed relatively easy by writing

q.answer(outputWord.suffix(q.getSuffix().length()));

instead. Your actual oracle needs to be adjusted accordingly.

Finally I have one question ...

It depends on what the learner is currently doing. For TTT, there is a distinct counterexample analysis phase where, for example, binary search is used to pinpoint a violating transition. This search is inherently sequential, so you often see query batches of size 1. Once the hypothesis is refined, the open transitions need to be closed. This typically results in multiple simultaneous queries and the discrimination tree-based learners should batch them as well (if they consistently don't, feel free to raise another issue). However, this scales with the number of input symbols, so in your example (final static String[] INPUT_ALPHABET = { "A" };) you still won't see any larger batches.

In contrast, the vanilla L* algorithm just adds all prefixes of the counterexample as rows to the observation table. Together with potentially multiple columns, this often results in batches of some quadratic size.

@Jojeker
Copy link
Author

Jojeker commented Jan 16, 2025

Hello,

thank you for the quick reply and the clarification! Applying the proposed snippet indeed fixes the issue, I ran into. I was not aware about the requirement to provide the suffix only instead of the entire word.

Regarding the batching, I tried to modify the alphabet to get more queries, by adding all letters from A to Z.

-  final static String[] INPUT_ALPHABET = { "A" };
+  final static String[] INPUT_ALPHABET = { "A", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
+      "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" };

However, it seems that one query per batch is executed nonetheless:

23:23:40.030 [main] INFO  uelearner.App -- ====== Learning started =======
23:23:40.042 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.042 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.042 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.042 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.042 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.043 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.044 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.045 [main] INFO  uelearner.App -- Trying to find counter example...
23:23:40.048 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.048 [main] INFO  uelearner.App -- Counter example found: Query[ε|i b / 1 0]
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.053 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.054 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.055 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.056 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.057 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.058 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.App -- Refinement successful!
23:23:40.059 [main] INFO  uelearner.App -- Trying to find counter example...
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.059 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.TraceLevelOracle -- Processing queries: 1
23:23:40.060 [main] INFO  uelearner.App -- Learning complete!

The result is consistent with my actual oracle.
I tried to look for some configuration options, but I did not find anything related to batch sizes for TTT. Do you know what I could be missing for this?

Thank you again for the help!

@mtf90
Copy link
Member

mtf90 commented Jan 17, 2025

Oh wow, this is actually an "issue" with the existing implementation. Specifically for the Mealy learner, creating new transitions results in an extra query to determine the transition output.

@Override
protected TTTTransition<I, Word<O>> createTransition(TTTState<I, Word<O>> state, I sym) {
TTTTransitionMealy<I, O> trans = new TTTTransitionMealy<>(state, sym);
trans.output = query(state, Word.fromLetter(sym)).firstSymbol();
return trans;
}

This is called in a simple for loop whenever initializing a new state, so you get k singleton queries repeatedly.

Furthermore, finalizing splitters also involves posing singleton queries.

for (TTTTransition<I, D> trans : curr.getIncoming()) {
D outcome = query(trans, discriminator);
curr.getSplitData().getIncoming(outcome).add(trans);
markAndPropagate(curr, outcome);
}

It seems like only the sifting of transitions properly utilizes batching. (You'll see it with discrimination trees with height > 1, e.g., if your hypothesis has more states).

From the looks of it, it should be possible to batch these operations as they have no sequential data-dependencies between them. I'll try to look into it in the upcoming days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants