fix: harden beam pattern numerical diagnostics
This commit is contained in:
@@ -95,13 +95,28 @@ test("half-wavelength two-microphone broadside cut has nulls, 60-degree HPBW, an
|
||||
assertNear(sampleAtAngle(pattern, 270), 0, 1e-12, "endfire null at -x");
|
||||
assertNear(sampleAtAngle(pattern, 0), 1, 1e-12, "steered broadside response");
|
||||
assertNear(sampleAtAngle(pattern, 180), 1, 1e-12, "planar front-back response");
|
||||
assertEqual(pattern.metrics.halfPowerBeamwidth.status, "bounded", "HPBW status");
|
||||
assertEqual(
|
||||
pattern.metrics.halfPowerBeamwidth.status,
|
||||
"sampled-bracketed-bisection",
|
||||
"HPBW status",
|
||||
);
|
||||
assertNear(pattern.metrics.halfPowerBeamwidth.widthDeg, 60, 1e-10, "power HPBW");
|
||||
const zeroDbAngles = pattern.metrics.sampledLocalMaxima.peaks
|
||||
.filter((peak) => Math.abs(peak.responseDb) < 1e-10)
|
||||
.map((peak) => peak.angleDeg)
|
||||
.sort((left, right) => left - right);
|
||||
assertEqual(zeroDbAngles.join(","), "0,180", "front-back maxima remain tied");
|
||||
assertEqual(
|
||||
pattern.metrics.sampledLocalMaxima.status,
|
||||
"tied-global-maxima-on-grid",
|
||||
"global tie is explicit",
|
||||
);
|
||||
assertEqual(pattern.metrics.sampledLocalMaxima.selected, null, "tie has no selected peak");
|
||||
assertEqual(
|
||||
pattern.metrics.sampledLocalMaxima.globalMaximumRegions.length,
|
||||
2,
|
||||
"both tied regions are preserved",
|
||||
);
|
||||
assertNear(pattern.responseReal[Math.floor(pattern.sampleCount / 2)], 1, 1e-12, "raw real");
|
||||
assertNear(pattern.responseImaginary[Math.floor(pattern.sampleCount / 2)], 0, 1e-12, "raw imaginary");
|
||||
assertNear(pattern.responsePower[Math.floor(pattern.sampleCount / 2)], 1, 1e-12, "raw power");
|
||||
@@ -258,6 +273,33 @@ test("coarse high-frequency cuts preserve raw samples but withhold unresolved me
|
||||
assertEqual(pattern.responsePower.length, 360, "raw sampled power remains available");
|
||||
});
|
||||
|
||||
test("non-constant cuts without sampled half-power crossings do not claim 360-degree HPBW", () => {
|
||||
const pattern = computeReceiveBeamPattern({
|
||||
array: arrayFromPositions([[-0.0005, 0, 0], [0.0005, 0, 0]]),
|
||||
frequencyHz: 1_000,
|
||||
speedOfSound: 343,
|
||||
cut: "xz",
|
||||
steeringAngleDeg: 0,
|
||||
sampleCount: 720,
|
||||
});
|
||||
assert(pattern.angularSampling.projectedApertureM > 0, "array is not constant on cut");
|
||||
assert(
|
||||
Math.min(...pattern.responsePower) > 0.5,
|
||||
"sampled cut stays above half power",
|
||||
);
|
||||
assertEqual(
|
||||
pattern.metrics.halfPowerBeamwidth.status,
|
||||
"no-sampled-half-power-crossing",
|
||||
"missing crossing remains unresolved",
|
||||
);
|
||||
assertEqual(pattern.metrics.halfPowerBeamwidth.widthDeg, null, "no fabricated width");
|
||||
assertEqual(
|
||||
pattern.metrics.halfPowerBeamwidth.fullCircleAboveHalfPower,
|
||||
null,
|
||||
"full-circle claim is withheld",
|
||||
);
|
||||
});
|
||||
|
||||
test("auto angular sampling increases resolution and reports when the safety cap is insufficient", () => {
|
||||
const moderate = computeReceiveBeamPattern({
|
||||
array: arrayFromPositions([[-0.225, 0, 0], [0.225, 0, 0]]),
|
||||
@@ -324,4 +366,26 @@ test("beam-pattern preflight rejects invalid input and excessive work", () => {
|
||||
microphoneLimitError.message.includes("1024"),
|
||||
"microphone cap is explicit",
|
||||
);
|
||||
|
||||
let baselineError = null;
|
||||
try {
|
||||
computeReceiveBeamPattern({
|
||||
array,
|
||||
frequencyHz: 1_000,
|
||||
maximumBaselineComparisons: 0,
|
||||
});
|
||||
} catch (error) {
|
||||
baselineError = error;
|
||||
}
|
||||
assert(baselineError instanceof RangeError, "baseline pair cap is checked");
|
||||
assert(baselineError.message.includes("1 projected-baseline"), "pair work is explicit");
|
||||
|
||||
let frequencyLimitError = null;
|
||||
try {
|
||||
computeReceiveBeamPattern({ array, frequencyHz: 1_000_001 });
|
||||
} catch (error) {
|
||||
frequencyLimitError = error;
|
||||
}
|
||||
assert(frequencyLimitError instanceof RangeError, "extreme analog frequency is bounded");
|
||||
assert(frequencyLimitError.message.includes("1000000"), "frequency cap is explicit");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user