ClassifierBenchmarkRunner.java
package org.hammer.audio.experimental.acoustic.benchmark.classifier;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.hammer.audio.experimental.acoustic.wingbeat.WingbeatFeatureVector;
/**
* Runs multiple {@link ClassifierBenchmark} implementations against the same labelled dataset and
* collects their results.
*
* <p>Adding a new classifier to the comparison requires only implementing {@link
* ClassifierBenchmark} and including it in the list passed to this runner.
*
* <p>This service is stateless and may be called concurrently as long as the registered benchmarks
* are also stateless.
*/
@SuppressWarnings("PMD.UseConcurrentHashMap")
public final class ClassifierBenchmarkRunner {
private final List<ClassifierBenchmark> benchmarks;
/**
* Create a runner with the given benchmarks.
*
* @param benchmarks benchmarks to run; must not be {@code null} or empty
*/
public ClassifierBenchmarkRunner(List<ClassifierBenchmark> benchmarks) {
Objects.requireNonNull(benchmarks, "benchmarks");
if (benchmarks.isEmpty()) {
throw new IllegalArgumentException("benchmarks must not be empty");
}
this.benchmarks = List.copyOf(benchmarks);
}
/**
* Create a runner containing the {@link RuleBasedClassifierBenchmark} as the sole default
* benchmark.
*
* @return default runner instance; never {@code null}
*/
public static ClassifierBenchmarkRunner defaultRunner() {
return new ClassifierBenchmarkRunner(List.of(new RuleBasedClassifierBenchmark()));
}
/**
* Run all registered benchmarks on the given labelled dataset.
*
* @param vectors feature vectors; must not be {@code null} or empty
* @param labels ground-truth labels, one per vector; must not be {@code null}; must have the same
* size as {@code vectors}
* @return map of benchmark name to result; order mirrors the registration order; never {@code
* null}
*/
public Map<String, ClassifierBenchmarkResult> run(
List<WingbeatFeatureVector> vectors, List<String> labels) {
Objects.requireNonNull(vectors, "vectors");
Objects.requireNonNull(labels, "labels");
if (vectors.isEmpty()) {
throw new IllegalArgumentException("vectors must not be empty");
}
if (vectors.size() != labels.size()) {
throw new IllegalArgumentException("vectors and labels must have the same size");
}
Map<String, ClassifierBenchmarkResult> results = new LinkedHashMap<>();
for (ClassifierBenchmark benchmark : benchmarks) {
results.put(benchmark.name(), benchmark.run(vectors, labels));
}
return Collections.unmodifiableMap(results);
}
}