Skip to content

Commit

Permalink
Merge branch 'develop' into javadoc
Browse files Browse the repository at this point in the history
  • Loading branch information
mtf90 committed Feb 6, 2024
2 parents 66b6cf1 + 59d0f87 commit f4a8837
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added

* LearnLib now supports JPMS modules. All artifacts now provide a `module-info` descriptor except of the distribution artifacts (for Maven-less environments) which only provide an `Automatic-Module-Name` due to non-modular dependencies. Note that while this is a Java 9+ feature, LearnLib still supports Java 8 byte code for the remaining class files.
* Added an `InterningMembershipOracle` (including refinements) to the `learnlib-cache` artifact that interns query responses to reduce memory consumption of large data structures. This exports the internal concepts of the DHC learner (which no longer interns query responses automatically).

### Changed

Expand Down
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ For developing the code base of LearnLib it is suggested to use one of the major

* For [IntelliJ IDEA][intellij]:
1. Select `File` -> `New` -> `Project from existing sources` and select the folder containing the development checkout.
1. Choose "Import Project from external model", select "Maven" and click `Next`.
1. Configure the project to your liking but make sure to check "Import Maven projects automatically" and have "Generated sources folders" set to "Detect automatically".
1. Click `Next` until the project is imported (no Maven profile needs to be selected).
1. Choose "Import Project from external model", select "Maven" and click `Create`.
1. In order to have both development versions of AutomataLib and LearnLib available at once, continue to import AutomataLib as documented in the project's README, but choose `File` -> `New` -> `Module from existing sources` as the first step.

* For [Eclipse][eclipse]:
Expand Down Expand Up @@ -106,10 +104,6 @@ If you have any questions regarding the usage of LearnLib or if you want to disc
[6]: https://github.com/mtf90
[7]: https://github.com/LearnLib/automatalib

[learnlib-qa]: https://groups.google.com/d/forum/learnlib-qa
[learnlib-discussion]: https://groups.google.com/d/forum/learnlib-discussion
[learnlib-internal]: https://groups.google.com/d/forum/learnlib-internal

[maven-central]: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22de.learnlib%22
[maven-central-distr]: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22de.learnlib.distribution%22
[intellij]: https://www.jetbrains.com/idea/
Expand Down
1 change: 1 addition & 0 deletions build-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ limitations under the License.
<exclude>de/learnlib/filter/reuse/**/Reuse*Builder.class</exclude>

<!-- generated refinements for oracles -->
<exclude>de/learnlib/filter/cache/**/*Interning*.class</exclude>
<exclude>de/learnlib/filter/statistic/**/DFA*.class</exclude>
<exclude>de/learnlib/filter/statistic/**/Mealy*.class</exclude>
<exclude>de/learnlib/filter/statistic/**/Moore*.class</exclude>
Expand Down
6 changes: 6 additions & 0 deletions filters/cache/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ limitations under the License.
<artifactId>slf4j-api</artifactId>
</dependency>

<!-- build -->
<dependency>
<groupId>de.learnlib.tooling</groupId>
<artifactId>annotations</artifactId>
</dependency>

<!-- only required in java doc and tests -->
<dependency>
<groupId>de.learnlib</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* Copyright (C) 2013-2024 TU Dortmund University
* This file is part of LearnLib, http://www.learnlib.de/.
*
* 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.filter.cache;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import de.learnlib.oracle.MembershipOracle;
import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle;
import de.learnlib.oracle.MembershipOracle.MooreMembershipOracle;
import de.learnlib.query.DefaultQuery;
import de.learnlib.query.Query;
import de.learnlib.tooling.annotation.refinement.GenerateRefinement;
import de.learnlib.tooling.annotation.refinement.Generic;
import de.learnlib.tooling.annotation.refinement.Interface;
import de.learnlib.tooling.annotation.refinement.Mapping;
import net.automatalib.word.Word;

