///|
/// A station magnitude measurement with quality provenance.
pub struct StationMagnitudeMeasurement {
station : String
scale : MagnitudeScale
value : Double
distance_km : Double
amplitude : Double
uncertainty : Double
weight : Double
}
///|
/// Network magnitude summary with dispersion diagnostics.
pub struct NetworkMagnitudeSummary {
scale : MagnitudeScale
value : Double
standard_deviation : Double
median_absolute_deviation : Double
station_count : Int
weighted_count : Double
minimum : Double
maximum : Double
}
///|
/// Build a station measurement from a magnitude estimate.
pub fn station_magnitude_measurement(
station : String,
estimate : MagnitudeEstimate,
) -> StationMagnitudeMeasurement {
{
station,
scale: estimate.scale,
value: estimate.value,
distance_km: estimate.distance,
amplitude: estimate.amplitude,
uncertainty: estimate.uncertainty,
weight: 1.0 / estimate.uncertainty.max(0.05),
}
}
///|
/// Compute a weighted magnitude consensus.
pub fn network_magnitude_summary(
measurements : Array[StationMagnitudeMeasurement],
) -> NetworkMagnitudeSummary {
let values = measurements.map(measurement => measurement.value)
let mut weighted_total = 0.0
let mut weights = 0.0
for measurement in measurements {
weighted_total += measurement.value * measurement.weight
weights += measurement.weight
}
let consensus = if weights <= 0.0 {
median_samples(values)
} else {
weighted_total / weights
}
{
scale: if measurements.is_empty() {
LocalMagnitude
} else {
measurements[0].scale
},
value: consensus,
standard_deviation: standard_deviation(values),
median_absolute_deviation: absolute_deviation_median(
values,
median_samples(values),
),
station_count: measurements.length(),
weighted_count: weights,
minimum: minimum_sample(values),
maximum: maximum_sample(values),
}
}
///|
/// Remove measurements farther than a robust dispersion threshold.
pub fn magnitude_inliers(
measurements : Array[StationMagnitudeMeasurement],
threshold : Double,
) -> Array[StationMagnitudeMeasurement] {
let center = median_samples(
measurements.map(measurement => measurement.value),
)
let mad = absolute_deviation_median(
measurements.map(measurement => measurement.value),
center,
).max(1.0e-6)
measurements.filter(measurement => {
(measurement.value - center).abs() <= threshold.abs() * 1.4826 * mad
})
}
///|
/// Estimate local magnitude from every trace and retain valid stations.
pub fn local_magnitudes_from_traces(
traces : Array[Trace],
distances_km : Array[Double],
) -> Array[StationMagnitudeMeasurement] {
let result = Array::new()
for index = 0
index < traces.length() && index < distances_km.length()
index = index + 1 {
match magnitude_from_trace(traces[index], distances_km[index]) {
Ok(estimate) =>
result.push(
station_magnitude_measurement(traces[index].station, estimate),
)
Err(_) => ()
}
}
result
}
///|
/// Format a network magnitude summary.
pub fn network_magnitude_text(summary : NetworkMagnitudeSummary) -> String {
"magnitude=" +
summary.value.to_string() +
" stations=" +
summary.station_count.to_string() +
" weighted=" +
summary.weighted_count.to_string() +
" std=" +
summary.standard_deviation.to_string() +
" mad=" +
summary.median_absolute_deviation.to_string() +
" min=" +
summary.minimum.to_string() +
" max=" +
summary.maximum.to_string()
}