de.learnlib.oracle.equivalence.AbstractTestWordEQOracle Maven / Gradle / Ivy
Show all versions of learnlib-equivalence-oracles Show documentation
/* Copyright (C) 2013-2018 TU Dortmund
* 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.oracle.equivalence;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.collect.Streams;
import de.learnlib.api.oracle.EquivalenceOracle;
import de.learnlib.api.oracle.MembershipOracle;
import de.learnlib.api.query.DefaultQuery;
import net.automatalib.automata.concepts.Output;
import net.automatalib.commons.util.collections.BatchingIterator;
import net.automatalib.words.Word;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An abstract equivalence oracle that takes care of query batching and hypothesis checking and allows extending classes
* to solely focus on test word generation by implementing {@link #generateTestWords(Output, Collection)}.
*
* Being {@link Stream stream}-based, this oracle encourages the lazy computation of counterexamples, so that all
* counterexamples do not have to be computed upfront, but only until the first valid counterexample is found.
*
* @param
* hypothesis type
* @param
* input symbol type
* @param
* output (domain) type
*
* @author frohme
*/
public abstract class AbstractTestWordEQOracle, I, D> implements EquivalenceOracle {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTestWordEQOracle.class);
private final MembershipOracle membershipOracle;
private final int batchSize;
public AbstractTestWordEQOracle(MembershipOracle membershipOracle) {
this(membershipOracle, 1);
}
public AbstractTestWordEQOracle(MembershipOracle membershipOracle, int batchSize) {
Preconditions.checkArgument(batchSize > 0);
this.membershipOracle = membershipOracle;
this.batchSize = batchSize;
}
@Nullable
@Override
public DefaultQuery findCounterExample(A hypothesis, Collection extends I> inputs) {
// Fail fast on empty inputs
if (inputs.isEmpty()) {
LOGGER.warn("Passed empty set of inputs to equivalence oracle; no counterexample can be found!");
return null;
}
final Stream> testWordStream = generateTestWords(hypothesis, inputs);
final Stream> queryStream = testWordStream.map(DefaultQuery::new);
final Stream> answeredQueryStream = answerQueries(queryStream);
final Stream> ceStream = answeredQueryStream.filter(query -> {
D hypOutput = hypothesis.computeOutput(query.getInput());
return !Objects.equals(hypOutput, query.getOutput());
});
return ceStream.findFirst().orElse(null);
}
/**
* Generate the stream of test words that should be used for the current equivalence check cycle.
*
* @param hypothesis
* the current hypothesis of the learning algorithm
* @param inputs
* the collection of inputs to consider
*
* @return the stream of test words used for equivalence testing
*
* @see EquivalenceOracle#findCounterExample(Object, Collection)
*/
protected abstract Stream> generateTestWords(A hypothesis, Collection extends I> inputs);
private Stream> answerQueries(final Stream> stream) {
if (isBatched()) {
/*
* FIXME: currently necessary because of a bug in the JDK
* see https://bugs.openjdk.java.net/browse/JDK-8075939
*/
return Streams.stream(Streams.stream(new BatchingIterator<>(stream.iterator(), this.batchSize))
.peek(membershipOracle::processQueries)
.flatMap(List::stream)
.iterator());
} else {
return stream.peek(membershipOracle::processQuery);
}
}
private boolean isBatched() {
return this.batchSize > 1;
}
}