///|
/// Peak-to-peak insertion loss over sampled frequencies, in dB. This is an
/// aggregate constraint: every point can satisfy an absolute maximum yet the
/// difference between the lowest and highest values can violate a ripple limit.
/// Witness point indices refer to the original input network.
pub struct RippleReport {
scope : String
status : String
start_hz : Double
end_hz : Double
input : Int
output : Int
maximum_ripple_db : Double
sample_count : Int
range_covered : Bool
start_sampled : Bool
end_sampled : Bool
ripple_db : Double?
ripple_state : String
lowest_loss : BandObservation?
highest_loss : BandObservation?
}
///|
pub impl ToJson for RippleReport with fn to_json(self) {
{
"scope": self.scope.to_json(),
"status": self.status.to_json(),
"start_hz": self.start_hz.to_json(),
"end_hz": self.end_hz.to_json(),
"input": self.input.to_json(),
"output": self.output.to_json(),
"maximum_ripple_db": self.maximum_ripple_db.to_json(),
"sample_count": self.sample_count.to_json(),
"range_covered": self.range_covered.to_json(),
"start_sampled": self.start_sampled.to_json(),
"end_sampled": self.end_sampled.to_json(),
"ripple_db": nullable_number(self.ripple_db),
"ripple_state": self.ripple_state.to_json(),
"lowest_loss": match self.lowest_loss {
Some(x) => x.to_json()
None => null
},
"highest_loss": match self.highest_loss {
Some(x) => x.to_json()
None => null
},
}
}
///|
pub extend RippleReport with ToJson::{to_json}
///|
/// Check max(loss)-min(loss), loss = -20log10(abs(S[output,input])). Inclusive
/// maximum, no interpolated endpoints and no implicit tolerance. At least TWO
/// measured points are required even for an equal-edge band. Uncovered ranges
/// cannot pass, but an observed violation always fails. One zero transmission
/// and one nonzero transmission imply infinite ripple; all-zero transmission
/// is undefined (infinity-infinity), never a falsely perfect flat passband.
pub fn Network::check_ripple(
self : Network,
start_hz : Double,
end_hz : Double,
input~ : Int,
output~ : Int,
maximum_ripple_db~ : Double,
) -> RippleReport raise {
validate_band_domain(start_hz, end_hz, input, output)
check_ports(self.n, output, input)
if !finite(maximum_ripple_db) || maximum_ripple_db < 0.0 {
raise Failure("maximum_ripple_db must be finite and nonnegative")
}
// Internal measurement rule has no pointwise bounds. Reuse the same tested
// selection, units, conversion and extrema implementation as check_bands.
let rule : BandLimit = {
metric: InsertionLossDb,
start_hz,
end_hz,
input,
output,
minimum: None,
maximum: None,
}
let (lo, hi) = band_indices(self.frequencies, rule)
if hi - lo > 2000000 {
raise Failure("ripple check exceeds 2000000 sampled evaluations")
}
let sample = check_band(self.convert("S"), rule, lo, hi, 0)
let (value, state) : (Double?, String) = match
(sample.lowest, sample.highest) {
(Some(low), Some(high)) =>
match (low.value, high.value) {
(Some(a), Some(b)) => {
let difference = b - a
if !finite(difference) {
raise Failure("ripple subtraction overflow")
}
(Some(difference), "finite")
}
(Some(_), None) => (None, "infinite")
_ => (None, "undefined")
}
_ => (None, "undefined")
}
let status = if state == "infinite" ||
(value is Some(x) && x > maximum_ripple_db) {
"fail"
} else if state == "undefined" ||
sample.sample_count < 2 ||
!sample.range_covered {
"inconclusive"
} else {
"pass"
}
{
scope: "sampled_points_only",
status,
start_hz,
end_hz,
input,
output,
maximum_ripple_db,
sample_count: sample.sample_count,
range_covered: sample.range_covered,
start_sampled: sample.start_sampled,
end_sampled: sample.end_sampled,
ripple_db: value,
ripple_state: state,
lowest_loss: sample.lowest,
highest_loss: sample.highest,
}
}