diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1ff89db77..d0a330f5f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -47,7 +47,7 @@ jobs:
mvn -B install -DskipTests
cd $GITHUB_WORKSPACE
- name: Run Maven
- run: mvn -B install site -Pintegration-tests,code-analysis,bundles,jlink
+ run: mvn -B install site -Pintegration-tests,code-analysis,bundles
platform-integration:
name: "Platform Integration (JDK: ${{ matrix.jdk }}, OS: ${{ matrix.os }})"
needs: [ tests-and-analysis ]
@@ -81,7 +81,7 @@ jobs:
mvn -B install -DskipTests
cd $GITHUB_WORKSPACE
- name: Run Maven
- run: mvn -B install -Pjlink
+ run: mvn -B install -Pcli
coverage:
name: "Coverage"
needs: [ platform-integration ]
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 000000000..2615c3306
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,35 @@
+name: Attach artifacts to GitHub release
+on:
+ release:
+ types: [published]
+jobs:
+ publish:
+ name: "Build and attach artifacts"
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ ubuntu-22.04, windows-2022, macos-15-intel, macos-15 ]
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-java@v5
+ with:
+ java-version: '25'
+ distribution: 'temurin'
+ cache: 'maven'
+ - name: Build AutomataLib # can be removed on actual stable releases
+ shell: bash
+ run: |
+ git clone -b develop --single-branch https://github.com/LearnLib/automatalib.git ${HOME}/automatalib-git
+ cd ${HOME}/automatalib-git
+ mvn -B install -DskipTests
+ cd $GITHUB_WORKSPACE
+ - name: Build
+ run: mvn -B package -DskipTests -Pcli
+ - name: Release
+ uses: softprops/action-gh-release@v3
+ with:
+ working_directory: cli/target
+ files: learnlib-cli-*.zip
+ fail_on_unmatched_files: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc046ef9d..a4ec2b907 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
+* GitHub releases now provide `learnlib-cli` artifacts for using LearnLib via the command-line interface without the need for Java or Maven.
* Added a new (L*-based) learning algorithm for *Mealy machines with local timers* (MMLTs), including support for parallel queries, caching, and conformance testing (thanks to [Paul Kogel](https://github.com/pdev55)).
* Added the Ls active learning algorithm for Mealy machines (thanks to [Wolffhardt Schwabe](https://github.com/stateMachinist)).
* Added an `EarlyExitEQOracle` which for a given `AdaptiveMembershipOracle` and `TestWordGenerator` stops the evaluation of (potentially long) Mealy-based equivalence tests as soon as a mismatch with the hypothesis is detected, potentially improving the symbol performance of the given equivalence oracle.
@@ -21,12 +22,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Statistics collection has received a major rework. Previously, classes would implement the `StatisticCollector` interface and return a `StatisticData` object which 1) only allows for describing a very limited amount of data, and 2) requires you to keep track of all the objects that collect data. This approach has been *replaced* by a new `StatisticsService`. Instances of this service can be obtained similar to a logger via `Statistics.getService()` and require you to provide an implementation of this service on the classpath (a default one is provided by the `learnlib-statistics` module). The new service allows arbitrary components to collect various data which can be conveniently extracted based on the new `StatisticsKey`s used by the components. For more details on advanced scenarios (such as multi-threaded benchmarking), see the documentation of the respective classes. While this may require you to adjust the way you are collecting statistics, all functionality from beforehand should still be available.
* `SimpleProfiler` has been replaced by the new clock-based statistics.
* Most learners now more rigorously implement the `LearningAlgorithm` contract that, e.g., duplicate invocations of `startLearning` or calling `refineHypothesis` / `getHypothesisModel` before `startLearning` throw `IllegalStateException`s.
+* `Experiment` now has type variables for the input symbol type and output domain type.
+* `{DFA,Mealy,Moore}Experiment` have been moved to the `de.learnlib.util` package.
* The `generateTestWords` method of `AbstractTestWordEQOracle` now needs to be public.
* The classes of `de.learnlib.testsupport.it.learner` have been split into the packages `de.learnlib.testsupport.it{,testcase,util,variant}` in the same module (`de.learnlib.testsupport:learnlib-learner-it-support`).
### Removed
-* All *adapters* from the `learnlib-procedural` learner have been removed to due main learners implementing `AccessSequenceTransformer` now. Use the constructors of the main learners instead.
+* All *adapters* from the `learnlib-procedural` learner have been removed due to main learners implementing `AccessSequenceTransformer` now. Use the constructors of the main learners instead.
### Fixed
diff --git a/algorithms/active/aaar/src/test/java/de/learnlib/algorithm/aaar/AbstractAAARTest.java b/algorithms/active/aaar/src/test/java/de/learnlib/algorithm/aaar/AbstractAAARTest.java
index 6b4998252..5988b8ce3 100644
--- a/algorithms/active/aaar/src/test/java/de/learnlib/algorithm/aaar/AbstractAAARTest.java
+++ b/algorithms/active/aaar/src/test/java/de/learnlib/algorithm/aaar/AbstractAAARTest.java
@@ -52,12 +52,12 @@ public void testAbstractHypothesisEquivalence() {
final WpMethodTestsIterator iter = new WpMethodTestsIterator<>(automaton, alphabet);
final List> testCases = IteratorUtil.list(iter);
- final SampleSetEQOracle eqo = new SampleSetEQOracle<>();
- eqo.addAll(new SimulatorOracle<>(automaton), testCases);
+ final SampleSetEQOracle eqo =
+ new SampleSetEQOracle().addAll(new SimulatorOracle<>(automaton), testCases);
final LearningAlgorithm learner =
new TranslatingLearnerWrapper<>((AbstractAAARLearner, A, A, I, I, D>) aaarLearner);
- final Experiment exp = new Experiment<>(learner, eqo, alphabet);
+ final Experiment exp = new Experiment<>(learner, eqo, alphabet);
exp.run();
diff --git a/algorithms/active/adt/src/main/java/de/learnlib/algorithm/adt/learner/ADTLearner.java b/algorithms/active/adt/src/main/java/de/learnlib/algorithm/adt/learner/ADTLearner.java
index d236176ec..f78b63b34 100644
--- a/algorithms/active/adt/src/main/java/de/learnlib/algorithm/adt/learner/ADTLearner.java
+++ b/algorithms/active/adt/src/main/java/de/learnlib/algorithm/adt/learner/ADTLearner.java
@@ -60,6 +60,7 @@
import de.learnlib.query.DefaultQuery;
import de.learnlib.tooling.annotation.builder.GenerateBuilder;
import de.learnlib.util.MQUtil;
+import de.learnlib.util.mealy.Adaptive2MembershipWrapper;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.alphabet.SupportsGrowingAlphabet;
import net.automatalib.automaton.transducer.MealyMachine;
@@ -104,12 +105,26 @@ public class ADTLearner implements LearningAlgorithm.MealyLearner,
private ADTHypothesis hypothesis;
private ADT, I, O> adt;
+ public ADTLearner(Alphabet alphabet, AdaptiveMembershipOracle oracle) {
+ this(alphabet,
+ oracle,
+ BuilderDefaults.leafSplitter(),
+ BuilderDefaults.adtExtender(),
+ BuilderDefaults.subtreeReplacer());
+ }
+
public ADTLearner(Alphabet alphabet,
AdaptiveMembershipOracle oracle,
LeafSplitter leafSplitter,
ADTExtender adtExtender,
SubtreeReplacer subtreeReplacer) {
- this(alphabet, oracle, leafSplitter, adtExtender, subtreeReplacer, true, LocalSuffixFinders.RIVEST_SCHAPIRE);
+ this(alphabet,
+ oracle,
+ leafSplitter,
+ adtExtender,
+ subtreeReplacer,
+ BuilderDefaults.useObservationTree(),
+ BuilderDefaults.suffixFinder());
}
@GenerateBuilder(defaults = BuilderDefaults.class)
diff --git a/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java b/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java
index a4f2fc19a..aca5a0198 100644
--- a/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java
+++ b/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java
@@ -47,7 +47,7 @@
import de.learnlib.testsupport.MQ2AQWrapper;
import de.learnlib.testsupport.it.AbstractMealyLearnerIT;
import de.learnlib.testsupport.it.variant.LearnerVariantList;
-import de.learnlib.util.Experiment.MealyExperiment;
+import de.learnlib.util.MealyExperiment;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.automaton.transducer.impl.CompactMealy;
import net.automatalib.exception.FormatException;
diff --git a/algorithms/active/lambda/src/test/java/de/learnlib/algorithm/lambda/AbstractCounterexampleQueueTest.java b/algorithms/active/lambda/src/test/java/de/learnlib/algorithm/lambda/AbstractCounterexampleQueueTest.java
index 001c563e7..16aab8bc3 100644
--- a/algorithms/active/lambda/src/test/java/de/learnlib/algorithm/lambda/AbstractCounterexampleQueueTest.java
+++ b/algorithms/active/lambda/src/test/java/de/learnlib/algorithm/lambda/AbstractCounterexampleQueueTest.java
@@ -19,7 +19,7 @@
import de.learnlib.oracle.MembershipOracle.DFAMembershipOracle;
import de.learnlib.oracle.equivalence.SampleSetEQOracle;
import de.learnlib.oracle.membership.DFASimulatorOracle;
-import de.learnlib.util.Experiment.DFAExperiment;
+import de.learnlib.util.Experiment;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.alphabet.impl.Alphabets;
import net.automatalib.automaton.fsa.DFA;
@@ -80,7 +80,8 @@ public void testPop() {
final Word b = new WordBuilder<>('b', 9).toWord();
eqOracle.addAll(mqOracle, Word.fromWords(b, a, b, a, b, a, b, a));
- final DFAExperiment experiment = new DFAExperiment<>(learner, eqOracle, alphabet);
+ final Experiment, Character, Boolean> experiment =
+ new Experiment<>(learner, eqOracle, alphabet);
experiment.run();
final DFA, Character> result = experiment.getFinalHypothesis();
diff --git a/algorithms/active/lambda/src/test/java/de/learnlib/algorithm/lambda/lstar/mealy/it/LLambdaMealyIT.java b/algorithms/active/lambda/src/test/java/de/learnlib/algorithm/lambda/lstar/mealy/it/LLambdaMealyIT.java
index 7aab7adea..668abd70b 100644
--- a/algorithms/active/lambda/src/test/java/de/learnlib/algorithm/lambda/lstar/mealy/it/LLambdaMealyIT.java
+++ b/algorithms/active/lambda/src/test/java/de/learnlib/algorithm/lambda/lstar/mealy/it/LLambdaMealyIT.java
@@ -30,7 +30,7 @@
import de.learnlib.query.DefaultQuery;
import de.learnlib.testsupport.it.AbstractMealyLearnerIT;
import de.learnlib.testsupport.it.variant.LearnerVariantList.MealyLearnerVariantList;
-import de.learnlib.util.Experiment.MealyExperiment;
+import de.learnlib.util.MealyExperiment;
import de.learnlib.util.mealy.MealyUtil;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.automaton.transducer.MealyMachine;
diff --git a/algorithms/active/nlstar/src/test/java/de/learnlib/algorithm/nlstar/NLStarTest.java b/algorithms/active/nlstar/src/test/java/de/learnlib/algorithm/nlstar/NLStarTest.java
index 1b09dc85f..e4af83c0e 100644
--- a/algorithms/active/nlstar/src/test/java/de/learnlib/algorithm/nlstar/NLStarTest.java
+++ b/algorithms/active/nlstar/src/test/java/de/learnlib/algorithm/nlstar/NLStarTest.java
@@ -62,7 +62,8 @@ public void testIssue70() {
final NLStarLearner learner = new NLStarLearner<>(alphabet, mqOracle);
- final Experiment> experiment = new Experiment<>(learner, eqOracle, alphabet);
+ final Experiment, Character, Boolean> experiment =
+ new Experiment<>(learner, eqOracle, alphabet);
experiment.run();
final NFA, Character> hyp = experiment.getFinalHypothesis();
diff --git a/algorithms/active/observation-pack-vpa/src/test/java/de/learnlib/algorithm/observationpack/vpa/DTVisualizationTest.java b/algorithms/active/observation-pack-vpa/src/test/java/de/learnlib/algorithm/observationpack/vpa/DTVisualizationTest.java
index 7c29f5b25..c3a062280 100644
--- a/algorithms/active/observation-pack-vpa/src/test/java/de/learnlib/algorithm/observationpack/vpa/DTVisualizationTest.java
+++ b/algorithms/active/observation-pack-vpa/src/test/java/de/learnlib/algorithm/observationpack/vpa/DTVisualizationTest.java
@@ -50,7 +50,7 @@ public DTVisualizationTest() {
final SimulatorEQOracle eqo = new SimulatorEQOracle<>(vpa);
this.learner = new OPLearnerVPA<>(alphabet, mqo, AcexAnalyzers.BINARY_SEARCH_FWD);
- final Experiment> exp = new Experiment<>(learner, eqo, alphabet);
+ final Experiment, Character, Boolean> exp = new Experiment<>(learner, eqo, alphabet);
exp.run();
}
diff --git a/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/sba/OptimizationsTest.java b/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/sba/OptimizationsTest.java
index 616fb65dc..c6a311c12 100644
--- a/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/sba/OptimizationsTest.java
+++ b/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/sba/OptimizationsTest.java
@@ -55,7 +55,7 @@ public void testOptimizations() {
final SBALearner learner = new SBALearner<>(alphabet, mqo, TTTLearnerDFA::new);
- final Experiment> experiment = new Experiment<>(learner, eqo, alphabet);
+ final Experiment, Character, Boolean> experiment = new Experiment<>(learner, eqo, alphabet);
experiment.run();
diff --git a/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/spmm/OptimizationsTest.java b/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/spmm/OptimizationsTest.java
index 7b110ce4b..bbda20446 100644
--- a/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/spmm/OptimizationsTest.java
+++ b/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/spmm/OptimizationsTest.java
@@ -57,7 +57,8 @@ public void testOptimizations() {
final SPMMLearner learner =
new SPMMLearner<>(alphabet, spmm.getErrorOutput(), mqo, TTTLearnerMealy::new);
- final Experiment> experiment = new Experiment<>(learner, eqo, alphabet);
+ final Experiment, Character, Word> experiment =
+ new Experiment<>(learner, eqo, alphabet);
experiment.run();
diff --git a/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java b/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java
index 71ab667dd..03c58e9ff 100644
--- a/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java
+++ b/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java
@@ -10,7 +10,7 @@
import de.learnlib.oracle.equivalence.DFAWMethodEQOracle;
import de.learnlib.oracle.membership.DFASimulatorOracle;
import de.learnlib.statistic.Statistics;
-import de.learnlib.util.Experiment.DFAExperiment;
+import de.learnlib.util.DFAExperiment;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.alphabet.impl.Alphabets;
import net.automatalib.automaton.fsa.DFA;
diff --git a/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java b/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java
index 71ab667dd..03c58e9ff 100644
--- a/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java
+++ b/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java
@@ -10,7 +10,7 @@
import de.learnlib.oracle.equivalence.DFAWMethodEQOracle;
import de.learnlib.oracle.membership.DFASimulatorOracle;
import de.learnlib.statistic.Statistics;
-import de.learnlib.util.Experiment.DFAExperiment;
+import de.learnlib.util.DFAExperiment;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.alphabet.impl.Alphabets;
import net.automatalib.automaton.fsa.DFA;
diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index 93cadfcdd..b6f18f236 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -80,6 +80,11 @@ limitations under the License.
de/learnlib/oracle/property/Mealy*Oracle.class
de/learnlib/oracle/property/DFA*Chain.class
de/learnlib/oracle/property/Mealy*Chain.class
+
+
+ de/learnlib/util/DFAExperiment.class
+ de/learnlib/util/MealyExperiment.class
+ de/learnlib/util/MooreExperiment.class
@@ -260,13 +265,7 @@ limitations under the License.
true
true
-
-
+ ${project.build.directory}/test-checkerframework
org.checkerframework
diff --git a/cli/pom.xml b/cli/pom.xml
new file mode 100644
index 000000000..4d233dd2c
--- /dev/null
+++ b/cli/pom.xml
@@ -0,0 +1,351 @@
+
+
+
+ 4.0.0
+
+
+ de.learnlib
+ learnlib-build-parent
+ 0.19.0-SNAPSHOT
+ ../build-parent/pom.xml
+
+
+ learnlib-cli
+
+ LearnLib :: CLI
+
+ A module for building a standalone command-line application of LearnLib. Note that this artifact is not intended
+ as a library and therefore not deployed to Maven Central but instead provided as a direct download (e.g., from
+ GitHub releases).
+
+
+
+ ${project.build.directory}/maven-jlink/default/bin/learnlib
+
+
+
+
+
+
+ de.learnlib
+ learnlib-api
+
+
+ de.learnlib
+ learnlib-cache
+
+
+ de.learnlib
+ learnlib-membership-oracles
+
+
+ de.learnlib
+ learnlib-parallelism
+
+
+ de.learnlib
+ learnlib-util
+
+
+ de.learnlib
+ learnlib-statistics
+
+
+
+
+ de.learnlib
+ learnlib-adt
+
+
+ de.learnlib
+ learnlib-dhc
+
+
+ de.learnlib
+ learnlib-equivalence-oracles
+
+
+ de.learnlib
+ learnlib-kearns-vazirani
+
+
+ de.learnlib
+ learnlib-lambda
+
+
+ de.learnlib
+ learnlib-lsharp
+
+
+ de.learnlib
+ learnlib-lstar
+
+
+ de.learnlib
+ learnlib-nlstar
+
+
+ de.learnlib
+ learnlib-observation-pack
+
+
+ de.learnlib
+ learnlib-observation-pack-vpa
+
+
+ de.learnlib
+ learnlib-procedural
+
+
+ de.learnlib
+ learnlib-sparse
+
+
+ de.learnlib
+ learnlib-ttt
+
+
+ de.learnlib
+ learnlib-ttt-vpa
+
+
+
+
+ net.automatalib
+ automata-api
+
+
+ net.automatalib
+ automata-commons-util
+
+
+ net.automatalib
+ automata-core
+
+
+ net.automatalib
+ automata-serialization-aut
+
+
+ net.automatalib
+ automata-serialization-ba
+
+
+ net.automatalib
+ automata-serialization-dot
+
+
+ net.automatalib
+ automata-serialization-learnlibv2
+
+
+ net.automatalib
+ automata-serialization-mata
+
+
+ net.automatalib
+ automata-serialization-saf
+
+
+ net.automatalib
+ automata-serialization-taf
+
+
+ net.automatalib
+ automata-util
+
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ info.picocli
+ picocli
+
+
+ org.apache.fory
+ fory-core
+
+
+ org.checkerframework
+ checker-qual
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ org.mockito
+ mockito-core
+
+
+ org.testng
+ testng
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+
+ true
+
+
+
+
+
+
+ kr.motd.maven
+ os-maven-plugin
+ ${os-plugin.version}
+
+
+
+
+
+
+ cli
+
+
+
+ maven-failsafe-plugin
+
+
+ check-binary
+
+ integration-test
+ verify
+
+
+
+ ${learnlib.binary.path}
+
+
+ **/CheckBinary.java
+
+
+ **/*IT.java
+
+
+
+
+
+
+ org.codehaus.mojo
+ license-maven-plugin
+
+ test,provided,system
+ true
+
+
+
+ add-third-party
+
+ add-third-party
+
+
+
+ download-licenses
+
+ download-licenses
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jlink-plugin
+
+
+ package
+
+ jlink
+
+
+ false
+
+
+ ${project.build.directory}/generated-sources/license/
+ THIRD-PARTY.txt
+ legal/third-party
+
+
+ ${project.build.directory}/generated-resources/licenses/
+ legal/third-party/licenses
+
+
+ true
+ true
+ true
+ ${project.artifactId}-${project.version}
+ ${project.artifactId}-${project.version}-${os.detected.classifier}
+ learnlib=de.learnlib.cli/de.learnlib.cli.Application
+
+
+
+
+
+
+
+
+ jlink-on-java-24
+
+ [24,]
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jlink-plugin
+
+
+
+
+ --add-opens=java.base/java.lang.invoke=org.apache.fory.core
+
+
+
+
+
+
+
+
+ windows
+
+
+ windows
+
+
+
+ ${project.build.directory}/maven-jlink/default/bin/learnlib.bat
+
+
+
+
diff --git a/cli/src/main/java/de/learnlib/cli/Application.java b/cli/src/main/java/de/learnlib/cli/Application.java
new file mode 100644
index 000000000..82bf9e5fe
--- /dev/null
+++ b/cli/src/main/java/de/learnlib/cli/Application.java
@@ -0,0 +1,70 @@
+/* Copyright (C) 2013-2026 TU Dortmund University
+ * This file is part of LearnLib .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.learnlib.cli;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import de.learnlib.cli.option.Options;
+import de.learnlib.cli.util.VersionProvider;
+import org.slf4j.LoggerFactory;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+
+@Command(name = "learnlib",
+ mixinStandardHelpOptions = true,
+ versionProvider = VersionProvider.class,
+ resourceBundle = Application.PROPERTIES,
+ showDefaultValues = true,
+ showAtFileInUsageHelp = true,
+ descriptionHeading = "%nDescription:%n%n",
+ parameterListHeading = "%nParameters:%n",
+ optionListHeading = "%nOptions:%n",
+ description = "Runs an active automata learning process by invoking the provided SUL(s) to answer membership queries. For learning acceptor-based formalisms, the tool will use the exitcode of the binary to determine acceptance where an exitcode of 0 equals 'accept' and an exitcode unequal to 0 equals 'reject'. For learning transduction-based formalisms, the tool expects the SUL to emit an output that is transformed into individual symbols using the provided 'delimiter'. For additional information on the involved components of LearnLib, you may use the documentation available at https://learnlib.de/learnlib/maven-site/.")
+public class Application implements Runnable {
+
+ public static final String PROPERTIES = "application";
+
+ @Mixin
+ private Options options;
+
+ public static void main(String[] args) {
+ CommandLine commandLine = new CommandLine(new Application());
+ commandLine.setCaseInsensitiveEnumValuesAllowed(true);
+ System.exit(commandLine.execute(args));
+ }
+
+ @Override
+ public void run() {
+ setLogLevel(options);
+ options.type.runner(options).run(options);
+ }
+
+ private void setLogLevel(Options options) {
+ if (options.verbosity != null) {
+ final Logger root = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+
+ Level level;
+ if (options.verbosity.length == 1) {
+ level = Level.DEBUG;
+ } else {
+ level = Level.TRACE;
+ }
+
+ root.setLevel(level);
+ }
+ }
+}
diff --git a/cli/src/main/java/de/learnlib/cli/adapter/ProceduralDFAAdapter.java b/cli/src/main/java/de/learnlib/cli/adapter/ProceduralDFAAdapter.java
new file mode 100644
index 000000000..11eb41287
--- /dev/null
+++ b/cli/src/main/java/de/learnlib/cli/adapter/ProceduralDFAAdapter.java
@@ -0,0 +1,91 @@
+/* Copyright (C) 2013-2026 TU Dortmund University
+ * This file is part of LearnLib .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.learnlib.cli.adapter;
+
+import de.learnlib.AccessSequenceTransformer;
+import de.learnlib.algorithm.LearningAlgorithm.DFALearner;
+import de.learnlib.algorithm.kv.dfa.KearnsVaziraniDFA;
+import de.learnlib.algorithm.lambda.lstar.LLambdaDFA;
+import de.learnlib.algorithm.lambda.ttt.dfa.TTTLambdaDFA;
+import de.learnlib.algorithm.lstar.dfa.ExtensibleLStarDFA;
+import de.learnlib.algorithm.malerpnueli.MalerPnueliDFA;
+import de.learnlib.algorithm.observationpack.dfa.OPLearnerDFA;
+import de.learnlib.algorithm.rivestschapire.RivestSchapireDFA;
+import de.learnlib.algorithm.ttt.dfa.TTTLearnerDFA;
+import de.learnlib.oracle.MembershipOracle;
+import net.automatalib.alphabet.Alphabet;
+import net.automatalib.alphabet.SupportsGrowingAlphabet;
+
+public interface ProceduralDFAAdapter
+ extends DFALearner, SupportsGrowingAlphabet, AccessSequenceTransformer {
+
+ final class KearnsVaziraniDFAAdapter extends KearnsVaziraniDFA implements ProceduralDFAAdapter {
+
+ public KearnsVaziraniDFAAdapter(Alphabet alphabet, MembershipOracle oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class LLambdaDFAAdapter extends LLambdaDFA implements ProceduralDFAAdapter {
+
+ public LLambdaDFAAdapter(Alphabet alphabet, MembershipOracle oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class ExtensibleLStarDFAAdapter extends ExtensibleLStarDFA implements ProceduralDFAAdapter {
+
+ public ExtensibleLStarDFAAdapter(Alphabet alphabet, MembershipOracle oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class MalerPnueliDFAAdapter extends MalerPnueliDFA implements ProceduralDFAAdapter {
+
+ public MalerPnueliDFAAdapter(Alphabet alphabet, MembershipOracle oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class OPLearnerDFAAdapter extends OPLearnerDFA implements ProceduralDFAAdapter {
+
+ public OPLearnerDFAAdapter(Alphabet alphabet, MembershipOracle oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class RivestSchapireDFAAdapter extends RivestSchapireDFA implements ProceduralDFAAdapter {
+
+ public RivestSchapireDFAAdapter(Alphabet alphabet, MembershipOracle oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class TTTLearnerDFAAdapter extends TTTLearnerDFA implements ProceduralDFAAdapter {
+
+ public TTTLearnerDFAAdapter(Alphabet alphabet, MembershipOracle oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class TTTLambdaDFAAdapter extends TTTLambdaDFA implements ProceduralDFAAdapter {
+
+ public TTTLambdaDFAAdapter(Alphabet alphabet, MembershipOracle oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+}
diff --git a/cli/src/main/java/de/learnlib/cli/adapter/ProceduralMealyAdapter.java b/cli/src/main/java/de/learnlib/cli/adapter/ProceduralMealyAdapter.java
new file mode 100644
index 000000000..056951949
--- /dev/null
+++ b/cli/src/main/java/de/learnlib/cli/adapter/ProceduralMealyAdapter.java
@@ -0,0 +1,111 @@
+/* Copyright (C) 2013-2026 TU Dortmund University
+ * This file is part of LearnLib .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.learnlib.cli.adapter;
+
+import de.learnlib.AccessSequenceTransformer;
+import de.learnlib.algorithm.LearningAlgorithm.MealyLearner;
+import de.learnlib.algorithm.dhc.mealy.MealyDHC;
+import de.learnlib.algorithm.kv.mealy.KearnsVaziraniMealy;
+import de.learnlib.algorithm.lambda.lstar.LLambdaMealy;
+import de.learnlib.algorithm.lambda.ttt.mealy.TTTLambdaMealy;
+import de.learnlib.algorithm.lstar.mealy.ExtensibleLStarMealy;
+import de.learnlib.algorithm.malerpnueli.MalerPnueliMealy;
+import de.learnlib.algorithm.observationpack.mealy.OPLearnerMealy;
+import de.learnlib.algorithm.rivestschapire.RivestSchapireMealy;
+import de.learnlib.algorithm.sparse.SparseLearner;
+import de.learnlib.algorithm.ttt.mealy.TTTLearnerMealy;
+import de.learnlib.oracle.MembershipOracle;
+import net.automatalib.alphabet.Alphabet;
+import net.automatalib.alphabet.SupportsGrowingAlphabet;
+import net.automatalib.word.Word;
+
+public interface ProceduralMealyAdapter
+ extends MealyLearner, SupportsGrowingAlphabet, AccessSequenceTransformer {
+
+ final class MealyDHCAdapter extends MealyDHC implements ProceduralMealyAdapter {
+
+ public MealyDHCAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class KearnsVaziraniMealyAdapter extends KearnsVaziraniMealy
+ implements ProceduralMealyAdapter {
+
+ public KearnsVaziraniMealyAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class LLambdaMealyAdapter extends LLambdaMealy implements ProceduralMealyAdapter {
+
+ public LLambdaMealyAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class ExtensibleLStarMealyAdapter extends ExtensibleLStarMealy
+ implements ProceduralMealyAdapter {
+
+ public ExtensibleLStarMealyAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class MalerPnueliMealyAdapter extends MalerPnueliMealy implements ProceduralMealyAdapter {
+
+ public MalerPnueliMealyAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class SparseLearnerAdapter extends SparseLearner implements ProceduralMealyAdapter {
+
+ public SparseLearnerAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class OPLearnerMealyAdapter extends OPLearnerMealy implements ProceduralMealyAdapter {
+
+ public OPLearnerMealyAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class RivestSchapireMealyAdapter extends RivestSchapireMealy
+ implements ProceduralMealyAdapter {
+
+ public RivestSchapireMealyAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class TTTLearnerMealyAdapter extends TTTLearnerMealy implements ProceduralMealyAdapter {
+
+ public TTTLearnerMealyAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+ final class TTTLambdaMealyAdapter extends TTTLambdaMealy implements ProceduralMealyAdapter {
+
+ public TTTLambdaMealyAdapter(Alphabet alphabet, MembershipOracle> oracle) {
+ super(alphabet, oracle);
+ }
+ }
+
+}
diff --git a/cli/src/main/java/de/learnlib/cli/factory/AlphabetFactory.java b/cli/src/main/java/de/learnlib/cli/factory/AlphabetFactory.java
new file mode 100644
index 000000000..3876816d8
--- /dev/null
+++ b/cli/src/main/java/de/learnlib/cli/factory/AlphabetFactory.java
@@ -0,0 +1,68 @@
+/* Copyright (C) 2013-2026 TU Dortmund University
+ * This file is part of LearnLib .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.learnlib.cli.factory;
+
+import java.util.Objects;
+
+import de.learnlib.cli.option.Options;
+import de.learnlib.cli.option.Symbols.ContextFreeSymbols;
+import de.learnlib.cli.option.Symbols.RegularSymbols;
+import net.automatalib.alphabet.Alphabet;
+import net.automatalib.alphabet.ProceduralInputAlphabet;
+import net.automatalib.alphabet.VPAlphabet;
+import net.automatalib.alphabet.impl.Alphabets;
+import net.automatalib.alphabet.impl.DefaultProceduralInputAlphabet;
+import net.automatalib.alphabet.impl.DefaultVPAlphabet;
+
+public final class AlphabetFactory {
+
+ private AlphabetFactory() {
+ // prevent instantiation
+ }
+
+ public static Alphabet getRegularAlphabet(Options options) {
+ final RegularSymbols inputs = validateRegularSymbols(options);
+ return Alphabets.fromList(inputs.symbols);
+ }
+
+ public static ProceduralInputAlphabet getProceduralAlphabet(Options options) {
+ final ContextFreeSymbols inputs = validateContextFreeSymbols(options);
+ if (inputs.returnSymbols.size() != 1) {
+ throw new IllegalArgumentException("Procedural systems require exactly one return symbol");
+ }
+ return new DefaultProceduralInputAlphabet<>(Alphabets.fromList(inputs.internalSymbols),
+ Alphabets.fromList(inputs.callSymbols),
+ inputs.returnSymbols.get(0));
+ }
+
+ public static VPAlphabet getVPAlphabet(Options options) {
+ final ContextFreeSymbols inputs = validateContextFreeSymbols(options);
+ return new DefaultVPAlphabet<>(Alphabets.fromList(inputs.internalSymbols),
+ Alphabets.fromList(inputs.callSymbols),
+ Alphabets.fromList(inputs.returnSymbols));
+ }
+
+ private static RegularSymbols validateRegularSymbols(Options options) {
+ return Objects.requireNonNull(options.symbols.regularSymbols,
+ String.format("Type '%s' requires a regular alphabet definition", options.type));
+ }
+
+ private static ContextFreeSymbols validateContextFreeSymbols(Options options) {
+ return Objects.requireNonNull(options.symbols.contextFreeSymbols,
+ String.format("Type '%s' requires a context-free alphabet definition",
+ options.type));
+ }
+}
diff --git a/cli/src/main/java/de/learnlib/cli/factory/EQOFactory.java b/cli/src/main/java/de/learnlib/cli/factory/EQOFactory.java
new file mode 100644
index 000000000..ca79845f7
--- /dev/null
+++ b/cli/src/main/java/de/learnlib/cli/factory/EQOFactory.java
@@ -0,0 +1,264 @@
+/* Copyright (C) 2013-2026 TU Dortmund University
+ * This file is part of LearnLib .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.learnlib.cli.factory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.regex.Pattern;
+
+import de.learnlib.cli.option.EQOracle;
+import de.learnlib.cli.option.Options;
+import de.learnlib.oracle.AdaptiveMembershipOracle;
+import de.learnlib.oracle.EquivalenceOracle;
+import de.learnlib.oracle.MembershipOracle;
+import de.learnlib.oracle.equivalence.EQOracleChain;
+import de.learnlib.oracle.equivalence.KWayStateCoverEQOracle;
+import de.learnlib.oracle.equivalence.KWayTransitionCoverEQOracle;
+import de.learnlib.oracle.equivalence.RandomWMethodEQOracle;
+import de.learnlib.oracle.equivalence.RandomWordsEQOracle;
+import de.learnlib.oracle.equivalence.RandomWpMethodEQOracle;
+import de.learnlib.oracle.equivalence.SampleSetEQOracle;
+import de.learnlib.oracle.equivalence.WMethodEQOracle;
+import de.learnlib.oracle.equivalence.WpMethodEQOracle;
+import de.learnlib.oracle.equivalence.vpa.RandomWellMatchedWordsEQOracle;
+import de.learnlib.util.mealy.Adaptive2MembershipWrapper;
+import net.automatalib.alphabet.impl.Alphabets;
+import net.automatalib.automaton.UniversalDeterministicAutomaton.RegularAutomaton;
+import net.automatalib.automaton.concept.SuffixOutput;
+import net.automatalib.automaton.fsa.DFA;
+import net.automatalib.automaton.fsa.NFA;
+import net.automatalib.automaton.procedural.SBA;
+import net.automatalib.automaton.procedural.SPA;
+import net.automatalib.automaton.procedural.SPMM;
+import net.automatalib.automaton.vpa.OneSEVPA;
+import net.automatalib.util.automaton.fsa.NFAs;
+import net.automatalib.word.Word;
+
+public final class EQOFactory {
+
+ public static final int BATCH_SIZE = 10;
+ public static final double RANDOM_CALL_PROB = 0.5;
+
+ private EQOFactory() {
+ // prevent instantiation
+ }
+
+ public static & SuffixOutput, D> EQOracleChain getRegularOracles(
+ Options options,
+ MembershipOracle mqo) {
+ final EQOracleChain chain = new EQOracleChain<>();
+
+ for (EQOracle e : options.eqos) {
+ EquivalenceOracle super M, String, D> eqo = switch (e) {
+ case W -> new WMethodEQOracle<>(mqo,
+ options.eqoParams.wMethod.lookahead,
+ options.eqoParams.wMethod.expectedSize,
+ computeBatchSize(options));
+ case WP -> new WpMethodEQOracle<>(mqo,
+ options.eqoParams.wpMethod.lookahead,
+ options.eqoParams.wpMethod.expectedSize,
+ computeBatchSize(options));
+ case RANDOM -> new RandomWordsEQOracle<>(mqo,
+ options.eqoParams.random.minLength,
+ options.eqoParams.random.maxLength,
+ options.eqoParams.random.maxTests,
+ new Random(options.eqoParams.random.seed),
+ computeBatchSize(options));
+ case RANDOM_W -> new RandomWMethodEQOracle<>(mqo,
+ options.eqoParams.randomWMethod.minimalSize,
+ options.eqoParams.randomWMethod.rndLength,
+ options.eqoParams.randomWMethod.bound,
+ new Random(options.eqoParams.randomWMethod.seed),
+ computeBatchSize(options));
+ case RANDOM_WP -> new RandomWpMethodEQOracle<>(mqo,
+ options.eqoParams.randomWpMethod.minimalSize,
+ options.eqoParams.randomWpMethod.rndLength,
+ options.eqoParams.randomWpMethod.bound,
+ new Random(options.eqoParams.randomWpMethod.seed),
+ computeBatchSize(options));
+ case KWAY_S -> new KWayStateCoverEQOracle<>(mqo,
+ new Random(options.eqoParams.kWayState.seed),
+ options.eqoParams.kWayState.randomWalkLen,
+ options.eqoParams.kWayState.k,
+ options.eqoParams.kWayState.combinationMethod,
+ computeBatchSize(options));
+ case KWAY_T -> new KWayTransitionCoverEQOracle<>(mqo,
+ new Random(options.eqoParams.kWayTransition.seed),
+ options.eqoParams.kWayTransition.randomWalkLen,
+ options.eqoParams.kWayTransition.numGeneratePaths,
+ options.eqoParams.kWayTransition.maxPathLen,
+ options.eqoParams.kWayTransition.maxNumberOfSteps,
+ options.eqoParams.kWayTransition.k,
+ options.eqoParams.kWayTransition.optimizationMetric,
+ options.eqoParams.kWayTransition.generationMethod,
+ computeBatchSize(options));
+ case SAMPLE -> buildSampleSetOracle(options, mqo);
+ };
+ chain.addOracle(eqo);
+ }
+
+ return chain;
+ }
+
+ public static & SuffixOutput>, O> EQOracleChain> getAdaptiveOracles(
+ Options options,
+ AdaptiveMembershipOracle mqo) {
+ return getRegularOracles(options, new Adaptive2MembershipWrapper<>(mqo));
+ }
+
+ public static EQOracleChain, String, Boolean> getNFAOracles(Options options,
+ MembershipOracle mqo) {
+ final EQOracleChain super DFA, String>, String, Boolean> chain = getRegularOracles(options, mqo);
+ final EQOracleChain, String, Boolean> result = new EQOracleChain<>();
+
+ for (EquivalenceOracle super DFA, String>, String, Boolean> eqo : chain.getOracles()) {
+ result.addOracle((hyp, inputs) -> eqo.findCounterExample(NFAs.determinize(hyp,
+ Alphabets.fromCollection(inputs)),
+ inputs));
+ }
+
+ return result;
+ }
+
+ public static EQOracleChain, String, Boolean> getSBAOracles(Options options,
+ MembershipOracle mqo) {
+ final EQOracleChain, String, Boolean> chain = new EQOracleChain<>();
+
+ for (EQOracle e : options.eqos) {
+ EquivalenceOracle super SBA, String>, String, Boolean> eqo = switch (e) {
+ case W -> new de.learnlib.oracle.equivalence.sba.WMethodEQOracle<>(mqo,
+ options.eqoParams.wMethod.lookahead,
+ options.eqoParams.wMethod.expectedSize,
+ computeBatchSize(options));
+ case RANDOM -> buildRandomWellMatchedOracle(options, mqo);
+ case SAMPLE -> buildSampleSetOracle(options, mqo);
+ default -> throw new UnsupportedCombinationException(options, e);
+ };
+ chain.addOracle(eqo);
+ }
+
+ return chain;
+ }
+
+ public static EQOracleChain, String, Boolean> getSPAOracles(Options options,
+ MembershipOracle mqo) {
+ final EQOracleChain, String, Boolean> chain = new EQOracleChain<>();
+
+ for (EQOracle e : options.eqos) {
+ EquivalenceOracle super SPA, String>, String, Boolean> eqo = switch (e) {
+ case W -> new de.learnlib.oracle.equivalence.spa.WMethodEQOracle<>(mqo,
+ options.eqoParams.wMethod.lookahead,
+ options.eqoParams.wMethod.expectedSize,
+ computeBatchSize(options));
+ case WP -> new de.learnlib.oracle.equivalence.spa.WpMethodEQOracle<>(mqo,
+ options.eqoParams.wpMethod.lookahead,
+ options.eqoParams.wpMethod.expectedSize,
+ computeBatchSize(options));
+ case RANDOM -> buildRandomWellMatchedOracle(options, mqo);
+ case SAMPLE -> buildSampleSetOracle(options, mqo);
+ default -> throw new UnsupportedCombinationException(options, e);
+ };
+ chain.addOracle(eqo);
+ }
+
+ return chain;
+ }
+
+ public static EQOracleChain, String, Word> getSPMMOracles(Options options,
+ MembershipOracle> mqo) {
+ final EQOracleChain, String, Word> chain = new EQOracleChain<>();
+
+ for (EQOracle e : options.eqos) {
+ EquivalenceOracle super SPMM, String, ?, O>, String, Word> eqo = switch (e) {
+ case W -> new de.learnlib.oracle.equivalence.spmm.WMethodEQOracle<>(mqo,
+ options.eqoParams.wMethod.lookahead,
+ options.eqoParams.wMethod.expectedSize,
+ computeBatchSize(options));
+ case RANDOM -> buildRandomWellMatchedOracle(options, mqo);
+ case SAMPLE -> buildSampleSetOracle(options, mqo);
+ default -> throw new UnsupportedCombinationException(options, e);
+ };
+ chain.addOracle(eqo);
+ }
+
+ return chain;
+ }
+
+ public static EQOracleChain, String, Boolean> getVPAOracles(Options options,
+ MembershipOracle mqo) {
+ final EQOracleChain, String, Boolean> chain = new EQOracleChain<>();
+
+ for (EQOracle e : options.eqos) {
+ EquivalenceOracle super OneSEVPA, String>, String, Boolean> eqo = switch (e) {
+ case RANDOM -> buildRandomWellMatchedOracle(options, mqo);
+ case SAMPLE -> buildSampleSetOracle(options, mqo);
+ default -> throw new UnsupportedCombinationException(options, e);
+ };
+ chain.addOracle(eqo);
+ }
+
+ return chain;
+ }
+
+ private static RandomWellMatchedWordsEQOracle buildRandomWellMatchedOracle(Options options,
+ MembershipOracle oracle) {
+ return new RandomWellMatchedWordsEQOracle<>(new Random(options.eqoParams.random.seed),
+ oracle,
+ RANDOM_CALL_PROB,
+ options.eqoParams.random.maxTests,
+ options.eqoParams.random.minLength,
+ options.eqoParams.random.maxLength,
+ computeBatchSize(options));
+ }
+
+ private static SampleSetEQOracle buildSampleSetOracle(Options options,
+ MembershipOracle oracle) {
+ final List samples = options.eqoParams.samples.samples;
+ final SampleSetEQOracle sampleSetOracle = new SampleSetEQOracle<>();
+
+ if (samples != null) {
+
+ final Pattern pattern = Pattern.compile(options.eqoParams.samples.split);
+ final List> tmp = new ArrayList<>(samples.size());
+
+ for (String s : samples) {
+ String[] words = pattern.split(s);
+ tmp.add(Word.fromArray(words, 0, words.length));
+ }
+
+ sampleSetOracle.addAll(oracle, tmp);
+ }
+
+ return sampleSetOracle;
+ }
+
+ private static int computeBatchSize(Options options) {
+ if (options.sul.size() == 1) {
+ return 1;
+ } else {
+ return options.sul.size() * BATCH_SIZE;
+ }
+ }
+
+ private static final class UnsupportedCombinationException extends IllegalArgumentException {
+
+ UnsupportedCombinationException(Options options, EQOracle eqo) {
+ super(String.format("Type '%s' does not support oracle '%s'", options.type, eqo));
+ }
+ }
+
+}
diff --git a/cli/src/main/java/de/learnlib/cli/factory/LearnerFactory.java b/cli/src/main/java/de/learnlib/cli/factory/LearnerFactory.java
new file mode 100644
index 000000000..2dbc10368
--- /dev/null
+++ b/cli/src/main/java/de/learnlib/cli/factory/LearnerFactory.java
@@ -0,0 +1,145 @@
+/* Copyright (C) 2013-2026 TU Dortmund University
+ * This file is part of LearnLib .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.learnlib.cli.factory;
+
+import de.learnlib.algorithm.adt.learner.ADTLearner;
+import de.learnlib.algorithm.lsharp.LSharpMealy;
+import de.learnlib.algorithm.nlstar.NLStarLearner;
+import de.learnlib.algorithm.observationpack.vpa.OPLearnerVPABuilder;
+import de.learnlib.algorithm.procedural.SymbolWrapper;
+import de.learnlib.algorithm.procedural.sba.SBALearner;
+import de.learnlib.algorithm.procedural.spa.SPALearner;
+import de.learnlib.algorithm.procedural.spmm.SPMMLearner;
+import de.learnlib.algorithm.ttt.vpa.TTTLearnerVPABuilder;
+import de.learnlib.cli.adapter.ProceduralDFAAdapter.ExtensibleLStarDFAAdapter;
+import de.learnlib.cli.adapter.ProceduralDFAAdapter.KearnsVaziraniDFAAdapter;
+import de.learnlib.cli.adapter.ProceduralDFAAdapter.LLambdaDFAAdapter;
+import de.learnlib.cli.adapter.ProceduralDFAAdapter.MalerPnueliDFAAdapter;
+import de.learnlib.cli.adapter.ProceduralDFAAdapter.OPLearnerDFAAdapter;
+import de.learnlib.cli.adapter.ProceduralDFAAdapter.RivestSchapireDFAAdapter;
+import de.learnlib.cli.adapter.ProceduralDFAAdapter.TTTLambdaDFAAdapter;
+import de.learnlib.cli.adapter.ProceduralDFAAdapter.TTTLearnerDFAAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.ExtensibleLStarMealyAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.KearnsVaziraniMealyAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.LLambdaMealyAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.MalerPnueliMealyAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.MealyDHCAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.OPLearnerMealyAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.RivestSchapireMealyAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.SparseLearnerAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.TTTLambdaMealyAdapter;
+import de.learnlib.cli.adapter.ProceduralMealyAdapter.TTTLearnerMealyAdapter;
+import de.learnlib.cli.option.Learner;
+import de.learnlib.cli.option.Options;
+import de.learnlib.cli.util.Constructor.AdaptiveConstructor;
+import de.learnlib.cli.util.Constructor.DFAConstructor;
+import de.learnlib.cli.util.Constructor.MealyConstructor;
+import de.learnlib.cli.util.Constructor.PresetConstructor;
+import net.automatalib.alphabet.Alphabet;
+import net.automatalib.alphabet.ProceduralInputAlphabet;
+import net.automatalib.alphabet.VPAlphabet;
+import net.automatalib.automaton.fsa.NFA;
+import net.automatalib.automaton.procedural.SBA;
+import net.automatalib.automaton.procedural.SPA;
+import net.automatalib.automaton.procedural.SPMM;
+import net.automatalib.automaton.transducer.MealyMachine;
+import net.automatalib.automaton.vpa.OneSEVPA;
+import net.automatalib.word.Word;
+
+public final class LearnerFactory {
+
+ private LearnerFactory() {
+ // prevent instantiation
+ }
+
+ public static DFAConstructor, I> getDFALearner(Options options) {
+ return switch (options.learner) {
+ case KEARNS_VAZIRANI -> KearnsVaziraniDFAAdapter::new;
+ case L_LAMBDA -> LLambdaDFAAdapter::new;
+ case L_STAR -> ExtensibleLStarDFAAdapter::new;
+ case MALER_PNUELI -> MalerPnueliDFAAdapter::new;
+ case OBSERVATION_PACK -> OPLearnerDFAAdapter::new;
+ case RIVEST_SCHAPIRE -> RivestSchapireDFAAdapter::new;
+ case TTT -> TTTLearnerDFAAdapter::new;
+ case TTT_LAMBDA -> TTTLambdaDFAAdapter::new;
+ default -> throw new UnsupportedCombinationException(options);
+ };
+ }
+
+ public static MealyConstructor, I, O> getMealyLearner(Options options) {
+ return switch (options.learner) {
+ case DHC -> MealyDHCAdapter::new;
+ case KEARNS_VAZIRANI -> KearnsVaziraniMealyAdapter::new;
+ case L_LAMBDA -> LLambdaMealyAdapter::new;
+ case L_STAR -> ExtensibleLStarMealyAdapter::new;
+ case MALER_PNUELI -> MalerPnueliMealyAdapter::new;
+ case OBSERVATION_PACK -> OPLearnerMealyAdapter::new;
+ case RIVEST_SCHAPIRE -> RivestSchapireMealyAdapter::new;
+ case SPARSE -> SparseLearnerAdapter::new;
+ case TTT -> TTTLearnerMealyAdapter::new;
+ case TTT_LAMBDA -> TTTLambdaMealyAdapter::new;
+ default -> throw new UnsupportedCombinationException(options);
+ };
+ }
+
+ public static AdaptiveConstructor, MealyMachine, I, ?, O>, I, O> getAdaptiveLearner(Options options) {
+ return switch (options.learner) {
+ case ADT -> ADTLearner::new;
+ case L_SHARP -> LSharpMealy::new;
+ default -> throw new UnsupportedCombinationException(options);
+ };
+ }
+
+ public static PresetConstructor, NFA, I>, I, Boolean> getNFALearner(Options options) {
+ if (options.learner == Learner.NL_STAR) {
+ return NLStarLearner::new;
+ }
+ throw new UnsupportedCombinationException(options);
+ }
+
+ public static PresetConstructor, SBA, I>, I, Boolean> getSBALearner(Options options) {
+ final DFAConstructor>, SymbolWrapper> learner = getDFALearner(options);
+ return (alph, mqo) -> new SBALearner<>(alph, mqo, learner::constructLearner);
+ }
+
+ public static PresetConstructor, SPA, I>, I, Boolean> getSPALearner(Options options) {
+ final DFAConstructor, I> learner = getDFALearner(options);
+ return (alph, mqo) -> new SPALearner<>(alph, mqo, learner::constructLearner);
+ }
+
+ public static PresetConstructor, SPMM, I, ?, String>, I, Word> getSPMMLearner(
+ Options options) {
+ final MealyConstructor>, SymbolWrapper, String> learner = getMealyLearner(options);
+ return (alph, mqo) -> new SPMMLearner<>(alph, "error", mqo, learner::constructLearner);
+ }
+
+ public static PresetConstructor, OneSEVPA, I>, I, Boolean> getVPALearner(Options options) {
+ return switch (options.learner) {
+ case OBSERVATION_PACK ->
+ (alphabet, mqo) -> new OPLearnerVPABuilder().withAlphabet(alphabet).withOracle(mqo).create();
+ case TTT ->
+ (alphabet, mqo) -> new TTTLearnerVPABuilder().withAlphabet(alphabet).withOracle(mqo).create();
+ default -> throw new UnsupportedCombinationException(options);
+ };
+ }
+
+ private static final class UnsupportedCombinationException extends IllegalArgumentException {
+
+ UnsupportedCombinationException(Options options) {
+ super(String.format("Learner '%s' does not support type '%s'", options.learner, options.type));
+ }
+ }
+}
diff --git a/cli/src/main/java/de/learnlib/cli/factory/MQOFactory.java b/cli/src/main/java/de/learnlib/cli/factory/MQOFactory.java
new file mode 100644
index 000000000..dfe4395de
--- /dev/null
+++ b/cli/src/main/java/de/learnlib/cli/factory/MQOFactory.java
@@ -0,0 +1,223 @@
+/* Copyright (C) 2013-2026 TU Dortmund University
+ * This file is part of LearnLib .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.learnlib.cli.factory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+import de.learnlib.cli.option.Options;
+import de.learnlib.filter.cache.dfa.DFACaches;
+import de.learnlib.filter.cache.mealy.MealyCaches;
+import de.learnlib.filter.statistic.oracle.CounterAdaptiveQueryOracle;
+import de.learnlib.filter.statistic.oracle.CounterOracle;
+import de.learnlib.oracle.AdaptiveMembershipOracle;
+import de.learnlib.oracle.MembershipOracle;
+import de.learnlib.oracle.membership.CLIOracle;
+import de.learnlib.oracle.membership.CLIOutputAdaptiveOracle;
+import de.learnlib.oracle.membership.CLIOutputOracle;
+import de.learnlib.oracle.membership.StdInOracle;
+import de.learnlib.oracle.membership.StdInOutputAdaptiveOracle;
+import de.learnlib.oracle.membership.StdInOutputOracle;
+import de.learnlib.oracle.parallelism.ParallelOracleBuilders;
+import net.automatalib.alphabet.Alphabet;
+import net.automatalib.word.Word;
+
+public final class MQOFactory {
+
+ static final String SUL_KEY = "sul";
+ static final String CACHE_KEY = "cache";
+
+ private MQOFactory() {
+ // prevent instantiation
+ }
+
+ public static MembershipOracle getAcceptorOracle(Options options, Alphabet alphabet) {
+ MembershipOracle oracle;
+
+ if (options.sul.size() == 1) {
+ oracle = buildSingleAcceptorOracle(options, options.sul.get(0));
+ } else {
+ final List> suls = new ArrayList<>(options.sul.size());
+ for (File sul : options.sul) {
+ suls.add(buildSingleAcceptorOracle(options, sul));
+ }
+ oracle = ParallelOracleBuilders.newStaticParallelOracle(suls).create();
+ }
+
+ if (options.statistics) {
+ oracle = new CounterOracle<>(oracle, SUL_KEY);
+ }
+
+ if (options.cache) {
+ oracle = DFACaches.createDAGCache(alphabet, oracle);
+ if (options.statistics) {
+ oracle = new CounterOracle<>(oracle, CACHE_KEY);
+ }
+ }
+
+ return oracle;
+ }
+
+ static MembershipOracle buildSingleAcceptorOracle(Options options, File path) {
+ if (options.stdin) {
+ return new StdInOracle<>(buildCommandLine(options, path), options.reset);
+ } else {
+ return new CLIOracle<>(buildCommandLine(options, path), options.reset);
+ }
+ }
+
+ public static MembershipOracle> getTransducerOracle(Options options,
+ Alphabet alphabet) {
+ MembershipOracle> oracle;
+
+ if (options.sul.size() == 1) {
+ oracle = buildSingleTransducerOracle(options, options.sul.get(0));
+ } else {
+ final List>> suls = new ArrayList<>(options.sul.size());
+ for (File sul : options.sul) {
+ suls.add(buildSingleTransducerOracle(options, sul));
+ }
+ oracle = ParallelOracleBuilders.newStaticParallelOracle(suls).create();
+ }
+
+ if (options.statistics) {
+ oracle = new CounterOracle<>(oracle, SUL_KEY);
+ }
+
+ if (options.cache) {
+ oracle = MealyCaches.createDAGCache(alphabet, oracle);
+ if (options.statistics) {
+ oracle = new CounterOracle<>(oracle, CACHE_KEY);
+ }
+ }
+
+ return oracle;
+ }
+
+ static MembershipOracle> buildSingleTransducerOracle(Options options, File path) {
+ if (options.stdin) {
+ return new StdInOutputOracle<>(buildCommandLine(options, path),
+ new OutputTransformer(options),
+ options.reset);
+ } else {
+ return new CLIOutputOracle<>(buildCommandLine(options, path),
+ new OutputTransformer(options),
+ options.reset);
+ }
+ }
+
+ public static AdaptiveMembershipOracle getAdaptiveOracle(Options options,
+ Alphabet alphabet) {
+ verifyReset(options);
+
+ AdaptiveMembershipOracle oracle;
+
+ if (options.sul.size() == 1) {
+ oracle = buildSingleAdaptiveOracle(options, options.sul.get(0));
+ } else {
+ final List> suls = new ArrayList<>(options.sul.size());
+ for (File sul : options.sul) {
+ suls.add(buildSingleAdaptiveOracle(options, sul));
+ }
+ oracle = ParallelOracleBuilders.newStaticParallelAdaptiveOracle(suls).create();
+ }
+
+ if (options.statistics) {
+ oracle = new CounterAdaptiveQueryOracle<>(oracle, SUL_KEY);
+ }
+
+ if (options.cache) {
+ oracle = MealyCaches.createAdaptiveQueryCache(alphabet, oracle);
+ if (options.statistics) {
+ oracle = new CounterAdaptiveQueryOracle<>(oracle, CACHE_KEY);
+ }
+ }
+
+ return oracle;
+ }
+
+ static AdaptiveMembershipOracle buildSingleAdaptiveOracle(Options options, File path) {
+ if (options.stdin) {
+ return new StdInOutputAdaptiveOracle<>(buildCommandLine(options, path), Function.identity(), options.reset);
+ } else {
+ return new CLIOutputAdaptiveOracle<>(buildCommandLine(options, path), Function.identity(), options.reset);
+ }
+ }
+
+ private static List buildCommandLine(Options options, File file) {
+ verifyPath(file);
+ final String absolutePath = file.getAbsolutePath();
+ if (options.additionalArgs == null) {
+ return Collections.singletonList(absolutePath);
+ } else {
+ final List cmd = new ArrayList<>(options.additionalArgs.size() + 1);
+ cmd.add(absolutePath);
+ cmd.addAll(options.additionalArgs);
+ return cmd;
+ }
+ }
+
+ private static void verifyPath(File file) {
+ if (!file.exists()) {
+ throw new IllegalArgumentException(String.format("Specified SUL '%s' does not exist", file));
+ }
+ }
+
+ private static void verifyReset(Options options) {
+ if (options.reset == null) {
+ throw new IllegalArgumentException(String.format(
+ "Learner '%s' requires a stateful oracle. Provide a --reset",
+ options.learner));
+ }
+ }
+
+ private static class OutputTransformer implements CLIOutputOracle.OutputTransformer>,
+ StdInOutputOracle.OutputTransformer> {
+
+ private final Pattern pattern;
+
+ OutputTransformer(Options options) {
+ this.pattern = Pattern.compile(options.delimiter);
+ }
+
+ @Override
+ public Word transform(String output, int prefixLength, int suffixLength) {
+ if (suffixLength == 0) {
+ return Word.epsilon();
+ }
+
+ if (output.isBlank()) {
+ throw new IllegalStateException("Received empty output when non-empty output was expected.");
+ }
+
+ final String[] orig = pattern.split(output);
+
+ if (orig.length != (prefixLength + suffixLength)) {
+ throw new IllegalStateException(String.format(
+ "The parsed output '%s' does not have the expected number (%d) of symbols.",
+ Arrays.toString(orig),
+ prefixLength + suffixLength));
+ }
+
+ return Word.fromArray(orig, prefixLength, suffixLength);
+ }
+ }
+}
diff --git a/cli/src/main/java/de/learnlib/cli/factory/SerializerFactory.java b/cli/src/main/java/de/learnlib/cli/factory/SerializerFactory.java
new file mode 100644
index 000000000..3a0dcac1e
--- /dev/null
+++ b/cli/src/main/java/de/learnlib/cli/factory/SerializerFactory.java
@@ -0,0 +1,122 @@
+/* Copyright (C) 2013-2026 TU Dortmund University
+ * This file is part of LearnLib .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.learnlib.cli.factory;
+
+import java.io.DataOutput;
+
+import de.learnlib.cli.option.Options;
+import de.learnlib.cli.option.Output;
+import net.automatalib.automaton.fsa.DFA;
+import net.automatalib.automaton.fsa.NFA;
+import net.automatalib.automaton.procedural.SBA;
+import net.automatalib.automaton.procedural.SPA;
+import net.automatalib.automaton.procedural.SPMM;
+import net.automatalib.automaton.transducer.MealyMachine;
+import net.automatalib.automaton.vpa.OneSEVPA;
+import net.automatalib.serialization.InputModelSerializer;
+import net.automatalib.serialization.aut.AUTWriter;
+import net.automatalib.serialization.ba.BAWriter;
+import net.automatalib.serialization.dot.DOTSerializationProvider;
+import net.automatalib.serialization.learnlibv2.LearnLibV2Serialization;
+import net.automatalib.serialization.mata.writer.MataNFAWriter;
+import net.automatalib.serialization.saf.SAFWriters;
+import net.automatalib.serialization.taf.writer.TAFWriters;
+
+public final class SerializerFactory {
+
+ private SerializerFactory() {
+ // prevent instantiation
+ }
+
+ public static InputModelSerializer> getDFASerializer(Options options) {
+ final InputModelSerializer> dfaSerializer =
+ SerializerFactory.getDFASerializerInternal(options);
+ return dfaSerializer::writeModel;
+ }
+
+ private static InputModelSerializer> getDFASerializerInternal(Options options) {
+ return switch (options.format) {
+ case AUT -> new AUTWriter<>();
+ case BA -> new BAWriter<>();
+ case DOT -> DOTSerializationProvider.forAutomaton();
+ case LEARNLIBV2 -> LearnLibV2Serialization.getInstance();
+ case MATA -> new MataNFAWriter<>();
+ case SAF -> SAFWriters.dfa();
+ case TAF -> TAFWriters.dfa();
+ };
+ }
+
+ public static InputModelSerializer> getMealySerializer(Options options) {
+ return switch (options.format) {
+ case DOT -> DOTSerializationProvider.forAutomaton();
+ case SAF -> SAFWriters.mealy(DataOutput::writeUTF);
+ case TAF -> TAFWriters.mealy();
+ default -> throw new UnsupportedCombinationException(options);
+ };
+ }
+
+ public static InputModelSerializer> getNFASerializer(Options options) {
+ final InputModelSerializer> nfaSerializer =
+ SerializerFactory.getNFASerializerInternal(options);
+ return nfaSerializer::writeModel;
+ }
+
+ private static InputModelSerializer> getNFASerializerInternal(Options options) {
+ return switch (options.format) {
+ case AUT -> new AUTWriter<>();
+ case BA -> new BAWriter<>();
+ case DOT -> DOTSerializationProvider.forAutomaton();
+ case MATA -> new MataNFAWriter<>();
+ case SAF -> SAFWriters.nfa();
+ default -> throw new UnsupportedCombinationException(options);
+ };
+ }
+
+ public static InputModelSerializer> getSBASerializer(Options options) {
+ if (options.format == Output.DOT) {
+ return DOTSerializationProvider.forGraphViewableInput();
+ }
+ throw new UnsupportedCombinationException(options);
+ }
+
+ public static InputModelSerializer> getSPASerializer(Options options) {
+ if (options.format == Output.DOT) {
+ return DOTSerializationProvider.forGraphViewableInput();
+ }
+ throw new UnsupportedCombinationException(options);
+ }
+
+ public static InputModelSerializer> getSPMMSerializer(Options options) {
+ if (options.format == Output.DOT) {
+ return DOTSerializationProvider.forGraphViewableInput();
+ }
+ throw new UnsupportedCombinationException(options);
+ }
+
+ public static InputModelSerializer> getVPASerializer(Options options) {
+ if (options.format == Output.DOT) {
+ return DOTSerializationProvider.forGraphViewableInput();
+ }
+ throw new UnsupportedCombinationException(options);
+ }
+
+ private static final class UnsupportedCombinationException extends IllegalArgumentException {
+
+ UnsupportedCombinationException(Options options) {
+ super(String.format("Type '%s' cannot be written into '%s' format", options.type, options.format));
+ }
+ }
+}
diff --git a/cli/src/main/java/de/learnlib/cli/option/EQOParams.java b/cli/src/main/java/de/learnlib/cli/option/EQOParams.java
new file mode 100644
index 000000000..f837e2db0
--- /dev/null
+++ b/cli/src/main/java/de/learnlib/cli/option/EQOParams.java
@@ -0,0 +1,246 @@
+/* Copyright (C) 2013-2026 TU Dortmund University
+ * This file is part of LearnLib .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.learnlib.cli.option;
+
+import java.util.List;
+
+import net.automatalib.util.automaton.conformance.KWayStateCoverTestsIterator.CombinationMethod;
+import net.automatalib.util.automaton.conformance.KWayTransitionCoverTestsIterator.GenerationMethod;
+import net.automatalib.util.automaton.conformance.KWayTransitionCoverTestsIterator.OptimizationMetric;
+import picocli.CommandLine.ArgGroup;
+import picocli.CommandLine.Option;
+
+public class EQOParams {
+
+ @ArgGroup(validate = false)
+ public KWayStateMethod kWayState = new KWayStateMethod();
+ @ArgGroup(validate = false)
+ public KWayTransitionMethod kWayTransition = new KWayTransitionMethod();
+ @ArgGroup(validate = false)
+ public RandomMethod random = new RandomMethod();
+ @ArgGroup(validate = false)
+ public RandomWMethod randomWMethod = new RandomWMethod();
+ @ArgGroup(validate = false)
+ public RandomWpMethod randomWpMethod = new RandomWpMethod();
+ @ArgGroup(validate = false)
+ public Samples samples = new Samples();
+ @ArgGroup(validate = false)
+ public WMethod wMethod = new WMethod();
+ @ArgGroup(validate = false)
+ public WpMethod wpMethod = new WpMethod();
+
+ public static class KWayStateMethod {
+
+ @Option(names = "--eqo-kway-s-k",
+ defaultValue = "2",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-s.k")
+ public int k;
+
+ @Option(names = "--eqo-kway-s-randomWalkLen",
+ defaultValue = "20",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-s.randomWalkLen")
+ public int randomWalkLen;
+
+ @Option(names = "--eqo-kway-s-combinationMethod",
+ defaultValue = "PERMUTATIONS",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-s.combinationMethod")
+ public CombinationMethod combinationMethod;
+
+ @Option(names = "--eqo-kway-s-seed",
+ defaultValue = "42",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-s.seed")
+ public int seed;
+ }
+
+ public static class KWayTransitionMethod {
+
+ @Option(names = "--eqo-kway-t-k",
+ defaultValue = "2",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-t.k")
+ public int k;
+
+ @Option(names = "--eqo-kway-t-randomWalkLen",
+ defaultValue = "10",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-t.randomWalkLen")
+ public int randomWalkLen;
+
+ @Option(names = "--eqo-kway-t-numGeneratePaths",
+ defaultValue = "100",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-t.numGeneratePaths")
+ public int numGeneratePaths;
+
+ @Option(names = "--eqo-kway-t-maxPathLen",
+ defaultValue = "50",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-t.maxPathLen")
+ public int maxPathLen;
+
+ @Option(names = "--eqo-kway-t-maxNumberOfSteps",
+ defaultValue = "100",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-t.maxNumberOfSteps")
+ public int maxNumberOfSteps;
+
+ @Option(names = "--eqo-kway-t-optimizationMetric",
+ defaultValue = "STEPS",
+ paramLabel = "",
+ descriptionKey = "param.eqo.kway-t.optimizationMetric")
+ public OptimizationMetric optimizationMetric;
+
+ @Option(names = "--eqo-kway-t-generationMethod",
+ defaultValue = "RANDOM",
+ paramLabel = "