LocalizationBenchmarkRunner.java

package org.hammer.audio.experimental.acoustic.benchmark.localization;

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.simulation.SimulationScenarios.SimulationScenario;

/**
 * Runs multiple {@link LocalizationBenchmark} implementations across a set of simulation scenarios
 * and aggregates the results.
 *
 * <p>Adding a new localization algorithm requires only implementing {@link LocalizationBenchmark}
 * 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 LocalizationBenchmarkRunner {

  private final List<LocalizationBenchmark> benchmarks;

  /**
   * Create a runner with the given benchmarks.
   *
   * @param benchmarks benchmarks to run; must not be {@code null} or empty
   */
  public LocalizationBenchmarkRunner(List<LocalizationBenchmark> 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 {@link TdoaLocalizationBenchmark} as the sole default benchmark.
   *
   * @return default runner instance; never {@code null}
   */
  public static LocalizationBenchmarkRunner defaultRunner() {
    return new LocalizationBenchmarkRunner(List.of(new TdoaLocalizationBenchmark()));
  }

  /**
   * Run all registered benchmarks on each scenario in the given list.
   *
   * <p>The outer map key is the benchmark name; the inner list contains one result per scenario in
   * the order supplied.
   *
   * @param scenarios the scenarios to benchmark; must not be {@code null} or empty
   * @return map of benchmark name to per-scenario results; never {@code null}
   */
  public Map<String, List<LocalizationBenchmarkResult>> run(List<SimulationScenario> scenarios) {
    Objects.requireNonNull(scenarios, "scenarios");
    if (scenarios.isEmpty()) {
      throw new IllegalArgumentException("scenarios must not be empty");
    }

    Map<String, List<LocalizationBenchmarkResult>> results = new LinkedHashMap<>();
    for (LocalizationBenchmark benchmark : benchmarks) {
      List<LocalizationBenchmarkResult> benchmarkResults =
          new java.util.ArrayList<>(scenarios.size());
      for (SimulationScenario scenario : scenarios) {
        benchmarkResults.add(benchmark.run(scenario));
      }
      results.put(benchmark.name(), Collections.unmodifiableList(benchmarkResults));
    }
    return Collections.unmodifiableMap(results);
  }
}