TdoaConsistencyReport.java
package org.hammer.audio.experimental.acoustic;
import java.util.List;
import java.util.Objects;
/**
* Aggregate physical and cycle-consistency evidence for pairwise TDOA estimates.
*
* @param findings ordered threshold violations
* @param evaluatedCycles number of complete microphone triples checked
* @param meanAbsoluteCycleResidualSeconds mean absolute residual over all complete triples
* @param maximumAbsoluteCycleResidualSeconds maximum absolute residual over all complete triples
* @param physicalViolationCount pair delays outside acoustic geometry bounds
* @param consistencyScore normalized confidence multiplier in {@code [0,1]}
*/
public record TdoaConsistencyReport(
List<TdoaConsistencyFinding> findings,
int evaluatedCycles,
double meanAbsoluteCycleResidualSeconds,
double maximumAbsoluteCycleResidualSeconds,
int physicalViolationCount,
double consistencyScore) {
// Validate and defensively copy one report.
public TdoaConsistencyReport {
findings = List.copyOf(Objects.requireNonNull(findings, "findings"));
if (evaluatedCycles < 0 || physicalViolationCount < 0) {
throw new IllegalArgumentException("report counts must be >= 0");
}
requireNonNegativeFinite(meanAbsoluteCycleResidualSeconds, "meanAbsoluteCycleResidualSeconds");
requireNonNegativeFinite(
maximumAbsoluteCycleResidualSeconds, "maximumAbsoluteCycleResidualSeconds");
if (!Double.isFinite(consistencyScore) || consistencyScore < 0.0 || consistencyScore > 1.0) {
throw new IllegalArgumentException("consistencyScore must be finite and in [0,1]");
}
}
/** Report used when fewer than three microphones or pair estimates are available. */
public static TdoaConsistencyReport notEvaluated() {
return new TdoaConsistencyReport(List.of(), 0, 0.0, 0.0, 0, 1.0);
}
/** Whether no physical violation exists and the consistency score remains usable. */
public boolean reliable() {
return physicalViolationCount == 0 && consistencyScore >= 0.5;
}
/** Confidence multiplier to apply to downstream pair-derived evidence. */
public double confidenceMultiplier() {
return physicalViolationCount == 0 ? consistencyScore : 0.0;
}
private static void requireNonNegativeFinite(double value, String name) {
if (Double.isFinite(value) && value >= 0.0) {
return;
}
throw new IllegalArgumentException(name + " must be finite and >= 0");
}
}