GeneratorComparisonReport.java

package org.hammer.audio.experimental.acoustic.simulation.calibration;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.hammer.audio.experimental.acoustic.feature.comparison.FeatureDifference;
import org.hammer.audio.experimental.acoustic.feature.comparison.SyntheticRealComparisonReport;

/**
 * Benchmark comparison between the original and calibrated generator.
 *
 * <p>Wraps the {@link SyntheticRealComparisonReport comparison reports} produced before and after
 * calibration and exposes improvement metrics:
 *
 * <ul>
 *   <li>{@link #meanRelativeDifferenceBefore()} / {@link #meanRelativeDifferenceAfter()} — overall
 *       deviation from real data for each generator
 *   <li>{@link #overallImprovement()} — relative reduction in mean deviation
 *   <li>{@link #perFeatureImprovement()} — per-feature absolute reduction in relative difference
 * </ul>
 *
 * @param beforeCalibration comparison report for the original (baseline) generator; must not be
 *     {@code null}
 * @param afterCalibration comparison report for the calibrated generator; must not be {@code null}
 */
public record GeneratorComparisonReport(
    SyntheticRealComparisonReport beforeCalibration,
    SyntheticRealComparisonReport afterCalibration) {

  // Validate fields.
  public GeneratorComparisonReport {
    Objects.requireNonNull(beforeCalibration, "beforeCalibration");
    Objects.requireNonNull(afterCalibration, "afterCalibration");
  }

  /**
   * Create a {@link GeneratorComparisonReport} from a {@link CalibrationResult}.
   *
   * @param result calibration result; must not be {@code null}
   * @return comparison report; never {@code null}
   */
  public static GeneratorComparisonReport of(CalibrationResult result) {
    Objects.requireNonNull(result, "result");
    return new GeneratorComparisonReport(result.beforeCalibration(), result.afterCalibration());
  }

  /**
   * Return the mean relative difference across all features before calibration.
   *
   * @return mean relative difference; {@code >= 0}
   */
  public double meanRelativeDifferenceBefore() {
    return beforeCalibration.differences().stream()
        .mapToDouble(FeatureDifference::relativeDifference)
        .average()
        .orElse(0.0);
  }

  /**
   * Return the mean relative difference across all features after calibration.
   *
   * @return mean relative difference; {@code >= 0}
   */
  public double meanRelativeDifferenceAfter() {
    return afterCalibration.differences().stream()
        .mapToDouble(FeatureDifference::relativeDifference)
        .average()
        .orElse(0.0);
  }

  /**
   * Return the overall improvement as {@code (before − after) / before}.
   *
   * <p>A positive value means the calibrated generator is closer to real data. Returns {@code 0}
   * when the before-calibration mean difference is zero.
   *
   * @return improvement fraction; positive = better
   */
  public double overallImprovement() {
    double before = meanRelativeDifferenceBefore();
    if (before == 0.0) {
      return 0.0;
    }
    return (before - meanRelativeDifferenceAfter()) / before;
  }

  /**
   * Return per-feature improvement as the absolute reduction in relative difference ({@code before
   * − after}) for each feature.
   *
   * <p>The list is ordered identically to {@link SyntheticRealComparisonReport#differences()}.
   * Positive values indicate features that improved; negative values indicate features that got
   * worse.
   *
   * @return per-feature improvement list; never {@code null}
   */
  public List<Double> perFeatureImprovement() {
    List<FeatureDifference> before = beforeCalibration.differences();
    List<FeatureDifference> after = afterCalibration.differences();
    int size = Math.min(before.size(), after.size());
    List<Double> improvements = new ArrayList<>(size);
    for (int i = 0; i < size; i++) {
      improvements.add(before.get(i).relativeDifference() - after.get(i).relativeDifference());
    }
    return List.copyOf(improvements);
  }
}