/**
* A {@link MembershipOracle} that interns query outputs. May be used to reduce memory consumption of data structures
* that store a lot of query responses. Typically, this oracle only makes sense for output types that are not already
* interned by the JVM (such as {@link Boolean}s in case of {@link DFAMembershipOracle}s).
*/
@GenerateRefinement(name = "InterningMealyMembershipOracle",
packageName = "de.learnlib.filter.cache.mealy",
generics = {@Generic(value = "I", desc = "input symbol type"),
@Generic(value = "O", desc = "output symbol type")},
parentGenerics = {@Generic("I"), @Generic(clazz = Word.class, generics = "O")},
interfaces = @Interface(clazz = MealyMembershipOracle.class,
generics = {@Generic("I"), @Generic("O")}),
typeMappings = @Mapping(from = MembershipOracle.class,
to = MealyMembershipOracle.class,
generics = {@Generic("I"), @Generic("O")}))
@GenerateRefinement(name = "InterningMooreMembershipOracle",
packageName = "de.learnlib.filter.cache.moore",
generics = {@Generic(value = "I", desc = "input symbol type"),
@Generic(value = "O", desc = "output symbol type")},
parentGenerics = {@Generic("I"), @Generic(clazz = Word.class, generics = "O")},
interfaces = @Interface(clazz = MooreMembershipOracle.class,
generics = {@Generic("I"), @Generic("O")}),
typeMappings = @Mapping(from = MembershipOracle.class,
to = MooreMembershipOracle.class,
generics = {@Generic("I"), @Generic("O")}))
public class InterningMembershipOracle<I, D> implements MembershipOracle<I, D> {

private final MembershipOracle<I, D> delegate;
private final Map<D, WeakReference<D>> cache;

public InterningMembershipOracle(MembershipOracle<I, D> delegate) {
this.delegate = delegate;
this.cache = new WeakHashMap<>();
}

@Override
public void processQueries(Collection<? extends Query<I, D>> queries) {
final List<DefaultQuery<I, D>> delegates = new ArrayList<>(queries.size());

for (Query<I, D> q : queries) {
delegates.add(new DefaultQuery<>(q));
}

this.delegate.processQueries(delegates);

final Iterator<? extends Query<I, D>> origIter = queries.iterator();
final Iterator<DefaultQuery<I, D>> delegateIter = delegates.iterator();

while (origIter.hasNext() && delegateIter.hasNext()) {
final Query<I, D> origNext = origIter.next();
final DefaultQuery<I, D> delegateNext = delegateIter.next();
final D delegateOutput = delegateNext.getOutput();

// Since the GC may free our references during the lookup, repeat until we have a (non-null) cache hit.
D origOutput;
do {
origOutput = cache.computeIfAbsent(delegateOutput, k -> new WeakReference<>(delegateOutput)).get();
} while (origOutput == null);

origNext.answer(origOutput);
}

assert !origIter.hasNext() && !delegateIter.hasNext();
}
}
1 change: 1 addition & 0 deletions filters/cache/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

// only required by documentation
requires static de.learnlib.oracle.parallelism;
requires static de.learnlib.tooling.annotation;

exports de.learnlib.filter.cache;
exports de.learnlib.filter.cache.dfa;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* Copyright (C) 2013-2024 TU Dortmund University
* This file is part of LearnLib, http://www.learnlib.de/.
*
* 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.filter.cache;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import de.learnlib.oracle.SingleQueryOracle.SingleQueryOracleMealy;
import de.learnlib.query.DefaultQuery;
import net.automatalib.common.util.Pair;
import net.automatalib.word.Word;
import org.testng.Assert;
import org.testng.annotations.Test;

public class InterningMembershipOracleTest {

@Test
public void testInterning() {
final DefaultQuery<Character, Word<Character>> q1 = new DefaultQuery<>(Word.epsilon(), Word.fromString("qwe"));
final DefaultQuery<Character, Word<Character>> q2 = new DefaultQuery<>(Word.fromString("asd"), Word.epsilon());

final Oracle<Character> oracle = new Oracle<>();
final InterningMembershipOracle<Character, Word<Character>> interning = new InterningMembershipOracle<>(oracle);

interning.processQueries(Arrays.asList(q1, q2));

final Word<Character> o1 = q1.getOutput();

Assert.assertSame(q1.getOutput(), o1);
Assert.assertSame(q2.getOutput(), o1);

// repeated queries
interning.processQueries(Arrays.asList(q1, q2));

Assert.assertSame(q1.getOutput(), o1);
Assert.assertSame(q2.getOutput(), o1);

// check executed queries
Assert.assertEquals(oracle.count, 4);
Assert.assertEquals(oracle.queries,
Set.of(Pair.of(Word.epsilon(), Word.fromString("qwe")),
Pair.of(Word.fromString("asd"), Word.epsilon())));
// check that oracle actually returns different objects
Assert.assertNotSame(oracle.answerQuery(q1.getInput()), o1);
Assert.assertEquals(oracle.answerQuery(q1.getInput()), o1);
}

private static class Oracle<I> implements SingleQueryOracleMealy<I, Character> {

private int count;
private final Set<Pair<Word<I>, Word<I>>> queries;

Oracle() {
this.count = 0;
this.queries = new HashSet<>();
}

@Override
public Word<Character> answerQuery(Word<I> prefix, Word<I> suffix) {
count++;
queries.add(Pair.of(prefix, suffix));
return Word.fromString("abcdef");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@
import net.automatalib.word.WordBuilder;

/**
* Implements an equivalence test by applying the W-method test on the given hypothesis automaton, as described in
* <a href="https://doi.org/10.1109/TSE.1978.231496">Testing Software Design Modeled by Finite-State Machines</a> by
* T.&nbsp;S.&nbsp;Chow. Instead of enumerating the test suite in order, this is a sampling implementation:
* <ul>
* <li>1. sample uniformly from the transitions for a prefix</li>
* <li>2. sample geometrically a random word</li>
* <li>3. sample a word from the set of suffixes / state identifiers</li>
* </ul>
* Implements an equivalence test based on a randomized version of the W-method as described in <a
* href="https://arxiv.org/abs/1611.02429">Complementing Model Learning with Mutation-Based Fuzzing</a> by Rick
* Smetsers, Joshua Moerman, Mark Janssen, Sicco Verwer. Instead of enumerating the test suite in order, this is a
* sampling implementation:
* <ol>
* <li>sample uniformly from the transitions for a prefix</li>
* <li>sample geometrically a random word</li>
* <li>sample a word from the set of suffixes / state identifiers</li>
* </ol>
* There are two parameters: minimalSize determines the minimal size of the random word, this is useful when one first
* performs a W(p)-method with some depth and continue with this randomized tester from that depth onward. The second
* parameter rndLength determines the expected length of the random word. (The expected length in effect is minimalSize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,16 @@
import net.automatalib.word.WordBuilder;

/**
* Implements an equivalence test by applying the Wp-method test on the given hypothesis automaton, as described in <a
* href="https://doi.org/10.1109/32.87284">Test selection based on finite state models</a> by S.&nbsp;Fujiwara et al.
* Instead of enumerating the test suite in order, this is a sampling implementation:
* <ul>
* <li>1. sample uniformly from the states for a prefix</li>
* <li>2. sample geometrically a random word</li>
* <li>3. sample a word from the set of suffixes / state identifiers</li>
* </ul>
* There are two parameters:minimalSize determines the minimal size of the random word, this is useful when one first
* Implements an equivalence test based on a randomized version of the W(p)-method as described in <a
* href="https://arxiv.org/abs/1611.02429">Complementing Model Learning with Mutation-Based Fuzzing</a> by Rick
* Smetsers, Joshua Moerman, Mark Janssen, Sicco Verwer. Instead of enumerating the test suite in order, this is a
* sampling implementation:
* <ol>
* <li>sample uniformly from the states for a prefix</li>
* <li>sample geometrically a random word</li>
* <li>sample a word from the set of suffixes / state identifiers</li>
* </ol>
* There are two parameters: minimalSize determines the minimal size of the random word, this is useful when one first
* performs a W(p)-method with some depth and continue with this randomized tester from that depth onward. The second
* parameter rndLength determines the expected length of the random word. (The expected length in effect is minimalSize
* + rndLength.) In the unbounded case it will not terminate for a correct hypothesis.
Expand Down
10 changes: 1 addition & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,10 @@ limitations under the License.

<!-- dependency versions -->
<automatalib.version>0.12.0-SNAPSHOT</automatalib.version>
<build-tools.version>0.1</build-tools.version>
<build-tools.version>0.1.1</build-tools.version>
<cacio.version>1.11.1</cacio.version>
<checkerframework.version>3.40.0</checkerframework.version>
<checkstyle.version>9.3</checkstyle.version>
<javapoet.version>1.13.0</javapoet.version>
<jaxb-api.version>2.3.1</jaxb-api.version>
<logback.version>1.3.12</logback.version>
<metainf-services.version>1.11</metainf-services.version>
Expand Down Expand Up @@ -599,13 +598,6 @@ limitations under the License.
<scope>test</scope>
</dependency>

<!-- Java Poet -->
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>${javapoet.version}</version>
</dependency>

<!-- Checker Annotations (@Nullable, @Nonnull, ...) -->
<dependency>
<groupId>org.checkerframework</groupId>
Expand Down

0 comments on commit f4a8837

Please sign in to comment.