diff --git a/crates/asap-aware-mapping/src/accuracy/budget.rs b/crates/asap-aware-mapping/src/accuracy/budget.rs index b8ca82573..da9b70cbe 100644 --- a/crates/asap-aware-mapping/src/accuracy/budget.rs +++ b/crates/asap-aware-mapping/src/accuracy/budget.rs @@ -9,23 +9,22 @@ //! splits are made. use super::*; -/// `shares` of `of` equal shares of `target`, in `metric`'s composition: -/// the target of one approximate input (`shares = 1`), or of a nested -/// sub-DAG holding `shares` of the `of` inputs. `None` for an exact target -/// or an ε that is not positive and finite: no approximate input meets it. -/// `of = 1` (or 0) is `target` itself. -pub fn equal_shares( +/// The target of one of `approximate_inputs` approximate inputs that +/// compose into a value with `target`, split equally in `metric`'s +/// composition (see the module doc). `None` for an exact target or an ε +/// that is not positive and finite: no approximate input meets it. One +/// input (or none) gets `target` itself. +pub fn per_input_target( target: &AccuracyTarget, - metric: ErrorMetric, - of: usize, - shares: usize, + metric: AccuracyMetric, + approximate_inputs: usize, ) -> Option { if target.is_exact() || !(target.error.is_finite() && target.error > 0.0) { return None; } - let fraction = shares.min(of.max(1)) as f64 / of.max(1) as f64; + let fraction = 1.0 / approximate_inputs.max(1) as f64; let error = match metric { - ErrorMetric::Relative => (1.0 + target.error).powf(fraction) - 1.0, + AccuracyMetric::Relative => (1.0 + target.error).powf(fraction) - 1.0, _ => target.error * fraction, }; Some(AccuracyTarget { @@ -35,15 +34,6 @@ pub fn equal_shares( }) } -/// One of `approximate_inputs` equal shares of `target` ([`equal_shares`]). -pub fn equal_share( - target: &AccuracyTarget, - metric: ErrorMetric, - approximate_inputs: usize, -) -> Option { - equal_shares(target, metric, approximate_inputs, 1) -} - #[cfg(test)] mod tests { use super::*; @@ -52,18 +42,18 @@ mod tests { /// target. #[test] fn an_additive_target_splits_epsilon_and_delta_equally() { - let target = AccuracyTarget::epsilon_delta(0.1, 0.02); - let share = equal_share(&target, ErrorMetric::ABSOLUTE, 2).unwrap(); - assert_eq!(share, AccuracyTarget::epsilon_delta(0.05, 0.01)); - let rest = equal_shares(&target, ErrorMetric::ABSOLUTE, 3, 2).unwrap(); - assert!((rest.error - 0.1 * 2.0 / 3.0).abs() < 1e-12); - assert!((rest.failure_probability.unwrap() - 0.02 * 2.0 / 3.0).abs() < 1e-12); + let target = AccuracyTarget::max_error(0.1).failure_probability(0.02); + let share = per_input_target(&target, AccuracyMetric::ABSOLUTE, 2).unwrap(); + assert_eq!( + share, + AccuracyTarget::max_error(0.05).failure_probability(0.01) + ); assert_eq!( - equal_share(&target, ErrorMetric::Rank, 1), + per_input_target(&target, AccuracyMetric::Rank, 1), Some(target.clone()) ); assert_eq!( - equal_share(&AccuracyTarget::epsilon(0.1), ErrorMetric::Rank, 2) + per_input_target(&AccuracyTarget::max_error(0.1), AccuracyMetric::Rank, 2) .unwrap() .failure_probability, None @@ -74,7 +64,12 @@ mod tests { /// not `n ε_i = ε`. #[test] fn a_relative_target_splits_multiplicatively() { - let share = equal_share(&AccuracyTarget::epsilon(0.21), ErrorMetric::Relative, 2).unwrap(); + let share = per_input_target( + &AccuracyTarget::max_error(0.21), + AccuracyMetric::Relative, + 2, + ) + .unwrap(); assert!((share.error - 0.1).abs() < 1e-12); } @@ -82,11 +77,11 @@ mod tests { #[test] fn an_exact_target_has_no_share() { assert_eq!( - equal_share(&AccuracyTarget::exact(), ErrorMetric::ABSOLUTE, 2), + per_input_target(&AccuracyTarget::exact(), AccuracyMetric::ABSOLUTE, 2), None ); assert_eq!( - equal_share(&AccuracyTarget::epsilon(0.0), ErrorMetric::ABSOLUTE, 2), + per_input_target(&AccuracyTarget::max_error(0.0), AccuracyMetric::ABSOLUTE, 2), None ); } diff --git a/crates/asap-aware-mapping/src/accuracy/composition.rs b/crates/asap-aware-mapping/src/accuracy/composition.rs index 710ebe8bc..b7b99850d 100644 --- a/crates/asap-aware-mapping/src/accuracy/composition.rs +++ b/crates/asap-aware-mapping/src/accuracy/composition.rs @@ -93,7 +93,7 @@ impl DefaultAccuracyModel { .collect(); deltas.push(local.spec.failure_probability.clone()); AccuracyGuarantee::new( - local.spec.metric, + local.spec.accuracy_metric, BoundExpr::Sum { terms }, ProbabilityExpr::UnionBound { terms: deltas }, composed_provenance(op, inputs, local, rule), @@ -134,7 +134,7 @@ impl DefaultAccuracyModel { .collect(); deltas.push(local.spec.failure_probability.clone()); AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Sum { terms }, ProbabilityExpr::UnionBound { terms: deltas }, composed_provenance(op, inputs, local, "relative_cross_term_union_bound"), @@ -168,7 +168,7 @@ impl DefaultAccuracyModel { }; let exact_local = AccuracyGuarantee::exact("deterministic Lipschitz transformation"); AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, bound, delta, composed_provenance( @@ -202,12 +202,12 @@ impl DefaultAccuracyModel { let absolute = absolute_bound(input).ok_or_else(|| AccuracyError::UnsupportedComposition { operator: op.clone(), - input_metrics: inputs.iter().map(|g| g.spec.metric).collect(), + input_metrics: inputs.iter().map(|g| g.spec.accuracy_metric).collect(), local_metric: None, reason: format!( "input {i} carries a {:?} guarantee, which has no registered \ conversion to an absolute value error", - input.spec.metric + input.spec.accuracy_metric ), })?; if let BoundExpr::Product { factors } = &absolute { @@ -231,7 +231,7 @@ impl DefaultAccuracyModel { "exact_sum_union_bound", )); Ok(AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, BoundExpr::Product { factors: vec![count.clone(), BoundExpr::Sum { terms }], }, @@ -257,11 +257,11 @@ impl DefaultAccuracyModel { } if inputs .iter() - .any(|input| input.spec.metric != ErrorMetric::ABSOLUTE) + .any(|input| input.spec.accuracy_metric != AccuracyMetric::ABSOLUTE) { return Err(AccuracyError::UnsupportedComposition { operator: op.clone(), - input_metrics: inputs.iter().map(|g| g.spec.metric).collect(), + input_metrics: inputs.iter().map(|g| g.spec.accuracy_metric).collect(), local_metric: None, reason: "exact average requires AbsoluteValue input guarantees".into(), }); @@ -276,7 +276,7 @@ impl DefaultAccuracyModel { "exact_average_union_bound", )); Ok(AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, BoundExpr::Max { terms: inputs.iter().map(|g| g.spec.error.clone()).collect(), }, @@ -306,14 +306,14 @@ impl DefaultAccuracyModel { if all_relative(inputs) { return Self::relative_fold(op, inputs, stats, false, "exact_extremum_relative"); } - let metric = inputs[0].spec.metric; + let metric = inputs[0].spec.accuracy_metric; if inputs .iter() - .any(|g| g.spec.metric != ErrorMetric::ABSOLUTE) + .any(|g| g.spec.accuracy_metric != AccuracyMetric::ABSOLUTE) { return Err(AccuracyError::UnsupportedComposition { operator: op.clone(), - input_metrics: inputs.iter().map(|g| g.spec.metric).collect(), + input_metrics: inputs.iter().map(|g| g.spec.accuracy_metric).collect(), local_metric: None, reason: "exact max/min requires AbsoluteValue input guarantees".into(), }); @@ -361,7 +361,7 @@ impl DefaultAccuracyModel { if needs_sign && stats.values_non_negative == Some(false) { return Err(AccuracyError::UnsupportedComposition { operator: op.clone(), - input_metrics: inputs.iter().map(|g| g.spec.metric).collect(), + input_metrics: inputs.iter().map(|g| g.spec.accuracy_metric).collect(), local_metric: None, reason: "relative error of a sum over signed values is unbounded".into(), }); @@ -407,7 +407,7 @@ impl DefaultAccuracyModel { rule, )); Ok(AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, bound, ProbabilityExpr::Scaled { count, @@ -432,14 +432,14 @@ impl DefaultAccuracyModel { ) -> Result { let unsupported = |reason: String| AccuracyError::UnsupportedComposition { operator: op.clone(), - input_metrics: inputs.iter().map(|g| g.spec.metric).collect(), + input_metrics: inputs.iter().map(|g| g.spec.accuracy_metric).collect(), local_metric: None, reason, }; if inputs.len() != 2 - || inputs - .iter() - .any(|input| input.spec.metric != ErrorMetric::Relative && !input.is_exact()) + || inputs.iter().any(|input| { + input.spec.accuracy_metric != AccuracyMetric::Relative && !input.is_exact() + }) { return Err(unsupported( "division needs two relative-value or exact guarantees".into(), @@ -510,7 +510,7 @@ impl DefaultAccuracyModel { } } Ok(AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Constant { value: (numerator + denominator) / (1.0 - denominator), }, @@ -558,7 +558,7 @@ fn all_relative(inputs: &[AccuracyGuarantee]) -> bool { !inputs.is_empty() && inputs .iter() - .all(|g| g.spec.metric == ErrorMetric::Relative) + .all(|g| g.spec.accuracy_metric == AccuracyMetric::Relative) } /// `stats.input_row_count` as a bound factor, or an `Unknown` leaf named by @@ -578,8 +578,8 @@ fn row_count(stats: &PropagationStats, provenance: &mut Vec) -> } } -/// `input` with a Count-Min ([`ErrorMetric::ABSOLUTE_STREAM_L1`]) or Count Sketch -/// ([`ErrorMetric::ABSOLUTE_STREAM_L2`]) bound restated as an additive absolute +/// `input` with a Count-Min ([`AccuracyMetric::ABSOLUTE_STREAM_L1`]) or Count Sketch +/// ([`AccuracyMetric::ABSOLUTE_STREAM_L2`]) bound restated as an additive absolute /// error: the normalized bound times its stream norm, a symbolic statistic /// until bound. The norm is the one `norm` names ([`StreamNorm`]), which /// plan selection can bind; without one (a weight computed from the @@ -590,8 +590,8 @@ pub(crate) fn additive_as_absolute( norm: Option<&StreamNorm>, ) -> AccuracyGuarantee { if !matches!( - input.spec.metric, - ErrorMetric::ABSOLUTE_STREAM_L1 | ErrorMetric::ABSOLUTE_STREAM_L2 + input.spec.accuracy_metric, + AccuracyMetric::ABSOLUTE_STREAM_L1 | AccuracyMetric::ABSOLUTE_STREAM_L2 ) || input.is_exact() { return input; @@ -628,7 +628,7 @@ pub(crate) fn additive_as_absolute( provenance, ..input }; - absolute.spec.metric = ErrorMetric::ABSOLUTE; + absolute.spec.accuracy_metric = AccuracyMetric::ABSOLUTE; absolute.spec.error = bound; absolute } @@ -653,14 +653,14 @@ fn unknown_statistics(bound: &BoundExpr, found: &mut dyn FnMut(&str)) { /// normalized metric by the (unknown) statistic it is normalized by. `None` /// for a metric with no such conversion (`Rank`). fn absolute_bound(input: &AccuracyGuarantee) -> Option { - let normalizer = match input.spec.metric { - ErrorMetric::ABSOLUTE => return Some(input.spec.error.clone()), - ErrorMetric::Relative => "true_value_magnitude", - ErrorMetric::ABSOLUTE_STREAM_L1 => STREAM_L1_NORM, - ErrorMetric::ABSOLUTE_STREAM_L2 => STREAM_L2_NORM, + let normalizer = match input.spec.accuracy_metric { + AccuracyMetric::ABSOLUTE => return Some(input.spec.error.clone()), + AccuracyMetric::Relative => "true_value_magnitude", + AccuracyMetric::ABSOLUTE_STREAM_L1 => STREAM_L1_NORM, + AccuracyMetric::ABSOLUTE_STREAM_L2 => STREAM_L2_NORM, // `Rank` has no distribution-free conversion to a value error; a // metric this crate does not know has no registered conversion. - ErrorMetric::Rank | _ => return None, + AccuracyMetric::Rank | _ => return None, }; if input.spec.error.is_zero() { return Some(BoundExpr::Zero); @@ -744,21 +744,23 @@ pub(super) fn propagate( }); } - let input_metrics: Vec = inputs.iter().map(|g| g.spec.metric).collect(); + let input_metrics: Vec = + inputs.iter().map(|g| g.spec.accuracy_metric).collect(); let unsupported = |reason: String| AccuracyError::UnsupportedComposition { operator: op.clone(), input_metrics: input_metrics.clone(), - local_metric: local.map(|g| g.spec.metric), + local_metric: local.map(|g| g.spec.accuracy_metric), reason, }; // An exact input is compatible with every metric; only approximate // inputs constrain the rule. let approximate: Vec<&AccuracyGuarantee> = inputs.iter().filter(|g| !g.is_exact()).collect(); - let same_metric = |metric: ErrorMetric| approximate.iter().all(|g| g.spec.metric == metric); + let same_metric = + |metric: AccuracyMetric| approximate.iter().all(|g| g.spec.accuracy_metric == metric); match op { CompositionOperator::CheckedRelativeDivision => { - if inputs.len() != 2 || local.is_some() || !same_metric(ErrorMetric::Relative) { + if inputs.len() != 2 || local.is_some() || !same_metric(AccuracyMetric::Relative) { return Err(unsupported( "checked division requires two exact/relative-value operands".into(), )); @@ -777,7 +779,7 @@ pub(super) fn propagate( return Err(unsupported("invalid relative division bounds".into())); } Ok(AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Constant { value: (a + b) / (1.0 - b) + 4.0 * f64::EPSILON, }, @@ -799,10 +801,10 @@ pub(super) fn propagate( let local = local.ok_or_else(|| { unsupported("approximate operator has no local guarantee to compose".into()) })?; - if !same_metric(local.spec.metric) { + if !same_metric(local.spec.accuracy_metric) { return Err(unsupported(format!( "no registered cross-metric rule from {input_metrics:?} to {:?}", - local.spec.metric + local.spec.accuracy_metric ))); } // A distinct count over approximate values is not within the @@ -813,14 +815,14 @@ pub(super) fn propagate( "no registered rule for a distinct count over approximate values".into(), )); } - match local.spec.metric { - ErrorMetric::ABSOLUTE => Ok(DefaultAccuracyModel::additive( + match local.spec.accuracy_metric { + AccuracyMetric::ABSOLUTE => Ok(DefaultAccuracyModel::additive( op, inputs, local, "additive_union_bound", )), - ErrorMetric::Relative => { + AccuracyMetric::Relative => { if stats.values_non_negative == Some(false) { return Err(unsupported( "relative-error composition cannot use a known signed input".into(), @@ -838,7 +840,7 @@ pub(super) fn propagate( statistic: VALUES_NON_NEGATIVE.into(), }); return Ok(AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Unknown { statistic: VALUES_NON_NEGATIVE.into(), }, @@ -850,12 +852,12 @@ pub(super) fn propagate( } Ok(DefaultAccuracyModel::multiplicative(op, inputs, local)) } - ErrorMetric::Rank - | ErrorMetric::ABSOLUTE_STREAM_L1 - | ErrorMetric::ABSOLUTE_STREAM_L2 + AccuracyMetric::Rank + | AccuracyMetric::ABSOLUTE_STREAM_L1 + | AccuracyMetric::ABSOLUTE_STREAM_L2 | _ => Err(unsupported(format!( "no registered same-metric composition rule for {:?} over {:?}", - local.spec.metric, local.spec.metric + local.spec.accuracy_metric, local.spec.accuracy_metric ))), } } @@ -865,12 +867,12 @@ pub(super) fn propagate( "Lipschitz constant {constant} is not a finite non-negative number" ))); } - if inputs.len() != 1 || !same_metric(ErrorMetric::ABSOLUTE) { + if inputs.len() != 1 || !same_metric(AccuracyMetric::ABSOLUTE) { return Err(unsupported( "Lipschitz rule is registered for exactly one AbsoluteValue input".into(), )); } - if local.is_some_and(|g| g.spec.metric != ErrorMetric::ABSOLUTE) { + if local.is_some_and(|g| g.spec.accuracy_metric != AccuracyMetric::ABSOLUTE) { return Err(unsupported( "Lipschitz rule needs an AbsoluteValue local guarantee".into(), )); @@ -942,7 +944,7 @@ pub(super) fn propagate( // membership and hence the values are unknown. let metric = approximate .first() - .map_or(ErrorMetric::ABSOLUTE, |score| score.spec.metric); + .map_or(AccuracyMetric::ABSOLUTE, |score| score.spec.accuracy_metric); if !same_metric(metric) { return Err(unsupported( "top-k selection needs scores of one metric".into(), @@ -1003,7 +1005,7 @@ mod tests { use asap_types::pre_asap::AggIntent; fn abs(bound: f64, delta: f64) -> AccuracyGuarantee { AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, BoundExpr::Constant { value: bound }, ProbabilityExpr::Constant { value: delta }, vec![], @@ -1011,7 +1013,7 @@ mod tests { } fn rel(bound: f64) -> AccuracyGuarantee { AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Constant { value: bound }, ProbabilityExpr::Zero, vec![], @@ -1025,9 +1027,9 @@ mod tests { contract: "enforced test population".into(), } } - fn with_metric(metric: ErrorMetric, bound: f64) -> AccuracyGuarantee { + fn with_metric(metric: AccuracyMetric, bound: f64) -> AccuracyGuarantee { let mut g = abs(bound, 0.0); - g.spec.metric = metric; + g.spec.accuracy_metric = metric; g } #[test] @@ -1039,7 +1041,7 @@ mod tests { .unwrap(); assert!((g.spec.error.evaluate().unwrap() - 0.02 / 0.99).abs() < 1e-14); let mut rank = inputs[0].clone(); - rank.spec.metric = ErrorMetric::Rank; + rank.spec.accuracy_metric = AccuracyMetric::Rank; assert!(DefaultAccuracyModel .propagate(&op, &[rank.clone(), rank], None, &Default::default()) .is_err()); @@ -1135,7 +1137,7 @@ mod tests { .unwrap(); assert_eq!(out.spec.error.evaluate(), Some(0.05)); assert_eq!(out.spec.failure_probability.evaluate(), Some(0.01)); - assert_eq!(out.spec.metric, ErrorMetric::ABSOLUTE); + assert_eq!(out.spec.accuracy_metric, AccuracyMetric::ABSOLUTE); } #[test] fn additive_bounds_and_delta_union_bound_compose() { @@ -1171,7 +1173,7 @@ mod tests { .unwrap(); // 0.1 + 0.2 + 0.1·0.2 = 0.32, not 0.3. assert!((out.spec.error.evaluate().unwrap() - 0.32).abs() < 1e-12); - assert_eq!(out.spec.metric, ErrorMetric::Relative); + assert_eq!(out.spec.accuracy_metric, AccuracyMetric::Relative); } #[test] fn relative_error_without_sign_knowledge_remains_symbolic() { @@ -1201,8 +1203,8 @@ mod tests { let err = DefaultAccuracyModel .propagate( &CompositionOperator::ApproximateAggregate, - &[with_metric(ErrorMetric::Relative, 0.01)], - Some(&with_metric(ErrorMetric::ABSOLUTE_STREAM_L1, 0.01)), + &[with_metric(AccuracyMetric::Relative, 0.01)], + Some(&with_metric(AccuracyMetric::ABSOLUTE_STREAM_L1, 0.01)), &PropagationStats::default(), ) .unwrap_err(); @@ -1210,15 +1212,15 @@ mod tests { err, AccuracyError::UnsupportedComposition { input_metrics, - local_metric: Some(ErrorMetric::ABSOLUTE_STREAM_L1), + local_metric: Some(AccuracyMetric::ABSOLUTE_STREAM_L1), .. - } if input_metrics == vec![ErrorMetric::Relative] + } if input_metrics == vec![AccuracyMetric::Relative] )); // Quantile rank error under value-additive logic. let err = DefaultAccuracyModel .propagate( &CompositionOperator::ApproximateAggregate, - &[with_metric(ErrorMetric::Rank, 0.01)], + &[with_metric(AccuracyMetric::Rank, 0.01)], Some(&abs(0.01, 0.0)), &PropagationStats::default(), ) @@ -1230,8 +1232,8 @@ mod tests { let err = DefaultAccuracyModel .propagate( &CompositionOperator::ApproximateAggregate, - &[with_metric(ErrorMetric::Rank, 0.01)], - Some(&with_metric(ErrorMetric::Rank, 0.01)), + &[with_metric(AccuracyMetric::Rank, 0.01)], + Some(&with_metric(AccuracyMetric::Rank, 0.01)), &PropagationStats::default(), ) .unwrap_err(); @@ -1260,7 +1262,7 @@ mod tests { &PropagationStats::default(), ) .unwrap(); - assert_eq!(out.spec.metric, ErrorMetric::ABSOLUTE); + assert_eq!(out.spec.accuracy_metric, AccuracyMetric::ABSOLUTE); assert_eq!( out.spec.error.evaluate(), None, @@ -1270,7 +1272,7 @@ mod tests { s, GuaranteeSource::UnavailableStatistic { statistic } if statistic == "input_row_count" ))); - assert!(!DefaultAccuracyModel.satisfies(&out, &AccuracyTarget::epsilon(1.0))); + assert!(!DefaultAccuracyModel.satisfies(&out, &AccuracyTarget::max_error(1.0))); let known = PropagationStats { input_row_count: Some(4), @@ -1319,7 +1321,7 @@ mod tests { }, ) .unwrap(); - assert_eq!(out.spec.metric, ErrorMetric::Relative); + assert_eq!(out.spec.accuracy_metric, AccuracyMetric::Relative); assert_eq!(out.spec.error.evaluate(), Some(0.02)); assert_eq!(out.spec.failure_probability.evaluate(), Some(0.0)); } @@ -1345,10 +1347,10 @@ mod tests { ) }; let known = with(Some(true)).unwrap(); - assert_eq!(known.spec.metric, ErrorMetric::Relative); + assert_eq!(known.spec.accuracy_metric, AccuracyMetric::Relative); assert_eq!(known.spec.error.evaluate(), Some(0.01), "{op:?}"); let unknown = with(None).unwrap(); - assert_eq!(unknown.spec.metric, ErrorMetric::Relative); + assert_eq!(unknown.spec.accuracy_metric, AccuracyMetric::Relative); assert_eq!( unknown.spec.error, BoundExpr::Unknown { @@ -1404,7 +1406,7 @@ mod tests { }, ) .unwrap(); - assert_eq!(out.spec.metric, ErrorMetric::ABSOLUTE); + assert_eq!(out.spec.accuracy_metric, AccuracyMetric::ABSOLUTE); assert_eq!(out.spec.error.evaluate(), Some(0.25)); assert_eq!(out.spec.failure_probability.evaluate(), Some(0.04)); } @@ -1455,7 +1457,7 @@ mod tests { }, ) .unwrap(); - assert_eq!(certified.spec.metric, ErrorMetric::ABSOLUTE); + assert_eq!(certified.spec.accuracy_metric, AccuracyMetric::ABSOLUTE); assert_eq!(certified.spec.error.evaluate(), Some(0.1)); assert_eq!(certified.spec.failure_probability.evaluate(), Some(0.005)); diff --git a/crates/asap-aware-mapping/src/accuracy/estimators/cardinality.rs b/crates/asap-aware-mapping/src/accuracy/estimators/cardinality.rs index b49a40209..5e69aa1c5 100644 --- a/crates/asap-aware-mapping/src/accuracy/estimators/cardinality.rs +++ b/crates/asap-aware-mapping/src/accuracy/estimators/cardinality.rs @@ -49,7 +49,7 @@ pub(super) fn guarantee( algorithm, params, query, - ErrorMetric::Relative, + AccuracyMetric::Relative, eps, ProbabilityExpr::Constant { value: kmv_failure_probability(*k, eps), @@ -61,7 +61,7 @@ pub(super) fn guarantee( algorithm, params, query, - ErrorMetric::Relative, + AccuracyMetric::Relative, 10.0 / f64::from(k.saturating_sub(2).max(1)).sqrt(), ProbabilityExpr::Constant { value: 0.01 }, "theta_variance_chebyshev_99_v1", diff --git a/crates/asap-aware-mapping/src/accuracy/estimators/cms.rs b/crates/asap-aware-mapping/src/accuracy/estimators/cms.rs index 9dbaed597..c19cd7137 100644 --- a/crates/asap-aware-mapping/src/accuracy/estimators/cms.rs +++ b/crates/asap-aware-mapping/src/accuracy/estimators/cms.rs @@ -15,7 +15,7 @@ pub(super) fn guarantee( algorithm, params, query, - ErrorMetric::ABSOLUTE_STREAM_L1, + AccuracyMetric::ABSOLUTE_STREAM_L1, std::f64::consts::E / f64::from(*width), ProbabilityExpr::Constant { value: (-f64::from(*depth)).exp(), @@ -54,12 +54,13 @@ mod tests { &SketchStatistic::Cardinality, ) .unwrap(); - assert_eq!(g.spec.metric, ErrorMetric::ABSOLUTE_STREAM_L1); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::ABSOLUTE_STREAM_L1); assert!(DefaultAccuracyModel.satisfies( &g, - &AccuracyTarget::epsilon_delta(0.01, 0.001) - .with_metric(ErrorMetric::ABSOLUTE_STREAM_L1) - .with_scope(asap_types::types::AccuracyScope::PerValue) + &AccuracyTarget::max_error(0.01) + .failure_probability(0.001) + .accuracy_metric(AccuracyMetric::ABSOLUTE_STREAM_L1) + .accuracy_scope(asap_types::types::AccuracyScope::PerValue) )); } @@ -80,6 +81,9 @@ mod tests { &SketchStatistic::TopK { k: 10 }, ) .expect("heap sketch still provides per-key frequency intervals"); - assert_eq!(topk_frequency.spec.metric, ErrorMetric::ABSOLUTE_STREAM_L1); + assert_eq!( + topk_frequency.spec.accuracy_metric, + AccuracyMetric::ABSOLUTE_STREAM_L1 + ); } } diff --git a/crates/asap-aware-mapping/src/accuracy/estimators/count_sketch.rs b/crates/asap-aware-mapping/src/accuracy/estimators/count_sketch.rs index b97db7159..6a6d60137 100644 --- a/crates/asap-aware-mapping/src/accuracy/estimators/count_sketch.rs +++ b/crates/asap-aware-mapping/src/accuracy/estimators/count_sketch.rs @@ -16,7 +16,7 @@ pub(super) fn guarantee( algorithm, params, query, - ErrorMetric::ABSOLUTE_STREAM_L2, + AccuracyMetric::ABSOLUTE_STREAM_L2, (3.0 / f64::from(*width)).sqrt(), ProbabilityExpr::Constant { value: count_sketch_failure_probability(*width, *depth)?, @@ -87,12 +87,16 @@ mod tests { }, ) .expect("CountSketch has a parameter-derived L2 guarantee"); - assert_eq!(guarantee.spec.metric, ErrorMetric::ABSOLUTE_STREAM_L2); + assert_eq!( + guarantee.spec.accuracy_metric, + AccuracyMetric::ABSOLUTE_STREAM_L2 + ); assert!(DefaultAccuracyModel.satisfies( &guarantee, - &AccuracyTarget::epsilon_delta(0.01, 0.01) - .with_metric(ErrorMetric::ABSOLUTE_STREAM_L2) - .with_scope(asap_types::types::AccuracyScope::PerValue) + &AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::ABSOLUTE_STREAM_L2) + .accuracy_scope(asap_types::types::AccuracyScope::PerValue) )); } diff --git a/crates/asap-aware-mapping/src/accuracy/estimators/ddsketch.rs b/crates/asap-aware-mapping/src/accuracy/estimators/ddsketch.rs index eafd6b3ce..9cde75477 100644 --- a/crates/asap-aware-mapping/src/accuracy/estimators/ddsketch.rs +++ b/crates/asap-aware-mapping/src/accuracy/estimators/ddsketch.rs @@ -13,7 +13,7 @@ pub(super) fn guarantee( algorithm, params, query, - ErrorMetric::Relative, + AccuracyMetric::Relative, *alpha, ProbabilityExpr::Zero, "ddsketch_relative_error_alpha_v1", diff --git a/crates/asap-aware-mapping/src/accuracy/estimators/hll.rs b/crates/asap-aware-mapping/src/accuracy/estimators/hll.rs index 3aeff8d71..fc49b3ae4 100644 --- a/crates/asap-aware-mapping/src/accuracy/estimators/hll.rs +++ b/crates/asap-aware-mapping/src/accuracy/estimators/hll.rs @@ -18,7 +18,7 @@ pub(super) fn generic_guarantee( algorithm, params, query, - ErrorMetric::Relative, + AccuracyMetric::Relative, relative_standard_error(*precision), ProbabilityExpr::Unknown { statistic: "hll_estimator_failure_probability".into(), @@ -96,7 +96,7 @@ pub(crate) fn classic_guarantee( let relative_error = classic_relative_error(precision, relative_error); let statistic = classic_failure_statistic(precision, relative_error, source); Some(AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Constant { value: relative_error, }, @@ -162,7 +162,7 @@ impl ClassicHllConfidence { pub fn guarantee(&self, precision: u8) -> Option { let delta = self.failure_probability(precision)?; Some(AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Constant { value: self.relative_error, }, @@ -227,20 +227,22 @@ impl ClassicHllConfidence { } } -/// The smallest precision from `precision` up whose classic failure +/// The smallest precision, at least `at_least`, whose classic failure /// probability at `relative_error` over a source of at most `max_distinct` -/// distinct items is at most `delta`; `precision` when none up to 18 is. -/// Plan selection's enlargement (#669b) applies this rule to the source's -/// bound, Pass 1's sizing ([`hll_precision`]) to a bound of `⌈1/ε⌉`. -pub(crate) fn precision_for( - precision: u8, +/// distinct items is at most `failure_probability`; `at_least` when none up +/// to 18 is. Plan selection's enlargement (#669b) applies this rule to the +/// source's bound, Pass 1's sizing ([`hll_precision`]) to a bound of `⌈1/ε⌉`. +pub(crate) fn min_precision( relative_error: f64, + failure_probability: f64, max_distinct: u64, - delta: f64, + at_least: u8, ) -> u8 { - (precision..=18) - .find(|&p| classic_failure_probability(p, relative_error, max_distinct) <= delta) - .unwrap_or(precision) + (at_least..=18) + .find(|&p| { + classic_failure_probability(p, relative_error, max_distinct) <= failure_probability + }) + .unwrap_or(at_least) } /// Classic HLL's precision for relative error `eps` with failure @@ -251,7 +253,7 @@ pub(crate) fn precision_for( /// which [`ClassicHllConfidence`] certifies `delta` at `eps` for every /// bound up to `⌈1/ε⌉` (18 when none up to 18 does). Plan selection then /// binds the source's own bound, and enlarges the precision by the same -/// rule ([`precision_for`]) when the bound or a smaller failure +/// rule ([`min_precision`]) when the bound or a smaller failure /// probability needs it. pub(crate) fn hll_precision(eps: f64, delta: f64) -> u8 { if !(eps.is_finite() && eps > 0.0 && delta.is_finite() && delta > 0.0) { @@ -491,15 +493,17 @@ mod tests { &SketchStatistic::Cardinality, ) .unwrap(); - assert_eq!(g.spec.metric, ErrorMetric::Relative); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::Relative); assert_eq!(g.spec.failure_probability.evaluate(), None); assert!(DefaultAccuracyModel.satisfies( &g, - &AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Relative) + &AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Relative) )); assert!(!DefaultAccuracyModel.satisfies( &g, - &AccuracyTarget::epsilon_delta(0.01, 0.01).with_metric(ErrorMetric::Relative) + &AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::Relative) )); } } diff --git a/crates/asap-aware-mapping/src/accuracy/estimators/kll.rs b/crates/asap-aware-mapping/src/accuracy/estimators/kll.rs index c0f117013..8c1fb52c8 100644 --- a/crates/asap-aware-mapping/src/accuracy/estimators/kll.rs +++ b/crates/asap-aware-mapping/src/accuracy/estimators/kll.rs @@ -30,7 +30,7 @@ pub(super) fn guarantee( algorithm, params, query, - ErrorMetric::Rank, + AccuracyMetric::Rank, kll_rank_error_99(*k) * scale, ProbabilityExpr::Constant { value: delta }, contract, @@ -200,17 +200,18 @@ mod tests { &SketchStatistic::Quantile { q: 0.99 }, ) .unwrap(); - assert_eq!(g.spec.metric, ErrorMetric::Rank); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::Rank); assert!(DefaultAccuracyModel.satisfies( &g, - &AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Rank) + &AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Rank) )); assert_eq!(g.spec.failure_probability.evaluate(), Some(0.01)); assert!(DefaultAccuracyModel.satisfies( &g, - &AccuracyTarget::epsilon_delta(0.01, 0.01) - .with_metric(ErrorMetric::Rank) - .with_scope(asap_types::types::AccuracyScope::PerValue) + &AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::Rank) + .accuracy_scope(asap_types::types::AccuracyScope::PerValue) )); assert_eq!(g.approximate_layer_count(), 1); assert!(g.provenance.iter().any(|source| matches!( @@ -241,9 +242,10 @@ mod tests { .unwrap(); assert!(DefaultAccuracyModel.satisfies( &g, - &AccuracyTarget::epsilon_delta(0.01, 0.001) - .with_metric(ErrorMetric::Rank) - .with_scope(asap_types::types::AccuracyScope::PerValue) + &AccuracyTarget::max_error(0.01) + .failure_probability(0.001) + .accuracy_metric(AccuracyMetric::Rank) + .accuracy_scope(asap_types::types::AccuracyScope::PerValue) )); assert_eq!(g.spec.failure_probability.evaluate(), Some(0.001)); } diff --git a/crates/asap-aware-mapping/src/accuracy/estimators/mod.rs b/crates/asap-aware-mapping/src/accuracy/estimators/mod.rs index 6eb50753e..759980d19 100644 --- a/crates/asap-aware-mapping/src/accuracy/estimators/mod.rs +++ b/crates/asap-aware-mapping/src/accuracy/estimators/mod.rs @@ -36,7 +36,7 @@ fn bounded_guarantee( algorithm: &SketchAlgorithm, params: &SketchParams, query: &SketchStatistic, - metric: ErrorMetric, + metric: AccuracyMetric, bound: f64, delta: ProbabilityExpr, contract: &str, @@ -78,7 +78,7 @@ pub(super) fn local_guarantee( /// UnivMon's heap, and KMV's `k` at the relative error it was sized for /// (#694). Other parameters are returned as they are: DDSketch never fails; /// classic HLL's failure probability comes from its source's distinct bound -/// ([`hll::precision_for`]); Theta is calibrated at a fixed 99%; a KMV not +/// ([`hll::min_precision`]); Theta is calibrated at a fixed 99%; a KMV not /// sized for a relative error and an L2 or entropy UnivMon are not /// enlarged. pub(crate) fn with_failure_probability(params: &SketchParams, delta: f64) -> SketchParams { @@ -587,10 +587,10 @@ mod tests { /// source's distinct bound meets the target, up to 18. #[test] fn classic_hll_precision_grows_for_its_failure_probability() { - let p = hll::precision_for(4, 0.05, 64, 0.01); + let p = hll::min_precision(0.05, 0.01, 64, 4); assert!(p > 4); assert!(hll::classic_failure_probability(p, 0.05, 64) <= 0.01); assert!(hll::classic_failure_probability(p - 1, 0.05, 64) > 0.01); - assert_eq!(hll::precision_for(12, 0.05, 4096, 1e-30), 12); + assert_eq!(hll::min_precision(0.05, 1e-30, 4096, 12), 12); } } diff --git a/crates/asap-aware-mapping/src/accuracy/estimators/univmon.rs b/crates/asap-aware-mapping/src/accuracy/estimators/univmon.rs index ece5b028d..8ac8ff31c 100644 --- a/crates/asap-aware-mapping/src/accuracy/estimators/univmon.rs +++ b/crates/asap-aware-mapping/src/accuracy/estimators/univmon.rs @@ -77,7 +77,7 @@ pub(super) fn guarantee( algorithm, params, query, - ErrorMetric::Relative, + AccuracyMetric::Relative, l2_relative_error(*sketch_cols)?, ProbabilityExpr::Constant { value: l2_failure_probability(*sketch_rows), @@ -258,7 +258,7 @@ pub(crate) fn distinct_guarantee(params: &SketchParams, source: &str) -> Option< let relative_error = (DISTINCT_VARIANCE / (f64::from(*heap_size) * chebyshev)).sqrt(); let statistic = incomplete_top_layer_statistic(*heap_size, *layers, source); Some(AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Constant { value: relative_error, }, @@ -385,7 +385,7 @@ mod tests { DefaultAccuracyModel.satisfies( guarantee, &crate::accuracy::native_target( - &AccuracyTarget::epsilon_delta(epsilon, delta), + &AccuracyTarget::max_error(epsilon).failure_probability(delta), guarantee, ), ) @@ -422,7 +422,7 @@ mod tests { for (eps, delta) in TARGETS { let params = size_params(&l2_intent(), eps, delta); let guarantee = l2(¶ms).expect("an L2 guarantee"); - assert_eq!(guarantee.spec.metric, ErrorMetric::Relative); + assert_eq!(guarantee.spec.accuracy_metric, AccuracyMetric::Relative); assert!( meets(&guarantee, eps, delta), "({eps}, {delta}): {params:?}" @@ -515,7 +515,7 @@ mod tests { }; for max_distinct in [9, 10_000_000] { let guarantee = bind(¶ms, max_distinct); - assert_eq!(guarantee.spec.metric, ErrorMetric::Relative); + assert_eq!(guarantee.spec.accuracy_metric, AccuracyMetric::Relative); assert!( meets(&guarantee, eps, delta), "({eps}, {delta}): {params:?}" diff --git a/crates/asap-aware-mapping/src/accuracy/evidence.rs b/crates/asap-aware-mapping/src/accuracy/evidence.rs index fcb316575..dd245bc77 100644 --- a/crates/asap-aware-mapping/src/accuracy/evidence.rs +++ b/crates/asap-aware-mapping/src/accuracy/evidence.rs @@ -72,10 +72,6 @@ pub struct PropagationStats { /// `input_row_count`), so a fold whose rows the data workload's input /// cardinality does not bound can leave a statistic nothing binds. pub row_count_statistic: Option<&'static str>, - /// Fresh key-frequency distribution evidence from the data workload. - /// Built-in rules preserve it for deployment-specific accuracy models; - /// they do not assume a favorable distribution when it is absent. - pub data_distribution: Option, /// Lower confidence bound of the kth selected TopK item, after widening /// the interval by the sketch's own estimation error. pub topk_selected_lower_bound: Option, @@ -152,7 +148,6 @@ impl AccuracyEvidenceProvider for WorkloadAccuracyEvidence<'_> { ) -> PropagationStats { PropagationStats { input_row_count: self.data.input_cardinality.value_at(self.now_ms).copied(), - data_distribution: self.data.distribution.value_at(self.now_ms).cloned(), ..PropagationStats::default() } } @@ -161,7 +156,7 @@ impl AccuracyEvidenceProvider for WorkloadAccuracyEvidence<'_> { #[cfg(test)] mod tests { use super::*; - use asap_types::workload::{DataDistribution, DataWorkload, Evidence, EvidenceSource}; + use asap_types::workload::{DataWorkload, Evidence, EvidenceSource}; #[test] fn workload_accuracy_evidence_uses_only_fresh_data_characteristics() { let data = DataWorkload { @@ -171,12 +166,6 @@ mod tests { observed_at_ms: Some(1_000), valid_for_ms: Some(500), }, - distribution: Evidence { - value: Some(DataDistribution::Bursty), - source: EvidenceSource::Observed, - observed_at_ms: Some(1_000), - valid_for_ms: Some(500), - }, ..Default::default() }; let provider = WorkloadAccuracyEvidence { @@ -192,7 +181,6 @@ mod tests { None, ); assert_eq!(fresh.input_row_count, Some(42)); - assert_eq!(fresh.data_distribution, Some(DataDistribution::Bursty)); let stale = WorkloadAccuracyEvidence { data: &data, @@ -207,6 +195,5 @@ mod tests { None, ); assert_eq!(stale.input_row_count, None); - assert_eq!(stale.data_distribution, None); } } diff --git a/crates/asap-aware-mapping/src/accuracy/mod.rs b/crates/asap-aware-mapping/src/accuracy/mod.rs index 072922e1f..c54ebf1df 100644 --- a/crates/asap-aware-mapping/src/accuracy/mod.rs +++ b/crates/asap-aware-mapping/src/accuracy/mod.rs @@ -12,7 +12,7 @@ pub mod estimators; pub mod evidence; pub mod reconciliation; -pub use budget::{equal_share, equal_shares}; +pub use budget::per_input_target; pub use evidence::{ AccuracyEvidenceProvider, NoAccuracyEvidence, OperandDomain, PropagationStats, WorkloadAccuracyEvidence, @@ -20,8 +20,9 @@ pub use evidence::{ use asap_types::ir::OperatorNode; use asap_types::post_asap::{ - AccuracyError, AccuracyGuarantee, BoundExpr, CompositionOperator, ErrorMetric, FieldDataType, - GuaranteeSource, ProbabilityExpr, SketchAlgorithm, SketchParams, SketchStatistic, + AccuracyError, AccuracyGuarantee, AccuracyMetric, BoundExpr, CompositionOperator, + FieldDataType, GuaranteeSource, ProbabilityExpr, SketchAlgorithm, SketchParams, + SketchStatistic, }; use asap_types::types::{AccuracyScope, AccuracyTarget}; @@ -122,8 +123,8 @@ impl AccuracyModel for DefaultAccuracyModel { return guarantee.is_exact(); } target - .metric - .is_none_or(|metric| metric_meets(guarantee.spec.metric, metric)) + .accuracy_metric + .is_none_or(|metric| metric_meets(guarantee.spec.accuracy_metric, metric)) && scope_meets(guarantee, target) && within(guarantee.spec.error.evaluate(), target.error) && target @@ -141,22 +142,22 @@ impl AccuracyModel for DefaultAccuracyModel { /// error has no distribution-free value error), which the default model /// does not make: it does not satisfy. The scope is compared separately /// ([`scope_meets`]). -fn metric_meets(guarantee: ErrorMetric, target: ErrorMetric) -> bool { +fn metric_meets(guarantee: AccuracyMetric, target: AccuracyMetric) -> bool { guarantee == target - || (guarantee == ErrorMetric::ABSOLUTE_STREAM_L2 - && target == ErrorMetric::ABSOLUTE_STREAM_L1) + || (guarantee == AccuracyMetric::ABSOLUTE_STREAM_L2 + && target == AccuracyMetric::ABSOLUTE_STREAM_L1) } /// Whether `guarantee`'s failure probability covers the values `target`'s /// does. A whole-result guarantee covers a per-value target (each value is /// in the result), but a per-value one does not cover a whole-result /// target: it must first be stated over the result's values -/// ([`whole_result_guarantee`]). The scope bounds only the failure +/// ([`AccuracyGuarantee::over_whole_result`]). The scope bounds only the failure /// probability, so it does not matter for a target without a δ requirement /// or for a guarantee that never fails. fn scope_meets(guarantee: &AccuracyGuarantee, target: &AccuracyTarget) -> bool { - target.scope == AccuracyScope::PerValue - || guarantee.spec.scope == AccuracyScope::WholeResult + target.accuracy_scope == AccuracyScope::PerValue + || guarantee.spec.accuracy_scope == AccuracyScope::WholeResult || target.failure_probability.is_none() || guarantee.spec.failure_probability.is_zero() } @@ -169,43 +170,8 @@ fn scope_meets(guarantee: &AccuracyGuarantee, target: &AccuracyTarget) -> bool { pub fn native_target(target: &AccuracyTarget, guarantee: &AccuracyGuarantee) -> AccuracyTarget { target .clone() - .with_metric(guarantee.spec.metric) - .with_scope(AccuracyScope::PerValue) -} - -/// The statistic an unbound number of output values per evaluation is left -/// as ([`whole_result_guarantee`]); nothing binds it, so the failure -/// probability stays unknown. -pub const OUTPUT_VALUES: &str = "output_values_per_evaluation"; - -/// `guarantee` stated over the whole result of one evaluation, of -/// `output_values` values (`G`): a per-value failure probability `δ` becomes -/// `G · δ` by the union bound over the values, whatever their dependence. -/// The error is unchanged. A guarantee that never fails (`δ = 0`) holds for -/// every value at once whatever `G`; an unbound `G` (`None`) leaves the -/// failure probability symbolic in [`OUTPUT_VALUES`]. A whole-result -/// guarantee is returned as it is. -pub fn whole_result_guarantee( - guarantee: &AccuracyGuarantee, - output_values: Option, -) -> AccuracyGuarantee { - let mut whole = guarantee.clone(); - if guarantee.spec.scope == AccuracyScope::WholeResult { - return whole; - } - whole.spec.scope = AccuracyScope::WholeResult; - if !guarantee.spec.failure_probability.is_zero() { - whole.spec.failure_probability = ProbabilityExpr::Scaled { - count: output_values.map_or_else( - || BoundExpr::Unknown { - statistic: OUTPUT_VALUES.into(), - }, - |value| BoundExpr::Constant { value }, - ), - inner: Box::new(guarantee.spec.failure_probability.clone()), - }; - } - whole + .accuracy_metric(guarantee.spec.accuracy_metric) + .accuracy_scope(AccuracyScope::PerValue) } pub(crate) use estimators::topk_capacity; @@ -215,7 +181,7 @@ mod tests { use super::*; fn abs(bound: f64, delta: f64) -> AccuracyGuarantee { AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, BoundExpr::Constant { value: bound }, ProbabilityExpr::Constant { value: delta }, vec![], @@ -227,13 +193,13 @@ mod tests { unknown.spec.error = BoundExpr::Unknown { statistic: "x".into(), }; - assert!(!DefaultAccuracyModel.satisfies(&unknown, &AccuracyTarget::epsilon(1.0))); + assert!(!DefaultAccuracyModel.satisfies(&unknown, &AccuracyTarget::max_error(1.0))); assert!(!DefaultAccuracyModel.satisfies(&abs(0.0, 0.01), &AccuracyTarget::exact())); assert!(DefaultAccuracyModel .satisfies(&AccuracyGuarantee::exact("x"), &AccuracyTarget::exact())); } - fn guarantee(metric: ErrorMetric, bound: f64, delta: f64) -> AccuracyGuarantee { + fn guarantee(metric: AccuracyMetric, bound: f64, delta: f64) -> AccuracyGuarantee { AccuracyGuarantee::new( metric, BoundExpr::Constant { value: bound }, @@ -252,27 +218,28 @@ mod tests { fn satisfies_requires_the_targets_metric() { let model = DefaultAccuracyModel; let target = |metric| { - AccuracyTarget::epsilon_delta(0.5, 0.5) - .with_metric(metric) - .with_scope(AccuracyScope::PerValue) + AccuracyTarget::max_error(0.5) + .failure_probability(0.5) + .accuracy_metric(metric) + .accuracy_scope(AccuracyScope::PerValue) }; let (l1, l2) = ( - ErrorMetric::ABSOLUTE_STREAM_L1, - ErrorMetric::ABSOLUTE_STREAM_L2, + AccuracyMetric::ABSOLUTE_STREAM_L1, + AccuracyMetric::ABSOLUTE_STREAM_L2, ); for (bound, wanted, meets) in [ (l1, l1, true), (l2, l2, true), (l2, l1, true), (l1, l2, false), - (l1, ErrorMetric::Relative, false), - (l1, ErrorMetric::ABSOLUTE, false), - (ErrorMetric::ABSOLUTE, l1, false), - (ErrorMetric::Relative, ErrorMetric::ABSOLUTE, false), - (ErrorMetric::ABSOLUTE, ErrorMetric::Relative, false), - (ErrorMetric::Rank, ErrorMetric::Relative, false), - (ErrorMetric::Relative, ErrorMetric::Rank, false), - (ErrorMetric::Rank, ErrorMetric::Rank, true), + (l1, AccuracyMetric::Relative, false), + (l1, AccuracyMetric::ABSOLUTE, false), + (AccuracyMetric::ABSOLUTE, l1, false), + (AccuracyMetric::Relative, AccuracyMetric::ABSOLUTE, false), + (AccuracyMetric::ABSOLUTE, AccuracyMetric::Relative, false), + (AccuracyMetric::Rank, AccuracyMetric::Relative, false), + (AccuracyMetric::Relative, AccuracyMetric::Rank, false), + (AccuracyMetric::Rank, AccuracyMetric::Rank, true), ] { assert_eq!( model.satisfies(&guarantee(bound, 0.01, 0.01), &target(wanted)), @@ -280,46 +247,55 @@ mod tests { "{bound:?} against {wanted:?}" ); } - assert!(model.satisfies(&AccuracyGuarantee::exact("x"), &target(ErrorMetric::Rank))); + assert!(model.satisfies( + &AccuracyGuarantee::exact("x"), + &target(AccuracyMetric::Rank) + )); } /// A target without a δ requirement checks only the error, even when /// the guarantee's failure probability is unknown. #[test] fn a_target_without_delta_ignores_the_failure_probability() { - let mut g = guarantee(ErrorMetric::ABSOLUTE, 0.01, 0.0); + let mut g = guarantee(AccuracyMetric::ABSOLUTE, 0.01, 0.0); g.spec.failure_probability = ProbabilityExpr::Unknown { statistic: "x".into(), }; - assert!(DefaultAccuracyModel.satisfies(&g, &AccuracyTarget::epsilon(0.01))); - assert!(!DefaultAccuracyModel.satisfies(&g, &AccuracyTarget::epsilon_delta(0.01, 0.5))); + assert!(DefaultAccuracyModel.satisfies(&g, &AccuracyTarget::max_error(0.01))); + assert!(!DefaultAccuracyModel.satisfies( + &g, + &AccuracyTarget::max_error(0.01).failure_probability(0.5) + )); } /// A per-value guarantee does not meet a whole-result δ target /// directly: over `G` output values it holds for all of them with - /// δ_whole = G · δ_per ([`whole_result_guarantee`]), and an unbound `G` + /// δ_whole = G · δ_per ([`AccuracyGuarantee::over_whole_result`]), and an unbound `G` /// leaves it unmet. A whole-result guarantee meets a per-value target /// directly. #[test] fn per_value_guarantees_meet_whole_result_targets_over_g_values() { let model = DefaultAccuracyModel; - let g = guarantee(ErrorMetric::ABSOLUTE, 0.01, 0.001); - assert_eq!(g.spec.scope, AccuracyScope::PerValue); - let whole = AccuracyTarget::epsilon_delta(0.01, 0.01); - assert_eq!(whole.scope, AccuracyScope::WholeResult); + let g = guarantee(AccuracyMetric::ABSOLUTE, 0.01, 0.001); + assert_eq!(g.spec.accuracy_scope, AccuracyScope::PerValue); + let whole = AccuracyTarget::max_error(0.01).failure_probability(0.01); + assert_eq!(whole.accuracy_scope, AccuracyScope::WholeResult); assert!(!model.satisfies(&g, &whole)); - let per_value = whole.clone().with_scope(AccuracyScope::PerValue); + let per_value = whole.clone().accuracy_scope(AccuracyScope::PerValue); assert!(model.satisfies(&g, &per_value)); - let over = |values| whole_result_guarantee(&g, values); - assert_eq!(over(Some(10.0)).spec.scope, AccuracyScope::WholeResult); + let over = |values| g.over_whole_result(values); + assert_eq!( + over(Some(10)).spec.accuracy_scope, + AccuracyScope::WholeResult + ); assert_eq!( - over(Some(10.0)).spec.failure_probability.evaluate(), + over(Some(10)).spec.failure_probability.evaluate(), Some(0.01) ); - assert!(model.satisfies(&over(Some(10.0)), &whole)); - assert!(!model.satisfies(&over(Some(11.0)), &whole)); + assert!(model.satisfies(&over(Some(10)), &whole)); + assert!(!model.satisfies(&over(Some(11)), &whole)); assert!(!model.satisfies(&over(None), &whole)); - assert!(model.satisfies(&over(Some(10.0)), &per_value)); + assert!(model.satisfies(&over(Some(10)), &per_value)); } /// The scope bounds only the failure probability: a target without a δ @@ -328,17 +304,19 @@ mod tests { #[test] fn scope_matters_only_with_a_failure_probability() { let model = DefaultAccuracyModel; - let g = guarantee(ErrorMetric::ABSOLUTE, 0.01, 0.001); - assert!(model.satisfies(&g, &AccuracyTarget::epsilon(0.01))); + let g = guarantee(AccuracyMetric::ABSOLUTE, 0.01, 0.001); + assert!(model.satisfies(&g, &AccuracyTarget::max_error(0.01))); let never_fails = AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Constant { value: 0.01 }, ProbabilityExpr::Zero, vec![], ); - let whole = AccuracyTarget::epsilon_delta(0.01, 0.01).with_metric(ErrorMetric::Relative); + let whole = AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::Relative); assert!(model.satisfies(&never_fails, &whole)); - assert!(model.satisfies(&whole_result_guarantee(&never_fails, None), &whole)); + assert!(model.satisfies(&never_fails.over_whole_result(None), &whole)); } /// A target whose metric the user did not set accepts a guarantee in @@ -348,14 +326,16 @@ mod tests { #[test] fn a_target_without_a_metric_accepts_any_metric() { let model = DefaultAccuracyModel; - let target = AccuracyTarget::epsilon_delta(0.01, 0.01).with_scope(AccuracyScope::PerValue); - assert_eq!(target.metric, None); + let target = AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_scope(AccuracyScope::PerValue); + assert_eq!(target.accuracy_metric, None); for metric in [ - ErrorMetric::ABSOLUTE, - ErrorMetric::ABSOLUTE_STREAM_L1, - ErrorMetric::ABSOLUTE_STREAM_L2, - ErrorMetric::Relative, - ErrorMetric::Rank, + AccuracyMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE_STREAM_L1, + AccuracyMetric::ABSOLUTE_STREAM_L2, + AccuracyMetric::Relative, + AccuracyMetric::Rank, ] { assert!( model.satisfies(&guarantee(metric, 0.01, 0.01), &target), diff --git a/crates/asap-aware-mapping/src/accuracy/reconciliation.rs b/crates/asap-aware-mapping/src/accuracy/reconciliation.rs index 8efb2992d..9f95ef202 100644 --- a/crates/asap-aware-mapping/src/accuracy/reconciliation.rs +++ b/crates/asap-aware-mapping/src/accuracy/reconciliation.rs @@ -169,7 +169,7 @@ use crate::replacement::{ type BindableAccuracyAggregate<'a> = ( &'a Reduction, &'a AggIntent, - &'a AccuracyTarget, + AccuracyTarget, &'a [String], &'a Rc, ); @@ -336,7 +336,7 @@ impl AccuracyReconciliationStrategy { && names == target_names && (Rc::ptr_eq(child, target_child) || child == target_child) && same_intent_except_accuracy(intent, target_intent) - && strictly_tighter(accuracy, target_accuracy) + && strictly_tighter(&accuracy, &target_accuracy) && candidate.schema.has_unique_key() }) .collect(); @@ -345,8 +345,8 @@ impl AccuracyReconciliationStrategy { bindable_accuracy_aggregate(a).expect("filter above already confirmed this shape"); let (.., b_accuracy, _, _) = bindable_accuracy_aggregate(b).expect("filter above already confirmed this shape"); - accuracy_budget(a_accuracy) - .partial_cmp(&accuracy_budget(b_accuracy)) + accuracy_budget(&a_accuracy) + .partial_cmp(&accuracy_budget(&b_accuracy)) .unwrap_or(Ordering::Equal) }); sources @@ -491,20 +491,20 @@ mod tests { #[test] fn a_smaller_epsilon_dominates_a_larger_one() { assert!(dominates( - &AccuracyTarget::epsilon(0.01), - &AccuracyTarget::epsilon(0.05) + &AccuracyTarget::max_error(0.01), + &AccuracyTarget::max_error(0.05) )); assert!(!dominates( - &AccuracyTarget::epsilon(0.05), - &AccuracyTarget::epsilon(0.01) + &AccuracyTarget::max_error(0.05), + &AccuracyTarget::max_error(0.01) )); } #[test] fn epsilon_delta_needs_both_dimensions_at_least_as_tight() { // Smaller epsilon but larger delta: neither Pareto-dominates. - let a = AccuracyTarget::epsilon_delta(0.01, 0.1); - let b = AccuracyTarget::epsilon_delta(0.05, 0.01); + let a = AccuracyTarget::max_error(0.01).failure_probability(0.1); + let b = AccuracyTarget::max_error(0.05).failure_probability(0.01); assert!(!dominates(&a, &b)); assert!(!dominates(&b, &a)); } @@ -513,10 +513,10 @@ mod tests { fn exact_never_dominates_or_is_dominated() { assert!(!dominates( &AccuracyTarget::exact(), - &AccuracyTarget::epsilon(0.5) + &AccuracyTarget::max_error(0.5) )); assert!(!dominates( - &AccuracyTarget::epsilon(0.5), + &AccuracyTarget::max_error(0.5), &AccuracyTarget::exact() )); assert!(!dominates( @@ -528,9 +528,9 @@ mod tests { #[test] fn equal_resolved_budgets_are_not_strictly_tighter_either_way() { // Same numeric (eps, delta) budget, different AccuracyTarget spelling. - let epsilon_only = AccuracyTarget::epsilon(0.01); + let epsilon_only = AccuracyTarget::max_error(0.01); let equivalent_epsilon_delta = - AccuracyTarget::epsilon_delta(0.01, crate::realization::DEFAULT_DELTA); + AccuracyTarget::max_error(0.01).failure_probability(crate::realization::DEFAULT_DELTA); assert!(dominates(&epsilon_only, &equivalent_epsilon_delta)); assert!(dominates(&equivalent_epsilon_delta, &epsilon_only)); assert!(!strictly_tighter(&epsilon_only, &equivalent_epsilon_delta)); @@ -542,8 +542,8 @@ mod tests { #[test] fn looser_quantile_proposes_reading_the_tighter_sibling() { let scan = metric_scan(); - let tight = quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); - let loose = quantile(0.99, AccuracyTarget::epsilon(0.05), &scan); + let tight = quantile(0.99, AccuracyTarget::max_error(0.01), &scan); + let loose = quantile(0.99, AccuracyTarget::max_error(0.05), &scan); let strategy = AccuracyReconciliationStrategy::new(&[Rc::clone(&tight), Rc::clone(&loose)]); @@ -570,8 +570,8 @@ mod tests { // isolation — proves this is actually wired into the round loop // alongside RollupStrategy/TopKLimitReuseStrategy. let scan = metric_scan(); - let tight = quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); - let loose = quantile(0.99, AccuracyTarget::epsilon(0.05), &scan); + let tight = quantile(0.99, AccuracyTarget::max_error(0.01), &scan); + let loose = quantile(0.99, AccuracyTarget::max_error(0.05), &scan); let space = crate::replacement::search_workload(vec![("tight", tight), ("loose", loose)]).unwrap(); @@ -603,7 +603,7 @@ mod tests { AggIntent::Quantile { col: Some(1), q: 0.99, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, &scan, ); @@ -612,7 +612,7 @@ mod tests { AggIntent::Quantile { col: Some(0), q: 0.99, - accuracy: AccuracyTarget::epsilon(0.05), + accuracy: AccuracyTarget::max_error(0.05), }, &scan, ); @@ -624,11 +624,11 @@ mod tests { fn topk_intents_differing_only_in_accuracy_are_near_duplicates() { let tight = AggIntent::TopK { k: 10, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }; let loose = AggIntent::TopK { k: 10, - accuracy: AccuracyTarget::epsilon(0.02), + accuracy: AccuracyTarget::max_error(0.02), }; assert!(same_intent_except_accuracy(&tight, &loose)); } @@ -636,13 +636,13 @@ mod tests { #[test] fn different_grouping_is_not_a_near_duplicate() { let scan = metric_scan(); - let tight = quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); + let tight = quantile(0.99, AccuracyTarget::max_error(0.01), &scan); let loose = agg( vec![], AggIntent::Quantile { col: None, q: 0.99, - accuracy: AccuracyTarget::epsilon(0.05), + accuracy: AccuracyTarget::max_error(0.05), }, &scan, ); @@ -654,7 +654,7 @@ mod tests { fn exact_accuracy_is_never_reconciled() { let scan = metric_scan(); let exact = quantile(0.99, AccuracyTarget::exact(), &scan); - let approx = quantile(0.99, AccuracyTarget::epsilon(0.05), &scan); + let approx = quantile(0.99, AccuracyTarget::max_error(0.05), &scan); let strategy = AccuracyReconciliationStrategy::new(&[Rc::clone(&exact), Rc::clone(&approx)]); assert!(!strategy.matches(&TargetSubDAG::new(&approx))); @@ -672,8 +672,8 @@ mod tests { // is the *only* place cross-accuracy sharing gets proposed, never // `share_common_sub_dags` itself. let scan = metric_scan(); - let a = quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); - let b = quantile(0.99, AccuracyTarget::epsilon(0.05), &scan); + let a = quantile(0.99, AccuracyTarget::max_error(0.01), &scan); + let b = quantile(0.99, AccuracyTarget::max_error(0.05), &scan); let roots = share_common_sub_dags(vec![("a", a), ("b", b)]); assert!( @@ -703,8 +703,8 @@ mod tests { // exact equality — unrelated to this module, but pins the contrast // with the test above. let scan = metric_scan(); - let a = quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); - let b = quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); + let a = quantile(0.99, AccuracyTarget::max_error(0.01), &scan); + let b = quantile(0.99, AccuracyTarget::max_error(0.01), &scan); let roots = share_common_sub_dags(vec![("a", a), ("b", b)]); assert!(Rc::ptr_eq(&roots[0].1, &roots[1].1)); @@ -720,8 +720,8 @@ mod tests { // `share_common_sub_dags`/`RollupStrategy` apply, which this // strategy must not bypass (module docs, point 5). let scan = metric_scan(); - let tight = ungrouped_quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); - let loose = ungrouped_quantile(0.99, AccuracyTarget::epsilon(0.05), &scan); + let tight = ungrouped_quantile(0.99, AccuracyTarget::max_error(0.01), &scan); + let loose = ungrouped_quantile(0.99, AccuracyTarget::max_error(0.05), &scan); assert!( !tight.schema.clone().has_unique_key(), @@ -740,8 +740,8 @@ mod tests { // `without(...)` grouping never carries a provable unique key, // regardless of the excluded set. let scan = metric_scan(); - let tight = without_quantile(0.99, AccuracyTarget::epsilon(0.01), vec![2], &scan); - let loose = without_quantile(0.99, AccuracyTarget::epsilon(0.05), vec![2], &scan); + let tight = without_quantile(0.99, AccuracyTarget::max_error(0.01), vec![2], &scan); + let loose = without_quantile(0.99, AccuracyTarget::max_error(0.05), vec![2], &scan); assert!( !tight.schema.clone().has_unique_key(), @@ -767,8 +767,8 @@ mod tests { // `rc`'s own build," which must not scale with `target`'s own // `consumer_count`. let scan = metric_scan(); - let tight = quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); - let loose = quantile(0.99, AccuracyTarget::epsilon(0.05), &scan); + let tight = quantile(0.99, AccuracyTarget::max_error(0.01), &scan); + let loose = quantile(0.99, AccuracyTarget::max_error(0.05), &scan); let strategy = AccuracyReconciliationStrategy::new(&[Rc::clone(&tight), Rc::clone(&loose)]); let candidate = strategy .replacements(&TargetSubDAG::new(&loose)) @@ -801,8 +801,8 @@ mod tests { #[test] fn cost_sorted_and_global_selection_handle_a_single_consumer_looser_target() { let scan = metric_scan(); - let tight = quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); - let loose = quantile(0.99, AccuracyTarget::epsilon(0.05), &scan); + let tight = quantile(0.99, AccuracyTarget::max_error(0.01), &scan); + let loose = quantile(0.99, AccuracyTarget::max_error(0.05), &scan); let space = crate::replacement::search_workload(vec![("tight", tight), ("loose", loose)]).unwrap(); let loose_root = &space.roots[1].1; @@ -857,9 +857,9 @@ mod tests { // own 2 consumers at the same time as this strategy's "read the // tight sibling instead" candidate. let scan = metric_scan(); - let loose_a = (*quantile(0.99, AccuracyTarget::epsilon(0.05), &scan)).clone(); - let loose_b = (*quantile(0.99, AccuracyTarget::epsilon(0.05), &scan)).clone(); - let tight = (*quantile(0.99, AccuracyTarget::epsilon(0.01), &scan)).clone(); + let loose_a = (*quantile(0.99, AccuracyTarget::max_error(0.05), &scan)).clone(); + let loose_b = (*quantile(0.99, AccuracyTarget::max_error(0.05), &scan)).clone(); + let tight = (*quantile(0.99, AccuracyTarget::max_error(0.01), &scan)).clone(); let space = crate::replacement::search_workload(vec![ ("loose_a", Rc::new(loose_a)), @@ -952,8 +952,8 @@ mod tests { } let scan = metric_scan(); - let tight = quantile(0.99, AccuracyTarget::epsilon(0.01), &scan); - let loose = quantile(0.99, AccuracyTarget::epsilon(0.05), &scan); + let tight = quantile(0.99, AccuracyTarget::max_error(0.01), &scan); + let loose = quantile(0.99, AccuracyTarget::max_error(0.05), &scan); let space = crate::replacement::search_workload(vec![ ("tight", Rc::clone(&tight)), ("loose", Rc::clone(&loose)), diff --git a/crates/asap-aware-mapping/src/analytical_cost.rs b/crates/asap-aware-mapping/src/analytical_cost.rs index 999258183..dadb312ba 100644 --- a/crates/asap-aware-mapping/src/analytical_cost.rs +++ b/crates/asap-aware-mapping/src/analytical_cost.rs @@ -3047,8 +3047,7 @@ mod tests { fn comparison_scope() -> ComparisonScope { use asap_types::ir::operator_properties::Source; use asap_types::workload::{ - DurationMs, QueryRecurrence, QueryTimeScope, RepeatedDemand, RepetitionInterval, - TimeSelection, TimestampMs, + DurationMs, QueryRecurrence, RepeatedDemand, RepetitionInterval, TimestampMs, }; ComparisonScope { @@ -3058,11 +3057,6 @@ mod tests { recurrence: QueryRecurrence::Repeated(RepeatedDemand::FixedInterval( RepetitionInterval(10_000), )), - time_selection: TimeSelection { - scope: QueryTimeScope::Longitudinal, - lookback: Some(DurationMs(300_000)), - as_of: Some(TimestampMs(1_000)), - }, sources: vec![ScanSelection { source: Source::Table { table_ref: "metrics".into(), @@ -3085,7 +3079,7 @@ mod tests { #[test] fn comparison_rejects_different_snapshot_predicate_time_or_horizon() { use asap_types::ir::{Predicate, ScalarExpr}; - use asap_types::workload::{DurationMs, TimestampMs}; + use asap_types::workload::DurationMs; let raw = comparison_scope(); assert_eq!(validate_comparison_scopes(&raw, &raw).unwrap(), 6); @@ -3106,15 +3100,6 @@ mod tests { Err(AnalyticalCostError::ComparisonScopeMismatch("sources")) ); - candidate = raw.clone(); - candidate.time_selection.as_of = Some(TimestampMs(2_000)); - assert_eq!( - validate_comparison_scopes(&raw, &candidate), - Err(AnalyticalCostError::ComparisonScopeMismatch( - "time_selection" - )) - ); - candidate = raw.clone(); candidate.horizon = DurationMs(120_000); assert_eq!( diff --git a/crates/asap-aware-mapping/src/asap_aware_cse.rs b/crates/asap-aware-mapping/src/asap_aware_cse.rs index 8960141e2..38813194f 100644 --- a/crates/asap-aware-mapping/src/asap_aware_cse.rs +++ b/crates/asap-aware-mapping/src/asap_aware_cse.rs @@ -1688,8 +1688,9 @@ mod tests { .map(|(i, (q, epsilon))| { let root = lower_promql( q, - asap_types::types::AccuracyTarget::epsilon_delta(*epsilon, 0.01) - .with_metric(asap_types::types::ErrorMetric::Rank), + asap_types::types::AccuracyTarget::max_error(*epsilon) + .failure_probability(0.01) + .accuracy_metric(asap_types::types::AccuracyMetric::Rank), ); ( i, @@ -2298,7 +2299,7 @@ mod tests { .map(|(i, q)| { let root = lower_promql( q, - asap_types::types::AccuracyTarget::epsilon_delta(0.01, 0.01), + asap_types::types::AccuracyTarget::max_error(0.01).failure_probability(0.01), ); (i, QueryRoot::Operator(root)) }) diff --git a/crates/asap-aware-mapping/src/empirical_cost.rs b/crates/asap-aware-mapping/src/empirical_cost.rs index 176a49c28..e6273a0b4 100644 --- a/crates/asap-aware-mapping/src/empirical_cost.rs +++ b/crates/asap-aware-mapping/src/empirical_cost.rs @@ -229,7 +229,7 @@ impl CostModel for EmpiricalCostModel { let Some(accuracy) = accuracy_target(intent) else { return candidates.to_vec(); }; - let (eps, delta) = accuracy_budget(accuracy); + let (eps, delta) = accuracy_budget(&accuracy); self.provider .rank_candidates(intent, candidates, eps, delta) } @@ -479,7 +479,7 @@ mod tests { implementation_version: "test-v1".into(), }; let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }; let records = [ (SketchAlgorithm::Cms, 20.0), diff --git a/crates/asap-aware-mapping/src/grouping.rs b/crates/asap-aware-mapping/src/grouping.rs index a4777a764..d84d597e0 100644 --- a/crates/asap-aware-mapping/src/grouping.rs +++ b/crates/asap-aware-mapping/src/grouping.rs @@ -263,7 +263,7 @@ impl<'a> HydraGroupingStrategy<'a> { input_metrics: node .guarantee .as_ref() - .map_or_else(Vec::new, |g| vec![g.spec.metric]), + .map_or_else(Vec::new, |g| vec![g.spec.accuracy_metric]), local_metric: None, reason: "invalid Hydra shared-grid collision or failure-probability evidence" .into(), @@ -275,13 +275,13 @@ impl<'a> HydraGroupingStrategy<'a> { if let (Some(target), Some(guarantee)) = (accuracy_target(intent), &patched.guarantee) { if !self.planning_inputs.accuracy.satisfies( &guarantee.optimistic_floor(), - &crate::accuracy::native_target(target, guarantee), + &crate::accuracy::native_target(&target, guarantee), ) { rejected.push(RejectedCandidate { strategy: "HydraGroupingStrategy", description: format!("{hydra_kind:?} over {sketch_kind:?}"), error: AccuracyError::TargetNotSatisfied { - metric: guarantee.spec.metric, + metric: guarantee.spec.accuracy_metric, bound: guarantee.spec.error.evaluate(), failure_probability: guarantee.spec.failure_probability.evaluate(), target: target.clone(), @@ -462,7 +462,7 @@ fn hydra_guarantee(inner: &AccuracyGuarantee, stats: &PropagationStats) -> Accur rule: "hydra_shared_grid_union_bound".into(), }); AccuracyGuarantee::new( - inner.spec.metric, + inner.spec.accuracy_metric, BoundExpr::Sum { terms: vec![ inner.spec.error.clone(), @@ -494,7 +494,7 @@ mod tests { use super::*; use crate::accuracy::DefaultAccuracyModel; use crate::test_support::{agg, agg_per_entity, metric_scan}; - use asap_types::post_asap::ErrorMetric; + use asap_types::post_asap::AccuracyMetric; use asap_types::pre_asap::agg_intent::{default_cardinality, default_quantile}; use asap_types::types::AccuracyTarget; @@ -528,14 +528,17 @@ mod tests { #[test] fn hydra_composes_inner_and_shared_grid_error_symbolically() { let inner = AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE_STREAM_L1, + AccuracyMetric::ABSOLUTE_STREAM_L1, BoundExpr::Constant { value: 0.01 }, ProbabilityExpr::Constant { value: 0.02 }, vec![], ); let composed = hydra_guarantee(&inner, &PropagationStats::default()); - assert_eq!(composed.spec.metric, ErrorMetric::ABSOLUTE_STREAM_L1); + assert_eq!( + composed.spec.accuracy_metric, + AccuracyMetric::ABSOLUTE_STREAM_L1 + ); assert!(matches!( composed.spec.error, BoundExpr::Sum { ref terms } @@ -559,7 +562,7 @@ mod tests { #[test] fn matches_a_grouped_count_with_an_unprovable_accuracy_target() { let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }; let q = agg(vec![2], intent, metric_scan(&["job"])); let target = TargetSubDAG::new(&q); @@ -603,7 +606,7 @@ mod tests { #[test] fn count_with_an_accuracy_target_keeps_uncertified_hydra_candidates() { let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }; let q = agg(vec![2], intent, metric_scan(&["job"])); let target = TargetSubDAG::new(&q); @@ -638,7 +641,7 @@ mod tests { #[test] fn hydra_shared_grid_evidence_is_consumed_by_candidate_construction() { let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }; let q = agg(vec![2], intent, metric_scan(&["job"])); let strategy = HydraGroupingStrategy::new_with_planning_inputs_and_evidence( @@ -674,7 +677,7 @@ mod tests { } } let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }; let q = agg(vec![2], intent, metric_scan(&["job"])); let strategy = HydraGroupingStrategy::new_with_planning_inputs_and_evidence( @@ -719,7 +722,7 @@ mod tests { let q = agg( vec![2], AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }, metric_scan(&["job"]), ); diff --git a/crates/asap-aware-mapping/src/pass/stage1_to_stage3.rs b/crates/asap-aware-mapping/src/pass/stage1_to_stage3.rs index fe5ff4b90..1a0e20c48 100644 --- a/crates/asap-aware-mapping/src/pass/stage1_to_stage3.rs +++ b/crates/asap-aware-mapping/src/pass/stage1_to_stage3.rs @@ -693,7 +693,9 @@ pub fn run_stage2_and_stage3( .as_ref() .ok()?; // As Stage 3 sized it for its queries' failure probabilities. - let sized = crate::stage3_plan_selection::sized_roots(roots, targets, data).ok()?; + let sized = + crate::stage3_plan_selection::resize_for_failure_probability(roots, targets, data) + .ok()?; Some(crate::stage3_plan_selection::with_confirmed_weights( &sized, data, )) diff --git a/crates/asap-aware-mapping/src/per_computation_candidates.rs b/crates/asap-aware-mapping/src/per_computation_candidates.rs index dd273ba54..2ca429a72 100644 --- a/crates/asap-aware-mapping/src/per_computation_candidates.rs +++ b/crates/asap-aware-mapping/src/per_computation_candidates.rs @@ -88,7 +88,7 @@ use asap_types::ir::{ Predicate, ProjectItem, QueryRoot, ScalarExpr, SchemaDerivationError, }; use asap_types::post_asap::guarantee::{ - AccuracyGuarantee, BoundExpr, CompositionOperator, ErrorMetric, GuaranteeSource, + AccuracyGuarantee, AccuracyMetric, BoundExpr, CompositionOperator, GuaranteeSource, ProbabilityExpr, }; use asap_types::post_asap::{ @@ -109,7 +109,7 @@ use crate::accuracy::composition::{ PRODUCT_OPERAND_DOMAINS, QUOTIENT_OPERAND_DOMAINS, }; use crate::accuracy::estimators::{hll, univmon}; -use crate::accuracy::{equal_share, AccuracyModel, DefaultAccuracyModel, PropagationStats}; +use crate::accuracy::{per_input_target, AccuracyModel, DefaultAccuracyModel, PropagationStats}; use crate::function_rules::function_rules; use crate::realization::{ accuracy_budget, accuracy_target, default_size_params, summary_candidates, @@ -362,7 +362,7 @@ pub fn computation_candidates_for_intent( }; if let Some(target) = accuracy_target(intent) { if !target.is_exact() { - let (epsilon, delta) = accuracy_budget(target); + let (epsilon, delta) = accuracy_budget(&target); let unit = |value: f64| value > 0.0 && value < 1.0; if !unit(epsilon) || !unit(delta) { return Err(PerComputationCandidateError::InvalidAccuracy { @@ -1091,7 +1091,7 @@ pub(crate) fn with_resized_summaries( /// rows of, as [`source_distinct_guarantee`] reads it: the source whose /// distinct bound gives its failure probability. `None` for a state over /// computed rows or several sources. -pub(crate) fn summary_source(state: &OperatorNode) -> Option { +pub(crate) fn distinct_bound_source(state: &OperatorNode) -> Option { let Some(ASAPOp::SummaryAgg { child, .. } | ASAPOp::PaneChain { child, .. }) = state.asap() else { return None; @@ -2170,9 +2170,14 @@ pub(crate) fn operand_domain_statistic( r: &AccuracyGuarantee, ) -> Option<&'static str> { let valued = |g: &AccuracyGuarantee| { - g.is_exact() || matches!(g.spec.metric, ErrorMetric::ABSOLUTE | ErrorMetric::Relative) + g.is_exact() + || matches!( + g.spec.accuracy_metric, + AccuracyMetric::ABSOLUTE | AccuracyMetric::Relative + ) }; - let relative = |g: &AccuracyGuarantee| g.is_exact() || g.spec.metric == ErrorMetric::Relative; + let relative = + |g: &AccuracyGuarantee| g.is_exact() || g.spec.accuracy_metric == AccuracyMetric::Relative; if !(valued(l) && valued(r)) { return None; } @@ -2180,8 +2185,8 @@ pub(crate) fn operand_domain_statistic( ArithmeticOpKind::Mul if !l.is_exact() && !r.is_exact() - && !(l.spec.metric == ErrorMetric::Relative - && r.spec.metric == ErrorMetric::Relative) => + && !(l.spec.accuracy_metric == AccuracyMetric::Relative + && r.spec.accuracy_metric == AccuracyMetric::Relative) => { Some(PRODUCT_OPERAND_DOMAINS) } @@ -2198,7 +2203,9 @@ pub(crate) fn operand_domain_statistic( /// `(1 + a)(1 + b) − 1 = a + b + a·b`, whatever the signs. `None` for any /// other pair. fn relative_product(l: AccuracyGuarantee, r: AccuracyGuarantee) -> Option { - if l.spec.metric != ErrorMetric::Relative || r.spec.metric != ErrorMetric::Relative { + if l.spec.accuracy_metric != AccuracyMetric::Relative + || r.spec.accuracy_metric != AccuracyMetric::Relative + { return None; } let bound = BoundExpr::Sum { @@ -2211,7 +2218,7 @@ fn relative_product(l: AccuracyGuarantee, r: AccuracyGuarantee) -> Option Option) -> Option { - match (input.spec.metric, factor) { - (ErrorMetric::Relative, _) => Some(input), + match (input.spec.accuracy_metric, factor) { + (AccuracyMetric::Relative, _) => Some(input), (_, Some(constant)) => compose( &CompositionOperator::Lipschitz { constant }, vec![input], @@ -2255,17 +2262,17 @@ fn symbolic( ) -> Option { let (metric, operator, rule) = match statistic { DIVISION_OPERAND_DOMAINS => ( - ErrorMetric::Relative, + AccuracyMetric::Relative, CompositionOperator::ExactDivision, "relative_division_needs_operand_domains", ), QUOTIENT_OPERAND_DOMAINS => ( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, CompositionOperator::ExactDivision, "absolute_division_needs_operand_domains", ), _ => ( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, CompositionOperator::ExactProduct, "absolute_product_needs_operand_domains", ), @@ -2286,7 +2293,7 @@ fn symbolic( /// A guarantee over two operands with the union bound on their failure /// probabilities, and its provenance. fn binary_guarantee( - metric: ErrorMetric, + metric: AccuracyMetric, bound: BoundExpr, l: AccuracyGuarantee, r: AccuracyGuarantee, @@ -2907,12 +2914,10 @@ impl Sizing { } /// `algorithm` sized for one of `share` equal shares of `intent`'s accuracy -/// requirement ([`equal_share`]), split in the metric of the summary's own +/// requirement ([`per_input_target`]), split in the metric of the summary's own /// bound: a relative error splits multiplicatively. fn sized(algorithm: &SketchAlgorithm, intent: &AggIntent, share: usize) -> SketchKind { - let target = accuracy_target(intent) - .cloned() - .unwrap_or_else(AccuracyTarget::exact); + let target = accuracy_target(intent).unwrap_or_else(AccuracyTarget::exact); let size = |target: &AccuracyTarget| { let (epsilon, delta) = accuracy_budget(target); SketchKind::new( @@ -2932,8 +2937,8 @@ fn sized(algorithm: &SketchAlgorithm, intent: &AggIntent, share: usize) -> Sketc &query, ) }) - .map(|guarantee| guarantee.spec.metric) - .or(target.metric); + .map(|guarantee| guarantee.spec.accuracy_metric) + .or(target.accuracy_metric); // The split needs a concrete metric. A summary without an error model // whose intent's target sets none is not split (never a guessed // metric): it stays sized for the whole target, and Stage 3 rejects the @@ -2945,7 +2950,7 @@ fn sized(algorithm: &SketchAlgorithm, intent: &AggIntent, share: usize) -> Sketc ); return whole; }; - equal_share(&target, metric, share).map_or(whole, |share| size(&share)) + per_input_target(&target, metric, share).map_or(whole, |share| size(&share)) } /// What a measure's state reads from each row of `input`, resolvable there. @@ -3148,7 +3153,7 @@ mod tests { #[test] fn approximate_count_keeps_one_exact_accumulator_and_universal_choices() { let choices = computation_candidates_for_intent(&AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.05, 0.01), + accuracy: AccuracyTarget::max_error(0.05).failure_probability(0.01), }) .unwrap() .candidates; @@ -3169,7 +3174,7 @@ mod tests { use crate::test_support::lower_promql; use asap_types::types::AccuracyTarget; - fn with_bound(metric: ErrorMetric, bound: f64, delta: f64) -> AccuracyGuarantee { + fn with_bound(metric: AccuracyMetric, bound: f64, delta: f64) -> AccuracyGuarantee { AccuracyGuarantee::new( metric, BoundExpr::Constant { value: bound }, @@ -3178,10 +3183,10 @@ mod tests { ) } fn abs(bound: f64, delta: f64) -> AccuracyGuarantee { - with_bound(ErrorMetric::ABSOLUTE, bound, delta) + with_bound(AccuracyMetric::ABSOLUTE, bound, delta) } fn rel(bound: f64) -> AccuracyGuarantee { - with_bound(ErrorMetric::Relative, bound, 0.0) + with_bound(AccuracyMetric::Relative, bound, 0.0) } /// `sum(m)` carrying `guarantee`. fn node(guarantee: AccuracyGuarantee) -> Rc { @@ -3238,7 +3243,7 @@ mod tests { let (x, y) = (scalar(abs(0.01, 0.001)), scalar(abs(0.02, 0.002))); for op in [Add, Sub] { let g = of(&arith(op.clone(), x.clone(), y.clone())).unwrap(); - assert_eq!(g.spec.metric, ErrorMetric::ABSOLUTE, "{op:?}"); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::ABSOLUTE, "{op:?}"); assert!(close(bound(&g), 0.03) && close(delta(&g), 0.003), "{op:?}"); for shifted in [ arith(op.clone(), x.clone(), lit(5.0)), @@ -3280,8 +3285,8 @@ mod tests { /// over each source's raw value column, for plan selection to bind. #[test] fn normalized_additive_errors_compose_as_absolute_errors() { - let cms = with_bound(ErrorMetric::ABSOLUTE_STREAM_L1, 0.01, 0.001); - let count_sketch = with_bound(ErrorMetric::ABSOLUTE_STREAM_L2, 0.02, 0.002); + let cms = with_bound(AccuracyMetric::ABSOLUTE_STREAM_L1, 0.01, 0.001); + let count_sketch = with_bound(AccuracyMetric::ABSOLUTE_STREAM_L2, 0.02, 0.002); // Summaries of each source's raw value column. let raw = |query: &str, guarantee: AccuracyGuarantee| { ScalarExpr::PromqlScalarFromVector(crate::test_support::estimate_over( @@ -3298,7 +3303,7 @@ mod tests { raw("count by (job) (n)", count_sketch), )) .unwrap(); - assert_eq!(g.spec.metric, ErrorMetric::ABSOLUTE); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::ABSOLUTE); assert!(close(delta(&g), 0.003)); let norms = |statistic: &str| match statistic { "stream_l1_norm[m]" => Some(100.0), @@ -3335,7 +3340,7 @@ mod tests { }; let (x, y) = (scalar(abs(0.01, 0.001)), scalar(abs(0.02, 0.002))); let product = of(&arith(Mul, x.clone(), y.clone())).unwrap(); - assert_eq!(product.spec.metric, ErrorMetric::ABSOLUTE); + assert_eq!(product.spec.accuracy_metric, AccuracyMetric::ABSOLUTE); assert_eq!(product.spec.error, unknown(PRODUCT_OPERAND_DOMAINS)); assert!(close(delta(&product), 0.003)); for quotient in [ @@ -3344,7 +3349,11 @@ mod tests { arith(Div, x.clone(), scalar(rel(0.01))), ] { let g = of("ient).unwrap(); - assert_eq!(g.spec.metric, ErrorMetric::ABSOLUTE, "{quotient:?}"); + assert_eq!( + g.spec.accuracy_metric, + AccuracyMetric::ABSOLUTE, + "{quotient:?}" + ); assert_eq!( g.spec.error, unknown(QUOTIENT_OPERAND_DOMAINS), @@ -3352,12 +3361,12 @@ mod tests { ); } let ratio = of(&arith(Div, scalar(rel(0.01)), scalar(rel(0.02)))).unwrap(); - assert_eq!(ratio.spec.metric, ErrorMetric::Relative); + assert_eq!(ratio.spec.accuracy_metric, AccuracyMetric::Relative); assert_eq!(ratio.spec.error, unknown(DIVISION_OPERAND_DOMAINS)); let relative = of(&arith(Mul, scalar(rel(0.01)), scalar(rel(0.02)))).unwrap(); - assert_eq!(relative.spec.metric, ErrorMetric::Relative); + assert_eq!(relative.spec.accuracy_metric, AccuracyMetric::Relative); assert!(close(bound(&relative), 0.01 + 0.02 + 0.0002)); - let rank = scalar(with_bound(ErrorMetric::Rank, 0.01, 0.001)); + let rank = scalar(with_bound(AccuracyMetric::Rank, 0.01, 0.001)); assert_eq!(of(&arith(Mul, rank.clone(), x.clone())), None); assert_eq!(of(&arith(Div, x, rank)), None); } @@ -3379,7 +3388,7 @@ mod tests { arith(Div, x.clone(), ScalarExpr::EvalTimestamp), ] { let g = of(&kept).unwrap(); - assert_eq!(g.spec.metric, ErrorMetric::Relative, "{kept:?}"); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::Relative, "{kept:?}"); assert!(close(bound(&g), 0.01), "{kept:?}"); } for none in [ @@ -3388,7 +3397,7 @@ mod tests { ] { assert_eq!(of(&none), None, "{none:?}"); } - let rank = scalar(with_bound(ErrorMetric::Rank, 0.01, 0.001)); + let rank = scalar(with_bound(AccuracyMetric::Rank, 0.01, 0.001)); for none in [ arith(Sub, rank.clone(), rank.clone()), arith(Mul, rank.clone(), lit(2.0)), @@ -3413,7 +3422,7 @@ mod tests { call("abs", vec![x.clone()]), ] { let g = of(&expr).unwrap(); - assert_eq!(g.spec.metric, ErrorMetric::ABSOLUTE, "{expr:?}"); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::ABSOLUTE, "{expr:?}"); assert!( close(bound(&g), 0.01) && close(delta(&g), 0.001), "{expr:?}" @@ -3430,7 +3439,7 @@ mod tests { vec![scalar(rel(0.01)), scalar(rel(0.02)), lit(10.0)], )) .unwrap(); - assert_eq!(relative.spec.metric, ErrorMetric::Relative); + assert_eq!(relative.spec.accuracy_metric, AccuracyMetric::Relative); assert!(close(bound(&relative), 0.02)); let mixed = call("promql_clamp_min", vec![x, scalar(rel(0.02))]); assert_eq!(of(&mixed), None); @@ -3753,7 +3762,7 @@ mod tests { #[test] fn the_absolute_bound_names_the_weighted_norm() { let frequency = AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE_STREAM_L1, + AccuracyMetric::ABSOLUTE_STREAM_L1, BoundExpr::Constant { value: 0.01 }, ProbabilityExpr::Constant { value: 0.001 }, vec![], @@ -3796,8 +3805,10 @@ mod tests { } fn inventory(query: &str) -> PerComputationCandidates { - let root = - crate::test_support::lower_promql(query, AccuracyTarget::epsilon_delta(0.01, 0.001)); + let root = crate::test_support::lower_promql( + query, + AccuracyTarget::max_error(0.01).failure_probability(0.001), + ); // Per-series state ranks each series' identity, which the rows carry // as a column; a query over aggregated rows is planned over labels. let root = @@ -3910,7 +3921,7 @@ mod tests { fn whole_expression_topk_partitions_index_the_heap_input() { let root = crate::test_support::lower_promql( "topk by (job) (2, sum by (service, job) (rate(m[1m])))", - AccuracyTarget::epsilon(0.1), + AccuracyTarget::max_error(0.1), ); let inventory = generate_per_computation_candidates(vec![(0, QueryRoot::Operator(root))]).unwrap(); diff --git a/crates/asap-aware-mapping/src/physical_operator_statistics.rs b/crates/asap-aware-mapping/src/physical_operator_statistics.rs index b4ab0680a..288c8e1e2 100644 --- a/crates/asap-aware-mapping/src/physical_operator_statistics.rs +++ b/crates/asap-aware-mapping/src/physical_operator_statistics.rs @@ -11,7 +11,7 @@ use asap_types::ir::Predicate; use asap_types::workload::{ DataArrival, DataWorkload, DurationMs, QueryRecurrence, QueryWorkloadEntry, RepeatedDemand, - TimeSelection, TimestampMs, + TimestampMs, }; use serde::{Deserialize, Serialize}; @@ -27,7 +27,6 @@ pub struct ComparisonScope { pub planning_time: TimestampMs, pub horizon: DurationMs, pub recurrence: QueryRecurrence, - pub time_selection: TimeSelection, pub sources: Vec, } @@ -62,7 +61,6 @@ impl ComparisonScope { planning_time, horizon, recurrence: query.recurrence.clone(), - time_selection: query.time_selection.clone(), sources, }; scope.validate()?; @@ -122,10 +120,6 @@ pub fn validate_comparison_scopes( ), ("horizon", raw.horizon == candidate.horizon), ("recurrence", raw.recurrence == candidate.recurrence), - ( - "time_selection", - raw.time_selection == candidate.time_selection, - ), ( "sources", raw.sources.len() == candidate.sources.len() diff --git a/crates/asap-aware-mapping/src/physical_plan_cost_model.rs b/crates/asap-aware-mapping/src/physical_plan_cost_model.rs index 1eb960eab..b49b8eaad 100644 --- a/crates/asap-aware-mapping/src/physical_plan_cost_model.rs +++ b/crates/asap-aware-mapping/src/physical_plan_cost_model.rs @@ -362,9 +362,7 @@ mod tests { use asap_types::ir::{NonASAPOp, OperatorNode}; use asap_types::pre_asap::{DataType, Field, Reduction, Schema, Source}; use asap_types::types::AccuracyTarget; - use asap_types::workload::{ - DataArrival, DurationMs, QueryRecurrence, QueryTimeScope, TimeSelection, TimestampMs, - }; + use asap_types::workload::{DataArrival, DurationMs, QueryRecurrence, TimestampMs}; use crate::analytical_cost::{ExecutionMultiplicity, PhysicalDAGNode, PhysicalOperator}; use crate::physical_operator_statistics::{ @@ -418,7 +416,7 @@ mod tests { OperatorNode::new_shared(asap_types::ir::Operator::NonASAP(NonASAPOp::Aggregate { reduction: Reduction::by(vec![]), measures: vec![AggIntent::Count { - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }], output_names: vec![], filters: vec![], @@ -437,11 +435,6 @@ mod tests { invocations: 10, execute_at: None, }, - time_selection: TimeSelection { - scope: QueryTimeScope::Longitudinal, - lookback: Some(DurationMs(10_000)), - as_of: Some(TimestampMs(1_000)), - }, sources: vec![ScanSelection { source: Source::Table { table_ref: "events".into(), diff --git a/crates/asap-aware-mapping/src/query_physical_lowering.rs b/crates/asap-aware-mapping/src/query_physical_lowering.rs index 7a3f65a28..f0978dc6d 100644 --- a/crates/asap-aware-mapping/src/query_physical_lowering.rs +++ b/crates/asap-aware-mapping/src/query_physical_lowering.rs @@ -1319,9 +1319,7 @@ mod tests { }; use asap_types::ir::{BinaryOperator, ExprSemantics, Predicate, SortKey, TimeRangeKind}; use asap_types::types::AccuracyTarget; - use asap_types::workload::{ - DataArrival, DurationMs, QueryRecurrence, QueryTimeScope, TimeSelection, TimestampMs, - }; + use asap_types::workload::{DataArrival, DurationMs, QueryRecurrence, TimestampMs}; use std::collections::HashMap; fn edge(rows: u64, bytes: u64) -> EdgeStatistics { @@ -1451,11 +1449,6 @@ mod tests { invocations: 1, execute_at: None, }, - time_selection: TimeSelection { - scope: QueryTimeScope::Longitudinal, - lookback: Some(DurationMs(1_000)), - as_of: Some(TimestampMs(1_000)), - }, sources, } } @@ -2891,7 +2884,6 @@ mod tests { predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, diff --git a/crates/asap-aware-mapping/src/realization.rs b/crates/asap-aware-mapping/src/realization.rs index 98d890f7f..f2e6d52cb 100644 --- a/crates/asap-aware-mapping/src/realization.rs +++ b/crates/asap-aware-mapping/src/realization.rs @@ -17,7 +17,7 @@ use asap_types::pre_asap::column_resolution::{resolve_column_ref, ResolveError}; use asap_types::pre_asap::expr_ir::ColumnRef; use asap_types::pre_asap::schema::ColumnId; use asap_types::pre_asap::Reduction; -use asap_types::types::AccuracyTarget; +use asap_types::types::{AccuracyMetric, AccuracyTarget}; use std::rc::Rc; use thiserror::Error; @@ -66,22 +66,40 @@ pub fn summary_candidates(intent: &AggIntent) -> &'static [SketchAlgorithm] { } } -/// The [`AccuracyTarget`] threaded onto an approximate-capable intent -/// (`Quantile`/`Cardinality`/`Count`/`TopK`), or `None` for every other -/// intent (no sketch candidate applies — [`realizations_for_intent`]'s own -/// match routes those elsewhere). Exposed so callers resolve the exact same -/// accuracy target [`realizations_for_intent`] does, without re-deriving it -/// from scratch. -pub fn accuracy_target(intent: &AggIntent) -> Option<&AccuracyTarget> { - match intent { - AggIntent::Quantile { accuracy, .. } - | AggIntent::Cardinality { accuracy, .. } - | AggIntent::FrequencyL2 { accuracy, .. } - | AggIntent::FrequencyEntropy { accuracy, .. } - | AggIntent::Count { accuracy } - | AggIntent::TopK { accuracy, .. } => Some(accuracy), - _ => None, +/// The [`AccuracyTarget`] an approximate-capable intent's summary is sized +/// and checked against, or `None` for every other intent (no sketch +/// candidate applies — [`realizations_for_intent`]'s own match routes those +/// elsewhere). It is the query's target restated in the intent's own metric +/// (#690 §4): an L1-scaled absolute error for counts and top-k frequencies, +/// a relative error for distinct counts and L2 frequency moments, an +/// absolute error for entropy, and for a quantile the query's metric or a +/// rank error when none is set. The query's own metric, set or not, is +/// checked at its root in Stage 3. +pub fn accuracy_target(intent: &AggIntent) -> Option { + let (target, metric) = match intent { + AggIntent::Quantile { accuracy, .. } => ( + accuracy, + accuracy.accuracy_metric.unwrap_or(AccuracyMetric::Rank), + ), + AggIntent::Cardinality { accuracy, .. } | AggIntent::FrequencyL2 { accuracy, .. } => { + (accuracy, AccuracyMetric::Relative) + } + AggIntent::FrequencyEntropy { accuracy, .. } => (accuracy, AccuracyMetric::ABSOLUTE), + AggIntent::Count { accuracy } | AggIntent::TopK { accuracy, .. } => { + (accuracy, AccuracyMetric::ABSOLUTE_STREAM_L1) + } + _ => return None, + }; + Some(target_in(target, metric)) +} + +/// `target`'s ε, δ and scope stated in `metric`. An exact target stays +/// exact with no metric: exact is exact in every metric. +pub(crate) fn target_in(target: &AccuracyTarget, metric: AccuracyMetric) -> AccuracyTarget { + if target.is_exact() { + return AccuracyTarget::exact().accuracy_scope(target.accuracy_scope); } + target.clone().accuracy_metric(metric) } /// Resolve an [`AccuracyTarget`] into the `(eps, delta)` budget diff --git a/crates/asap-aware-mapping/src/replacement.rs b/crates/asap-aware-mapping/src/replacement.rs index 8cfee24f0..df8dc43c4 100644 --- a/crates/asap-aware-mapping/src/replacement.rs +++ b/crates/asap-aware-mapping/src/replacement.rs @@ -378,7 +378,8 @@ use std::rc::{Rc, Weak}; use crate::accuracy::reconciliation::AccuracyReconciliationStrategy; use crate::accuracy::{ - equal_shares, AccuracyEvidenceProvider, AccuracyModel, DefaultAccuracyModel, NoAccuracyEvidence, + per_input_target, AccuracyEvidenceProvider, AccuracyModel, DefaultAccuracyModel, + NoAccuracyEvidence, }; use crate::cost_model::{ raw_recompute_cost_rate, CostModel, CseCandidate, DefaultCostModel, ExactCompositionCostInputs, @@ -1527,8 +1528,8 @@ impl<'a> ASAPStrategies<'a> { else { continue; }; - let (metric, layer_count) = (local.spec.metric, 1 + child_layers); - let Some(outer_target) = equal_shares(target, metric, layer_count, 1) else { + let (metric, layer_count) = (local.spec.accuracy_metric, 1 + child_layers); + let Some(outer_target) = per_input_target(&target, metric, layer_count) else { proposals.rejected.push(RejectedCandidate { strategy: "ASAPStrategies", description: rationale.clone(), @@ -1543,7 +1544,7 @@ impl<'a> ASAPStrategies<'a> { .and_then(|child| bindable_intent(child)) .and_then(accuracy_target); let inner_target = (layer_count > 1) - .then(|| equal_shares(target, metric, layer_count, layer_count - 1)) + .then(|| nested_layers_target(&target, metric, layer_count, layer_count - 1)) .flatten(); let (eps, delta) = accuracy_budget(&outer_target); let resized = Realization::Sketch(SketchKind::new( @@ -1554,7 +1555,7 @@ impl<'a> ASAPStrategies<'a> { )); // Identical to the as-declared composition already recorded // above — nothing new to propose. - if resized == realization && inner_target.as_ref() == declared_child_target { + if resized == realization && inner_target == declared_child_target { continue; } let note = GuaranteeSource::BudgetAllocation { @@ -1636,7 +1637,7 @@ impl Proposals { fn per_value(target: &AccuracyTarget) -> AccuracyTarget { target .clone() - .with_scope(asap_types::types::AccuracyScope::PerValue) + .accuracy_scope(asap_types::types::AccuracyScope::PerValue) } /// The `child` of a [`bindable_intent`]-shaped `Aggregate`. @@ -1791,7 +1792,7 @@ pub(crate) fn realize_child( /// [`realize_child`] with every model explicit, plus an optional /// `end_to_end_target` for `root`'s own value (issue #172): when an -/// equal budget split ([`equal_shares`]) hands an approximate child a share of its +/// equal budget split ([`per_input_target`]) hands an approximate child a share of its /// parent's budget, the child is re-enumerated with that share substituted /// for its declared `AccuracyTarget` — sizing its sketch (and, recursively, /// re-splitting for its own approximate children) under the allocated @@ -2033,7 +2034,7 @@ fn realize_binary( .filter_map(|(index, guarantee)| { guarantee .filter(|guarantee| !guarantee.is_exact()) - .map(|guarantee| (index, guarantee.spec.metric)) + .map(|guarantee| (index, guarantee.spec.accuracy_metric)) }) .collect::>(); if !approximate.is_empty() { @@ -2044,7 +2045,7 @@ fn realize_binary( { return Ok(None); } - let Some(local_target) = equal_shares(target, metric, approximate.len(), 1) else { + let Some(local_target) = per_input_target(target, metric, approximate.len()) else { return Ok(None); }; for (operand_index, _) in approximate { @@ -2823,7 +2824,7 @@ fn construct_summary_agg( )); } if let (Some(n), FieldDataType::Sketch(kind, grouping)) = (bound, &family) { - let (eps, delta) = accuracy_budget(accuracy_target(intent).expect("TopK target")); + let (eps, delta) = accuracy_budget(&accuracy_target(intent).expect("TopK target")); let params = default_size_params( kind.algorithm().clone(), intent, @@ -3033,7 +3034,7 @@ fn construct_summary_agg( // contributions that already violate the target reject it here. if !estimator.satisfies( &score.optimistic_floor(), - &crate::accuracy::native_target(target, &score), + &crate::accuracy::native_target(&target, &score), ) { return Err(RealizationError::PhysicalRealization( "weighted TopK scores miss accuracy target", @@ -3058,7 +3059,7 @@ fn construct_summary_agg( }; if !estimator.satisfies( &joint.optimistic_floor(), - &crate::accuracy::native_target(target, &joint), + &crate::accuracy::native_target(&target, &joint), ) { return Err(RealizationError::PhysicalRealization( "weighted TopK joint guarantee misses target", @@ -3482,10 +3483,10 @@ fn compose_guarantee( // its requested target. if !accuracy.satisfies( &guarantee.optimistic_floor(), - &crate::accuracy::native_target(target, &guarantee), + &crate::accuracy::native_target(&target, &guarantee), ) { return Err(AccuracyError::TargetNotSatisfied { - metric: guarantee.spec.metric, + metric: guarantee.spec.accuracy_metric, bound: guarantee.spec.error.evaluate(), failure_probability: guarantee.spec.failure_probability.evaluate(), target: target.clone(), @@ -6150,14 +6151,14 @@ pub fn search_workload_with_targets<'s, Id>( .as_ref() .map(|g| { ( - g.spec.metric, + g.spec.accuracy_metric, g.spec.error.evaluate(), g.spec.failure_probability.evaluate(), ) }) - .unwrap_or((asap_types::post_asap::ErrorMetric::ABSOLUTE, None, None)), + .unwrap_or((asap_types::post_asap::AccuracyMetric::ABSOLUTE, None, None)), Replacement::ExactComposition(_) => { - (asap_types::post_asap::ErrorMetric::ABSOLUTE, None, None) + (asap_types::post_asap::AccuracyMetric::ABSOLUTE, None, None) } }; RejectedCandidate { @@ -6185,7 +6186,7 @@ fn strictest_sibling_accuracy( root: &OperatorNode, siblings: &[Rc], ) -> Option { - fn approximate(intent: &AggIntent) -> Option<&AccuracyTarget> { + fn approximate(intent: &AggIntent) -> Option { accuracy_target(intent).filter(|accuracy| !accuracy.is_exact()) } let Some(NonASAPOp::Aggregate { @@ -6198,7 +6199,7 @@ fn strictest_sibling_accuracy( return None; }; let intent = bindable_intent(root)?; - let own = accuracy_budget(approximate(intent)?); + let own = accuracy_budget(&approximate(intent)?); let (mut eps, mut delta) = own; for sibling in siblings { let Some(NonASAPOp::Aggregate { @@ -6220,14 +6221,14 @@ fn strictest_sibling_accuracy( (AggIntent::Quantile { col, .. }, AggIntent::Quantile { col: other_col, .. }) => { col == other_col } - _ => override_accuracy(intent, accuracy) == *other, + _ => override_accuracy(intent, &accuracy) == override_accuracy(other, &accuracy), }; if same_intent && sibling_reduction == reduction && sibling_filters == filters && (Rc::ptr_eq(sibling_child, child) || sibling_child == child) { - let (sibling_eps, sibling_delta) = accuracy_budget(accuracy); + let (sibling_eps, sibling_delta) = accuracy_budget(&accuracy); eps = eps.min(sibling_eps); delta = delta.min(sibling_delta); } @@ -6235,9 +6236,9 @@ fn strictest_sibling_accuracy( if (eps, delta) == own { None } else if delta == DEFAULT_DELTA { - Some(AccuracyTarget::epsilon(eps)) + Some(AccuracyTarget::max_error(eps)) } else { - Some(AccuracyTarget::epsilon_delta(eps, delta)) + Some(AccuracyTarget::max_error(eps).failure_probability(delta)) } } @@ -6548,9 +6549,77 @@ fn walk_children( } } +/// `shares` of `of` equal shares of `target`, in `metric`'s composition: +/// the target of a nested sub-DAG holding `shares` of a node's `of` +/// approximate layers ([`per_input_target`] is one share). `None` for an +/// exact target or an ε that is not positive and finite. +fn nested_layers_target( + target: &AccuracyTarget, + metric: asap_types::types::AccuracyMetric, + of: usize, + shares: usize, +) -> Option { + if target.is_exact() || !(target.error.is_finite() && target.error > 0.0) { + return None; + } + let fraction = shares.min(of.max(1)) as f64 / of.max(1) as f64; + let error = match metric { + asap_types::types::AccuracyMetric::Relative => (1.0 + target.error).powf(fraction) - 1.0, + _ => target.error * fraction, + }; + Some(AccuracyTarget { + error, + failure_probability: target.failure_probability.map(|delta| delta * fraction), + ..target.clone() + }) +} + #[cfg(test)] mod tests { use super::*; + + /// Siblings that differ only in their written targets share the + /// strictest one: two `count by (job)` (or distinct-count) siblings at + /// ε = 0.05 and 0.01, with no metric set, size the looser one for 0.01. + #[test] + fn identical_count_and_distinct_siblings_share_the_strictest_target() { + let scan = crate::test_support::metric_scan(&["job"]); + for intent in [ + |accuracy| AggIntent::Count { accuracy }, + |accuracy| AggIntent::Cardinality { + cols: vec![], + accuracy, + }, + ] { + let sibling = |error| { + crate::test_support::agg( + vec![2], + intent(AccuracyTarget::max_error(error)), + scan.clone(), + ) + }; + let (loose, strict) = (sibling(0.05), sibling(0.01)); + assert_eq!( + strictest_sibling_accuracy(&loose, &[loose.clone(), strict.clone()]), + Some(AccuracyTarget::max_error(0.01)) + ); + assert_eq!( + strictest_sibling_accuracy(&strict, &[loose, strict.clone()]), + None + ); + } + } + + /// A nested sub-DAG holding `shares` of `of` approximate layers gets + /// that fraction of an additive target's ε and δ. + #[test] + fn a_nested_sub_dag_gets_its_layers_share_of_the_target() { + let target = AccuracyTarget::max_error(0.1).failure_probability(0.02); + let rest = nested_layers_target(&target, asap_types::types::AccuracyMetric::ABSOLUTE, 3, 2) + .unwrap(); + assert!((rest.error - 0.1 * 2.0 / 3.0).abs() < 1e-12); + assert!((rest.failure_probability.unwrap() - 0.02 * 2.0 / 3.0).abs() < 1e-12); + } use crate::accuracy::PropagationStats; use crate::cost_model::Cost; use crate::test_support::{ @@ -6595,7 +6664,7 @@ mod tests { let (estimate, query) = assemble( "quantile_over_time(0.99, m[1m])", - AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Rank), + AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Rank), ); assert!(estimate.contains_asap()); assert!(Rc::ptr_eq(&estimate, &query)); @@ -6648,7 +6717,7 @@ mod tests { }; let intent = AggIntent::TopK { k: 5, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }; let family = FieldDataType::Sketch( SketchKind::new( @@ -6689,7 +6758,7 @@ mod tests { fn search_rejects_a_root_with_an_asap_operator() { let root = lower_promql( "quantile_over_time(0.99, m[1m])", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ); let space = search_workload(vec![("q", root)]).unwrap(); let summary = space @@ -6731,7 +6800,10 @@ mod tests { fn rate_candidate_inventories_have_no_timing_only_duplicates() { for (query, accuracy) in [ ("sum by(job)(rate(m[1m]))", AccuracyTarget::exact()), - ("topk by(job)(2, rate(m[1m]))", AccuracyTarget::epsilon(0.1)), + ( + "topk by(job)(2, rate(m[1m]))", + AccuracyTarget::max_error(0.1), + ), ] { let root = lower_promql(query, accuracy); let inventory = search_workload(vec![(0usize, root)]) @@ -6958,7 +7030,7 @@ mod tests { fn approximate_temporal_topk_admits_exact_maintained_values() { let root = lower_promql( "topk by(job)(1,count_over_time(a[5m]))", - AccuracyTarget::epsilon_delta(0.01, 0.01), + AccuracyTarget::max_error(0.01).failure_probability(0.01), ); let planning_inputs = CandidatePlanningInputs::with_default_accuracy(&crate::cost_model::DefaultCostModel); @@ -7028,9 +7100,10 @@ mod tests { }) } } - let requested = - AccuracyTarget::epsilon_delta(0.01, 0.01).with_metric(ErrorMetric::Relative); - let target = requested.target_in(ErrorMetric::Relative); + let requested = AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::Relative); + let target = requested.clone().accuracy_metric(AccuracyMetric::Relative); let inputs = CandidatePlanningInputs { evidence: &Domain, ..CandidatePlanningInputs::with_default_accuracy(&DefaultCostModel) @@ -7050,8 +7123,8 @@ mod tests { // Missing domain proof permits an uncertified direct quantile ratio only. #[test] fn quantile_ratio_without_input_proof_has_no_root_guarantee() { - let requested = AccuracyTarget::epsilon_delta(0.01, 0.01); - let target = requested.target_in(ErrorMetric::Relative); + let requested = AccuracyTarget::max_error(0.01).failure_probability(0.01); + let target = requested.clone().accuracy_metric(AccuracyMetric::Relative); let root = lower_promql( "quantile_over_time(0.5,a[5m]) / quantile_over_time(0.9,a[5m])", requested.clone(), @@ -7142,12 +7215,12 @@ mod tests { AggIntent::Quantile { col: None, q, - accuracy: AccuracyTarget::epsilon(e), + accuracy: AccuracyTarget::max_error(e), } } fn eps(e: f64) -> AccuracyTarget { - AccuracyTarget::epsilon(e) + AccuracyTarget::max_error(e) } // ── realizations_for_intent / sizing ─────────────────────────────── @@ -7426,7 +7499,7 @@ mod tests { #[test] fn epsilon_delta_sizes_cms_depth() { let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.001, 0.001), + accuracy: AccuracyTarget::max_error(0.001).failure_probability(0.001), }; assert_eq!( preferred(&intent), @@ -7794,7 +7867,7 @@ mod tests { fn fitting_cardinality() -> AggIntent { AggIntent::Cardinality { cols: vec![], - accuracy: AccuracyTarget::epsilon(0.05), + accuracy: AccuracyTarget::max_error(0.05), } } @@ -7825,7 +7898,7 @@ mod tests { vec![2], AggIntent::Cardinality { cols: vec![], - accuracy: AccuracyTarget::epsilon_delta(0.05, 0.01), + accuracy: AccuracyTarget::max_error(0.05).failure_probability(0.01), }, metric_scan(&["job"]), ); @@ -8119,7 +8192,7 @@ mod tests { #[test] fn single_bindable_aggregate_keeps_unprovable_hydra_candidates() { let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }; let root = agg(vec![2], intent, metric_scan(&["job"])); let space = search_workload(vec![("q", root)]).unwrap(); @@ -8210,7 +8283,11 @@ mod tests { let root = agg(vec![2], fitting_cardinality(), metric_scan(&["job"])); let targeted = search_workload_with_targets( - vec![("q", root, Some(AccuracyTarget::epsilon_delta(0.01, 0.01)))], + vec![( + "q", + root, + Some(AccuracyTarget::max_error(0.01).failure_probability(0.01)), + )], &default_strategies(), &DefaultAccuracyModel, ) @@ -8563,7 +8640,7 @@ mod tests { fn first_grouping(estimated_count: usize) -> GroupingStrategy { let model = EstimatedSubpopulations(estimated_count); let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }; let root = agg(vec![2, 3], intent, metric_scan(&["tenant_id", "endpoint"])); let strategies = default_strategies_with(&model); @@ -9858,7 +9935,7 @@ mod tests { fn explicit_empty_by_aggregate_realizes_summary_agg_with_reduce_reduction() { let intent = AggIntent::Cardinality { cols: vec![], - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }; let q = agg(vec![], intent, metric_scan(&["job"])); let root = realize(&q).unwrap(); @@ -9938,7 +10015,7 @@ mod tests { for (cols, want) in cases { let intent = AggIntent::Cardinality { cols: cols.clone(), - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }; let root = realize(&agg(vec![0], intent, metric_scan(&["job"]))).unwrap(); let bound = find_summary_input(&root) @@ -10057,7 +10134,7 @@ mod tests { vec![2], AggIntent::TopK { k: 5, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, metric_scan(&["job"]), ); @@ -10070,7 +10147,7 @@ mod tests { let inner = agg( vec![2], AggIntent::Count { - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, metric_scan(&["job"]), ); @@ -10078,7 +10155,7 @@ mod tests { vec![], AggIntent::TopK { k: 5, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, inner, ); @@ -10120,7 +10197,7 @@ mod tests { let inner = agg( vec![2], AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }, metric_scan(&["job"]), ); @@ -10128,7 +10205,7 @@ mod tests { vec![], AggIntent::TopK { k: 5, - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.01), }, inner, ); @@ -10153,7 +10230,7 @@ mod tests { let inner = agg( vec![2], AggIntent::Count { - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, metric_scan(&["service"]), ); @@ -10161,7 +10238,7 @@ mod tests { vec![], AggIntent::TopK { k: 10, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, inner, ); @@ -10227,7 +10304,7 @@ mod tests { vec![], AggIntent::TopK { k: 5, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, inner, ); @@ -10301,7 +10378,7 @@ mod tests { vec![2], AggIntent::TopK { k: 5, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, inner, ); @@ -10344,7 +10421,7 @@ mod tests { let inner = agg( vec![2, 3], AggIntent::Count { - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, metric_scan(&["service", "region"]), ); @@ -10352,7 +10429,7 @@ mod tests { vec![], AggIntent::TopK { k: 10, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, inner, ); @@ -10394,7 +10471,7 @@ mod tests { let inner = agg( vec![2, 3, 4], AggIntent::Count { - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, metric_scan(&["service", "method", "region"]), ); @@ -10404,7 +10481,7 @@ mod tests { vec![2], AggIntent::TopK { k: 10, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, inner, ); @@ -10476,7 +10553,7 @@ mod tests { // ── Accuracy guarantees and fail-closed composition (issue #172) ───── - use asap_types::post_asap::ErrorMetric; + use asap_types::post_asap::AccuracyMetric; /// A test-only `AccuracyModel` that *registers* a rule the default /// deliberately lacks — a sketch over rank-bounded inputs composes @@ -10501,19 +10578,21 @@ mod tests { local: Option<&AccuracyGuarantee>, stats: &PropagationStats, ) -> Result { - let rank = |g: &AccuracyGuarantee| g.is_exact() || g.spec.metric == ErrorMetric::Rank; + let rank = |g: &AccuracyGuarantee| { + g.is_exact() || g.spec.accuracy_metric == AccuracyMetric::Rank + }; if let (CompositionOperator::ApproximateAggregate, true, Some(local)) = (op, inputs.iter().all(rank), local) { let relabel = |g: &AccuracyGuarantee| { let mut g = g.clone(); - g.spec.metric = ErrorMetric::ABSOLUTE; + g.spec.accuracy_metric = AccuracyMetric::ABSOLUTE; g }; let inputs: Vec<_> = inputs.iter().map(relabel).collect(); let mut out = DefaultAccuracyModel.propagate(op, &inputs, Some(&relabel(local)), stats)?; - out.spec.metric = local.spec.metric; + out.spec.accuracy_metric = local.spec.accuracy_metric; return Ok(out); } DefaultAccuracyModel.propagate(op, inputs, local, stats) @@ -10528,7 +10607,7 @@ mod tests { AggIntent::Quantile { col: None, q, - accuracy: AccuracyTarget::epsilon(eps), + accuracy: AccuracyTarget::max_error(eps), } } @@ -10564,7 +10643,7 @@ mod tests { matches!( &rejection.error, AccuracyError::UnsupportedComposition { input_metrics, .. } - if input_metrics == &vec![ErrorMetric::Rank] + if input_metrics == &vec![AccuracyMetric::Rank] ), "{:?}", rejection.error @@ -10589,13 +10668,13 @@ mod tests { assert!(proposals.rejected.iter().all(|r| matches!( &r.error, AccuracyError::UnsupportedComposition { input_metrics, local_metric, .. } - if input_metrics == &vec![ErrorMetric::Relative] - && *local_metric == Some(ErrorMetric::Rank) + if input_metrics == &vec![AccuracyMetric::Relative] + && *local_metric == Some(AccuracyMetric::Rank) ))); assert!(proposals.candidates.iter().all(|candidate| matches!( &candidate.replacement, Replacement::SubDAG(node) - if node.guarantee.as_ref().is_some_and(|g| g.spec.metric == ErrorMetric::Relative) + if node.guarantee.as_ref().is_some_and(|g| g.spec.accuracy_metric == AccuracyMetric::Relative) ))); } @@ -10610,7 +10689,7 @@ mod tests { .guarantee .as_ref() .expect("a evaluation carries a guarantee"); - assert_eq!(guarantee.spec.metric, ErrorMetric::Rank); + assert_eq!(guarantee.spec.accuracy_metric, AccuracyMetric::Rank); assert_eq!( guarantee.spec.error.evaluate(), Some(crate::accuracy::estimators::kll::kll_rank_error_99(269)) @@ -10690,7 +10769,7 @@ mod tests { ) && node.guarantee.as_ref().is_some_and(|guarantee| { DefaultAccuracyModel.satisfies( guarantee, - &crate::accuracy::native_target(&AccuracyTarget::epsilon(0.1), guarantee), + &crate::accuracy::native_target(&AccuracyTarget::max_error(0.1), guarantee), ) }) })); @@ -10716,7 +10795,7 @@ mod tests { node.guarantee.as_ref().is_some_and(|g| { DefaultAccuracyModel.satisfies( g, - &crate::accuracy::native_target(&AccuracyTarget::epsilon(0.1), g), + &crate::accuracy::native_target(&AccuracyTarget::max_error(0.1), g), ) }) } @@ -10752,7 +10831,7 @@ mod tests { vec![( "q", Rc::clone(&q), - Some(AccuracyTarget::epsilon(0.001).with_metric(ErrorMetric::Rank)), + Some(AccuracyTarget::max_error(0.001).accuracy_metric(AccuracyMetric::Rank)), )], &default_strategies(), &DefaultAccuracyModel, @@ -10777,7 +10856,7 @@ mod tests { vec![( "q", Rc::clone(&q), - Some(AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Rank)), + Some(AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Rank)), )], &default_strategies(), &DefaultAccuracyModel, @@ -10812,7 +10891,7 @@ mod tests { let inner = agg( vec![2], AggIntent::Count { - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, metric_scan(&["job"]), ); @@ -10820,12 +10899,12 @@ mod tests { vec![], AggIntent::TopK { k: 10, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, inner, ); let space = search_workload_with_targets( - vec![("q", Rc::clone(&q), Some(AccuracyTarget::epsilon(0.01)))], + vec![("q", Rc::clone(&q), Some(AccuracyTarget::max_error(0.01)))], &default_strategies(), &DefaultAccuracyModel, ) @@ -10924,7 +11003,7 @@ mod tests { vec![], AggIntent::TopK { k: 2, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, agg(vec![2], AggIntent::Sum { col: None }, raw.clone()), ); @@ -10962,7 +11041,7 @@ mod tests { AggIntent::Quantile { q: 0.9, col: None, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, metric_scan(&["job"]), ); diff --git a/crates/asap-aware-mapping/src/rollup.rs b/crates/asap-aware-mapping/src/rollup.rs index 3b679d84c..7eda22e12 100644 --- a/crates/asap-aware-mapping/src/rollup.rs +++ b/crates/asap-aware-mapping/src/rollup.rs @@ -662,7 +662,7 @@ mod tests { fn approximate_count_does_not_roll_up_via_sum() { let scan = metric_scan(); let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }; let fine = agg(vec![2, 3], intent.clone(), &scan); let coarse = agg(vec![2], intent, &scan); @@ -718,7 +718,7 @@ mod tests { #[test] fn workload_search_does_not_roll_up_approximate_count() { let intent = AggIntent::Count { - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }; let fine = agg(vec![2, 3], intent.clone(), &metric_scan()); let coarse = agg(vec![2], intent, &metric_scan()); diff --git a/crates/asap-aware-mapping/src/stage2_physical_asap_optimization.rs b/crates/asap-aware-mapping/src/stage2_physical_asap_optimization.rs index 0ee9aafec..1b9449323 100644 --- a/crates/asap-aware-mapping/src/stage2_physical_asap_optimization.rs +++ b/crates/asap-aware-mapping/src/stage2_physical_asap_optimization.rs @@ -886,7 +886,7 @@ mod tests { fn everything_runs_at_query_time() { let root = lower_promql( "topk by (job) (10, sum_over_time(m[1m]))", - AccuracyTarget::epsilon_delta(0.01, 0.001), + AccuracyTarget::max_error(0.01).failure_probability(0.001), ); let inventory = crate::per_computation_candidates::generate_per_computation_candidates( vec![(0, QueryRoot::Operator(root))], @@ -928,8 +928,9 @@ mod tests { fn summary_root(query: &str) -> QueryRoot { let root = lower_promql( query, - AccuracyTarget::epsilon_delta(0.01, 0.001) - .with_metric(asap_types::types::ErrorMetric::Rank), + AccuracyTarget::max_error(0.01) + .failure_probability(0.001) + .accuracy_metric(asap_types::types::AccuracyMetric::Rank), ); let inventory = crate::per_computation_candidates::generate_per_computation_candidates( vec![(0, QueryRoot::Operator(root))], diff --git a/crates/asap-aware-mapping/src/stage3_plan_selection.rs b/crates/asap-aware-mapping/src/stage3_plan_selection.rs index 8067a5824..b33c36f99 100644 --- a/crates/asap-aware-mapping/src/stage3_plan_selection.rs +++ b/crates/asap-aware-mapping/src/stage3_plan_selection.rs @@ -109,9 +109,8 @@ use asap_types::ir::{ ScalarExpr, SchemaDerivationError, TimeRangeKind, }; use asap_types::post_asap::sketch::KLL_MAX_K; -use asap_types::post_asap::ExecutionDataStateError; use asap_types::post_asap::{ - AccuracyGuarantee, CompositionOperator, ErrorMetric, ExecutionTiming, FieldDataType, + AccuracyGuarantee, AccuracyMetric, CompositionOperator, ExecutionTiming, FieldDataType, Materialization, SketchAlgorithm, SketchParams, SketchStatistic, WeightDomain, }; use asap_types::pre_asap::{AggIntent, DataType, Reduction, ScalarValue, Schema}; @@ -127,13 +126,13 @@ use crate::accuracy::composition::{ }; use crate::accuracy::estimators::with_failure_probability; use crate::accuracy::estimators::{hll, univmon}; -use crate::accuracy::{whole_result_guarantee, OperandDomain, PropagationStats}; +use crate::accuracy::{OperandDomain, PropagationStats}; use crate::analytical_cost::{ estimate_operator, AnalyticalCostError, PhysicalOperator, ResourceCalibration, ResourceEstimate, }; use crate::per_computation_candidates::{ - approximate_inputs, operand_domain_statistic, operand_guarantee, operand_of, - project_column_guarantee, root_guarantee, summary_source, with_resized_summaries, + approximate_inputs, distinct_bound_source, operand_domain_statistic, operand_guarantee, + operand_of, project_column_guarantee, root_guarantee, with_resized_summaries, }; use crate::physical_operator_statistics::{ EdgeStatistics, OperatorStatistics, PartitionStatistics, UnaryEdgeStatistics, @@ -265,10 +264,10 @@ pub struct Rejection { #[derive(Debug, Clone, PartialEq)] pub struct AcceptedAccuracy { /// The target's metric; `None` when the user did not set one. - pub target_metric: Option, + pub target_metric: Option, /// The metric the accepted guarantee is stated in; `None` for an exact /// output, which meets every metric. - pub accepted_metric: Option, + pub accepted_metric: Option, /// The sketch algorithms the query's output reads, in plan order /// (e.g. `["Kll"]`); empty for an exact plan. pub sketches: Vec, @@ -278,7 +277,7 @@ impl std::fmt::Display for AcceptedAccuracy { /// E.g. `metric not set; accepted rank (Kll)`, or /// `metric relative; accepted relative (DDSketch)`. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let name = |metric: ErrorMetric| { + let name = |metric: AccuracyMetric| { serde_json::to_value(metric) .ok() .and_then(|value| value.as_str().map(str::to_owned)) @@ -308,9 +307,8 @@ pub struct Selection { /// to run. The Stage 2 candidate keeps the open requirement. pub plan: Vec, pub costs: BTreeMap, - /// For each valid candidate, per query, the accuracy Stage 3 accepted - /// (`None` for a query without a target). - pub accepted: BTreeMap>>, + /// For each valid candidate, per query, the accuracy Stage 3 accepted. + pub accepted: BTreeMap>, pub rejected: Vec, } @@ -332,13 +330,6 @@ pub enum SelectionError { }, #[error("no valid candidate: {0:?}")] NoValidCandidate(Vec), - /// A candidate's summaries could not be rebuilt with the parameters its - /// queries' failure probabilities need. - #[error("candidate {candidate}: sizing for the failure probability: {error}")] - Sizing { - candidate: String, - error: SizingError, - }, } /// Stage 3 (plan selection): reject candidates that miss a query's accuracy @@ -366,13 +357,17 @@ pub fn select_plan( entries: entries.len(), }); } - let candidate = - &sized_for_failure_probability(candidate, targets, data).map_err(|error| { - SelectionError::Sizing { - candidate: candidate.id.clone(), - error, - } - })?; + let candidate = &match sized_for_failure_probability(candidate, targets, data) { + Ok(sized) => sized, + Err(reason) => { + rejected.push(Rejection { + id: candidate.id.clone(), + valid: false, + reason, + }); + continue; + } + }; let accepted_accuracy = match accuracy_check(candidate, targets, data, &models) { Ok(accepted_accuracy) => accepted_accuracy, Err(reason) => { @@ -448,16 +443,6 @@ fn cheaper(cost: f64, best: f64) -> bool { cost < best - best.abs() * 1e-9 } -/// Why a candidate's summaries could not be rebuilt for a failure -/// probability ([`sized_for_failure_probability`]). -#[derive(Debug, Error)] -pub enum SizingError { - #[error(transparent)] - Structure(#[from] SchemaDerivationError), - #[error(transparent)] - Export(#[from] ExecutionDataStateError), -} - /// Without a planning time, only evidence that cannot expire is fresh. const ANY_TIME: u64 = u64::MAX; @@ -468,14 +453,14 @@ const ANY_TIME: u64 = u64::MAX; /// series never outnumber its input series. `None` when unbound: any other /// SQL result, until a bound on the groups of its source is an input, and a /// range vector. -fn output_values(root: &QueryRoot, data: &DataWorkload) -> Option { +fn values_per_result(root: &QueryRoot, data: &DataWorkload) -> Option { match root { - QueryRoot::Scalar(_) => Some(1.0), - QueryRoot::Operator(node) if at_most_one_row(node) => Some(1.0), + QueryRoot::Scalar(_) => Some(1), + QueryRoot::Operator(node) if at_most_one_row(node) => Some(1), QueryRoot::Operator(node) if node.result_kind == OperatorResultKind::InstantVector => data .input_cardinality .value_at(ANY_TIME) - .map(|series| (*series).max(1) as f64), + .map(|series| (*series).max(1)), QueryRoot::Operator(_) => None, } } @@ -543,7 +528,7 @@ fn at_most_one_row(node: &OperatorNode) -> bool { /// for its queries' targets (#669b), before pricing, so that cost reflects /// the deeper sketch. A query with a target with failure probability `δ`, /// over `n` approximate inputs ([`approximate_inputs`]) and `G` values per -/// evaluation ([`output_values`]; 1 for a per-value target), needs each +/// evaluation ([`values_per_result`]; 1 for a per-value target), needs each /// input to fail with probability at most `δ / (n · G)`: `n · G` such /// failures union-bound to `δ` over the whole result. A summary several /// queries read takes the smallest. The parameters that depend on it are @@ -551,13 +536,18 @@ fn at_most_one_row(node: &OperatorNode) -> bool { /// source's distinct bound), and the guarantees above are composed again. /// A query whose `G` is unbound enlarges nothing: its whole-result target /// stays unmet. Stage 1 sizes for ε and reads no data workload; only this -/// stage binds `G`. +/// stage binds `G`. `Err` is the reason the candidate is rejected when its +/// resized summaries do not rebuild into a plan. fn sized_for_failure_probability( candidate: &PhysicalCandidate, targets: &[AccuracyTarget], data: &DataWorkload, -) -> Result { - let roots = sized_roots(&candidate.roots, targets, data)?; +) -> Result { + let resize_failed = |error: &dyn std::fmt::Display| { + format!("resizing for the failure probability failed: {error}") + }; + let roots = resize_for_failure_probability(&candidate.roots, targets, data) + .map_err(|error| resize_failed(&error))?; if roots .iter() .zip(&candidate.roots) @@ -565,7 +555,9 @@ fn sized_for_failure_probability( { return Ok(candidate.clone()); } - let mut dag = compile_physical_asap_workload_with_node_ids(&roots)?.dag; + let mut dag = compile_physical_asap_workload_with_node_ids(&roots) + .map_err(|error| resize_failed(&error))? + .dag; // The DAG has the same nodes, so each keeps the placement Stage 2 gave it. if dag.nodes.len() == candidate.dag.nodes.len() { for (node, placed) in dag.nodes.iter_mut().zip(&candidate.dag.nodes) { @@ -585,7 +577,7 @@ fn sized_for_failure_probability( /// `roots` with their summaries enlarged as [`sized_for_failure_probability`] /// says: the plan plan selection prices and runs, for the logical roots of /// the selected candidate too. -pub fn sized_roots( +pub fn resize_for_failure_probability( roots: &[QueryRoot], targets: &[AccuracyTarget], data: &DataWorkload, @@ -599,13 +591,13 @@ pub fn sized_roots( else { continue; }; - let values = match target.scope { - AccuracyScope::PerValue => Some(1.0), - AccuracyScope::WholeResult => output_values(root, data), + let values = match target.accuracy_scope { + AccuracyScope::PerValue => Some(1), + AccuracyScope::WholeResult => values_per_result(root, data), }; let Some(values) = values else { continue }; let inputs = approximate_inputs(root); - let share = delta / (inputs.len() as f64 * values); + let share = delta / (inputs.len() as f64 * values as f64); for state in &inputs { let entry = required.entry(Rc::as_ptr(state)).or_insert(share); *entry = entry.min(share); @@ -621,16 +613,16 @@ pub fn sized_roots( precision, relative_error, } => { - let source = summary_source(state)?; + let source = distinct_bound_source(state)?; let bound = data.max_distinct_per_evaluation.value_at(ANY_TIME)?; // The guarantee keeps the relative error Pass 1 sized for. let relative_error = hll::classic_relative_error(*precision, *relative_error); SketchParams::Hll { - precision: hll::precision_for( - *precision, + precision: hll::min_precision( relative_error, - *bound.get(&source)?, delta, + *bound.get(&source)?, + *precision, ), relative_error: Some(relative_error), } @@ -662,7 +654,7 @@ fn accuracy_check( targets: &[AccuracyTarget], data: &DataWorkload, models: &PlanningModels<'_>, -) -> Result>, String> { +) -> Result, String> { let mut accepted = Vec::with_capacity(targets.len()); for (query, (root, target)) in candidate.roots.iter().zip(targets).enumerate() { let query = query + 1; @@ -705,9 +697,9 @@ fn accuracy_check( } // A guarantee per value is stated over the whole result's `G` // values for a whole-result target. - let values = output_values(root, data); - let over_scope = |g: AccuracyGuarantee| match target.scope { - AccuracyScope::WholeResult => whole_result_guarantee(&g, values), + let values = values_per_result(root, data); + let over_scope = |g: AccuracyGuarantee| match target.accuracy_scope { + AccuracyScope::WholeResult => g.over_whole_result(values), AccuracyScope::PerValue => g, }; let guarantee = bound_root_guarantee(root, data, models).map(over_scope); @@ -720,17 +712,17 @@ fn accuracy_check( .filter(meets) }); if let Some(met) = met { - accepted.push(Some(AcceptedAccuracy { - target_metric: target.metric, - accepted_metric: (!met.is_exact()).then_some(met.spec.metric), + accepted.push(AcceptedAccuracy { + target_metric: target.accuracy_metric, + accepted_metric: (!met.is_exact()).then_some(met.spec.accuracy_metric), sketches, - })); + }); continue; } return Err(match guarantee { Some(g) if values.is_none() - && target.scope == AccuracyScope::WholeResult + && target.accuracy_scope == AccuracyScope::WholeResult && target.failure_probability.is_some() && g.spec.failure_probability.evaluate().is_none() => { @@ -746,7 +738,7 @@ fn accuracy_check( "q{query}: the output guarantees a {:?} bound {:?}, failure probability {:?}, \ which misses target {target:?}, and the deployment has no evidence that it \ meets it", - g.spec.metric, + g.spec.accuracy_metric, g.spec.error.evaluate(), g.spec.failure_probability.evaluate(), ), @@ -1087,10 +1079,10 @@ fn domain_bound( let (mx, my) = (magnitude(&dx), magnitude(&dy)); let error = |g: &AccuracyGuarantee, m: f64| { let bound = g.spec.error.evaluate()?; - match g.spec.metric { + match g.spec.accuracy_metric { _ if g.is_exact() => Some(0.0), - ErrorMetric::ABSOLUTE => Some(bound), - ErrorMetric::Relative => Some(bound * m), + AccuracyMetric::ABSOLUTE => Some(bound), + AccuracyMetric::Relative => Some(bound * m), _ => None, } }; @@ -1974,7 +1966,7 @@ mod tests { use crate::test_support::{entry, entry_with, lower_promql}; use asap_types::ir::{ExprSemantics, ScalarExpr}; use asap_types::post_asap::{ - BoundExpr, ErrorMetric, GroupingStrategy, ProbabilityExpr, SketchKind, + AccuracyMetric, BoundExpr, GroupingStrategy, ProbabilityExpr, SketchKind, }; use asap_types::pre_asap::expr_ir::ArithmeticOpKind; use asap_types::types::AccuracyTarget; @@ -2032,7 +2024,7 @@ mod tests { fn candidates() -> Vec { let root = lower_promql( "topk by (job) (10, sum_over_time(m[1m]))", - AccuracyTarget::epsilon_delta(0.01, 0.001), + AccuracyTarget::max_error(0.01).failure_probability(0.001), ); let inventory = crate::per_computation_candidates::generate_per_computation_candidates( vec![(0, QueryRoot::Operator(root))], @@ -2069,9 +2061,9 @@ mod tests { /// estimates carry no guarantee since #575, so a quantile summary is used.) #[test] fn accuracy_failing_candidate_is_rejected_as_invalid() { - let planned = AccuracyTarget::epsilon_delta(0.01, 0.001); + let planned = AccuracyTarget::max_error(0.01).failure_probability(0.001); let candidates = all_candidates("quantile_over_time(0.99, m[1m])", &planned); - let strict = AccuracyTarget::epsilon_delta(1e-6, 1e-9); + let strict = AccuracyTarget::max_error(1e-6).failure_probability(1e-9); let summaries: Vec<_> = candidates .iter() .filter(|c| has_summary_estimate(c)) @@ -2116,11 +2108,13 @@ mod tests { /// non-negative is invalid. #[test] fn count_min_over_signed_weights_is_rejected_as_invalid() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.001); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.001); let selection = select_plan( &candidates(), &[entry_with( - target.target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + target + .clone() + .accuracy_metric(AccuracyMetric::ABSOLUTE_STREAM_L1), repeating(10_000), )], &data(), @@ -2139,7 +2133,7 @@ mod tests { #[test] fn count_min_over_samples_needs_a_declared_counter() { use asap_types::workload::MetricType; - let target = AccuracyTarget::epsilon_delta(0.01, 0.001); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.001); let with_types = |types: &[(&str, MetricType)]| DataWorkload { metric_types: types.iter().map(|(m, t)| (m.to_string(), *t)).collect(), ..data() @@ -2154,7 +2148,9 @@ mod tests { let selection = select_plan( &candidates(), &[entry_with( - target.target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + target + .clone() + .accuracy_metric(AccuracyMetric::ABSOLUTE_STREAM_L1), repeating(10_000), )], &data, @@ -2188,7 +2184,7 @@ mod tests { fn the_sign_gate_opens_only_at_one() { use crate::accuracy::composition::samples_sign_statistic; let gated = AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Product { factors: vec![ BoundExpr::Constant { value: 0.01 }, @@ -2235,7 +2231,7 @@ mod tests { fn sign_of_counter_samples_is_bound_from_the_declared_type() { use crate::accuracy::composition::samples_sign_statistic; let gated = AccuracyGuarantee::new( - ErrorMetric::Relative, + AccuracyMetric::Relative, BoundExpr::Product { factors: vec![ BoundExpr::Constant { value: 0.01 }, @@ -2277,7 +2273,7 @@ mod tests { #[test] fn the_selected_plan_carries_confirmed_weights() { use asap_types::workload::MetricType; - let target = AccuracyTarget::epsilon_delta(0.01, 0.001); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.001); let root = lower_promql("topk by (job) (10, sum_over_time(m[1m]))", target.clone()); let root = asap_types::ir::schema_support::with_promql_series_identity(&root).unwrap(); let data = DataWorkload { @@ -2375,11 +2371,13 @@ mod tests { #[test] fn per_node_costs_cover_the_dag_and_sum_to_the_total() { let candidates = candidates(); - let target = AccuracyTarget::epsilon_delta(0.01, 0.001); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.001); let selection = select_plan( &candidates, &[entry_with( - target.target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + target + .clone() + .accuracy_metric(AccuracyMetric::ABSOLUTE_STREAM_L1), repeating(10_000), )], &data(), @@ -2510,16 +2508,81 @@ mod tests { /// The KLL candidates of `quantile_over_time(0.99, x[5m])`, per series, /// within rank error 0.01 at δ = 0.01 over `scope`. fn per_series_kll(scope: AccuracyScope) -> (Vec, AccuracyTarget) { - let requested = AccuracyTarget::epsilon_delta(0.01, 0.01).with_metric(ErrorMetric::Rank); + let requested = AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::Rank); let candidates = all_candidates("quantile_over_time(0.99, x[5m])", &requested) .into_iter() .filter(|c| !kll_params(&c.roots).is_empty()) .collect::>(); assert!(!candidates.is_empty()); - let target = requested.target_in(ErrorMetric::Rank).with_scope(scope); + let target = requested + .clone() + .accuracy_metric(AccuracyMetric::Rank) + .accuracy_scope(scope); (candidates, target) } + /// A candidate whose summaries, resized for its query's failure + /// probability, do not rebuild into a plan (here a root with no + /// execution timing) is rejected with the reason; selection goes on and + /// selects another candidate instead of failing. + #[test] + fn a_candidate_that_fails_to_resize_is_rejected_alone() { + let requested = AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::Rank); + let mut candidates = all_candidates("quantile_over_time(0.99, x[5m])", &requested); + let broken = candidates + .iter() + .position(|c| !kll_params(&c.roots).is_empty()) + .expect("a KLL candidate"); + let QueryRoot::Operator(node) = &candidates[broken].roots[0] else { + panic!("an operator root") + }; + let mut untimed = (**node).clone(); + untimed.timing = None; + candidates[broken].roots = vec![QueryRoot::Operator(Rc::new(untimed))]; + let selection = select_plan( + &candidates, + &[entry_with(requested, repeating(10_000))], + &data(), + PlanningModels::builtin(), + ) + .expect("the other candidates are still judged"); + let rejection = selection + .rejected + .iter() + .find(|r| r.id == candidates[broken].id) + .unwrap(); + assert!(!rejection.valid); + assert!( + rejection + .reason + .starts_with("resizing for the failure probability failed"), + "{}", + rejection.reason + ); + assert_ne!(selection.selected, candidates[broken].id); + } + + /// Bad input: a workload with a different number of entries than a + /// candidate has query roots is an error, not a rejection. + #[test] + fn an_entry_count_mismatch_is_an_error() { + let target = AccuracyTarget::max_error(0.01).failure_probability(0.001); + let entries = vec![entry_with(target, repeating(10_000)); 2]; + let Err(SelectionError::EntryCount { + candidate, + roots, + entries, + }) = select_plan(&candidates(), &entries, &data(), PlanningModels::builtin()) + else { + panic!("an entry count mismatch is an error") + }; + assert_eq!((candidate.as_str(), roots, entries), ("P1", 1, 2)); + } + fn kll_params(roots: &[QueryRoot]) -> Vec { roots .iter() @@ -2567,7 +2630,7 @@ mod tests { }; assert_eq!(*failure_probability, Some(0.01 / 10_000.0)); assert!(k > k1, "{k} vs {k1}"); - let per_value = whole.clone().with_scope(AccuracyScope::PerValue); + let per_value = whole.clone().accuracy_scope(AccuracyScope::PerValue); assert_eq!(kll_params(&select(&per_value).plan), stage1); } @@ -2605,13 +2668,16 @@ mod tests { false, )]), ); - assert_eq!(output_values(&QueryRoot::Operator(table), &data()), None); assert_eq!( - output_values( + values_per_result(&QueryRoot::Operator(table), &data()), + None + ); + assert_eq!( + values_per_result( &QueryRoot::Scalar(ScalarExpr::literal_f64(1.0)), &DataWorkload::default() ), - Some(1.0) + Some(1) ); } @@ -2624,16 +2690,19 @@ mod tests { fn a_root_of_at_most_one_row_returns_one_value() { let none = DataWorkload::default(); let root = - |query: &str| QueryRoot::Operator(lower_promql(query, AccuracyTarget::epsilon(0.01))); - assert_eq!(output_values(&root("sum(rate(x[5m]))"), &none), Some(1.0)); - assert_eq!(output_values(&root("topk(5, rate(x[5m]))"), &none), None); + |query: &str| QueryRoot::Operator(lower_promql(query, AccuracyTarget::max_error(0.01))); + assert_eq!(values_per_result(&root("sum(rate(x[5m]))"), &none), Some(1)); + assert_eq!( + values_per_result(&root("topk(5, rate(x[5m]))"), &none), + None + ); assert_eq!( - output_values(&root("sum by (job) (rate(x[5m]))"), &none), + values_per_result(&root("sum by (job) (rate(x[5m]))"), &none), None ); assert_eq!( - output_values(&root("sum by (job) (rate(x[5m]))"), &data()), - Some(10_000.0) + values_per_result(&root("sum by (job) (rate(x[5m]))"), &data()), + Some(10_000) ); let table = || { crate::test_support::scan_from( @@ -2652,10 +2721,13 @@ mod tests { let global = crate::test_support::agg(vec![], count(), table()); let grouped = crate::test_support::agg(vec![0], count(), table()); assert_eq!( - output_values(&QueryRoot::Operator(global.clone()), &none), - Some(1.0) + values_per_result(&QueryRoot::Operator(global.clone()), &none), + Some(1) + ); + assert_eq!( + values_per_result(&QueryRoot::Operator(grouped), &data()), + None ); - assert_eq!(output_values(&QueryRoot::Operator(grouped), &data()), None); let cross = OperatorNode::new_shared(Operator::NonASAP(NonASAPOp::Join { kind: asap_types::ir::operator_properties::JoinKind::Cross, pred: asap_types::ir::Predicate(ScalarExpr::Literal(ScalarValue::Boolean(true))), @@ -2663,7 +2735,10 @@ mod tests { right: global, })) .unwrap(); - assert_eq!(output_values(&QueryRoot::Operator(cross), &none), Some(1.0)); + assert_eq!( + values_per_result(&QueryRoot::Operator(cross), &none), + Some(1) + ); } /// Deployment evidence that every output was measured exact. @@ -2677,7 +2752,7 @@ mod tests { /// Deployment evidence of a small error in a fixed metric for every /// output that is not exact. - struct EveryOutputMeasuredIn(ErrorMetric); + struct EveryOutputMeasuredIn(AccuracyMetric); impl AccuracyEvidenceProvider for EveryOutputMeasuredIn { fn output_guarantee(&self, _output: &QueryRoot) -> Option { @@ -2700,8 +2775,9 @@ mod tests { metric_types: [("m".to_string(), asap_types::workload::MetricType::Counter)].into(), ..data() }; - let target = - AccuracyTarget::epsilon_delta(0.5, 0.5).target_in(ErrorMetric::ABSOLUTE_STREAM_L1); + let target = AccuracyTarget::max_error(0.5) + .failure_probability(0.5) + .accuracy_metric(AccuracyMetric::ABSOLUTE_STREAM_L1); let select = |metric| { select_plan( &candidates(), @@ -2711,7 +2787,7 @@ mod tests { ) .unwrap() }; - let relative = select(ErrorMetric::Relative); + let relative = select(AccuracyMetric::Relative); assert_eq!(relative.selected, "P1"); let mut invalid: Vec<_> = relative.rejected.iter().filter(|r| !r.valid).collect(); invalid.sort_by(|a, b| a.id.cmp(&b.id)); @@ -2726,7 +2802,7 @@ mod tests { rejected.reason ); } - let l1 = select(ErrorMetric::ABSOLUTE_STREAM_L1); + let l1 = select(AccuracyMetric::ABSOLUTE_STREAM_L1); assert!(l1.rejected.iter().all(|r| r.valid), "{:?}", l1.rejected); } @@ -2736,7 +2812,7 @@ mod tests { /// output admits them. #[test] fn combined_estimates_need_evidence_about_the_output() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.001); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.001); let candidates = all_candidates( "quantile_over_time(0.99, m[1m]) - quantile_over_time(0.5, m[1m])", &target, @@ -2751,7 +2827,7 @@ mod tests { let builtin = select_plan( &candidates, &[entry_with( - target.target_in(ErrorMetric::ABSOLUTE), + target.clone().accuracy_metric(AccuracyMetric::ABSOLUTE), repeating(10_000), )], &data(), @@ -2772,7 +2848,7 @@ mod tests { let measured = select_plan( &candidates, &[entry_with( - target.target_in(ErrorMetric::ABSOLUTE), + target.clone().accuracy_metric(AccuracyMetric::ABSOLUTE), repeating(10_000), )], &data(), @@ -2811,7 +2887,7 @@ mod tests { /// query runs is not charged.) #[test] fn exact_quantile_state_costs_more_than_a_sketch_over_large_groups() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.01); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.01); for (query, sketch_wins) in [ ("quantile(0.99, rate(x[5m]))", true), ("quantile by (job) (0.99, rate(x[5m]))", false), @@ -2820,7 +2896,7 @@ mod tests { let selection = select_plan( &candidates, &[entry_with( - target.target_in(ErrorMetric::Rank), + target.clone().accuracy_metric(AccuracyMetric::Rank), repeating(10_000), )], &data(), @@ -3012,7 +3088,7 @@ mod tests { /// sketch is. #[test] fn exact_distinct_count_beats_a_sketch_over_small_groups() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.01); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.01); let one_series = DataWorkload { input_cardinality: Evidence { value: Some(1), @@ -3025,7 +3101,7 @@ mod tests { let selection = select_plan( &candidates, &[entry_with( - target.target_in(ErrorMetric::Relative), + target.clone().accuracy_metric(AccuracyMetric::Relative), repeating(10_000), )], &data, @@ -3068,12 +3144,12 @@ mod tests { /// At δ = 0.001 a KLL candidate meets the target and is valid. #[test] fn kll_candidate_meets_a_failure_probability_below_one_percent() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.001); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.001); let candidates = all_candidates("quantile(0.99, rate(x[5m]))", &target); let selection = select_plan( &candidates, &[entry_with( - target.target_in(ErrorMetric::Rank), + target.clone().accuracy_metric(AccuracyMetric::Rank), repeating(10_000), )], &data(), @@ -3101,7 +3177,7 @@ mod tests { fn scalar_root_guarantees_compose_through_arithmetic() { let node = lower_promql("sum(m)", AccuracyTarget::exact()); let approximate = AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, BoundExpr::Constant { value: 0.01 }, ProbabilityExpr::Constant { value: 0.001 }, vec![], @@ -3122,7 +3198,7 @@ mod tests { Some(approximate.clone()) ); let scaled = root_guarantee(&doubled(with(Some(approximate.clone())))).unwrap(); - assert_eq!(scaled.spec.metric, ErrorMetric::ABSOLUTE); + assert_eq!(scaled.spec.accuracy_metric, AccuracyMetric::ABSOLUTE); assert!((scaled.spec.error.evaluate().unwrap() - 0.02).abs() < 1e-12); assert_eq!(scaled.spec.failure_probability.evaluate(), Some(0.001)); let difference = QueryRoot::Scalar(ScalarExpr::Arithmetic { @@ -3134,7 +3210,7 @@ mod tests { let sum = root_guarantee(&difference).unwrap(); assert!((sum.spec.error.evaluate().unwrap() - 0.02).abs() < 1e-12); let mut rank = approximate; - rank.spec.metric = ErrorMetric::Rank; + rank.spec.accuracy_metric = AccuracyMetric::Rank; assert_eq!(root_guarantee(&doubled(with(Some(rank)))), None); assert!( root_guarantee(&doubled(with(Some(AccuracyGuarantee::exact("exact"))))) @@ -3205,7 +3281,7 @@ mod tests { /// result is a rank interval: no guarantee in the MVP. #[test] fn extremum_and_scaling_over_per_series_estimates_compose() { - let target = AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Relative); + let target = AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Relative); for query in [ "max by (job) (quantile_over_time(0.99, m[1m]))", "2 * max by (job) (quantile_over_time(0.99, m[1m]))", @@ -3218,7 +3294,7 @@ mod tests { assert!(!ddsketch.is_empty(), "{query}"); for candidate in ddsketch { let g = output_guarantee(candidate).expect(query); - assert_eq!(g.spec.metric, ErrorMetric::Relative, "{query}"); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::Relative, "{query}"); assert!(g.spec.error.evaluate().unwrap() <= 0.01, "{query}"); assert!(selection.costs.contains_key(&candidate.id), "{query}"); } @@ -3237,7 +3313,7 @@ mod tests { /// guarantee in the MVP, so those are not. #[test] fn lipschitz_functions_keep_and_shifts_and_comparisons_drop_a_relative_bound() { - let target = AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Relative); + let target = AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Relative); let q = "quantile_over_time(0.99, m[1m])"; for (query, kept) in [ (format!("-{q}"), true), @@ -3258,7 +3334,7 @@ mod tests { let g = output_guarantee(candidate); if kept { let g = g.expect(&query); - assert_eq!(g.spec.metric, ErrorMetric::Relative, "{query}"); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::Relative, "{query}"); assert!(g.spec.error.evaluate().unwrap() <= 0.01, "{query}"); assert!(selection.costs.contains_key(&candidate.id), "{query}"); } else { @@ -3276,7 +3352,7 @@ mod tests { /// the query, and the fold has no guarantee. #[test] fn a_fold_over_subquery_estimates_counts_its_steps() { - let target = AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Relative); + let target = AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Relative); let q = "quantile_over_time(0.99, m[1m])"; let (candidates, selection) = run( &format!("max_over_time({q}[10m:1m])"), @@ -3291,7 +3367,7 @@ mod tests { assert!(!ddsketch.is_empty()); for candidate in ddsketch { let g = output_guarantee(candidate).expect("a guarantee"); - assert_eq!(g.spec.metric, ErrorMetric::Relative); + assert_eq!(g.spec.accuracy_metric, AccuracyMetric::Relative); assert!(g.spec.error.evaluate().unwrap() <= 0.01); let ProbabilityExpr::Scaled { count, .. } = &g.spec.failure_probability else { panic!("{g:?}"); @@ -3317,7 +3393,7 @@ mod tests { /// are labels, not the estimates (#669). #[test] fn counting_estimates_by_a_label_stays_exact() { - let target = AccuracyTarget::epsilon(0.01); + let target = AccuracyTarget::max_error(0.01); let (candidates, _) = run( "count by (le) (quantile_over_time(0.99, m[1m]))", &target, @@ -3350,7 +3426,7 @@ mod tests { /// symbolic and plan selection treats it as unmet. #[test] fn average_over_relative_estimates_needs_the_sign() { - let target = AccuracyTarget::epsilon(0.01); + let target = AccuracyTarget::max_error(0.01); let (candidates, selection) = run( "avg by (job) (quantile_over_time(0.99, m[1m]))", &target, @@ -3374,7 +3450,9 @@ mod tests { /// workload's input cardinality; without one, the target is unmet. #[test] fn stage3_binds_the_input_row_count_from_the_data_workload() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.001).with_metric(ErrorMetric::Relative); + let target = AccuracyTarget::max_error(0.01) + .failure_probability(0.001) + .accuracy_metric(AccuracyMetric::Relative); let query = "max by (job) (quantile_over_time(0.99, m[1m]))"; let ddsketch = |candidates: &[PhysicalCandidate]| -> Vec { candidates @@ -3420,8 +3498,8 @@ mod tests { /// twice that, is checked against a looser one. #[test] fn ddsketch_ratio_meets_its_target_only_with_operand_domains() { - let planned = AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Relative); - let target = AccuracyTarget::epsilon(0.05).with_metric(ErrorMetric::Relative); + let planned = AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Relative); + let target = AccuracyTarget::max_error(0.05).accuracy_metric(AccuracyMetric::Relative); let query = "quantile_over_time(0.99, m[1m]) / quantile_over_time(0.5, m[1m])"; let run = |models| run_selecting(query, &planned, &target, &data(), models); let (candidates, without) = run(PlanningModels::builtin()); @@ -3443,7 +3521,7 @@ mod tests { ScalarExpr::PromqlScalarFromVector(Rc::new((*node).clone().with_guarantee(Some(guarantee)))) } - fn guarantee(metric: ErrorMetric, bound: f64, delta: f64) -> AccuracyGuarantee { + fn guarantee(metric: AccuracyMetric, bound: f64, delta: f64) -> AccuracyGuarantee { AccuracyGuarantee::new( metric, BoundExpr::Constant { value: bound }, @@ -3507,11 +3585,11 @@ mod tests { ArithmeticOpKind::Sub, raw( "count by (job) (a)", - guarantee(ErrorMetric::ABSOLUTE_STREAM_L1, 0.01, 0.001), + guarantee(AccuracyMetric::ABSOLUTE_STREAM_L1, 0.01, 0.001), ), raw( "count by (job) (b)", - guarantee(ErrorMetric::ABSOLUTE_STREAM_L1, 0.02, 0.001), + guarantee(AccuracyMetric::ABSOLUTE_STREAM_L1, 0.02, 0.001), ), ); let models = PlanningModels::builtin(); @@ -3533,7 +3611,7 @@ mod tests { fn stage3_binds_a_stream_norm_only_for_the_raw_value_column() { use asap_types::post_asap::SummaryInputExpr; use asap_types::pre_asap::ColumnRef; - let frequency = guarantee(ErrorMetric::ABSOLUTE_STREAM_L1, 0.01, 0.001); + let frequency = guarantee(AccuracyMetric::ABSOLUTE_STREAM_L1, 0.01, 0.001); let doubled = |summary: Rc| { scalar_root( ArithmeticOpKind::Mul, @@ -3578,8 +3656,8 @@ mod tests { #[test] fn stage3_binds_products_and_quotients_from_operand_domains() { let (x, y) = ( - scalar_of("sum(a)", guarantee(ErrorMetric::ABSOLUTE, 0.01, 0.001)), - scalar_of("sum(b)", guarantee(ErrorMetric::ABSOLUTE, 0.02, 0.001)), + scalar_of("sum(a)", guarantee(AccuracyMetric::ABSOLUTE, 0.01, 0.001)), + scalar_of("sum(b)", guarantee(AccuracyMetric::ABSOLUTE, 0.02, 0.001)), ); let product = scalar_root(ArithmeticOpKind::Mul, x.clone(), y.clone()); let quotient = scalar_root(ArithmeticOpKind::Div, x, y); @@ -3599,8 +3677,8 @@ mod tests { } let ratio = scalar_root( ArithmeticOpKind::Div, - scalar_of("sum(a)", guarantee(ErrorMetric::Relative, 0.01, 0.0)), - scalar_of("sum(b)", guarantee(ErrorMetric::Relative, 0.01, 0.0)), + scalar_of("sum(a)", guarantee(AccuracyMetric::Relative, 0.01, 0.0)), + scalar_of("sum(b)", guarantee(AccuracyMetric::Relative, 0.01, 0.0)), ); let g = bound_root_guarantee(&ratio, &data(), &PlanningModels::builtin()).unwrap(); assert!(g.spec.error.has_unknown(), "{g:?}"); @@ -3612,8 +3690,8 @@ mod tests { /// inherited it, so the candidate is selectable. #[test] fn a_scaled_ratio_binds_at_its_division() { - let planned = AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Relative); - let target = AccuracyTarget::epsilon(0.05).with_metric(ErrorMetric::Relative); + let planned = AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Relative); + let target = AccuracyTarget::max_error(0.05).accuracy_metric(AccuracyMetric::Relative); let query = "(quantile_over_time(0.99, m[1m]) / quantile_over_time(0.5, m[1m])) \ / max_over_time(m[1m])"; let (candidates, selection) = run_selecting( @@ -3640,7 +3718,7 @@ mod tests { /// have expired) is not used; timeless declared evidence is. #[test] fn stage3_binds_only_fresh_input_cardinality() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.001); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.001); let query = "max by (job) (quantile_over_time(0.99, m[1m]))"; let expiring = DataWorkload { input_cardinality: Evidence { @@ -3670,7 +3748,7 @@ mod tests { }; let root = lower_promql( "quantile_over_time(0.99, m[1m])", - AccuracyTarget::epsilon_delta(0.01, 0.01), + AccuracyTarget::max_error(0.01).failure_probability(0.01), ); let inventory = crate::per_computation_candidates::generate_per_computation_candidates( vec![(0, QueryRoot::Operator(root))], @@ -4021,7 +4099,9 @@ mod tests { /// The p99 target [`pane_candidates`] are sized for. fn p99_target() -> AccuracyTarget { - AccuracyTarget::epsilon_delta(0.01, 0.001).with_metric(ErrorMetric::Rank) + AccuracyTarget::max_error(0.01) + .failure_probability(0.001) + .accuracy_metric(AccuracyMetric::Rank) } /// A predictable p99 query evaluated every minute, with no latency bound. diff --git a/crates/asap-aware-mapping/src/test_support.rs b/crates/asap-aware-mapping/src/test_support.rs index 39cc96169..c34cb8f9b 100644 --- a/crates/asap-aware-mapping/src/test_support.rs +++ b/crates/asap-aware-mapping/src/test_support.rs @@ -7,7 +7,7 @@ use asap_types::ir::OperatorNode; use asap_types::types::AccuracyTarget; use asap_types::workload::{ BatchEntry, DataWorkload, DurationMs, Evidence, PlanningWorkload, Predictability, Query, - QueryLanguage, QueryRequirements, QueryWorkload, TimeSelection, + QueryLanguage, QueryRequirements, QueryWorkload, }; pub(crate) fn lower_promql(query: &str, accuracy: AccuracyTarget) -> Rc { @@ -23,7 +23,6 @@ pub(crate) fn lower_promql(query: &str, accuracy: AccuracyTarget) -> Rc OperatorNode { use asap_types::ir::{ASAPOp, QueryRoot}; use asap_types::post_asap::{FieldDataType, SketchAlgorithm}; - let target = AccuracyTarget::epsilon_delta(0.01, 0.01); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.01); let root = lower_promql(query, target.clone()); let run = crate::pass::run_stage1_to_stage3( vec![(0, QueryRoot::Operator(root))], diff --git a/crates/asap-aware-mapping/tests/per_computation_candidates.rs b/crates/asap-aware-mapping/tests/per_computation_candidates.rs index 420072c01..25231a576 100644 --- a/crates/asap-aware-mapping/tests/per_computation_candidates.rs +++ b/crates/asap-aware-mapping/tests/per_computation_candidates.rs @@ -20,7 +20,7 @@ use asap_types::{ use std::rc::Rc; fn approximate() -> AccuracyTarget { - AccuracyTarget::epsilon_delta(0.05, 0.01) + AccuracyTarget::max_error(0.05).failure_probability(0.01) } fn candidates_for(intent: AggIntent) -> Vec { computation_candidates_for_intent(&intent) @@ -180,7 +180,8 @@ fn scalar_root_producers_are_discovered_once() { /// #509 Example 1 Q1 is exact: each target's one exact option is its exact /// accumulator state (grouped sum, rate); no plain exact form and no summary. -/// One ImplicitExact PromQL query lowered through the workload frontend. +/// One PromQL query with the default exact target, lowered through the +/// workload frontend. fn exact_promql(query: &str) -> Vec { use asap_types::workload::{ BatchEntry, PlanningWorkload, Query, QueryLanguage, QueryRequirements, QueryWorkload, @@ -197,7 +198,6 @@ fn exact_promql(query: &str) -> Vec { predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: Default::default(), }]), repeating_queries: None, }, @@ -300,9 +300,10 @@ fn assigned_physical_annotation_asap_input_and_invalid_accuracy_are_rejected() { generate_per_computation_candidates(vec![(0, QueryRoot::Operator(summary))]), Err(PerComputationCandidateError::ContainsAsap) )); - let epsilon_delta = |epsilon, delta| AccuracyTarget::epsilon_delta(epsilon, delta); + let epsilon_delta = + |epsilon, delta| AccuracyTarget::max_error(epsilon).failure_probability(delta); for target in [ - AccuracyTarget::epsilon(f64::NAN), + AccuracyTarget::max_error(f64::NAN), epsilon_delta(0.0, 0.01), epsilon_delta(-0.1, 0.01), epsilon_delta(1.0, 0.01), @@ -378,7 +379,7 @@ fn summary_candidates_are_unsized() { accuracy: approximate(), }); let tight = candidates_for(AggIntent::Count { - accuracy: AccuracyTarget::epsilon_delta(0.0001, 0.0001), + accuracy: AccuracyTarget::max_error(0.0001).failure_probability(0.0001), }); assert_eq!(loose, tight); } @@ -660,7 +661,7 @@ fn global_aggregate_having_stays_above() { } } -/// #509 Example 1 Q2 under ImplicitExact: the top-k's one option is plain +/// #509 Example 1 Q2 under the default exact target: the top-k's one option is plain /// `Exact`, its heap summaries pruned `RequiresExact`; `sum_over_time` gets /// its exact Sum accumulator. #[test] diff --git a/crates/asap-aware-mapping/tests/physical_handoff_cost.rs b/crates/asap-aware-mapping/tests/physical_handoff_cost.rs index 141750f61..0c267a36c 100644 --- a/crates/asap-aware-mapping/tests/physical_handoff_cost.rs +++ b/crates/asap-aware-mapping/tests/physical_handoff_cost.rs @@ -6,9 +6,7 @@ use asap_aware_mapping::physical_operator_statistics::{ ComparisonScope, EdgeStatistics, OperatorStatistics, ScanSelection, UnaryEdgeStatistics, }; use asap_types::ir::operator_properties::Source; -use asap_types::workload::{ - DataArrival, DurationMs, QueryRecurrence, QueryTimeScope, TimeSelection, TimestampMs, -}; +use asap_types::workload::{DataArrival, DurationMs, QueryRecurrence, TimestampMs}; use std::collections::HashMap; fn fixture() -> (EvidenceBackedPhysicalDAG, ComparisonScope) { @@ -28,11 +26,6 @@ fn fixture() -> (EvidenceBackedPhysicalDAG, ComparisonScope) { invocations: 3, execute_at: None, }, - time_selection: TimeSelection { - scope: QueryTimeScope::Longitudinal, - lookback: Some(DurationMs(1000)), - as_of: Some(TimestampMs(100)), - }, sources: vec![coverage.clone()], }; let edge = EdgeStatistics { diff --git a/crates/asap-aware-mapping/tests/storage_io.rs b/crates/asap-aware-mapping/tests/storage_io.rs index 811328f6b..a9e749fd1 100644 --- a/crates/asap-aware-mapping/tests/storage_io.rs +++ b/crates/asap-aware-mapping/tests/storage_io.rs @@ -6,9 +6,7 @@ use asap_aware_mapping::physical_operator_statistics::{ ComparisonScope, EdgeStatistics, OperatorStatistics, ScanSelection, UnaryEdgeStatistics, }; use asap_types::ir::operator_properties::Source; -use asap_types::workload::{ - DataArrival, DurationMs, QueryRecurrence, QueryTimeScope, TimeSelection, TimestampMs, -}; +use asap_types::workload::{DataArrival, DurationMs, QueryRecurrence, TimestampMs}; use std::collections::HashMap; fn fixture() -> (EvidenceBackedPhysicalDAG, ComparisonScope) { @@ -28,11 +26,6 @@ fn fixture() -> (EvidenceBackedPhysicalDAG, ComparisonScope) { invocations: 3, execute_at: None, }, - time_selection: TimeSelection { - scope: QueryTimeScope::Longitudinal, - lookback: Some(DurationMs(1000)), - as_of: Some(TimestampMs(100)), - }, sources: vec![coverage.clone()], }; let edge = EdgeStatistics { diff --git a/crates/asap-physical-operators/src/physical_planner/candidates.rs b/crates/asap-physical-operators/src/physical_planner/candidates.rs index 5eaedf98e..9bb1b5b47 100644 --- a/crates/asap-physical-operators/src/physical_planner/candidates.rs +++ b/crates/asap-physical-operators/src/physical_planner/candidates.rs @@ -356,7 +356,6 @@ mod tests { predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, diff --git a/crates/asap-physical-operators/tests/current_series_heap.rs b/crates/asap-physical-operators/tests/current_series_heap.rs index 9d908ca95..a180ccaab 100644 --- a/crates/asap-physical-operators/tests/current_series_heap.rs +++ b/crates/asap-physical-operators/tests/current_series_heap.rs @@ -311,7 +311,6 @@ fn planner_current_series_candidate_compiles_with_dynamic_identity() { predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, diff --git a/crates/asap-physical-operators/tests/deployment_computation.rs b/crates/asap-physical-operators/tests/deployment_computation.rs index c97b2225f..780d85831 100644 --- a/crates/asap-physical-operators/tests/deployment_computation.rs +++ b/crates/asap-physical-operators/tests/deployment_computation.rs @@ -31,7 +31,6 @@ fn lower_with(query: &str, accuracy: AccuracyTarget) -> Rc Rc { predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, @@ -62,7 +61,7 @@ const CURRENT_SERIES_TOPK: &str = "topk by(job)(1, m)"; #[test] fn selection_never_commits_a_series_identity_heap() { - let accuracy = AccuracyTarget::epsilon(0.1); + let accuracy = AccuracyTarget::max_error(0.1); let root = lower(CURRENT_SERIES_TOPK, &accuracy); assert!(!carries_identity(&selected_dag(root, accuracy))); } diff --git a/crates/asap-physical-operators/tests/precompute_candidates.rs b/crates/asap-physical-operators/tests/precompute_candidates.rs index 81859d65b..fec500f20 100644 --- a/crates/asap-physical-operators/tests/precompute_candidates.rs +++ b/crates/asap-physical-operators/tests/precompute_candidates.rs @@ -38,7 +38,6 @@ fn exact_promql_root(query: &str) -> std::rc::Rc, algorithm: &SketchAlgorithm, ) -> Rc { - logical_candidates(root, AccuracyTarget::epsilon(0.1)) + logical_candidates(root, AccuracyTarget::max_error(0.1)) .into_iter() .find(|candidate| builds(&compile_physical_asap_dag(candidate).unwrap(), algorithm)) .unwrap_or_else(|| panic!("missing {algorithm:?} candidate")) @@ -46,7 +46,7 @@ fn heap_candidate( fn assert_weighted_binding(algorithm: SketchAlgorithm) { let root = lower_promql( "topk by(job)(2, sum by(service, job)(rate(m[1m])))", - AccuracyTarget::epsilon(0.1), + AccuracyTarget::max_error(0.1), ) .unwrap(); let plan = heap_candidate(root, &algorithm); @@ -177,7 +177,7 @@ fn assert_weighted_binding(algorithm: SketchAlgorithm) { use asap_frontend_promql::lower_promql_workload; use planner_types::workload::{ BatchEntry, DataWorkload, DurationMs, Evidence as WorkloadEvidence, PlanningWorkload, - Predictability, Query, QueryLanguage, QueryRequirements, QueryWorkload, TimeSelection, + Predictability, Query, QueryLanguage, QueryRequirements, QueryWorkload, }; pub fn lower_promql( query: &str, @@ -195,7 +195,6 @@ pub fn lower_promql( predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, @@ -256,8 +255,11 @@ fn direct_rate_topk_preserves_dynamic_unreferenced_labels() { use asap_physical_operators::physical_planner::promql_rows::{ decode_series_identity, series_row, with_series_identity, SERIES_IDENTITY_COLUMN, }; - let logical = - lower_promql("topk by(job)(2, rate(m[1m]))", AccuracyTarget::epsilon(0.1)).unwrap(); + let logical = lower_promql( + "topk by(job)(2, rate(m[1m]))", + AccuracyTarget::max_error(0.1), + ) + .unwrap(); let root = with_series_identity(&logical).unwrap(); for algorithm in [SketchAlgorithm::CountSketchWithHeap] { let candidate = &heap_candidate(Rc::clone(&root), &algorithm); @@ -558,7 +560,11 @@ fn maintained_rate_heap_compiles_fixed_window_precompute() { }; let root = Rc::new( with_series_identity( - &lower_promql("topk by(job)(2, rate(m[1m]))", AccuracyTarget::epsilon(0.1)).unwrap(), + &lower_promql( + "topk by(job)(2, rate(m[1m]))", + AccuracyTarget::max_error(0.1), + ) + .unwrap(), ) .unwrap(), ); diff --git a/crates/devtools/src/bin/sketch_coverage.rs b/crates/devtools/src/bin/sketch_coverage.rs index 9bfae2a23..d2d198d58 100644 --- a/crates/devtools/src/bin/sketch_coverage.rs +++ b/crates/devtools/src/bin/sketch_coverage.rs @@ -219,7 +219,7 @@ fn parse_ingestion_interval() -> u64 { async fn main() { let epsilon = parse_epsilon(); let interval_ms = parse_ingestion_interval(); - let accuracy = AccuracyTarget::epsilon(epsilon); + let accuracy = AccuracyTarget::max_error(epsilon); let mut results = Vec::new(); // ── PromQL corpora — same set as `variant_coverage` ── diff --git a/crates/devtools/src/bin/stage_pipeline.rs b/crates/devtools/src/bin/stage_pipeline.rs index 46509ee71..41849da67 100644 --- a/crates/devtools/src/bin/stage_pipeline.rs +++ b/crates/devtools/src/bin/stage_pipeline.rs @@ -80,7 +80,7 @@ use asap_devtools::SqlCatalog; use asap_types::ir::flat::{flatten, FlatDag}; use asap_types::ir::QueryRoot; use asap_types::pre_asap::schema::{DataType, Field, Schema}; -use asap_types::types::{default_metric_for_root, AccuracyTarget, ErrorMetric}; +use asap_types::types::{default_metric_for_root, AccuracyMetric, AccuracyTarget}; use asap_types::workload::{ BatchEntry, DataArrival, DataWorkload, DurationMs, PlanningWorkload, Query, QueryLanguage, QueryRecurrence, QueryRequirements, QueryWorkload, RepeatedDemand, SqlDialect, @@ -170,7 +170,7 @@ fn run(args: Vec) -> Result<(), Failure> { let mut queries: Vec<(QueryLanguage, String, Option)> = Vec::new(); let mut tables = Vec::new(); let (mut epsilon, mut delta, mut interval_ms) = (None, None, 15_000u64); - let mut metric: Option = None; + let mut metric: Option = None; let mut max_displayed = 64usize; let mut max_combinations = MAX_COMBINATIONS; let mut out = None; @@ -240,13 +240,15 @@ fn run(args: Vec) -> Result<(), Failure> { (None, false) => { let accuracy = match (epsilon, delta) { (None, None) => AccuracyTarget::exact(), - (Some(epsilon), None) => AccuracyTarget::epsilon(epsilon), - (Some(epsilon), Some(delta)) => AccuracyTarget::epsilon_delta(epsilon, delta), + (Some(epsilon), None) => AccuracyTarget::max_error(epsilon), + (Some(epsilon), Some(delta)) => { + AccuracyTarget::max_error(epsilon).failure_probability(delta) + } (None, Some(_)) => return Err("--delta needs --epsilon".into()), }; let accuracy = match metric { Some(_) if epsilon.is_none() => return Err("--metric needs --epsilon".into()), - Some(metric) => accuracy.with_metric(metric), + Some(metric) => accuracy.accuracy_metric(metric), None => accuracy, }; let catalog = sql_catalog(&tables)?; @@ -410,13 +412,11 @@ fn plan_selection_json( let queries: Vec = queries .iter() .map(|accepted| { - accepted.as_ref().map_or(Value::Null, |accepted| { - json!({ - "target_metric": accepted.target_metric, - "accepted_metric": accepted.accepted_metric, - "sketches": accepted.sketches, - "summary": accepted.to_string(), - }) + json!({ + "target_metric": accepted.target_metric, + "accepted_metric": accepted.accepted_metric, + "sketches": accepted.sketches, + "summary": accepted.to_string(), }) }) .collect(); @@ -483,7 +483,7 @@ fn with_default_metrics(mut queries: Vec, roots: &[QueryRoot]) -> Vec(accuracy.clone()) else { continue; }; - if target.metric.is_none() && !target.is_exact() { + if target.accuracy_metric.is_none() && !target.is_exact() { query["requirements"]["default_metric"] = json!(default_metric_for_root(root)); } } @@ -565,7 +565,6 @@ fn query_batch( predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: Default::default(), }) .collect(), ), diff --git a/crates/devtools/src/lib.rs b/crates/devtools/src/lib.rs index 7ad7f8e9c..b2e40a6d3 100644 --- a/crates/devtools/src/lib.rs +++ b/crates/devtools/src/lib.rs @@ -35,7 +35,7 @@ pub fn lower_promql_with_data_ingestion_interval( ) -> Result, PromqlError> { use asap_types::workload::{ BatchEntry, DataWorkload, DurationMs, Evidence, PlanningWorkload, Predictability, Query, - QueryRequirements, QueryWorkload, TimeSelection, + QueryRequirements, QueryWorkload, }; let workload = PlanningWorkload { @@ -50,7 +50,6 @@ pub fn lower_promql_with_data_ingestion_interval( predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, diff --git a/crates/devtools/src/planner_layering_examples.rs b/crates/devtools/src/planner_layering_examples.rs index f1ab6ac61..da1824ac4 100644 --- a/crates/devtools/src/planner_layering_examples.rs +++ b/crates/devtools/src/planner_layering_examples.rs @@ -2,12 +2,11 @@ //! (`docs/design_docs/proposals/planner-layering.md`), shared by //! `stage_pipeline` and the planner-layering acceptance tests. -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::{AccuracyMetric, AccuracyTarget}; use asap_types::workload::{ - BatchEntry, DataArrival, DataDistribution, DataWorkload, DurationMs, Evidence, EvidenceSource, - MetricType, PlanningWorkload, Predictability, Query, QueryLanguage, QueryRequirements, - QueryTimeScope, QueryWorkload, Rate, RepeatedDemand, RepeatingEntry, RepetitionInterval, - SqlDialect, TimeSelection, TimestampMs, + BatchEntry, DataArrival, DataWorkload, DurationMs, Evidence, EvidenceSource, MetricType, + PlanningWorkload, Predictability, Query, QueryLanguage, QueryRequirements, QueryWorkload, Rate, + RepeatedDemand, RepeatingEntry, RepetitionInterval, SqlDialect, TimestampMs, }; pub fn declared(value: T) -> Evidence { @@ -28,14 +27,12 @@ pub const T_MS: u64 = 1_767_225_600_000; pub fn shared_data_workload(arrival: DataArrival) -> DataWorkload { DataWorkload { data_ingestion_interval: declared(DurationMs(15_000)), - ingestion_volume: Evidence::default(), // Data at rest has no ingestion (`validate` rejects a positive rate). ingestion_rate: match arrival { DataArrival::AtRest => Evidence::default(), _ => declared(Rate(1_000_000.0 / 15.0)), }, input_cardinality: declared(1_000_000), - distribution: declared(DataDistribution::Zipf), stream_norms: Evidence::default(), max_distinct_per_evaluation: Evidence::default(), arrival, @@ -58,11 +55,6 @@ pub fn example1() -> PlanningWorkload { max_latency_ms, }, predictability: Predictability::Predictable { known_at: None }, - time_selection: TimeSelection { - scope: QueryTimeScope::RealTime, - lookback: Some(DurationMs(MINUTE_MS)), - as_of: None, - }, }; PlanningWorkload { query_workload: QueryWorkload { @@ -72,7 +64,7 @@ pub fn example1() -> PlanningWorkload { panel(EXAMPLE1_Q1, AccuracyTarget::exact(), None), panel( EXAMPLE1_Q2, - AccuracyTarget::epsilon_delta(0.01, 0.001), + AccuracyTarget::max_error(0.01).failure_probability(0.001), Some(100.0), ), ]), @@ -133,15 +125,10 @@ pub fn example2() -> PlanningWorkload { query: Query((*query).into()), demand: RepeatedDemand::FixedInterval(RepetitionInterval(10_000)), requirements: QueryRequirements { - accuracy: AccuracyTarget::epsilon_delta(epsilon, delta), + accuracy: AccuracyTarget::max_error(epsilon).failure_probability(delta), max_latency_ms: None, }, predictability: Predictability::Predictable { known_at: None }, - time_selection: TimeSelection { - scope: QueryTimeScope::RealTime, - lookback: Some(DurationMs(MINUTE_MS)), - as_of: None, - }, }) .collect(), ), @@ -154,7 +141,8 @@ pub fn example2() -> PlanningWorkload { } } -/// Pattern A's five queries: (PromQL, `lookback`, T − `as_of`). +/// Pattern A's five queries: (PromQL, its range window in ms, its offset +/// before T in ms); the window and offset restate the query text. pub const PATTERN_A: [(&str, u64, u64); 5] = [ ("quantile_over_time(0.99, latency_ms[5y])", 5 * YEAR_MS, 0), ("quantile_over_time(0.99, latency_ms[1y])", YEAR_MS, 0), @@ -187,7 +175,9 @@ pub enum PatternARecurrence { fn pattern_a_requirements() -> QueryRequirements { QueryRequirements { - accuracy: AccuracyTarget::epsilon_delta(0.005, 0.01).with_metric(ErrorMetric::Rank), + accuracy: AccuracyTarget::max_error(0.005) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::Rank), max_latency_ms: None, } } @@ -199,17 +189,12 @@ pub fn pattern_a(recurrence: PatternARecurrence, arrival: DataArrival) -> Planni Some( PATTERN_A .iter() - .map(|&(query, lookback, before_t)| BatchEntry { + .map(|&(query, ..)| BatchEntry { query: Query(query.into()), requirements: pattern_a_requirements(), predictability: Predictability::AdHoc, invocations: 1, execute_at: Some(TimestampMs(T_MS)), - time_selection: TimeSelection { - scope: QueryTimeScope::Longitudinal, - lookback: Some(DurationMs(lookback)), - as_of: Some(TimestampMs(T_MS - before_t)), - }, }) .collect(), ), @@ -220,7 +205,7 @@ pub fn pattern_a(recurrence: PatternARecurrence, arrival: DataArrival) -> Planni Some( PATTERN_A .iter() - .map(|&(query, lookback, _)| RepeatingEntry { + .map(|&(query, ..)| RepeatingEntry { query: Query(query.into()), demand: RepeatedDemand::FixedInterval(RepetitionInterval( (30 * 24 * 60 * MINUTE_MS) as u32, @@ -229,11 +214,6 @@ pub fn pattern_a(recurrence: PatternARecurrence, arrival: DataArrival) -> Planni predictability: Predictability::Predictable { known_at: Some(TimestampMs(T_MS)), }, - time_selection: TimeSelection { - scope: QueryTimeScope::Longitudinal, - lookback: Some(DurationMs(lookback)), - as_of: None, - }, }) .collect(), ), @@ -265,16 +245,12 @@ pub fn pattern_b() -> PlanningWorkload { PATTERN_B_INTERVAL_MS as u32, )), requirements: QueryRequirements { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.01) - .with_metric(ErrorMetric::Rank), + accuracy: AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::Rank), max_latency_ms: Some(200.0), }, predictability: Predictability::Predictable { known_at: None }, - time_selection: TimeSelection { - scope: QueryTimeScope::RealTime, - lookback: Some(DurationMs(PATTERN_B_WINDOW_MS)), - as_of: None, - }, }]), }, data_workload: Some(shared_data_workload(DataArrival::ContinuouslyIngesting)), diff --git a/crates/devtools/tests/stage_pipeline.rs b/crates/devtools/tests/stage_pipeline.rs index 4a84c3ba0..96dadc012 100644 --- a/crates/devtools/tests/stage_pipeline.rs +++ b/crates/devtools/tests/stage_pipeline.rs @@ -611,7 +611,7 @@ fn a_quantile_target_without_a_metric_accepts_rank_or_relative() { let unset = document_of(&query); assert_eq!( unset["workload"]["queries"][0]["requirements"]["accuracy"], - serde_json::json!({"error": 0.01, "scope": "whole_result"}) + serde_json::json!({"error": 0.01, "accuracy_scope": "whole_result"}) ); // A quantile has no single default metric to show. assert!(unset["workload"]["queries"][0]["requirements"] diff --git a/crates/frontend-metricsql/src/lib.rs b/crates/frontend-metricsql/src/lib.rs index 1dc746246..04c93f20d 100644 --- a/crates/frontend-metricsql/src/lib.rs +++ b/crates/frontend-metricsql/src/lib.rs @@ -10,7 +10,7 @@ use asap_types::pre_asap::{ AggIntent, ArithmeticOpKind, BinaryOpKind, ColumnRef, CompareOpKind, GroupKeys, PromQLVectorSetOpKind, Reduction, ScalarValue, Source, }; -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::AccuracyTarget; use asap_types::workload::{DurationMs, PlanningWorkload, QueryLanguage, WorkloadError}; use metricsql_parser::ast::{AggregateModifier, DurationExpr, Expr, MetricExpr, RollupExpr}; use metricsql_parser::functions::{AggregateFunction, BuiltinFunction, RollupFunction}; @@ -221,7 +221,7 @@ impl Lowerer { RollupFunction::MaxOverTime => AggIntent::Max { col: None }, RollupFunction::SumOverTime => AggIntent::Sum { col: None }, RollupFunction::CountOverTime => AggIntent::Count { - accuracy: self.accuracy.target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + accuracy: self.accuracy.clone(), }, RollupFunction::StddevOverTime => AggIntent::StdDev { col: None, @@ -245,7 +245,7 @@ impl Lowerer { RollupFunction::QuantileOverTime => AggIntent::Quantile { col: None, q: number_arg(&function.args, 0)?, - accuracy: self.accuracy.for_quantile(), + accuracy: self.accuracy.clone(), }, _ => { return Err(unsupported(format!( @@ -295,7 +295,7 @@ impl Lowerer { AggregateFunction::Max => AggIntent::Max { col: None }, // `count(v)` counts series, not distinct sample values. AggregateFunction::Count => AggIntent::Count { - accuracy: self.accuracy.target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + accuracy: self.accuracy.clone(), }, AggregateFunction::StdDev => AggIntent::StdDev { col: None, @@ -309,7 +309,7 @@ impl Lowerer { AggregateFunction::Quantile => AggIntent::Quantile { col: None, q: number_arg(&expr.args, 0)?, - accuracy: self.accuracy.for_quantile(), + accuracy: self.accuracy.clone(), }, _ => return Err(unsupported(format!("aggregate `{}`", expr.name()))), }; diff --git a/crates/frontend-metricsql/tests/lowering.rs b/crates/frontend-metricsql/tests/lowering.rs index 58f88c2bf..089e1d473 100644 --- a/crates/frontend-metricsql/tests/lowering.rs +++ b/crates/frontend-metricsql/tests/lowering.rs @@ -12,7 +12,7 @@ use asap_types::types::AccuracyTarget; const INTERVAL: Duration = Duration::from_secs(15); fn lower(query: &str) -> Rc { - lower_metricsql(query, AccuracyTarget::epsilon(0.01), INTERVAL).unwrap() + lower_metricsql(query, AccuracyTarget::max_error(0.01), INTERVAL).unwrap() } #[test] diff --git a/crates/frontend-metricsql/tests/lowering_semantics.rs b/crates/frontend-metricsql/tests/lowering_semantics.rs index 32ad27ce5..438cf1db4 100644 --- a/crates/frontend-metricsql/tests/lowering_semantics.rs +++ b/crates/frontend-metricsql/tests/lowering_semantics.rs @@ -7,7 +7,7 @@ use asap_types::pre_asap::AggIntent; use asap_types::types::AccuracyTarget; use asap_types::workload::{ BatchEntry, DataWorkload, DurationMs, Evidence, PlanningWorkload, Query, QueryLanguage, - QueryRequirements, QueryWorkload, TimeSelection, + QueryRequirements, QueryWorkload, }; const INTERVAL: Duration = Duration::from_secs(15); @@ -29,7 +29,6 @@ fn workload(language: QueryLanguage, query: &str) -> PlanningWorkload { predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, diff --git a/crates/frontend-promql/src/lib.rs b/crates/frontend-promql/src/lib.rs index a86d64ad9..17601ffb6 100644 --- a/crates/frontend-promql/src/lib.rs +++ b/crates/frontend-promql/src/lib.rs @@ -159,7 +159,7 @@ mod tests { use asap_types::ir::{NonASAPOp, TimeRangeKind}; use asap_types::workload::{ BatchEntry, DataWorkload, Evidence, PlanningWorkload, Query, QueryRequirements, - QueryWorkload, TimeSelection, + QueryWorkload, }; use super::*; @@ -174,7 +174,6 @@ mod tests { predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, diff --git a/crates/frontend-promql/src/promql.rs b/crates/frontend-promql/src/promql.rs index be73a867e..82cd8df61 100644 --- a/crates/frontend-promql/src/promql.rs +++ b/crates/frontend-promql/src/promql.rs @@ -79,7 +79,7 @@ use asap_types::pre_asap::agg_intent::{topk, AggIntent, TimeFunc}; use asap_types::pre_asap::{ ArithmeticOpKind, ColumnRef, CompareOpKind, InfoMatcher, SampleKind, ScalarValue, }; -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::AccuracyTarget; /// Every scalar expression this front end builds follows PromQL's numeric rules. const PROMQL: ExprSemantics = ExprSemantics::Promql; @@ -785,7 +785,7 @@ fn build_over_sub_dag(outer: Outer, keys: Vec, child: Unresolved) -> keys, AggIntent::TopK { k: k as usize, - accuracy: current_accuracy().target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + accuracy: current_accuracy(), }, child, )); @@ -852,7 +852,7 @@ fn walk_histogram(call: &Call) -> Result { let func = AggIntent::Quantile { col: None, q: phi, - accuracy: current_accuracy().for_quantile(), + accuracy: current_accuracy(), }; return Ok(outer_aggregate(vec![], func, walk(arg_expr)?)); } @@ -923,7 +923,7 @@ fn walk_histogram_quantiles(call: &Call) -> Result { let intent = AggIntent::Quantile { col: None, q: phi, - accuracy: current_accuracy().for_quantile(), + accuracy: current_accuracy(), }; Unresolved::Aggregate { reduction: reduction_for(&[], intent.is_per_series()), @@ -1691,7 +1691,7 @@ fn build(inner: Inner, keys: Vec, outer: Outer) -> Result reduction: Reduction::Reduce(keys.into()), measures: vec![AggIntent::TopK { k: k as usize, - accuracy: current_accuracy().target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + accuracy: current_accuracy(), }], output_names: vec![], filters: vec![], @@ -1733,7 +1733,7 @@ fn build(inner: Inner, keys: Vec, outer: Outer) -> Result reduction: Reduction::Reduce(keys.into()), measures: vec![AggIntent::TopK { k: k as usize, - accuracy: current_accuracy().target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + accuracy: current_accuracy(), }], output_names: vec![], filters: vec![], @@ -1884,7 +1884,7 @@ fn ingestion_lookback(child: Unresolved) -> Unresolved { /// Count vector elements regardless of their sample values. fn count() -> AggIntent { AggIntent::Count { - accuracy: current_accuracy().target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + accuracy: current_accuracy(), } } @@ -1892,20 +1892,20 @@ fn inner_intent(f: &InnerFunc) -> AggIntent { match f { InnerFunc::FrequencyL2 => AggIntent::FrequencyL2 { col: None, - accuracy: current_accuracy().target_in(ErrorMetric::ABSOLUTE), + accuracy: current_accuracy(), }, InnerFunc::FrequencyEntropy => AggIntent::FrequencyEntropy { col: None, - accuracy: current_accuracy().target_in(ErrorMetric::ABSOLUTE), + accuracy: current_accuracy(), }, InnerFunc::Cardinality => AggIntent::Cardinality { cols: vec![], - accuracy: current_accuracy().target_in(ErrorMetric::Relative), + accuracy: current_accuracy(), }, InnerFunc::Quantile(q) => AggIntent::Quantile { col: None, q: *q, - accuracy: current_accuracy().for_quantile(), + accuracy: current_accuracy(), }, InnerFunc::Avg => AggIntent::Avg { col: None }, InnerFunc::Min => AggIntent::Min { col: None }, @@ -1920,7 +1920,7 @@ fn inner_intent(f: &InnerFunc) -> AggIntent { population: true, }, InnerFunc::Count => AggIntent::Count { - accuracy: current_accuracy().target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + accuracy: current_accuracy(), }, InnerFunc::Rate => AggIntent::Rate, InnerFunc::IRate => AggIntent::IRate, @@ -1962,7 +1962,7 @@ fn outer_intent(o: &OuterIntent) -> AggIntent { OuterIntent::Quantile(q) => AggIntent::Quantile { col: None, q: *q, - accuracy: current_accuracy().for_quantile(), + accuracy: current_accuracy(), }, OuterIntent::Group => AggIntent::Group, } diff --git a/crates/frontend-promql/tests/count_planning.rs b/crates/frontend-promql/tests/count_planning.rs index bc0563964..35046932d 100644 --- a/crates/frontend-promql/tests/count_planning.rs +++ b/crates/frontend-promql/tests/count_planning.rs @@ -26,7 +26,7 @@ fn reads_hydra(node: &Rc) -> bool { #[test] #[ignore = "Pass 1 offers no Hydra state for a grouped count yet (#605)"] fn grouped_count_offers_hydra_but_selection_keeps_per_group_state() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.01); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.01); // Hydra hashes a non-null item per row: the series identity (PromQL // labels are nullable). let root = asap_types::ir::schema_support::with_promql_series_identity( @@ -64,7 +64,7 @@ fn exact_counts_select_count_accumulators() { #[test] fn frequency_count_candidates_use_unit_weights() { for query in ["count_over_time(up[5m])", "count(up)"] { - let accuracy = AccuracyTarget::epsilon(0.02); + let accuracy = AccuracyTarget::max_error(0.02); let root = lower_promql(query, accuracy.clone()).unwrap(); let candidates = logical_candidates(root, accuracy); let mut algorithms = Vec::new(); @@ -227,7 +227,7 @@ fn count_over_time_counts_scrapes_not_sample_values() { #[test] fn cms_count_updates_total_ten_for_zero_positive_and_negative_samples() { use asap_types::pre_asap::ColumnRef; - let accuracy = AccuracyTarget::epsilon(0.02); + let accuracy = AccuracyTarget::max_error(0.02); let root = lower_promql("count_over_time(up[5m])", accuracy.clone()).unwrap(); let candidates = logical_candidates(root, accuracy); let dag = candidates @@ -281,7 +281,7 @@ fn cms_count_updates_total_ten_for_zero_positive_and_negative_samples() { #[test] #[should_panic(expected = "does not compose")] fn logical_candidates_fails_on_a_candidate_that_does_not_compose() { - let accuracy = AccuracyTarget::epsilon(0.01); + let accuracy = AccuracyTarget::max_error(0.01); let root = lower_promql( "sum by (job) (topk by (job, instance) (1, rate(m[5m])))", accuracy.clone(), diff --git a/crates/frontend-promql/tests/observability/metrics_observability.rs b/crates/frontend-promql/tests/observability/metrics_observability.rs index 9d6afd971..8c640b374 100644 --- a/crates/frontend-promql/tests/observability/metrics_observability.rs +++ b/crates/frontend-promql/tests/observability/metrics_observability.rs @@ -71,7 +71,7 @@ fn benchmark_corpora_are_total_and_report_coverage() { total += 1; // Approximate accuracy exercises the sketch-replacement boundary // used by the existing post-ASAP corpus binding test. - match lower_promql(query, AccuracyTarget::epsilon(0.01)) { + match lower_promql(query, AccuracyTarget::max_error(0.01)) { Ok(expr) => { lowered += 1; match logical_plan(&expr) { @@ -119,7 +119,7 @@ fn kubernetes_mixin_aggregates_over_topk_compose() { r#"sum without (id, metrics_path, name, image, endpoint, job, node) ( topk by (cluster, namespace, pod, container, instance) (1, increase( container_cpu_cfs_throttled_periods_total{container!="", job="cadvisor", } [5m]) ) ) / on (cluster, namespace, pod, container, instance) group_left sum without (id, metrics_path, name, image, endpoint, job, node) ( topk by (cluster, namespace, pod, container, instance) (1, increase( container_cpu_cfs_periods_total{job="cadvisor", } [5m]) ) ) > ( 25 / 100 )"#, r#"histogram_quantile(0.99, sum by (cluster, instance, le) ( topk by (cluster, instance, le, operation_type) (1, rate(kubelet_pod_worker_duration_seconds_bucket{job="kubelet"}[5m]) ) ) ) * on(cluster, instance) group_left(node) topk by (cluster, instance, node) (1, kubelet_node_name{job="kubelet"} ) > 60"#, ] { - let root = lower_promql(query, AccuracyTarget::epsilon(0.01)).unwrap(); + let root = lower_promql(query, AccuracyTarget::max_error(0.01)).unwrap(); if let Err(error) = logical_plan(&root) { panic!("{query}: {error}"); } diff --git a/crates/frontend-promql/tests/observability/promql_corpus.rs b/crates/frontend-promql/tests/observability/promql_corpus.rs index 54a022e3c..d460a9257 100644 --- a/crates/frontend-promql/tests/observability/promql_corpus.rs +++ b/crates/frontend-promql/tests/observability/promql_corpus.rs @@ -92,7 +92,7 @@ fn bind_tally(corpus: &str, accuracy: AccuracyTarget) -> BindTally { #[test] fn binding_is_total_over_the_entire_corpus() { - let accuracy = AccuracyTarget::epsilon(0.01); + let accuracy = AccuracyTarget::max_error(0.01); let docs = bind_tally(DOCS, accuracy.clone()); let td = bind_tally(TESTDATA, accuracy); eprintln!("docs corpus post-ASAP binding: {docs:?}"); diff --git a/crates/frontend-promql/tests/promql_conformance.rs b/crates/frontend-promql/tests/promql_conformance.rs index bdb06702b..45e919f8c 100644 --- a/crates/frontend-promql/tests/promql_conformance.rs +++ b/crates/frontend-promql/tests/promql_conformance.rs @@ -45,7 +45,6 @@ use asap_types::pre_asap::{ Reduction, SampleKind, ScalarValue, Source, TimeFunc, }; use asap_types::types::AccuracyTarget; -use asap_types::types::ErrorMetric; use support::{lower_promql, promql_scalar}; // ── harness helpers ───────────────────────────────────────────────────────────── @@ -703,12 +702,12 @@ fn count_maps_to_count_and_inherits_accuracy() { intents(&exact) ); - let approx = lower_promql("count by (job) (up)", AccuracyTarget::epsilon(0.01)).unwrap(); + let approx = lower_promql("count by (job) (up)", AccuracyTarget::max_error(0.01)).unwrap(); assert!( has(&approx, |i| matches!( i, AggIntent::Count { accuracy } if (accuracy.error - 0.01).abs() < 1e-9 - && accuracy.metric == Some(ErrorMetric::ABSOLUTE_STREAM_L1) + && accuracy.accuracy_metric.is_none() )), "Count must carry the approximate target, got {:?}", intents(&approx) diff --git a/crates/frontend-promql/tests/promql_lowering.rs b/crates/frontend-promql/tests/promql_lowering.rs index b311b0a05..81fdeec23 100644 --- a/crates/frontend-promql/tests/promql_lowering.rs +++ b/crates/frontend-promql/tests/promql_lowering.rs @@ -9,10 +9,10 @@ use asap_types::ir::{ use asap_types::pre_asap::{ AggIntent, ArithmeticOpKind, BinaryOpKind, CompareOpKind, Reduction, ScalarValue, Source, }; -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::AccuracyTarget; use asap_types::workload::{ BatchEntry, DataWorkload, DurationMs, Evidence, PlanningWorkload, Predictability, Query, - QueryLanguage, QueryRequirements, QueryWorkload, TimeSelection, + QueryLanguage, QueryRequirements, QueryWorkload, }; use asap_frontend_promql::{lower_promql_workload, PromqlError as LoweringError}; @@ -59,13 +59,13 @@ fn distinct_over_time_preserves_cardinality_accuracy_and_nested_windows() { "distinct_over_time((cpu_usage + 1)[5m:1m])", "sum by(job)(distinct_over_time(cpu_usage[5m]))", ] { - for accuracy in [AccuracyTarget::exact(), AccuracyTarget::epsilon(0.02)] { + for accuracy in [AccuracyTarget::exact(), AccuracyTarget::max_error(0.02)] { let dag = lower_promql(query, accuracy.clone()).unwrap(); let mut intents = Vec::new(); collect_intents(&dag, &mut intents); assert!( intents.iter().any(|intent| matches!( - intent, AggIntent::Cardinality { accuracy: actual, .. } if *actual == accuracy.target_in(ErrorMetric::Relative) + intent, AggIntent::Cardinality { accuracy: actual, .. } if *actual == accuracy )), "{query}: {dag:?}" ); @@ -1102,7 +1102,7 @@ fn fill_modifiers_are_rejected_not_ignored() { fn accuracy_target_flows_into_quantile_intent() { let qe = lower_promql( "quantile_over_time(0.9, m[5m])", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); let NonASAPOp::Aggregate { measures, .. } = qe.expect_non_asap() else { @@ -1174,18 +1174,16 @@ fn batch_lowers_each_entry_and_reads_per_query_accuracy() { predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }, BatchEntry { query: Query("quantile_over_time(0.9, b[5m])".into()), requirements: QueryRequirements { - accuracy: AccuracyTarget::epsilon(0.02), + accuracy: AccuracyTarget::max_error(0.02), ..Default::default() }, predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }, ]), repeating_queries: None, @@ -1214,7 +1212,6 @@ fn batch_rejects_non_promql_language() { predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, diff --git a/crates/frontend-promql/tests/support.rs b/crates/frontend-promql/tests/support.rs index 737dbe087..d972f48ab 100644 --- a/crates/frontend-promql/tests/support.rs +++ b/crates/frontend-promql/tests/support.rs @@ -8,7 +8,7 @@ use asap_types::pre_asap::ScalarValue; use asap_types::types::AccuracyTarget; use asap_types::workload::{ BatchEntry, DataWorkload, DurationMs, Evidence, PlanningWorkload, Predictability, Query, - QueryLanguage, QueryRequirements, QueryWorkload, TimeSelection, + QueryLanguage, QueryRequirements, QueryWorkload, }; pub fn workload(query: &str, accuracy: AccuracyTarget) -> PlanningWorkload { @@ -24,7 +24,6 @@ pub fn workload(query: &str, accuracy: AccuracyTarget) -> PlanningWorkload { predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, diff --git a/crates/frontend-promql/tests/univmon_candidates.rs b/crates/frontend-promql/tests/univmon_candidates.rs index aeed7651a..6a6076e98 100644 --- a/crates/frontend-promql/tests/univmon_candidates.rs +++ b/crates/frontend-promql/tests/univmon_candidates.rs @@ -5,7 +5,7 @@ mod support; use asap_types::ir::cse::share_common_sub_dags; use asap_types::ir::{ASAPOp, Operator, OperatorNode}; use asap_types::post_asap::{ - ErrorMetric, FieldDataType, SketchAlgorithm, SketchStatistic, SummaryInputExpr, + AccuracyMetric, FieldDataType, SketchAlgorithm, SketchStatistic, SummaryInputExpr, }; use asap_types::types::AccuracyTarget; use support::{logical_candidates, lower_promql, post_asap_dag, run_stage1_to_stage3}; @@ -36,7 +36,7 @@ fn four_evaluations_size_their_value_frequency_states_and_keep_honest_guarantees // own shapes, while the total and entropy keep the baseline shape and so // share one state. Sharing across readouts is the summary-capability // rule's job. - let accuracy = AccuracyTarget::epsilon(0.02); + let accuracy = AccuracyTarget::max_error(0.02); let roots: Vec<_> = [ ("distinct_over_time(m[5m])", accuracy.clone()), ("count_over_time(m[5m])", accuracy.clone()), @@ -83,7 +83,7 @@ fn four_evaluations_size_their_value_frequency_states_and_keep_honest_guarantees // source's distinct bound, which Stage 1 does not read. 0 => { let guarantee = guarantee.expect("a distinct-count guarantee"); - assert_eq!(guarantee.spec.metric, ErrorMetric::Relative); + assert_eq!(guarantee.spec.accuracy_metric, AccuracyMetric::Relative); assert_eq!( guarantee.spec.error.evaluate().map(|e| e <= 0.02), Some(true) @@ -102,10 +102,10 @@ fn four_evaluations_size_their_value_frequency_states_and_keep_honest_guarantees } 2 => { let guarantee = guarantee.expect("an L2 guarantee"); - assert_eq!(guarantee.spec.metric, ErrorMetric::Relative); + assert_eq!(guarantee.spec.accuracy_metric, AccuracyMetric::Relative); assert!(DefaultAccuracyModel.satisfies( guarantee, - &AccuracyTarget::epsilon(0.02).with_metric(ErrorMetric::Relative) + &AccuracyTarget::max_error(0.02).accuracy_metric(AccuracyMetric::Relative) )); assert!(guarantee.spec.failure_probability.evaluate().unwrap() <= 0.01); } @@ -129,8 +129,8 @@ fn uncalibrated_frequency_evaluations_do_not_bypass_accuracy_targets() { for query in ["entropy_over_time(m[5m])"] { for target in [ AccuracyTarget::exact(), - AccuracyTarget::epsilon(0.02), - AccuracyTarget::epsilon_delta(0.02, 0.01), + AccuracyTarget::max_error(0.02), + AccuracyTarget::max_error(0.02).failure_probability(0.01), ] { let root = lower_promql(query, target.clone()).unwrap(); let run = run_stage1_to_stage3(root, target.clone()); diff --git a/crates/frontend-sql/src/frequency.rs b/crates/frontend-sql/src/frequency.rs index fe6334178..d923a6b18 100644 --- a/crates/frontend-sql/src/frequency.rs +++ b/crates/frontend-sql/src/frequency.rs @@ -18,7 +18,7 @@ use asap_types::pre_asap::{ AggIntent, ArithmeticOpKind, CompareOpKind, DataType, GroupKeys, JoinKind, Reduction, ScalarValue, WindowFrameBound, WindowFrameOffset, WindowFuncKind, }; -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::AccuracyTarget; use std::rc::Rc; /// The recognized form of a query whose root is the L2 or natural-log entropy @@ -151,8 +151,9 @@ fn frequency_l2_rewrite(root: &Rc) -> Option> { input, AggIntent::FrequencyL2 { col: Some(key), - // The count's ε and δ, stated as the norm's relative error. - accuracy: accuracy.with_metric(ErrorMetric::Relative), + // The count's target; Stage 1 sizes the norm's summary for it as + // a relative error. + accuracy, }, "frequency_l2", ScalarExpr::Column(0), @@ -329,8 +330,9 @@ fn frequency_entropy_rewrite(root: &Rc) -> Option input, AggIntent::FrequencyEntropy { col: Some(key), - // The count's ε and δ, stated as an absolute error in bits. - accuracy: accuracy.with_metric(ErrorMetric::ABSOLUTE), + // The count's target; Stage 1 sizes the entropy's summary for it + // as an absolute error in bits. + accuracy, }, "frequency_entropy", nats, diff --git a/crates/frontend-sql/src/sql/mod.rs b/crates/frontend-sql/src/sql/mod.rs index fa4dcfc4d..c19190ca2 100644 --- a/crates/frontend-sql/src/sql/mod.rs +++ b/crates/frontend-sql/src/sql/mod.rs @@ -69,7 +69,7 @@ use asap_types::pre_asap::{ resolve_column_ref, ColumnRef, CompareOpKind, JoinKind, RelationalSetOpKind, ScalarValue, WindowFuncKind, }; -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::AccuracyTarget; use asap_types::workload::SqlDialect; use crate::error::SqlError as LoweringError; @@ -1784,11 +1784,11 @@ fn lower_agg_intent(expr: &Expr) -> Result, LoweringError> } args => AggIntent::Cardinality { cols: args.iter().map(distinct_col).collect::>()?, - accuracy: current_accuracy().target_in(ErrorMetric::Relative), + accuracy: current_accuracy(), }, }, AggSemantic::Count => AggIntent::Count { - accuracy: current_accuracy().target_in(ErrorMetric::ABSOLUTE_STREAM_L1), + accuracy: current_accuracy(), }, AggSemantic::Sum => AggIntent::Sum { col: col(&agg_fn.params.args)?, @@ -1829,11 +1829,11 @@ fn lower_agg_intent(expr: &Expr) -> Result, LoweringError> _ => extract_percentile_q(&agg_fn.params.args)?, }, }, - accuracy: current_accuracy().for_quantile(), + accuracy: current_accuracy(), }, AggSemantic::Cardinality => AggIntent::Cardinality { cols: vec![reducer_col(&name, &agg_fn.params.args)?], - accuracy: current_accuracy().target_in(ErrorMetric::Relative), + accuracy: current_accuracy(), }, }) } diff --git a/crates/frontend-sql/tests/frequency_entropy.rs b/crates/frontend-sql/tests/frequency_entropy.rs index 152c94c50..334958660 100644 --- a/crates/frontend-sql/tests/frequency_entropy.rs +++ b/crates/frontend-sql/tests/frequency_entropy.rs @@ -5,7 +5,7 @@ use asap_frontend_sql::{lower_sql, SqlCatalog, SqlLowerer}; use asap_types::{ ir::{NonASAPOp, OperatorNode}, pre_asap::{AggIntent, DataType, Field, Schema}, - types::{AccuracyTarget, ErrorMetric}, + types::AccuracyTarget, }; fn catalog(nullable: bool) -> SqlCatalog { @@ -71,7 +71,7 @@ async fn declines_non_equivalent_entropy_shapes() { /// is an exact count. #[tokio::test] async fn entropy_accuracy_and_population_guard_are_separate() { - let target = AccuracyTarget::epsilon_delta(0.05, 0.01); + let target = AccuracyTarget::max_error(0.05).failure_probability(0.01); let node = lower_sql("SELECT -SUM(p*LN(p)) FROM (SELECT COUNT(*)*1.0/SUM(COUNT(*)) OVER () AS p FROM flows GROUP BY src_ip) f", &catalog(false), target.clone()).await.unwrap(); let NonASAPOp::Project { child, .. } = node.expect_non_asap() else { panic!("project"); @@ -83,7 +83,7 @@ async fn entropy_accuracy_and_population_guard_are_separate() { panic!("entropy"); }; assert!( - matches!(&measures[0], AggIntent::FrequencyEntropy { accuracy, .. } if *accuracy == target.target_in(ErrorMetric::ABSOLUTE)) + matches!(&measures[0], AggIntent::FrequencyEntropy { accuracy, .. } if *accuracy == target) ); let NonASAPOp::Aggregate { measures, .. } = right.expect_non_asap() else { panic!("count"); diff --git a/crates/frontend-sql/tests/frequency_l2.rs b/crates/frontend-sql/tests/frequency_l2.rs index 2006a69e0..cc26b2a9e 100644 --- a/crates/frontend-sql/tests/frequency_l2.rs +++ b/crates/frontend-sql/tests/frequency_l2.rs @@ -5,7 +5,7 @@ use asap_frontend_sql::{lower_sql, lower_sql_batch, SqlCatalog, SqlLowerer}; use asap_types::{ ir::{NonASAPOp, OperatorNode}, pre_asap::{AggIntent, DataType, Field, Schema}, - types::{AccuracyTarget, ErrorMetric}, + types::AccuracyTarget, }; use std::rc::Rc; @@ -81,7 +81,7 @@ async fn declines_non_equivalent_frequency_shapes() { /// The L2 intent carries the query's accuracy target, not a default. #[tokio::test] async fn frequency_l2_preserves_accuracy_target() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.01); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.01); let node = lower_sql(CAST_COUNT, &catalog(false), target.clone()) .await .unwrap(); @@ -94,16 +94,14 @@ async fn frequency_l2_preserves_accuracy_target() { let NonASAPOp::Aggregate { measures, .. } = left.expect_non_asap() else { panic!("aggregate"); }; - assert!( - matches!(&measures[0], AggIntent::FrequencyL2 { accuracy, .. } if *accuracy == target.target_in(ErrorMetric::Relative)) - ); + assert!(matches!(&measures[0], AggIntent::FrequencyL2 { accuracy, .. } if *accuracy == target)); } /// An approximate L2 estimate never decides whether SQL returns NULL: the /// empty-input guard is an exact count. #[tokio::test] async fn frequency_empty_input_guard_uses_an_exact_count() { - let target = AccuracyTarget::epsilon_delta(0.01, 0.01); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.01); let node = lower_sql(CAST_COUNT, &catalog(false), target) .await .unwrap(); @@ -127,7 +125,7 @@ async fn frequency_empty_input_guard_uses_an_exact_count() { async fn batch_lowering_recognizes_frequency_l2() { use asap_types::workload::{ BatchEntry, Predictability, Query, QueryLanguage, QueryRequirements, QueryWorkload, - SqlDialect, TimeSelection, + SqlDialect, }; let workload = QueryWorkload { language: QueryLanguage::SQL(SqlDialect::DataFusionSQL), @@ -140,7 +138,6 @@ async fn batch_lowering_recognizes_frequency_l2() { predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }; @@ -156,7 +153,7 @@ async fn stage1_offers_exact_and_summary_l2_candidates() { generate_per_computation_candidates, ComputationCandidate, }; use asap_types::ir::QueryRoot; - let target = AccuracyTarget::epsilon_delta(0.01, 0.01); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.01); let root = lower_sql(CAST_COUNT, &catalog(false), target) .await .unwrap(); diff --git a/crates/frontend-sql/tests/sql_lowering.rs b/crates/frontend-sql/tests/sql_lowering.rs index c65f84be2..0a752461c 100644 --- a/crates/frontend-sql/tests/sql_lowering.rs +++ b/crates/frontend-sql/tests/sql_lowering.rs @@ -1459,7 +1459,7 @@ async fn median_threads_the_accuracy_target() { let qe = lower_sql( "SELECT approx_median(latency) FROM metrics", &catalog(), - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .await .expect("approx_median should lower"); diff --git a/crates/integration-tests/src/lib.rs b/crates/integration-tests/src/lib.rs index 95c508d80..e4fb361c7 100644 --- a/crates/integration-tests/src/lib.rs +++ b/crates/integration-tests/src/lib.rs @@ -18,7 +18,7 @@ pub mod fixtures { use asap_types::types::AccuracyTarget; use asap_types::workload::{ BatchEntry, DataWorkload, DurationMs, Evidence, PlanningWorkload, Predictability, Query, - QueryLanguage, QueryRequirements, QueryWorkload, TimeSelection, + QueryLanguage, QueryRequirements, QueryWorkload, }; use std::rc::Rc; @@ -52,7 +52,6 @@ pub mod fixtures { predictability: Predictability::Unknown, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), }]), repeating_queries: None, }, diff --git a/crates/integration-tests/tests/exact_composition.rs b/crates/integration-tests/tests/exact_composition.rs index 62b54ecc9..87cc56229 100644 --- a/crates/integration-tests/tests/exact_composition.rs +++ b/crates/integration-tests/tests/exact_composition.rs @@ -808,7 +808,7 @@ fn physical_export_carries_explicit_timing_for_a_composed_plan() { fn promql_max_by_zone_over_quantile_over_time_composes() { let expr = lower_promql( "max by (zone) (quantile_over_time(0.99, latency[5m]))", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); let space = plan(vec![("q", expr)], &StatsModel); diff --git a/crates/integration-tests/tests/filtered_aggregates.rs b/crates/integration-tests/tests/filtered_aggregates.rs index 8c31f9c28..a348a2896 100644 --- a/crates/integration-tests/tests/filtered_aggregates.rs +++ b/crates/integration-tests/tests/filtered_aggregates.rs @@ -222,7 +222,7 @@ async fn hand_filtered( family: impl Fn(&FieldDataType) -> bool, filter: &(Predicate, Schema), ) -> Vec> { - let target = AccuracyTarget::epsilon_delta(0.01, 0.01); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.01); let mut result = vec![]; for root in logical_candidates(lower(sql, target.clone()).await, target) { let builds: Vec<_> = OperatorNode::reachable(&root) @@ -290,7 +290,7 @@ async fn hand_filtered_kll_compiles_and_executes() { /// The KLL candidates of `sql`, unchanged. async fn kll_candidates(sql: &str) -> Vec> { - let target = AccuracyTarget::epsilon_delta(0.01, 0.01); + let target = AccuracyTarget::max_error(0.01).failure_probability(0.01); let roots: Vec<_> = logical_candidates(lower(sql, target.clone()).await, target) .into_iter() .filter(|root| { diff --git a/crates/integration-tests/tests/frequency_execution.rs b/crates/integration-tests/tests/frequency_execution.rs index 67535ad2c..74e4879fa 100644 --- a/crates/integration-tests/tests/frequency_execution.rs +++ b/crates/integration-tests/tests/frequency_execution.rs @@ -124,7 +124,7 @@ fn standin_exact_and_univmon_candidates_execute() { 0.01, ), ] { - let accuracy = AccuracyTarget::epsilon_delta(epsilon, delta); + let accuracy = AccuracyTarget::max_error(epsilon).failure_probability(delta); let root = lower_promql(query, accuracy.clone()).unwrap(); let root = promql_rows::with_series_identity(&root).unwrap(); let mut executed = Vec::new(); diff --git a/crates/integration-tests/tests/materialization_e2e.rs b/crates/integration-tests/tests/materialization_e2e.rs index b95faeb14..6f3c8ed67 100644 --- a/crates/integration-tests/tests/materialization_e2e.rs +++ b/crates/integration-tests/tests/materialization_e2e.rs @@ -25,7 +25,7 @@ use asap_types::ir::{ use asap_types::post_asap::maintained_population::PopulationInput; use asap_types::post_asap::ExecutionTiming; use asap_types::pre_asap::Source; -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::{AccuracyMetric, AccuracyTarget}; mod physical_common; @@ -34,7 +34,7 @@ const AT_INGESTION: Materialization = Materialization::MatAtIngestion(Medium::Me /// The logical DAG the planner pipeline selects for `query`, optionally with /// a PromQL series identity (per-entity state needs one). fn selected(query: &str, series_identity: bool) -> Rc { - selected_with(query, series_identity, AccuracyTarget::epsilon(0.01)) + selected_with(query, series_identity, AccuracyTarget::max_error(0.01)) } /// [`selected`] for a quantile query, whose target names its metric (a @@ -43,7 +43,7 @@ fn selected_quantile(query: &str, series_identity: bool) -> Rc { selected_with( query, series_identity, - AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Rank), + AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Rank), ) } @@ -58,7 +58,7 @@ fn selected_with(query: &str, series_identity: bool, accuracy: AccuracyTarget) - /// The first logical candidate for `query` that builds a sketch: plan /// selection picks the exact plan while every summary runs at query time. fn sketch_candidate(query: &str) -> Rc { - let accuracy = AccuracyTarget::epsilon(0.01); + let accuracy = AccuracyTarget::max_error(0.01); let root = lower_promql(query, accuracy.clone()).unwrap(); logical_candidates(root, accuracy) .into_iter() @@ -257,7 +257,7 @@ fn materialization_decides_precompute_contents() { #[test] fn population_materialization_decides_precompute_contents() { let root = promql_rows::with_series_identity( - &lower_promql("topk by(job)(1, m)", AccuracyTarget::epsilon(0.01)).unwrap(), + &lower_promql("topk by(job)(1, m)", AccuracyTarget::max_error(0.01)).unwrap(), ) .unwrap(); let root = MaintainedPopulationStrategy::new(std::slice::from_ref(&root)) diff --git a/crates/integration-tests/tests/operator_design_examples.rs b/crates/integration-tests/tests/operator_design_examples.rs index 47760bed8..aec64fbef 100644 --- a/crates/integration-tests/tests/operator_design_examples.rs +++ b/crates/integration-tests/tests/operator_design_examples.rs @@ -289,7 +289,6 @@ async fn batch_planning_replaces_summary_operators() { predictability: Predictability::Unknown, invocations: 2, execute_at: None, - time_selection: TimeSelection::default(), }) .collect(), ), diff --git a/crates/integration-tests/tests/operator_sharing.rs b/crates/integration-tests/tests/operator_sharing.rs index da81dcc64..be6a1fe23 100644 --- a/crates/integration-tests/tests/operator_sharing.rs +++ b/crates/integration-tests/tests/operator_sharing.rs @@ -128,7 +128,7 @@ async fn project_above_and_scan_below_a_summary_are_both_non_asap_nodes() { async fn exact_aggregate_and_sketch_share_one_scan() { let root = plan( "SELECT avg(l_extendedprice), approx_percentile_cont(l_discount, 0.99) FROM lineitem", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .await; let exact = find_all(&root, |node| { @@ -162,7 +162,7 @@ async fn each_side_of_union_all_holds_a_summary_estimate() { let root = plan( "SELECT approx_distinct(l_partkey) FROM lineitem \ UNION ALL SELECT approx_distinct(l_suppkey) FROM lineitem", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .await; // The SQL front end lowers UNION ALL to `SetOp { all: true }`. diff --git a/crates/integration-tests/tests/per_computation_candidates_sql.rs b/crates/integration-tests/tests/per_computation_candidates_sql.rs index dfbabed9f..39b149006 100644 --- a/crates/integration-tests/tests/per_computation_candidates_sql.rs +++ b/crates/integration-tests/tests/per_computation_candidates_sql.rs @@ -21,7 +21,7 @@ async fn targets(sql: &str) -> Vec { Field::plain("bytes", DataType::Float64, false), ]), ); - let accuracy = AccuracyTarget::epsilon_delta(0.01, 0.01); + let accuracy = AccuracyTarget::max_error(0.01).failure_probability(0.01); let root = lower_sql(sql, &catalog, accuracy) .await .unwrap_or_else(|e| panic!("lower failed for {sql:?}: {e}")); @@ -96,7 +96,7 @@ async fn a_filter_or_group_key_over_an_estimate_has_no_guarantee() { Field::plain("bytes", DataType::Float64, false), ]), ); - let accuracy = AccuracyTarget::epsilon(0.01); + let accuracy = AccuracyTarget::max_error(0.01); for sql in [ "SELECT count(*) FILTER (WHERE p > 5) FROM \ (SELECT src, approx_percentile_cont(bytes, 0.99) AS p FROM flows GROUP BY src) s", @@ -163,7 +163,7 @@ async fn a_sql_fold_over_estimates_leaves_its_row_count_unbound() { Field::plain("bytes", DataType::Float64, false), ]), ); - let accuracy = AccuracyTarget::epsilon(0.01); + let accuracy = AccuracyTarget::max_error(0.01); let sql = "SELECT max(p) FROM \ (SELECT src, approx_percentile_cont(bytes, 0.99) AS p FROM flows GROUP BY src) s"; let root = lower_sql(sql, &catalog, accuracy.clone()).await.unwrap(); @@ -191,7 +191,7 @@ async fn heap_updates(key: &str, key_type: DataType, sql: &str) -> Vec<(String, Field::plain("bytes", DataType::Float64, false), ]), ); - let accuracy = AccuracyTarget::epsilon_delta(0.01, 0.01); + let accuracy = AccuracyTarget::max_error(0.01).failure_probability(0.01); let root = lower_sql(sql, &catalog, accuracy).await.unwrap(); let inventory = generate_per_computation_candidates(vec![(0, QueryRoot::Operator(root))]).unwrap(); diff --git a/crates/integration-tests/tests/planner_layering_common/mod.rs b/crates/integration-tests/tests/planner_layering_common/mod.rs index e881f4ff3..2eb2ace23 100644 --- a/crates/integration-tests/tests/planner_layering_common/mod.rs +++ b/crates/integration-tests/tests/planner_layering_common/mod.rs @@ -561,12 +561,6 @@ pub fn evaluation_interval_ms(workload: &PlanningWorkload, query: usize) -> Opti } } -pub fn lookback_ms(workload: &PlanningWorkload, query: usize) -> u64 { - let entry = workload.query_workload.entries().nth(query).expect("entry"); - let asap_types::workload::DurationMs(ms) = entry.time_selection.lookback.expect("lookback"); - ms -} - // ── Materialization ────────────────────────────────────────────────────── /// A node's materialization option (#509 "Materialization"), without its diff --git a/crates/integration-tests/tests/planner_layering_example2.rs b/crates/integration-tests/tests/planner_layering_example2.rs index 2e7a0f54d..62a378d8c 100644 --- a/crates/integration-tests/tests/planner_layering_example2.rs +++ b/crates/integration-tests/tests/planner_layering_example2.rs @@ -26,9 +26,9 @@ use asap_types::post_asap::{ use asap_types::pre_asap::DataType; use asap_types::types::{AccuracyScope, AccuracyTarget}; use asap_types::workload::{ - DataArrival, DataDistribution, DataWorkload, DurationMs, PlanningWorkload, Predictability, - Query, QueryLanguage, QueryRequirements, QueryTimeScope, QueryWorkload, Rate, RepeatedDemand, - RepeatingEntry, RepetitionInterval, SqlDialect, TimeSelection, + DataArrival, DataWorkload, DurationMs, PlanningWorkload, Predictability, Query, QueryLanguage, + QueryRequirements, QueryWorkload, Rate, RepeatedDemand, RepeatingEntry, RepetitionInterval, + SqlDialect, }; use planner_layering_common::*; @@ -59,17 +59,12 @@ fn panel(query: &str, (epsilon, delta): (f64, f64), scope: AccuracyScope) -> Rep demand: RepeatedDemand::FixedInterval(RepetitionInterval(10_000)), requirements: QueryRequirements { accuracy: AccuracyTarget { - scope, - ..AccuracyTarget::epsilon_delta(epsilon, delta) + accuracy_scope: scope, + ..AccuracyTarget::max_error(epsilon).failure_probability(delta) }, max_latency_ms: None, }, predictability: Predictability::Predictable { known_at: None }, - time_selection: TimeSelection { - scope: QueryTimeScope::RealTime, - lookback: Some(DurationMs(60_000)), - as_of: None, - }, } } @@ -82,10 +77,8 @@ fn data_workload(ingestion_interval: bool) -> DataWorkload { } else { Default::default() }, - ingestion_volume: Default::default(), ingestion_rate: declared(Rate(1_000_000.0 / 15.0)), input_cardinality: declared(10_000_000), - distribution: declared(DataDistribution::Zipf), stream_norms: Default::default(), max_distinct_per_evaluation: Default::default(), metric_types: Default::default(), @@ -678,8 +671,8 @@ fn stage3_plan_selection_accepts_kmv_for_any_failure_probability_without_a_bound .as_mut() .expect("repeating queries")[0]; q1.requirements.accuracy = AccuracyTarget { - scope: AccuracyScope::PerValue, - ..AccuracyTarget::epsilon_delta(0.02, delta) + accuracy_scope: AccuracyScope::PerValue, + ..AccuracyTarget::max_error(0.02).failure_probability(delta) }; let run = run_stages(&workload, lower_promql(&workload)); for id in q1_only_candidates(&run, "Kmv") { diff --git a/crates/integration-tests/tests/planner_layering_example3.rs b/crates/integration-tests/tests/planner_layering_example3.rs index 670234f4c..0d1828a4a 100644 --- a/crates/integration-tests/tests/planner_layering_example3.rs +++ b/crates/integration-tests/tests/planner_layering_example3.rs @@ -63,7 +63,6 @@ fn workload_encodes_example3() { let b = pattern_b(); b.validate().expect("valid Pattern B"); assert_eq!(evaluation_interval_ms(&b, 0), Some(PATTERN_B_INTERVAL_MS)); - assert_eq!(lookback_ms(&b, 0), PATTERN_B_WINDOW_MS); } // ── Pattern A: query-language frontends ─────────────────────────────── diff --git a/crates/integration-tests/tests/planner_layering_example4.rs b/crates/integration-tests/tests/planner_layering_example4.rs index fcbbb32f4..9f5cf6099 100644 --- a/crates/integration-tests/tests/planner_layering_example4.rs +++ b/crates/integration-tests/tests/planner_layering_example4.rs @@ -133,12 +133,14 @@ fn stage2_physical_asap_optimization_at_rest_runs_nothing_at_ingestion_time() { /// A materialized output is kept for as long as its consumers read: it covers every reader's lookback and offset. #[test] fn stage2_physical_asap_optimization_materialized_output_covers_its_consumers() { + // Each query's range window and offset, as its PromQL text states them. + let windows: Vec = PATTERN_A.iter().map(|(_, window, _)| *window).collect(); let offsets: Vec = PATTERN_A.iter().map(|(_, _, before_t)| *before_t).collect(); - for (workload, offsets) in [ - (once(), offsets.clone()), - (monthly(), offsets.clone()), - (at_rest(), offsets), - (pattern_b(), vec![0]), + for (workload, windows, offsets) in [ + (once(), windows.clone(), offsets.clone()), + (monthly(), windows.clone(), offsets.clone()), + (at_rest(), windows, offsets), + (pattern_b(), vec![PATTERN_B_WINDOW_MS], vec![0]), ] { let run = run_promql(&workload); for p in &run.physical { @@ -148,7 +150,7 @@ fn stage2_physical_asap_optimization_materialized_output_covers_its_consumers() } let need = readers(p.dag(), &p.query_roots, n.id) .into_iter() - .map(|q| lookback_ms(&workload, q) + offsets[q]) + .map(|q| windows[q] + offsets[q]) .max() .unwrap_or(0); let kept = retention_ms(p, n.id).unwrap_or_else(|| { diff --git a/crates/integration-tests/tests/precompute_raw_samples.rs b/crates/integration-tests/tests/precompute_raw_samples.rs index 72278948a..1410093f8 100644 --- a/crates/integration-tests/tests/precompute_raw_samples.rs +++ b/crates/integration-tests/tests/precompute_raw_samples.rs @@ -397,10 +397,10 @@ fn check( #[test] fn raw_sample_summaries_compile_and_match_their_kernels() { let exact = AccuracyTarget::exact(); - let sketch = AccuracyTarget::epsilon(0.02); + let sketch = AccuracyTarget::max_error(0.02); // A UnivMon sized for a distinct count at ε = 0.02 holds a heap of // millions of keys on each of its 19 layers, past the default 64 MiB. - let distinct = AccuracyTarget::epsilon_delta(0.2, 0.1); + let distinct = AccuracyTarget::max_error(0.2).failure_probability(0.1); let queries = [ ("sum_over_time(m[5m])", &exact), ("count_over_time(m[5m])", &exact), diff --git a/crates/integration-tests/tests/promql_numeric_regressions.rs b/crates/integration-tests/tests/promql_numeric_regressions.rs index 71aae1320..5924ffa1f 100644 --- a/crates/integration-tests/tests/promql_numeric_regressions.rs +++ b/crates/integration-tests/tests/promql_numeric_regressions.rs @@ -109,7 +109,7 @@ fn sum_rate_and_increase_have_real_exact_accumulator_nodes() { fn checked_ratio_must_not_certify_cross_zero_interpolation() { let node = plan( "quantile_over_time(0.5, data[5m]) / quantile_over_time(0.9, data[5m])", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ); assert!( matches!(node.operator, Operator::NonASAP(NonASAPOp::BinaryOp { .. })) @@ -151,7 +151,7 @@ fn quantile_over_temporal_average_keeps_a_legal_candidate() { "quantile(0.9, avg_over_time(a[5m]))", "quantile(0.9, avg_over_time(a[5m]) + avg_over_time(b[5m]))", ] { - let accuracy = AccuracyTarget::epsilon(0.01); + let accuracy = AccuracyTarget::max_error(0.01); let pre = lower_promql(query, accuracy.clone()).unwrap(); let node = logical_candidates(pre, accuracy) .into_iter() @@ -180,7 +180,7 @@ fn sketch_counts_use_unit_weights_and_signed_sums_keep_value_weights() { } else { "topk(1, sum_over_time(up[5m]))" }; - let accuracy = AccuracyTarget::epsilon(0.01); + let accuracy = AccuracyTarget::max_error(0.01); let pre = lower_promql(query, accuracy.clone()).unwrap(); let candidates = logical_candidates(pre, accuracy); // The heap state each summarized candidate builds. @@ -272,7 +272,7 @@ fn sketch_counts_use_unit_weights_and_signed_sums_keep_value_weights() { #[test] #[should_panic(expected = "does not compose")] fn logical_candidates_fails_on_a_candidate_that_does_not_compose() { - let accuracy = AccuracyTarget::epsilon(0.01); + let accuracy = AccuracyTarget::max_error(0.01); let pre = lower_promql( "sum by (job) (topk by (job, instance) (1, rate(m[5m])))", accuracy.clone(), diff --git a/crates/integration-tests/tests/promql_to_post_asap.rs b/crates/integration-tests/tests/promql_to_post_asap.rs index 12f4d5ff2..2dc5f8061 100644 --- a/crates/integration-tests/tests/promql_to_post_asap.rs +++ b/crates/integration-tests/tests/promql_to_post_asap.rs @@ -31,8 +31,8 @@ use asap_types::post_asap::{ }; use asap_types::pre_asap::expr_ir::ColumnRef; use asap_types::pre_asap::schema::DataType; +use asap_types::types::AccuracyMetric; use asap_types::types::AccuracyTarget; -use asap_types::types::ErrorMetric; /// This crate has no "bind me one tree" public API any more — /// `ASAPStrategies::replacements` always returns every candidate, and @@ -67,7 +67,7 @@ fn distinct_over_time_offers_hll_cardinality_evaluation() { // function-specific post-ASAP node or a sample-count rewrite. let root = lower_promql( "distinct_over_time(cpu_usage{job=\"worker\"}[5m])", - AccuracyTarget::epsilon(0.02), + AccuracyTarget::max_error(0.02), ) .unwrap(); let candidates = ASAPStrategies::default_cost_model().replacements(&TargetSubDAG::new(&root)); @@ -266,7 +266,7 @@ impl AccuracyEvidenceProvider for SeparatedTopK { fn grouped_rate_topk_consumes_finalized_rate_values() { let root = lower_promql( "topk by(job)(2, sum by(service, job)(rate(m[1m])))", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); let strategy = ASAPStrategies::new_with_planning_inputs_and_evidence( @@ -329,7 +329,7 @@ fn weighted_topk_keeps_candidates_with_missing_population_evidence() { } let root = lower_promql( "topk by(job)(2, sum by(service, job)(rate(m[1m])))", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); let strategy = ASAPStrategies::new_with_planning_inputs_and_evidence( @@ -349,7 +349,7 @@ fn weighted_topk_keeps_candidates_with_missing_population_evidence() { fn weighted_topk_exports_symbolic_evidence_requirements() { let root = lower_promql( "topk by(job)(2, sum by(service, job)(rate(m[1m])))", - AccuracyTarget::epsilon_delta(0.01, 0.01), + AccuracyTarget::max_error(0.01).failure_probability(0.01), ) .unwrap(); let candidates = ASAPStrategies::default_cost_model().replacements(&TargetSubDAG::new(&root)); @@ -379,7 +379,7 @@ fn weighted_topk_rejects_invalid_population_evidence() { } let root = lower_promql( "topk by(job)(2, sum by(service, job)(rate(m[1m])))", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); let strategy = ASAPStrategies::new_with_planning_inputs_and_evidence( @@ -398,7 +398,11 @@ fn rate_and_increase_topk_use_summary_scores_and_grouped_limits() { "topk by(job)(2, sum by(service, job)(rate(m[1m])))", "topk(2, sum by(job)(increase(m[6h])))", ] { - let root = lower_promql(query, AccuracyTarget::epsilon_delta(0.01, 0.01)).unwrap(); + let root = lower_promql( + query, + AccuracyTarget::max_error(0.01).failure_probability(0.01), + ) + .unwrap(); let strategy = ASAPStrategies::new_with_planning_inputs_and_evidence( &DefaultCostModel, &DefaultAccuracyModel, @@ -493,7 +497,7 @@ fn rate_and_increase_topk_use_summary_scores_and_grouped_limits() { assert!(guarantee.spec.failure_probability.evaluate().unwrap() <= 0.01); assert!(guarantee.provenance.iter().any(|source| matches!(source, asap_types::post_asap::GuaranteeSource::ChildGuarantee { guarantee, .. } - if guarantee.spec.metric == asap_types::post_asap::ErrorMetric::ABSOLUTE_STREAM_L1))); + if guarantee.spec.accuracy_metric == asap_types::post_asap::AccuracyMetric::ABSOLUTE_STREAM_L1))); } } @@ -540,7 +544,7 @@ fn promql_binary_arithmetic_preserves_nested_structure_and_rejects_modifiers() { fn promql_binary_arithmetic_never_relabels_approximate_children_as_exact() { let pre = lower_promql( "quantile_over_time(0.9, a[1m]) + quantile_over_time(0.9, b[1m])", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .expect("lowering failed"); let root = realize(&pre).expect("binding failed"); @@ -560,8 +564,10 @@ fn ddsketch_quantile_ratio_meets_the_shared_relative_error_target() { // Enforced finite, positive, nonempty windows justify both DDSketch // interpolation bounds and a nonzero denominator. Shared state does not // require independence for the deterministic ratio bound. - let requested = AccuracyTarget::epsilon_delta(0.01, 0.01).with_metric(ErrorMetric::Relative); - let target = requested.target_in(ErrorMetric::Relative); + let requested = AccuracyTarget::max_error(0.01) + .failure_probability(0.01) + .accuracy_metric(AccuracyMetric::Relative); + let target = requested.clone(); let query = lower_promql( "quantile_over_time(0.9, data[5m]) / quantile_over_time(0.5, data[5m])", requested, @@ -631,8 +637,11 @@ fn planner_only_e2e_temporal_topk_preserves_query_update_and_evaluation_contract ), ]; for (source, expected_update, expected_family, excluded_labels) in cases { - let pre = lower_promql(source, AccuracyTarget::epsilon_delta(0.01, 0.01)) - .expect("lower temporal Top-K"); + let pre = lower_promql( + source, + AccuracyTarget::max_error(0.01).failure_probability(0.01), + ) + .expect("lower temporal Top-K"); let strategy = ASAPStrategies::new_with_planning_inputs_and_evidence( &DefaultCostModel, &DefaultAccuracyModel, @@ -793,7 +802,11 @@ fn planner_heap_topk_reference_execution_matches_ground_truth() { vec![("worker", 100.0), ("cron", 30.0)], ), ] { - let pre = lower_promql(query, AccuracyTarget::epsilon_delta(0.01, 0.01)).unwrap(); + let pre = lower_promql( + query, + AccuracyTarget::max_error(0.01).failure_probability(0.01), + ) + .unwrap(); let strategy = ASAPStrategies::new_with_planning_inputs_and_evidence( &DefaultCostModel, &DefaultAccuracyModel, @@ -833,7 +846,7 @@ fn planner_heap_topk_reference_execution_matches_ground_truth() { fn promql_quantile_of_rate_binds_kll_over_rate_accumulator() { let pre_asap = lower_promql( "quantile(0.99, rate(http_requests_total[5m]))", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .expect("lowering failed"); let root = realize(&pre_asap).expect("binding failed"); @@ -1052,7 +1065,7 @@ fn nested_summary_explicitly_finalizes_exact_child_at_ingestion_time() { // sketch must not interpret exact accumulator bytes as input samples. let pre = lower_promql( "quantile(0.9, sum_over_time(m[1m]))", - AccuracyTarget::epsilon(0.05), + AccuracyTarget::max_error(0.05), ) .unwrap(); let space = search_workload(vec![("query", pre)]).unwrap(); @@ -1115,7 +1128,7 @@ fn physical_node_owns_phase_independently_of_binary_payload() { ExecutionTiming::QueryTime, ), ] { - let input = lower_promql(query, AccuracyTarget::epsilon(0.05)).unwrap(); + let input = lower_promql(query, AccuracyTarget::max_error(0.05)).unwrap(); let search = search_workload(vec![("q", input)]).unwrap(); let choice = search.global_selection(&DefaultCostModel); let plan = choice @@ -1149,14 +1162,14 @@ fn physical_node_owns_phase_independently_of_binary_payload() { fn ddsketch_ratio_without_domain_proof_is_uncertified() { let pre = lower_promql( "quantile_over_time(0.9, data[5m]) / quantile_over_time(0.5, data[5m])", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); let root = realize(&pre).unwrap(); assert!(matches!(root.non_asap(), Some(NonASAPOp::BinaryOp { .. }))); assert!(root.guarantee.is_none()); let space = search_workload_with_targets( - vec![("unproven", pre, Some(AccuracyTarget::epsilon(0.01)))], + vec![("unproven", pre, Some(AccuracyTarget::max_error(0.01)))], &asap_aware_mapping::default_strategies(), &DefaultAccuracyModel, ) @@ -1223,7 +1236,7 @@ fn ddsketch_ratio_rejects_unsafe_domains() { let evidence = FixtureQuantileDomain { lower, upper }; let pre = lower_promql( "quantile_over_time(0.9, data[5m]) / quantile_over_time(0.5, data[5m])", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); let strategy = ASAPStrategies::new_with_planning_inputs_and_evidence( @@ -1263,7 +1276,7 @@ fn ddsketch_ratio_rejects_one_invalid_domain_when_the_other_is_missing() { let pre = lower_promql( "quantile_over_time(0.9, data[5m]) / quantile_over_time(0.5, data[5m])", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); let strategy = ASAPStrategies::new_with_planning_inputs_and_evidence( @@ -1284,7 +1297,7 @@ fn ddsketch_ratio_bound_holds_for_signed_pinned_sketch_evaluations() { }; let pre = lower_promql( "quantile_over_time(0.9, data[5m]) / quantile_over_time(0.5, data[5m])", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); let strategy = ASAPStrategies::new_with_planning_inputs_and_evidence( @@ -1364,7 +1377,7 @@ fn ddsketch_ratio_requires_a_supported_population_size() { } let pre = lower_promql( "quantile_over_time(0.9, data[5m]) / quantile_over_time(0.5, data[5m])", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .unwrap(); for count in [0, (1u64 << 53) + 1] { @@ -1384,12 +1397,12 @@ fn ddsketch_ratio_requires_a_supported_population_size() { fn without_aggregation_candidates_export_valid_dags() { for accuracy in [ AccuracyTarget::exact(), - AccuracyTarget::epsilon_delta(0.01, 0.01), + AccuracyTarget::max_error(0.01).failure_probability(0.01), ] { for query in ["sum without (pod) (m)", "quantile without (pod) (0.5, m)"] { let root = lower_promql(query, accuracy.clone()).unwrap(); let space = search_workload_with_targets( - vec![(0, root, Some(accuracy.target_in(ErrorMetric::ABSOLUTE)))], + vec![(0, root, Some(accuracy.clone()))], &asap_aware_mapping::default_strategies(), &DefaultAccuracyModel, ) diff --git a/crates/integration-tests/tests/sql_to_post_asap.rs b/crates/integration-tests/tests/sql_to_post_asap.rs index 633358954..3548a76dd 100644 --- a/crates/integration-tests/tests/sql_to_post_asap.rs +++ b/crates/integration-tests/tests/sql_to_post_asap.rs @@ -244,7 +244,7 @@ fn inner_aggregate(node: &Rc) -> &Rc { async fn sql_full_query_retains_project_and_binds_inner_aggregate() { let pre_asap = lower( "SELECT approx_percentile_cont(latency, 0.99) AS p99 FROM metrics", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .await; let Some(NonASAPOp::Project { @@ -443,7 +443,7 @@ async fn sql_relational_parents_retain_summary_bound_aggregate() { (SELECT service, approx_percentile_cont(latency, 0.9) AS p \ FROM metrics GROUP BY service) t \ WHERE t.p > 100 ORDER BY t.p DESC LIMIT 5", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .await; let space = search_workload(vec![("query", Rc::clone(&pre_asap))]).unwrap(); @@ -496,7 +496,7 @@ async fn sql_filter_keeps_read_predicate_and_summary_population_selection() { (SELECT service, approx_percentile_cont(latency, 0.9) AS p \ FROM metrics WHERE service = 'api' GROUP BY service) t \ WHERE t.p > 100", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .await; let expected_read_predicate = { @@ -639,7 +639,7 @@ async fn sql_filter_preserves_local_fallback_boundary_for_unsupported_child() { async fn sql_quantile_binds_kll_sketch_over_named_column() { let pre_asap = lower( "SELECT approx_percentile_cont(latency, 0.99) FROM metrics", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .await; let agg = inner_aggregate(&pre_asap); @@ -741,7 +741,7 @@ async fn sql_quantile_binds_kll_sketch_over_named_column() { async fn sql_count_distinct_with_epsilon_binds_hll_over_named_column() { let pre_asap = lower( "SELECT COUNT(DISTINCT service) FROM metrics", - AccuracyTarget::epsilon(0.01), + AccuracyTarget::max_error(0.01), ) .await; let agg = inner_aggregate(&pre_asap); diff --git a/crates/integration-tests/tests/stage1_logical_asap_optimization.rs b/crates/integration-tests/tests/stage1_logical_asap_optimization.rs index 5b50ccaa6..3a89967ad 100644 --- a/crates/integration-tests/tests/stage1_logical_asap_optimization.rs +++ b/crates/integration-tests/tests/stage1_logical_asap_optimization.rs @@ -31,7 +31,7 @@ use futures::StreamExt; mod physical_common; fn accuracy() -> AccuracyTarget { - AccuracyTarget::epsilon_delta(0.01, 0.01) + AccuracyTarget::max_error(0.01).failure_probability(0.01) } async fn sql(query: &str) -> QueryRoot { @@ -62,7 +62,6 @@ fn promql(query: &str) -> QueryRoot { predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: Default::default(), }]), repeating_queries: None, }, diff --git a/crates/integration-tests/tests/stage2_physical_asap_optimization_sql.rs b/crates/integration-tests/tests/stage2_physical_asap_optimization_sql.rs index 06b4ff3a7..fdd6b39bf 100644 --- a/crates/integration-tests/tests/stage2_physical_asap_optimization_sql.rs +++ b/crates/integration-tests/tests/stage2_physical_asap_optimization_sql.rs @@ -14,7 +14,7 @@ use asap_types::pre_asap::AggIntent; use asap_types::types::AccuracyTarget; fn approximate() -> AccuracyTarget { - AccuracyTarget::epsilon_delta(0.01, 0.001) + AccuracyTarget::max_error(0.01).failure_probability(0.001) } fn names(node: &OperatorNode) -> Vec { diff --git a/crates/integration-tests/tests/summary_coverage_e2e.rs b/crates/integration-tests/tests/summary_coverage_e2e.rs index 8023c7c93..4614e0d10 100644 --- a/crates/integration-tests/tests/summary_coverage_e2e.rs +++ b/crates/integration-tests/tests/summary_coverage_e2e.rs @@ -20,7 +20,7 @@ use asap_types::pre_asap::schema::{DataType, Field, Schema}; use asap_types::types::AccuracyTarget; fn accuracy() -> AccuracyTarget { - AccuracyTarget::epsilon_delta(0.01, 0.01) + AccuracyTarget::max_error(0.01).failure_probability(0.01) } /// The single KLL `SummaryAgg` among the logical candidates for `pre`. diff --git a/crates/integration-tests/tests/tumbling_pane_merge.rs b/crates/integration-tests/tests/tumbling_pane_merge.rs index 088922bfa..327f03289 100644 --- a/crates/integration-tests/tests/tumbling_pane_merge.rs +++ b/crates/integration-tests/tests/tumbling_pane_merge.rs @@ -24,7 +24,7 @@ use asap_types::ir::{ NonASAPOp, Operator, OperatorNode, QueryRoot, TimingMemo, }; use asap_types::post_asap::Schema; -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::{AccuracyMetric, AccuracyTarget}; const EVALUATION_MS: i64 = 300_000; const PANE_MS: i64 = 60_000; @@ -425,7 +425,7 @@ fn exact_sum_over_tumbling_panes_matches_single_build() { fn quantile_over_tumbling_panes_matches_single_build() { let p99 = assert_pane_merge_matches_single_build( "quantile_over_time(0.99, m[5m])", - AccuracyTarget::epsilon(0.05).with_metric(ErrorMetric::Relative), + AccuracyTarget::max_error(0.05).accuracy_metric(AccuracyMetric::Relative), ); let mut p99 = p99; p99.sort_by(f64::total_cmp); diff --git a/crates/planner/tests/e2e_plan.rs b/crates/planner/tests/e2e_plan.rs index fe6284a02..71062a4d7 100644 --- a/crates/planner/tests/e2e_plan.rs +++ b/crates/planner/tests/e2e_plan.rs @@ -10,18 +10,18 @@ use asap_aware_mapping::pass::{ use asap_frontend_sql::{lower_sql_dialect, SqlCatalog}; use asap_planner::{e2e_plan, FrontendInput, PlanError, UserInput, UserInputError}; use asap_types::pre_asap::schema::{DataType, Field, Schema}; -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::{AccuracyMetric, AccuracyTarget}; use asap_types::workload::{ BatchEntry, DataArrival, DataWorkload, DurationMs, Evidence, PlanningWorkload, Predictability, Query, QueryLanguage, QueryRequirements, QueryWorkload, RepeatedDemand, RepeatingEntry, - RepetitionInterval, SqlDialect, TimeSelection, + RepetitionInterval, SqlDialect, }; const NOW_MS: u64 = 1_700_000_000_000; fn approximate() -> QueryRequirements { QueryRequirements { - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), max_latency_ms: None, } } @@ -31,7 +31,8 @@ fn approximate() -> QueryRequirements { fn approximate_for(sql: &str) -> QueryRequirements { let mut requirements = approximate(); if sql.contains("percentile") { - requirements.accuracy = AccuracyTarget::epsilon(0.01).with_metric(ErrorMetric::Rank); + requirements.accuracy = + AccuracyTarget::max_error(0.01).accuracy_metric(AccuracyMetric::Rank); } requirements } @@ -43,7 +44,6 @@ fn batch(sql: &str) -> BatchEntry { predictability: Predictability::AdHoc, invocations: 1, execute_at: None, - time_selection: TimeSelection::default(), } } @@ -175,7 +175,6 @@ async fn lowers_repeating_sql_entries_too() { demand: RepeatedDemand::FixedInterval(RepetitionInterval(60_000)), requirements: approximate_for("approx_percentile_cont"), predictability: Predictability::Unknown, - time_selection: TimeSelection::default(), }]), ); let catalog = lineitem_catalog(); @@ -316,7 +315,6 @@ async fn each_plan_carries_its_selected_root() { demand: RepeatedDemand::FixedInterval(RepetitionInterval(60_000)), requirements: approximate(), predictability: Predictability::Unknown, - time_selection: TimeSelection::default(), }]), }, data_workload: Some(DataWorkload { diff --git a/crates/planner/tests/plan_stages.rs b/crates/planner/tests/plan_stages.rs index 56dd774e4..d05d6e4c4 100644 --- a/crates/planner/tests/plan_stages.rs +++ b/crates/planner/tests/plan_stages.rs @@ -12,7 +12,7 @@ use asap_types::workload::{ const NOW_MS: u64 = 1_700_000_000_000; fn promql_stages(query: &str, epsilon: f64) -> Stage1ToStage3Run { - let target = AccuracyTarget::epsilon(epsilon); + let target = AccuracyTarget::max_error(epsilon); let workload = PlanningWorkload { query_workload: QueryWorkload { language: QueryLanguage::PromQL, @@ -25,7 +25,6 @@ fn promql_stages(query: &str, epsilon: f64) -> Stage1ToStage3Run { predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: Default::default(), }]), repeating_queries: None, }, diff --git a/crates/planner/tests/summary_sharing.rs b/crates/planner/tests/summary_sharing.rs index 86d926cc8..1e8411b2e 100644 --- a/crates/planner/tests/summary_sharing.rs +++ b/crates/planner/tests/summary_sharing.rs @@ -1,8 +1,8 @@ //! Structurally identical summary producers chosen by different queries are -//! shared: one `Rc` across their plans. The planner pipeline -//! plans no such sharing until Pass 2 (ASAP-aware CSE), and plan selection -//! prefers raw plans while every summary runs at query time (#580); the -//! sharing tests are ignored until then. +//! shared: one `Rc` across their plans, planned by Pass 2 +//! (ASAP-aware CSE). Three tests stay ignored: with the built-in cost model, +//! plan selection still selects an exact plan for at least one of their +//! queries (#580). use asap_types::ir::{ASAPOp, OperatorNode, QueryRoot}; use std::rc::Rc; @@ -17,11 +17,11 @@ use asap_planner::{e2e_plan, FrontendInput, UserInput}; use asap_types::post_asap::{FieldDataType, SketchAlgorithm, SketchParams}; use asap_types::pre_asap::agg_intent::default_quantile; use asap_types::pre_asap::schema::{DataType, Field, Schema}; -use asap_types::types::{AccuracyTarget, ErrorMetric}; +use asap_types::types::{AccuracyMetric, AccuracyTarget}; use asap_types::workload::{ DataArrival, DataWorkload, DurationMs, Evidence, PlanningWorkload, Predictability, Query, QueryLanguage, QueryRequirements, QueryWorkload, Rate, RepeatedDemand, RepeatingEntry, - RepetitionInterval, SqlDialect, TimeSelection, + RepetitionInterval, SqlDialect, }; const NOW_MS: u64 = 1_700_000_000_000; @@ -29,9 +29,9 @@ const NOW_MS: u64 = 1_700_000_000_000; /// `epsilon` as a query's requested accuracy; a quantile query's target /// names its metric (a rank error). fn requested(query: &str, epsilon: f64) -> AccuracyTarget { - let requested = AccuracyTarget::epsilon(epsilon); + let requested = AccuracyTarget::max_error(epsilon); if query.contains("quantile") { - requested.with_metric(ErrorMetric::Rank) + requested.accuracy_metric(AccuracyMetric::Rank) } else { requested } @@ -51,7 +51,6 @@ fn repeating(query: &str, epsilon: f64) -> RepeatingEntry { demand: RepeatedDemand::FixedInterval(RepetitionInterval(600_000)), requirements: requirements(query, epsilon), predictability: Predictability::Unknown, - time_selection: TimeSelection::default(), } } diff --git a/crates/types/src/post_asap/guarantee.rs b/crates/types/src/post_asap/guarantee.rs index 00cc73f84..f1cc7c4a4 100644 --- a/crates/types/src/post_asap/guarantee.rs +++ b/crates/types/src/post_asap/guarantee.rs @@ -25,7 +25,7 @@ //! ``` //! //! for each output value separately ([`AccuracyScope::PerValue`]), where -//! `err_metric` is fixed by [`ErrorMetric`]: an absolute error (in value +//! `err_metric` is fixed by [`AccuracyMetric`]: an absolute error (in value //! units or in multiples of a stream norm), a relative error, or a rank //! error. Metrics are **not** interchangeable: converting between them //! needs an explicit rule with evidence, never an implicit "compare the @@ -53,7 +53,7 @@ use serde::{Deserialize, Serialize}; use crate::types::AccuracyTarget; -pub use crate::types::{AccuracyScope, AccuracySpec, ErrorMetric, NormScale}; +pub use crate::types::{AccuracyMetric, AccuracyScope, AccuracySpec, NormScale}; /// A symbolic, serializable error-bound expression. Non-negative real /// arithmetic only — there is no subtraction, so a bound can never be @@ -269,7 +269,7 @@ impl ProbabilityExpr { /// How a parent operator consumes its inputs' values — the shape an /// `AccuracyModel::propagate` rule is registered against. `#[non_exhaustive]` -/// for the same reason [`ErrorMetric`] is. +/// for the same reason [`AccuracyMetric`] is. #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "op", rename_all = "snake_case")] @@ -384,6 +384,11 @@ pub enum GuaranteeSource { }, } +/// The statistic an unbound number of values per result is left as +/// ([`AccuracyGuarantee::over_whole_result`]); nothing binds it, so the +/// failure probability stays unknown. +pub const VALUES_PER_RESULT: &str = "values_per_result"; + /// The machine-readable accuracy statement attached to a finalized /// post-ASAP value — see the module docs for its semantics. Its /// [`AccuracySpec`] is symbolic, for plan selection to bind; a guarantee a @@ -402,17 +407,17 @@ impl AccuracyGuarantee { /// A per-value guarantee: error at most `error` in `metric`, failing /// with probability at most `failure_probability`. pub fn new( - metric: ErrorMetric, + metric: AccuracyMetric, error: BoundExpr, failure_probability: ProbabilityExpr, provenance: Vec, ) -> Self { Self { spec: AccuracySpec { - metric, + accuracy_metric: metric, error, failure_probability, - scope: AccuracyScope::PerValue, + accuracy_scope: AccuracyScope::PerValue, }, provenance, } @@ -445,12 +450,12 @@ impl AccuracyGuarantee { } /// The zero-error, zero-failure guarantee of a deterministic exact - /// computation. `metric` is [`ErrorMetric::ABSOLUTE`]: an exact value + /// computation. `metric` is [`AccuracyMetric::ABSOLUTE`]: an exact value /// is exact under every metric, and absolute error is the one every /// same-metric rule accepts as a zero input. pub fn exact(reason: impl Into) -> Self { Self::new( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, BoundExpr::Zero, ProbabilityExpr::Zero, vec![GuaranteeSource::Exact { @@ -459,6 +464,36 @@ impl AccuracyGuarantee { ) } + /// This guarantee stated over the whole result of one evaluation of + /// `values_per_result` values (`G`): a per-value failure probability + /// `δ` becomes `G · δ` by the union bound over the values, whatever + /// their dependence. The error is unchanged. A guarantee that never + /// fails (`δ = 0`) holds for every value at once whatever `G`; an + /// unbound `G` (`None`) leaves the failure probability symbolic in + /// [`VALUES_PER_RESULT`]. A whole-result guarantee is returned as it is. + /// Plan selection uses it to check a whole-result target. + pub fn over_whole_result(&self, values_per_result: Option) -> Self { + let mut whole = self.clone(); + if self.spec.accuracy_scope == AccuracyScope::WholeResult { + return whole; + } + whole.spec.accuracy_scope = AccuracyScope::WholeResult; + if !self.spec.failure_probability.is_zero() { + whole.spec.failure_probability = ProbabilityExpr::Scaled { + count: values_per_result.map_or_else( + || BoundExpr::Unknown { + statistic: VALUES_PER_RESULT.into(), + }, + |values| BoundExpr::Constant { + value: values as f64, + }, + ), + inner: Box::new(self.spec.failure_probability.clone()), + }; + } + whole + } + /// `true` iff this guarantee promises zero error with certainty. pub fn is_exact(&self) -> bool { self.spec.error.is_zero() && self.spec.failure_probability.is_zero() @@ -496,8 +531,8 @@ pub enum AccuracyError { )] UnsupportedComposition { operator: CompositionOperator, - input_metrics: Vec, - local_metric: Option, + input_metrics: Vec, + local_metric: Option, reason: String, }, /// An approximate input carries no guarantee at all, so nothing can be @@ -515,7 +550,7 @@ pub enum AccuracyError { {failure_probability:?}) does not satisfy {target:?}" )] TargetNotSatisfied { - metric: ErrorMetric, + metric: AccuracyMetric, bound: Option, failure_probability: Option, target: AccuracyTarget, @@ -557,7 +592,7 @@ mod tests { #[test] fn optimistic_floor_preserves_known_contributions_without_certifying() { let guarantee = AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE_STREAM_L1, + AccuracyMetric::ABSOLUTE_STREAM_L1, BoundExpr::Sum { terms: vec![ BoundExpr::Constant { value: 0.1 }, @@ -621,7 +656,7 @@ mod tests { #[test] fn guarantee_round_trips_through_json() { let g = AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE_STREAM_L1, + AccuracyMetric::ABSOLUTE_STREAM_L1, BoundExpr::Sum { terms: vec![ BoundExpr::Constant { value: 0.01 }, @@ -640,8 +675,8 @@ mod tests { }], ); let json = serde_json::to_value(&g).unwrap(); - assert_eq!(json["spec"]["metric"], "absolute_stream_l1"); - assert_eq!(json["spec"]["scope"], "per_value"); + assert_eq!(json["spec"]["accuracy_metric"], "absolute_stream_l1"); + assert_eq!(json["spec"]["accuracy_scope"], "per_value"); assert_eq!(json["spec"]["error"]["op"], "sum"); let back: AccuracyGuarantee = serde_json::from_value(json).unwrap(); assert_eq!(back, g); @@ -655,7 +690,7 @@ mod tests { statistic: "input_row_count".into(), }; let g = AccuracyGuarantee::new( - ErrorMetric::ABSOLUTE, + AccuracyMetric::ABSOLUTE, BoundExpr::Product { factors: vec![BoundExpr::Constant { value: 0.01 }, rows()], }, diff --git a/crates/types/src/post_asap/mod.rs b/crates/types/src/post_asap/mod.rs index 6da106382..ec50e5aa4 100644 --- a/crates/types/src/post_asap/mod.rs +++ b/crates/types/src/post_asap/mod.rs @@ -41,8 +41,8 @@ pub use family_capability::{ FamilyCapability, MergeCapability, MergedError, Readout, SelectionRelation, }; pub use guarantee::{ - AccuracyError, AccuracyGuarantee, BoundExpr, CompositionOperator, ErrorMetric, GuaranteeSource, - ProbabilityExpr, + AccuracyError, AccuracyGuarantee, AccuracyMetric, BoundExpr, CompositionOperator, + GuaranteeSource, ProbabilityExpr, VALUES_PER_RESULT, }; pub use query_time::{ classic_cms_sizing, cms_posterior_error_bound, count_sketch_posterior_error_bound, diff --git a/crates/types/src/pre_asap/agg_intent.rs b/crates/types/src/pre_asap/agg_intent.rs index 7d0f66053..d5e0ee0ce 100644 --- a/crates/types/src/pre_asap/agg_intent.rs +++ b/crates/types/src/pre_asap/agg_intent.rs @@ -718,7 +718,7 @@ fn accuracy_target_to_f64(t: &AccuracyTarget) -> f64 { pub fn default_cardinality() -> AggIntent { AggIntent::Cardinality { cols: vec![], - accuracy: AccuracyTarget::epsilon(1.04 / ((1u64 << 14) as f64).sqrt()), + accuracy: AccuracyTarget::max_error(1.04 / ((1u64 << 14) as f64).sqrt()), } } @@ -727,7 +727,7 @@ pub fn default_quantile(q: f64) -> AggIntent { AggIntent::Quantile { col: None, q, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), } } @@ -761,7 +761,7 @@ mod tests { fn distinct_tuple_cardinality_contract() { let intent = AggIntent::Cardinality { cols: vec![2, 5], - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }; assert_eq!(intent.input_cols(), vec![2, 5]); assert!(!agg_is_exact(&intent)); @@ -794,7 +794,7 @@ mod tests { AggIntent::::Quantile { col: None, q: 0.99, - accuracy: AccuracyTarget::epsilon(0.01) + accuracy: AccuracyTarget::max_error(0.01) } .output_column(&v) .name, @@ -884,12 +884,12 @@ mod tests { AggIntent::Quantile { col: None, q: 0.99, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, AggIntent::Quantile { col: Some(3), q: 0.99, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, AggIntent::Cardinality { cols: vec![2], @@ -901,7 +901,7 @@ mod tests { }, AggIntent::TopK { k: 10, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), }, ] { let json = serde_json::to_string(&v).unwrap(); @@ -914,9 +914,9 @@ mod tests { #[test] fn cardinality_rejects_legacy_col_payloads() { for payload in [ - r#"{"kind":"cardinality","col":2,"accuracy":{"metric":"absolute","error":0.0,"failure_probability":0.0,"scope":"whole_result"}}"#, - r#"{"kind":"cardinality","col":null,"accuracy":{"metric":"absolute","error":0.0,"failure_probability":0.0,"scope":"whole_result"}}"#, - r#"{"kind":"cardinality","col":2,"cols":[1],"accuracy":{"metric":"absolute","error":0.0,"failure_probability":0.0,"scope":"whole_result"}}"#, + r#"{"kind":"cardinality","col":2,"accuracy":{"accuracy_metric":"absolute","error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#, + r#"{"kind":"cardinality","col":null,"accuracy":{"accuracy_metric":"absolute","error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#, + r#"{"kind":"cardinality","col":2,"cols":[1],"accuracy":{"accuracy_metric":"absolute","error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#, ] { assert!( serde_json::from_str::(payload).is_err(), @@ -929,7 +929,7 @@ mod tests { /// with no `col` key — still deserializes, as the sample-value convention `None`. #[test] fn agg_intent_serde_reads_pre_115_payloads() { - let legacy = r#"{"kind":"quantile","q":0.99,"accuracy":{"error":0.0,"failure_probability":0.0,"scope":"whole_result"}}"#; + let legacy = r#"{"kind":"quantile","q":0.99,"accuracy":{"error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#; assert_eq!( serde_json::from_str::(legacy).unwrap(), AggIntent::Quantile { @@ -939,7 +939,7 @@ mod tests { } ); - let legacy = r#"{"kind":"top_k","k":5,"accuracy":{"error":0.0,"failure_probability":0.0,"scope":"whole_result"}}"#; + let legacy = r#"{"kind":"top_k","k":5,"accuracy":{"error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#; assert_eq!( serde_json::from_str::(legacy).unwrap(), AggIntent::TopK { @@ -948,7 +948,7 @@ mod tests { } ); - let legacy = r#"{"kind":"cardinality","accuracy":{"error":0.0,"failure_probability":0.0,"scope":"whole_result"}}"#; + let legacy = r#"{"kind":"cardinality","accuracy":{"error":0.0,"failure_probability":0.0,"accuracy_scope":"whole_result"}}"#; assert_eq!( serde_json::from_str::(legacy).unwrap(), AggIntent::Cardinality { diff --git a/crates/types/src/types.rs b/crates/types/src/types.rs index a6ced39a5..6ee25a2bf 100644 --- a/crates/types/src/types.rs +++ b/crates/types/src/types.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; use crate::ir::{NonASAPOp, Operator, OperatorNode, QueryRoot, ScalarExpr}; use crate::pre_asap::AggIntent; -/// The stream norm an [`ErrorMetric::Absolute`] error is stated in +/// The stream norm an [`AccuracyMetric::Absolute`] error is stated in /// multiples of. A scaled error `ε` means `|estimate − truth| ≤ ε · ‖f‖`, /// where `f` is the frequency vector of the one stream the estimate's /// summaries read: `‖f‖₁` for Count-Min, `‖f‖₂` for Count Sketch. Two scaled @@ -35,7 +35,7 @@ pub enum NormScale { #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(into = "MetricName", from = "MetricName")] -pub enum ErrorMetric { +pub enum AccuracyMetric { /// `|estimate − truth| ≤ ε`: in the value's own units when `scale` is /// `None`, or in multiples of a stream norm ([`NormScale`]). Absolute { scale: Option }, @@ -48,7 +48,7 @@ pub enum ErrorMetric { Rank, } -impl ErrorMetric { +impl AccuracyMetric { /// Absolute error in the value's own units. pub const ABSOLUTE: Self = Self::Absolute { scale: None }; /// Absolute error in multiples of the stream's L1 norm (Count-Min). @@ -61,7 +61,7 @@ impl ErrorMetric { }; } -/// The wire name of an [`ErrorMetric`]. +/// The wire name of an [`AccuracyMetric`]. #[derive(Serialize, Deserialize)] #[serde(rename_all = "snake_case")] enum MetricName { @@ -72,23 +72,23 @@ enum MetricName { Rank, } -impl From for MetricName { - fn from(metric: ErrorMetric) -> Self { +impl From for MetricName { + fn from(metric: AccuracyMetric) -> Self { match metric { - ErrorMetric::Absolute { scale: None } => Self::Absolute, - ErrorMetric::Absolute { + AccuracyMetric::Absolute { scale: None } => Self::Absolute, + AccuracyMetric::Absolute { scale: Some(NormScale::StreamL1), } => Self::AbsoluteStreamL1, - ErrorMetric::Absolute { + AccuracyMetric::Absolute { scale: Some(NormScale::StreamL2), } => Self::AbsoluteStreamL2, - ErrorMetric::Relative => Self::Relative, - ErrorMetric::Rank => Self::Rank, + AccuracyMetric::Relative => Self::Relative, + AccuracyMetric::Rank => Self::Rank, } } } -impl From for ErrorMetric { +impl From for AccuracyMetric { fn from(name: MetricName) -> Self { match name { MetricName::Absolute => Self::ABSOLUTE, @@ -118,47 +118,51 @@ pub enum AccuracyScope { /// The outer shape of an accuracy guarantee /// ([`crate::post_asap::AccuracyGuarantee`]): /// `Pr[err_metric(estimate, truth) > error] ≤ failure_probability` over the -/// values `scope` names. An [`AccuracyTarget`] has the same fields, with +/// values `accuracy_scope` names. An [`AccuracyTarget`] has the same fields, with /// concrete numbers and an optional metric. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct AccuracySpec { - pub metric: ErrorMetric, - /// ε, in `metric`'s units. + pub accuracy_metric: AccuracyMetric, + /// ε, in `accuracy_metric`'s units. pub error: E, /// δ. pub failure_probability: P, - pub scope: AccuracyScope, + pub accuracy_scope: AccuracyScope, } /// An accuracy requirement, as a workload, a caller or a frontend states /// it: `Pr[err_metric(estimate, truth) > error] ≤ failure_probability` over -/// the values `scope` names. +/// the values `accuracy_scope` names. /// -/// `metric: None` means the user did not set one: plan selection then -/// accepts a guarantee in any metric whose ε, δ and scope meet this +/// `accuracy_metric: None` means the user did not set one: plan selection +/// then accepts a guarantee in any metric whose ε, δ and scope meet this /// target's, and reports the metric it accepted. A set metric accepts only /// guarantees in that metric (or convertible to it with evidence). /// `failure_probability: None` imposes no δ requirement (sizing then uses /// the planner's default δ). Exact is `error = 0`, /// `failure_probability = Some(0)`, whatever the metric. /// -/// JSON: `{"metric": "rank", "error": 0.01, "failure_probability": 0.001, -/// "scope": "whole_result"}`; every field but `error` may be omitted -/// (`scope` defaults to `whole_result`). Exact is +/// Built with [`Self::exact`] or [`Self::max_error`] plus the optional +/// builders [`Self::failure_probability`], [`Self::accuracy_metric`] and +/// [`Self::accuracy_scope`]. +/// +/// JSON: `{"accuracy_metric": "rank", "error": 0.01, "failure_probability": +/// 0.001, "accuracy_scope": "whole_result"}`; every field but `error` may be +/// omitted (`accuracy_scope` defaults to `whole_result`). Exact is /// `{"error": 0.0, "failure_probability": 0.0}`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct AccuracyTarget { #[serde(default, skip_serializing_if = "Option::is_none")] - pub metric: Option, + pub accuracy_metric: Option, /// ε, in the metric's units. pub error: f64, /// δ. #[serde(default, skip_serializing_if = "Option::is_none")] pub failure_probability: Option, #[serde(default = "whole_result")] - pub scope: AccuracyScope, + pub accuracy_scope: AccuracyScope, } fn whole_result() -> AccuracyScope { @@ -169,74 +173,52 @@ impl AccuracyTarget { /// No approximation permitted. pub const fn exact() -> Self { Self { - metric: None, + accuracy_metric: None, error: 0.0, failure_probability: Some(0.0), - scope: AccuracyScope::WholeResult, + accuracy_scope: AccuracyScope::WholeResult, } } - /// Error at most `epsilon`, with no δ requirement and no metric set. - /// [`Self::with_metric`] sets one. - pub const fn epsilon(epsilon: f64) -> Self { + /// Error at most `error` over the whole result, with no δ requirement + /// and no metric set. + pub const fn max_error(error: f64) -> Self { Self { - metric: None, - error: epsilon, + accuracy_metric: None, + error, failure_probability: None, - scope: AccuracyScope::WholeResult, + accuracy_scope: AccuracyScope::WholeResult, } } - /// Error at most `epsilon` with probability at least `1 − delta`, and - /// no metric set. [`Self::with_metric`] sets one. - pub const fn epsilon_delta(epsilon: f64, delta: f64) -> Self { + /// This target, met with probability at least `1 − failure_probability`. + pub const fn failure_probability(self, failure_probability: f64) -> Self { Self { - failure_probability: Some(delta), - ..Self::epsilon(epsilon) + failure_probability: Some(failure_probability), + ..self } } - /// This target with its metric set to `metric`. - pub fn with_metric(self, metric: ErrorMetric) -> Self { + /// This target with its error stated in `accuracy_metric`. + pub const fn accuracy_metric(self, accuracy_metric: AccuracyMetric) -> Self { Self { - metric: Some(metric), + accuracy_metric: Some(accuracy_metric), ..self } } - /// This target over `scope`. - pub fn with_scope(self, scope: AccuracyScope) -> Self { - Self { scope, ..self } + /// This target over `accuracy_scope`. + pub const fn accuracy_scope(self, accuracy_scope: AccuracyScope) -> Self { + Self { + accuracy_scope, + ..self + } } /// Whether this target permits no approximation. pub fn is_exact(&self) -> bool { self.error == 0.0 && self.failure_probability == Some(0.0) } - - /// This target's ε, δ and scope, stated in `metric`: the target an - /// aggregate intent's summary is sized against, in the intent's own - /// metric. An exact target stays [`Self::exact`]: exact is exact in - /// every metric. - pub fn target_in(&self, metric: ErrorMetric) -> Self { - if self.is_exact() { - return Self::exact().with_scope(self.scope); - } - self.clone().with_metric(metric) - } - - /// The target a quantile intent is sized against: the set metric, or - /// `Rank` when none is set. A summary is sized in its own native terms - /// (#690 §4), so only ε, δ and the scope matter there; the query's own - /// metric, set or not, is checked at its root. - /// - /// The other intents with a target are stated in their own metric with - /// [`Self::target_in`]: an L1-scaled absolute error for counts and top-k - /// frequencies, a relative error for distinct counts, and an absolute - /// value error for frequency moments. - pub fn for_quantile(&self) -> Self { - self.target_in(self.metric.unwrap_or(ErrorMetric::Rank)) - } } /// The metric a query's result is most naturally stated in, from the @@ -248,22 +230,24 @@ impl AccuracyTarget { /// /// Used only to size and to display: a target whose metric is not set /// accepts a guarantee in any metric (#690). -pub fn default_metric_for_root(root: &QueryRoot) -> Option { +pub fn default_metric_for_root(root: &QueryRoot) -> Option { match root { QueryRoot::Operator(node) => node_metric(node), QueryRoot::Scalar(ScalarExpr::PromqlScalarFromVector(node)) | QueryRoot::Scalar(ScalarExpr::ScalarSubquery(node)) => node_metric(node), - QueryRoot::Scalar(_) => Some(ErrorMetric::ABSOLUTE), + QueryRoot::Scalar(_) => Some(AccuracyMetric::ABSOLUTE), } } /// The default metric of an intent's value, `None` for a quantile and for /// intents without a listed default. -fn intent_metric(intent: &AggIntent) -> Option { +fn intent_metric(intent: &AggIntent) -> Option { match intent { - AggIntent::Count { .. } | AggIntent::TopK { .. } => Some(ErrorMetric::ABSOLUTE_STREAM_L1), + AggIntent::Count { .. } | AggIntent::TopK { .. } => { + Some(AccuracyMetric::ABSOLUTE_STREAM_L1) + } AggIntent::Cardinality { .. } | AggIntent::FrequencyL2 { .. } => { - Some(ErrorMetric::Relative) + Some(AccuracyMetric::Relative) } _ => None, } @@ -271,9 +255,9 @@ fn intent_metric(intent: &AggIntent) -> Option { /// The metric of `node`'s values, looking through operators that only /// select, order or rename rows. -fn node_metric(node: &OperatorNode) -> Option { +fn node_metric(node: &OperatorNode) -> Option { let Operator::NonASAP(op) = &node.operator else { - return Some(ErrorMetric::ABSOLUTE); + return Some(AccuracyMetric::ABSOLUTE); }; match op { NonASAPOp::Filter { child, .. } @@ -295,7 +279,7 @@ fn node_metric(node: &OperatorNode) -> Option { .collect::>>() .map(|metrics| match metrics.split_first() { Some((first, rest)) if rest.iter().all(|metric| metric == first) => *first, - _ => ErrorMetric::ABSOLUTE, + _ => AccuracyMetric::ABSOLUTE, }), NonASAPOp::Aggregate { measures, .. } => { let mut metrics = Vec::new(); @@ -304,17 +288,19 @@ fn node_metric(node: &OperatorNode) -> Option { AggIntent::Quantile { .. } | AggIntent::HistogramQuantile { .. } => { return None } - intent => metrics.push(intent_metric(intent).unwrap_or(ErrorMetric::ABSOLUTE)), + intent => { + metrics.push(intent_metric(intent).unwrap_or(AccuracyMetric::ABSOLUTE)) + } } } // Measures with different default metrics share one value-unit // statement. Some(match metrics.split_first() { Some((first, rest)) if rest.iter().all(|metric| metric == first) => *first, - _ => ErrorMetric::ABSOLUTE, + _ => AccuracyMetric::ABSOLUTE, }) } - _ => Some(ErrorMetric::ABSOLUTE), + _ => Some(AccuracyMetric::ABSOLUTE), } } @@ -322,10 +308,10 @@ fn node_metric(node: &OperatorNode) -> Option { /// projections, `CASE` value branches, joins and operators that only /// select, order or rename rows, down to an aggregate's measure. A group /// key or any other expression is absolute. -fn column_metric(node: &OperatorNode, column: usize) -> Option { +fn column_metric(node: &OperatorNode, column: usize) -> Option { use crate::ir::operator_properties::JoinKind; let Operator::NonASAP(op) = &node.operator else { - return Some(ErrorMetric::ABSOLUTE); + return Some(AccuracyMetric::ABSOLUTE); }; match op { NonASAPOp::Filter { child, .. } @@ -351,15 +337,15 @@ fn column_metric(node: &OperatorNode, column: usize) -> Option { ScalarExpr::Cast { expr, .. } if matches!(**expr, ScalarExpr::Literal(_)) => {} ScalarExpr::Column(id) => metrics.push(column_metric(child, *id)?), - _ => return Some(ErrorMetric::ABSOLUTE), + _ => return Some(AccuracyMetric::ABSOLUTE), } } Some(match metrics.split_first() { Some((first, rest)) if rest.iter().all(|metric| metric == first) => *first, - _ => ErrorMetric::ABSOLUTE, + _ => AccuracyMetric::ABSOLUTE, }) } - _ => Some(ErrorMetric::ABSOLUTE), + _ => Some(AccuracyMetric::ABSOLUTE), }, NonASAPOp::Join { kind: @@ -380,11 +366,11 @@ fn column_metric(node: &OperatorNode, column: usize) -> Option { let keys = node.schema.fields.len().saturating_sub(measures.len()); match column.checked_sub(keys).and_then(|m| measures.get(m)) { Some(AggIntent::Quantile { .. } | AggIntent::HistogramQuantile { .. }) => None, - Some(intent) => Some(intent_metric(intent).unwrap_or(ErrorMetric::ABSOLUTE)), - None => Some(ErrorMetric::ABSOLUTE), + Some(intent) => Some(intent_metric(intent).unwrap_or(AccuracyMetric::ABSOLUTE)), + None => Some(AccuracyMetric::ABSOLUTE), } } - _ => Some(ErrorMetric::ABSOLUTE), + _ => Some(AccuracyMetric::ABSOLUTE), } } @@ -426,7 +412,7 @@ mod tests { AggIntent::Quantile { col: None, q: 0.99, - accuracy: AccuracyTarget::epsilon(0.01), + accuracy: AccuracyTarget::max_error(0.01), } } @@ -441,25 +427,28 @@ mod tests { let read = |value: serde_json::Value| serde_json::from_value::(value); assert_eq!( read(json!({"error": 0.01})).unwrap(), - AccuracyTarget::epsilon(0.01) + AccuracyTarget::max_error(0.01) ); assert_eq!( read(json!({"error": 0.0, "failure_probability": 0.0})).unwrap(), AccuracyTarget::exact() ); - let rank = AccuracyTarget::epsilon_delta(0.01, 0.001).with_metric(ErrorMetric::Rank); + let rank = AccuracyTarget::max_error(0.01) + .failure_probability(0.001) + .accuracy_metric(AccuracyMetric::Rank); assert_eq!( - read(json!({"metric": "rank", "error": 0.01, "failure_probability": 0.001})).unwrap(), + read(json!({"accuracy_metric": "rank", "error": 0.01, "failure_probability": 0.001})) + .unwrap(), rank ); assert_eq!( serde_json::to_value(&rank).unwrap(), - json!({"metric": "rank", "error": 0.01, "failure_probability": 0.001, - "scope": "whole_result"}) + json!({"accuracy_metric": "rank", "error": 0.01, "failure_probability": 0.001, + "accuracy_scope": "whole_result"}) ); assert_eq!( - serde_json::to_value(AccuracyTarget::epsilon(0.01)).unwrap(), - json!({"error": 0.01, "scope": "whole_result"}) + serde_json::to_value(AccuracyTarget::max_error(0.01)).unwrap(), + json!({"error": 0.01, "accuracy_scope": "whole_result"}) ); for old in [ json!("Exact"), @@ -475,15 +464,15 @@ mod tests { #[test] fn metric_wire_names() { for (metric, name) in [ - (ErrorMetric::ABSOLUTE, "absolute"), - (ErrorMetric::ABSOLUTE_STREAM_L1, "absolute_stream_l1"), - (ErrorMetric::ABSOLUTE_STREAM_L2, "absolute_stream_l2"), - (ErrorMetric::Relative, "relative"), - (ErrorMetric::Rank, "rank"), + (AccuracyMetric::ABSOLUTE, "absolute"), + (AccuracyMetric::ABSOLUTE_STREAM_L1, "absolute_stream_l1"), + (AccuracyMetric::ABSOLUTE_STREAM_L2, "absolute_stream_l2"), + (AccuracyMetric::Relative, "relative"), + (AccuracyMetric::Rank, "rank"), ] { assert_eq!(serde_json::to_value(metric).unwrap(), name); assert_eq!( - serde_json::from_value::(name.into()).unwrap(), + serde_json::from_value::(name.into()).unwrap(), metric ); } @@ -501,41 +490,47 @@ mod tests { metric(AggIntent::Count { accuracy: exact.clone() }), - ErrorMetric::ABSOLUTE_STREAM_L1 + AccuracyMetric::ABSOLUTE_STREAM_L1 + ); + assert_eq!( + metric(AggIntent::Sum { col: None }), + AccuracyMetric::ABSOLUTE ); - assert_eq!(metric(AggIntent::Sum { col: None }), ErrorMetric::ABSOLUTE); assert_eq!( metric(AggIntent::TopK { k: 5, accuracy: exact.clone() }), - ErrorMetric::ABSOLUTE_STREAM_L1 + AccuracyMetric::ABSOLUTE_STREAM_L1 ); assert_eq!( metric(AggIntent::Cardinality { cols: vec![], accuracy: exact.clone() }), - ErrorMetric::Relative + AccuracyMetric::Relative ); assert_eq!( metric(AggIntent::FrequencyL2 { col: None, accuracy: exact.clone() }), - ErrorMetric::Relative + AccuracyMetric::Relative ); assert_eq!( metric(AggIntent::FrequencyEntropy { col: None, accuracy: exact.clone() }), - ErrorMetric::ABSOLUTE + AccuracyMetric::ABSOLUTE + ); + assert_eq!( + metric(AggIntent::Avg { col: None }), + AccuracyMetric::ABSOLUTE ); - assert_eq!(metric(AggIntent::Avg { col: None }), ErrorMetric::ABSOLUTE); assert_eq!( default_metric_for_root(&QueryRoot::Scalar(ScalarExpr::literal_f64(1.0))), - Some(ErrorMetric::ABSOLUTE) + Some(AccuracyMetric::ABSOLUTE) ); } @@ -593,18 +588,21 @@ mod tests { })) }; let metric = |root: QueryRoot| default_metric_for_root(&root).unwrap(); - assert_eq!(metric(guard(ScalarExpr::Column(0))), ErrorMetric::Relative); + assert_eq!( + metric(guard(ScalarExpr::Column(0))), + AccuracyMetric::Relative + ); // The count's column, or arithmetic over the statistic. assert_eq!( metric(guard(ScalarExpr::Column(1))), - ErrorMetric::ABSOLUTE_STREAM_L1 + AccuracyMetric::ABSOLUTE_STREAM_L1 ); assert_eq!( metric(guard(ScalarExpr::Negative { expr: Box::new(ScalarExpr::Column(0)), semantics: crate::ir::ExprSemantics::Sql, })), - ErrorMetric::ABSOLUTE + AccuracyMetric::ABSOLUTE ); } @@ -621,17 +619,19 @@ mod tests { #[test] fn exact_targets() { assert!(AccuracyTarget::exact().is_exact()); - assert_eq!(AccuracyTarget::exact().metric, None); + assert_eq!(AccuracyTarget::exact().accuracy_metric, None); assert!(AccuracyTarget::exact() - .with_metric(ErrorMetric::Rank) + .accuracy_metric(AccuracyMetric::Rank) .is_exact()); - assert!(!AccuracyTarget::epsilon(0.0).is_exact()); + assert!(!AccuracyTarget::max_error(0.0).is_exact()); assert_eq!( - AccuracyTarget::epsilon_delta(0.01, 0.001).failure_probability, + AccuracyTarget::max_error(0.01) + .failure_probability(0.001) + .failure_probability, Some(0.001) ); assert_eq!( - AccuracyTarget::epsilon(0.01).scope, + AccuracyTarget::max_error(0.01).accuracy_scope, AccuracyScope::WholeResult ); } diff --git a/crates/types/src/workload.rs b/crates/types/src/workload.rs index 91820d757..a25823d19 100644 --- a/crates/types/src/workload.rs +++ b/crates/types/src/workload.rs @@ -59,7 +59,7 @@ pub enum QueryLanguage { /// Independent accuracy and response-latency constraints attached to one /// query in the workload. /// -/// JSON: `{"accuracy": {"error": 0.01, "metric": "rank"}, "max_latency_ms": +/// JSON: `{"accuracy": {"error": 0.01, "accuracy_metric": "rank"}, "max_latency_ms": /// 200.0}`. An omitted `accuracy` is [`AccuracyTarget::exact`] and an /// omitted `max_latency_ms` imposes no bound. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -112,61 +112,6 @@ pub enum Predictability { Unknown, } -/// Semantic relationship between a query and event time. This is supplied by -/// the workload author rather than inferred from issue time or range bounds: -/// those values do not distinguish, for example, a historical replay from a -/// live query with the same lookback. -/// -/// `RealTime` follows the newest data, `Longitudinal` analyzes a fixed or -/// historical interval, and `Mixed` combines both (for example, comparing -/// the current hour with the same hour last week). -/// -/// # Example -/// -/// ``` -/// use asap_types::workload::QueryTimeScope; -/// -/// let live_dashboard = QueryTimeScope::RealTime; -/// let historical_report = QueryTimeScope::Longitudinal; -/// let week_over_week_dashboard = QueryTimeScope::Mixed; -/// ``` -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum QueryTimeScope { - RealTime, - Longitudinal, - Mixed, - #[default] - Unknown, -} - -/// Concrete event-time interval selected by a query, kept separate from its -/// semantic real-time/longitudinal classification. -/// -/// # Example -/// -/// A live dashboard evaluated at `200_000` ms with a five-minute lookback -/// selects events from the preceding five minutes. `as_of: None` makes its -/// upper bound the evaluation time rather than a fixed historical timestamp. -/// -/// ``` -/// use asap_types::workload::{DurationMs, QueryTimeScope, TimeSelection}; -/// -/// let last_five_minutes = TimeSelection { -/// scope: QueryTimeScope::RealTime, -/// lookback: Some(DurationMs(5 * 60 * 1_000)), -/// as_of: None, -/// }; -/// ``` -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct TimeSelection { - pub scope: QueryTimeScope, - pub lookback: Option, - /// Fixed upper bound. `None` means the planning/evaluation time. - pub as_of: Option, -} - /// Confidence assigned to an estimated demand value, expressed in `[0, 1]`. /// Validation rejects values outside that range. /// @@ -302,11 +247,6 @@ pub enum QueryRecurrence { /// }, /// invocations: 1, /// execute_at: Some(TimestampMs(10_000)), -/// time_selection: TimeSelection { -/// scope: QueryTimeScope::Longitudinal, -/// lookback: Some(DurationMs(24 * 60 * 60 * 1_000)), -/// as_of: Some(TimestampMs(10_000)), -/// }, /// }; /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -317,7 +257,6 @@ pub struct BatchEntry { pub predictability: Predictability, pub invocations: u64, pub execute_at: Option, - pub time_selection: TimeSelection, } /// One query with repeated demand. Its recurrence does not imply that the @@ -334,11 +273,6 @@ pub struct BatchEntry { /// demand: RepeatedDemand::FixedInterval(RepetitionInterval(10_000)), /// requirements: QueryRequirements::default(), /// predictability: Predictability::Predictable { known_at: None }, -/// time_selection: TimeSelection { -/// scope: QueryTimeScope::RealTime, -/// lookback: Some(DurationMs(5 * 60 * 1_000)), -/// as_of: None, -/// }, /// }; /// ``` #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -348,12 +282,11 @@ pub struct RepeatingEntry { pub demand: RepeatedDemand, pub requirements: QueryRequirements, pub predictability: Predictability, - pub time_selection: TimeSelection, } /// Planner-facing normalized form of either [`BatchEntry`] or -/// [`RepeatingEntry`]. It keeps requirements, predictability, recurrence, and -/// event-time selection as separate axes. Planning takes one per query, in +/// [`RepeatingEntry`]. It keeps requirements, predictability and recurrence +/// as separate axes; the event time a query reads is in its text. Planning takes one per query, in /// root order: Stage 2 (physical ASAP-aware optimization) reads the /// recurrence and predictability, Stage 3 (plan selection) the accuracy /// target, the latency bound and the recurrence. The default is an empty @@ -372,11 +305,6 @@ pub struct RepeatingEntry { /// recurrence: QueryRecurrence::Repeated( /// RepeatedDemand::FixedInterval(RepetitionInterval(10_000)), /// ), -/// time_selection: TimeSelection { -/// scope: QueryTimeScope::RealTime, -/// lookback: Some(DurationMs(5 * 60 * 1_000)), -/// as_of: None, -/// }, /// }; /// ``` #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] @@ -392,8 +320,6 @@ pub struct QueryWorkloadEntry { /// How often it runs: sets how often each node is priced, and only /// repeated queries may keep state from ingestion time (default: unknown). pub recurrence: QueryRecurrence, - /// The event time it reads (default: unknown scope, no lookback). - pub time_selection: TimeSelection, } impl From<&BatchEntry> for QueryWorkloadEntry { @@ -406,7 +332,6 @@ impl From<&BatchEntry> for QueryWorkloadEntry { invocations: entry.invocations, execute_at: entry.execute_at, }, - time_selection: entry.time_selection.clone(), } } } @@ -418,7 +343,6 @@ impl From<&RepeatingEntry> for QueryWorkloadEntry { requirements: entry.requirements.clone(), predictability: entry.predictability.clone(), recurrence: QueryRecurrence::Repeated(entry.demand.clone()), - time_selection: entry.time_selection.clone(), } } } @@ -437,23 +361,6 @@ pub enum DataArrival { Unknown, } -/// Statistical distribution of keys in the incoming data stream. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum DataDistribution { - /// Zipf-distributed keys (s ≈ 1.1). A small number of keys dominate, - /// so only a fraction of sketch cells are touched per window. Typical - /// production case. - #[default] - Zipf, - /// All keys are equally probable. Every window fills the sketch more - /// uniformly; delta-compression benefit is lower. - Uniform, - /// Traffic arrives in bursts with a concentrated key set. Effective - /// fill rate is lower on average but spikes can reach Uniform levels. - Bursty, -} - /// Where an empirical workload value came from. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -533,10 +440,8 @@ pub struct DataWorkload { /// selectors use this as their explicit selection horizon. #[serde(default)] pub data_ingestion_interval: Evidence, - pub ingestion_volume: Evidence, pub ingestion_rate: Evidence, pub input_cardinality: Evidence, - pub distribution: Evidence, /// Stream norms per source (PromQL metric or SQL table name): the /// inputs Count-Min and Count Sketch additive error bounds are /// normalized by. Plan selection binds them to turn those bounds into @@ -780,7 +685,7 @@ mod tests { assert_eq!( empty, serde_json::json!({"accuracy": {"error": 0.0, "failure_probability": 0.0, - "scope": "whole_result"}, "max_latency_ms": null}) + "accuracy_scope": "whole_result"}, "max_latency_ms": null}) ); let bounded = serde_json::to_value( parse(r#"{"accuracy": {"error": 0.01}, "max_latency_ms": 200.0}"#).unwrap(), @@ -849,7 +754,7 @@ mod tests { } #[test] - fn entries_preserve_recurrence_and_time_scope_as_independent_axes() { + fn entries_keep_each_entrys_recurrence() { let mut workload = base_workload(); workload.query_batch = Some(vec![BatchEntry { query: Query("historical".into()), @@ -857,22 +762,12 @@ mod tests { predictability: Predictability::AdHoc, invocations: 1, execute_at: None, - time_selection: TimeSelection { - scope: QueryTimeScope::Longitudinal, - lookback: Some(DurationMs(300_000)), - as_of: Some(TimestampMs(1_000_000)), - }, }]); workload.repeating_queries = Some(vec![RepeatingEntry { query: Query("dashboard".into()), demand: RepeatedDemand::FixedInterval(RepetitionInterval(10_000)), requirements: QueryRequirements::default(), predictability: Predictability::Predictable { known_at: None }, - time_selection: TimeSelection { - scope: QueryTimeScope::RealTime, - lookback: Some(DurationMs(300_000)), - as_of: None, - }, }]); let entries: Vec<_> = workload.entries().collect(); @@ -885,11 +780,6 @@ mod tests { entries[1].recurrence, QueryRecurrence::Repeated(RepeatedDemand::FixedInterval(_)) )); - assert_eq!( - entries[0].time_selection.scope, - QueryTimeScope::Longitudinal - ); - assert_eq!(entries[1].time_selection.scope, QueryTimeScope::RealTime); workload.validate().unwrap(); } @@ -962,15 +852,10 @@ mod tests { query: Query("rate(requests_total[5m])".into()), demand: RepeatedDemand::FixedInterval(RepetitionInterval(10_000)), requirements: QueryRequirements { - accuracy: AccuracyTarget::epsilon_delta(0.01, 0.001), + accuracy: AccuracyTarget::max_error(0.01).failure_probability(0.001), max_latency_ms: Some(100.0), }, predictability: Predictability::Predictable { known_at: None }, - time_selection: TimeSelection { - scope: QueryTimeScope::RealTime, - lookback: Some(DurationMs(300_000)), - as_of: None, - }, }]), }, data_workload: Some(DataWorkload { @@ -1058,7 +943,6 @@ mod tests { }), requirements: QueryRequirements::default(), predictability: Predictability::Unknown, - time_selection: TimeSelection::default(), }]); assert_eq!( workload.validate(), diff --git a/docs/design_docs/architecture/input-output-workflow.md b/docs/design_docs/architecture/input-output-workflow.md index 7fee48691..586580186 100644 --- a/docs/design_docs/architecture/input-output-workflow.md +++ b/docs/design_docs/architecture/input-output-workflow.md @@ -15,7 +15,7 @@ Post-ASAP alternatives for the workload. | Input | Fields | Required | |---|---|---:| | `PlanningWorkload.query_workload` | Query language and one-time/repeating query workloads | Yes | -| `PlanningWorkload.data_workload` | Data arrival and optional evidence about ingestion, cardinality, and distribution | No implicit default. Set `None` when unavailable for non-PromQL workloads; PromQL requires `Some(DataWorkload)` with a nonzero ingestion interval. | +| `PlanningWorkload.data_workload` | Data arrival and optional evidence about ingestion and cardinality | No implicit default. Set `None` when unavailable for non-PromQL workloads; PromQL requires `Some(DataWorkload)` with a nonzero ingestion interval. | | Frontend-specific dependencies (outside `PlanningWorkload`) | `SqlCatalog` for SQL; `now_ms` and, when needed, `HistogramCatalog` for PromQL | `SqlCatalog` is required for SQL lowering; `now_ms` is required for PromQL lowering | | Planning models | Candidate cost/ranking and accuracy composition/checking | Used by the relevant APIs; built-in `DefaultCostModel` and `DefaultAccuracyModel` are available | | External evidence and capabilities | Domain facts, measured costs, workload statistics, and runtime support | Supply when available and when the chosen optimization depends on them; absence is not proof | @@ -160,7 +160,6 @@ struct BatchEntry { predictability: Predictability, invocations: u64, execute_at: Option, - time_selection: TimeSelection, } ``` @@ -171,7 +170,6 @@ struct BatchEntry { | `predictability` | Yes as a field; `Unknown` is allowed | Whether the query is ad hoc, known in advance, or unknown. `known_at` records when a predictable query became known. | A report known at 10:00 and scheduled for 11:00 could have its summary prepared before execution. `AdHoc` or `Unknown` does not establish that eligibility. | | `invocations` | Yes, nonzero | Number of executions in this finite batch. | Ten executions can amortize one summary build differently from one execution. | | `execute_at` | Optional | Known execution time. | The prepared case above also needs an execution time; without it no preparation window can be established. | -| `time_selection` | Yes | Whether the query follows current data or a historical interval, its lookback, and any fixed upper bound. | A moving five-minute window can require deletion/window support that a fixed historical interval does not. | ##### `repeating_queries: Option>` @@ -181,7 +179,6 @@ struct RepeatingEntry { demand: RepeatedDemand, requirements: QueryRequirements, predictability: Predictability, - time_selection: TimeSelection, } ``` @@ -191,21 +188,17 @@ struct RepeatingEntry { | `demand` | Yes | A nonzero fixed interval, fixed interval with evaluation phase, nonempty explicit schedule, or evidence-backed estimated rate. | A query every minute produces more expected reads over a horizon than one every hour. | | `requirements` | Yes | Accuracy and response-latency requirements. | An exact dashboard query cannot use an approximate summary solely because it is cheaper. | | `predictability` | Yes as a field; `Unknown` is allowed | Records whether future executions are known in advance; independent of recurrence. | Current planner code does not use this field for repeating entries; set `Unknown` if no predictability claim is available. | -| `time_selection` | Yes | Event-time scope, optional lookback, and optional fixed `as_of` time. | A live five-minute lookback differs from a fixed historical range when checking maintenance capabilities. | ##### Shared entry fields -`BatchEntry` and `RepeatingEntry` both contain `QueryRequirements`, -`Predictability`, and `TimeSelection`. The nested requirement and time-selection -fields expand as follows: +`BatchEntry` and `RepeatingEntry` both contain `QueryRequirements` and +`Predictability`. The event time a query reads (its range and offset) is in +its query text. The nested requirement fields expand as follows: | Structure | Field | Meaning | |---|---|---| | `QueryRequirements` | `accuracy` | An `AccuracyTarget`; omitted, it is `AccuracyTarget::exact()`. Set ε (and δ) when approximation is allowed. | | `QueryRequirements` | `max_latency_ms` | An optional finite, non-negative maximum in milliseconds for one evaluation; omitted, there is no bound. | -| `TimeSelection` | `scope` | `RealTime`, `Longitudinal`, `Mixed`, or `Unknown`. | -| `TimeSelection` | `lookback` | Optional event-time duration selected before the upper bound. | -| `TimeSelection` | `as_of` | Optional fixed upper-bound timestamp; `None` means planning/evaluation time. | Frontend lowering produces one Pre-ASAP `Rc` root for each normalized query entry. The caller must retain each root's association with its workload @@ -220,10 +213,8 @@ entry for later recurrence and materialization planning. struct DataWorkload { arrival: DataArrival, data_ingestion_interval: Evidence, - ingestion_volume: Evidence, ingestion_rate: Evidence, input_cardinality: Evidence, - distribution: Evidence, } ``` @@ -231,10 +222,8 @@ struct DataWorkload { |---|---:|---| | `arrival` | Present; may be `Unknown` | Distinguishes data at rest, continuously ingesting data, and mixed data. Continuous-maintenance decisions are limited when unknown. | | `data_ingestion_interval` | Required and nonzero for PromQL; otherwise optional | Sampling cadence for each PromQL source. Bare instant selectors use it as their explicit selection horizon. | -| `ingestion_volume` | Optional evidence | Total ingestion volume when known. Dependent resource estimates remain unavailable when absent or stale. | | `ingestion_rate` | Optional evidence | Updates per second used to price continuous maintenance. It must be finite and non-negative; at-rest data cannot declare a positive rate. | | `input_cardinality` | Optional evidence | Input row/sample count used by applicable sizing, accuracy, or cost rules. | -| `distribution` | Optional evidence | `Zipf`, `Uniform`, or `Bursty` key distribution used only by rules that explicitly consume it. | ##### `Evidence` fields diff --git a/docs/design_docs/proposals/asap-aware-mapping/analytical-resource-cost.md b/docs/design_docs/proposals/asap-aware-mapping/analytical-resource-cost.md index 6e270159e..6edba7713 100644 --- a/docs/design_docs/proposals/asap-aware-mapping/analytical-resource-cost.md +++ b/docs/design_docs/proposals/asap-aware-mapping/analytical-resource-cost.md @@ -213,10 +213,9 @@ normalized workload, lowered query IR, and freshness-aware statistics: | Cost concept | Canonical source | |---|---| | Source row cardinality | Fresh `DataWorkload.input_cardinality: Evidence`. | -| Data distribution | Fresh `DataWorkload.distribution: Evidence`. | -| Arrival and update rate | `DataWorkload.arrival`, `ingestion_volume`, and `ingestion_rate`. | +| Arrival and update rate | `DataWorkload.arrival` and `ingestion_rate`. | | Query demand | `QueryWorkloadEntry.recurrence`, normalized over an explicit horizon. | -| Event-time range | `QueryWorkloadEntry.time_selection`. | +| Event-time range | The query text (its range and offset). | | Accuracy and latency requirements | `QueryWorkloadEntry.requirements`. | | Operator shape, grouping keys, and constants such as Top-K limit and offset | Lowered query and selected physical plan. | @@ -327,7 +326,6 @@ canonical workload and query terms rather than defining parallel strings: | Arrival mode | `DataArrival`; this model accepts only `AtRest`. | | Planning instant and finite horizon | `TimestampMs` and `DurationMs`. | | Invocation schedule | `QueryRecurrence`; the evaluation count is derived, not copied. | -| Event-time coverage | `TimeSelection`. | | Logical sources | the existing query-IR `Source`, one per scan. | | Source selection | canonical bound `Predicate` values for ordinary scans and symbolic `InfoMatcher` values for info-metric scans. | | Physical source contents | provider-owned `source_snapshot_id` per source. | diff --git a/docs/design_docs/proposals/asap-aware-mapping/end-to-end-accuracy-guarantees.md b/docs/design_docs/proposals/asap-aware-mapping/end-to-end-accuracy-guarantees.md index d705f6b7a..e1546e1b6 100644 --- a/docs/design_docs/proposals/asap-aware-mapping/end-to-end-accuracy-guarantees.md +++ b/docs/design_docs/proposals/asap-aware-mapping/end-to-end-accuracy-guarantees.md @@ -234,7 +234,7 @@ Finalized post-ASAP values may carry a `ResultGuarantee`: ```rust struct ResultGuarantee { - metric: ErrorMetric, + metric: AccuracyMetric, bound: BoundExpr, failure_probability: ProbabilityExpr, provenance: Vec, diff --git a/docs/design_docs/proposals/planner-layering.md b/docs/design_docs/proposals/planner-layering.md index dbfc7be12..7fb4e24ce 100644 --- a/docs/design_docs/proposals/planner-layering.md +++ b/docs/design_docs/proposals/planner-layering.md @@ -263,7 +263,7 @@ selection picks one. [Example 1](#example-1-aggregation-over-dimensions--the-can | Stage | Input | Decides | Output | |---|---|---|---| | 0. Frontends | `query`, `language` | Parse and convert to a common logical form; reject what cannot be represented | `CandidateLogicalDAGs` | -| 1. Logical ASAP-aware optimization | Logical DAGs; accuracy requirements, `time_selection`, repetition interval | Summary replacement (Pass 1); ASAP-aware Common Subexpression Elimination (CSE) (Pass 2) | `CandidateLogicalASAPDAGs` | +| 1. Logical ASAP-aware optimization | Logical DAGs; accuracy requirements, repetition interval | Summary replacement (Pass 1); ASAP-aware Common Subexpression Elimination (CSE) (Pass 2) | `CandidateLogicalASAPDAGs` | | 2. Physical ASAP-aware optimization | Logical ASAP DAGs; `recurrence`, `predictability`, `DataWorkload` | Materialization; physical operators; parallelism and resources (TODO) | `CandidatePhysicalASAPDAGs` | | 3. Plan selection | Physical candidates; `requirements`; cost model, accuracy model, capabilities | Reject invalid candidates; pick the cheapest plan for the whole workload | One `PhysicalASAPDAG` | | 4. Execution (deployment) | The selected `PhysicalASAPDAG` | Run ingestion, storage and query-time computation | Query results | @@ -886,10 +886,11 @@ To see examples in DAG Viewer, following the instructions [here](TODO: write an Each example's workload is shown as tables. Field names in code font are the fields of -[`workload.rs`](https://github.com/ProjectASAP/ASAPPlanner/blob/main/crates/types/src/workload.rs). -Each query reads the event-time window [`as_of` − `lookback`, `as_of`] (fields -of `TimeSelection`). `as_of` is the window's end; `lookback` is its length. An -`as_of` of "evaluation time" means `as_of: None`: the window ends whenever the +[`workload.rs`](https://github.com/ProjectASAP/ASAPPlanner/blob/main/crates/types/src/workload.rs), +except `lookback` and `as_of`, which the query text states (its range and +offset). Each query reads the event-time window [`as_of` − `lookback`, `as_of`]. +`as_of` is the window's end; `lookback` is its length. An +`as_of` of "evaluation time" means no offset: the window ends whenever the query runs, so it moves forward with each evaluation. A fixed `as_of`, such as T − 1 y, pins the window to a historical interval. Approximate accuracy targets are `EpsilonDelta`: the answer's error is at most ε with probability at least 1 − δ. @@ -913,10 +914,8 @@ Unless an example says otherwise, every example uses this data workload: |---|---|---| | `arrival` | Whether the data is at rest, still arriving, or both | `continuously_ingesting` | | `data_ingestion_interval` | How often each series delivers one sample (the scrape interval in Prometheus). PromQL uses it as the look-back horizon of instant selectors. | 15 s | -| `ingestion_volume` | Total amount of ingested data | unknown | | `ingestion_rate` | Samples arriving per second across all series | about 66,667 samples/s | | `input_cardinality` | Number of distinct series (or keys) | 1,000,000 series | -| `distribution` | How samples are spread over keys | `zipf` | With 1,000,000 series each sampled every 15 s, the ingestion rate is 1,000,000 / 15 ≈ 66,667 samples/s. diff --git a/docs/develop_docs/end-to-end-accuracy-guarantees.md b/docs/develop_docs/end-to-end-accuracy-guarantees.md index 94c69d988..fdc4c5015 100644 --- a/docs/develop_docs/end-to-end-accuracy-guarantees.md +++ b/docs/develop_docs/end-to-end-accuracy-guarantees.md @@ -283,7 +283,7 @@ accuracy-targeted candidate. Never copy the inner guarantee onto Hydra alone. `Exact`, `Epsilon`, and `EpsilonDelta` are the current vocabulary, not a closed set. If a new summary has a final-result requirement they cannot express: -1. Define the target's result semantics and compatible `ErrorMetric`. +1. Define the target's result semantics and compatible `AccuracyMetric`. 2. Define the parameters and evidence needed to prove satisfaction. 3. Add the satisfaction rule, including invalid, unknown, and unsupported behavior. diff --git a/docs/develop_docs/library-api.md b/docs/develop_docs/library-api.md index 8e1685496..bea56ae4d 100644 --- a/docs/develop_docs/library-api.md +++ b/docs/develop_docs/library-api.md @@ -104,7 +104,6 @@ fn main() -> Result<(), Box> { predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: Default::default(), }]), repeating_queries: None, }, @@ -223,7 +222,6 @@ fn main() -> Result<(), Box> { predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: Default::default(), }]), repeating_queries: None, }, @@ -371,7 +369,6 @@ fn main() -> Result<(), Box> { predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: Default::default(), }]), repeating_queries: None, }, @@ -597,7 +594,6 @@ fn main() -> Result<(), Box> { predictability: Default::default(), invocations: 1, execute_at: None, - time_selection: Default::default(), }]), repeating_queries: None, }, diff --git a/tools/dag-viewer/README.md b/tools/dag-viewer/README.md index 7fb11c473..0c77ad224 100644 --- a/tools/dag-viewer/README.md +++ b/tools/dag-viewer/README.md @@ -62,7 +62,7 @@ time column). The server runs `stage_pipeline` with `--sql`, `--promql`, { "format": "asap-stage-pipeline/v1", "workload": { "queries": [{ "id": "q1", "language": "promql", "text": "...", - "requirements": { "accuracy": { "error": 0.0, "failure_probability": 0.0, "scope": "whole_result" }, + "requirements": { "accuracy": { "error": 0.0, "failure_probability": 0.0, "accuracy_scope": "whole_result" }, "repeat_interval_ms": 10000 } }] }, "stage0_query_language_frontends": { "dag": "" }, "stage1_logical_asap_optimization": { @@ -92,7 +92,7 @@ time column). The server runs `stage_pipeline` with `--sql`, `--promql`, and standalone scalar queries in `scalar_roots` as `[query position, expr]`. - A query's `requirements.accuracy` is its accuracy target's own JSON - (`metric` omitted when not set, `failure_probability` omitted without a + (`accuracy_metric` omitted when not set, `failure_probability` omitted without a δ). An approximate target without a metric also has `requirements.default_metric`, the metric the query's result is naturally stated in (null for a quantile), for display only. diff --git a/tools/dag-viewer/examples/planner-layering-example1.json b/tools/dag-viewer/examples/planner-layering-example1.json index 85bf14d9e..75f72c119 100644 --- a/tools/dag-viewer/examples/planner-layering-example1.json +++ b/tools/dag-viewer/examples/planner-layering-example1.json @@ -544,10 +544,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -617,14 +616,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -740,14 +739,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -897,14 +896,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -1036,14 +1035,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -1093,14 +1092,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -1216,14 +1215,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -1373,14 +1372,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -1447,14 +1446,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -1467,10 +1466,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -1540,14 +1538,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -1663,14 +1661,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -1820,14 +1818,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -1959,14 +1957,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2016,14 +2014,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2173,14 +2171,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2247,14 +2245,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2267,10 +2265,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -2340,14 +2337,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2463,14 +2460,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2620,14 +2617,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2759,14 +2756,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2894,14 +2891,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2968,14 +2965,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -2988,10 +2985,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -3061,14 +3057,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -3326,14 +3322,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -3465,14 +3461,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -3522,14 +3518,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -3645,14 +3641,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -3802,14 +3798,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -3876,14 +3872,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -3896,10 +3892,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -3969,14 +3964,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -4234,14 +4229,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -4373,14 +4368,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -4430,14 +4425,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -4587,14 +4582,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -4661,14 +4656,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -4681,10 +4676,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -4754,14 +4748,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -4877,14 +4871,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -5034,14 +5028,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -5173,14 +5167,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -5230,14 +5224,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -5495,14 +5489,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -5569,14 +5563,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -5589,10 +5583,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -5662,14 +5655,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -5785,14 +5778,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -5942,14 +5935,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -6081,14 +6074,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -6280,14 +6273,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -6354,14 +6347,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -6374,10 +6367,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -6447,14 +6439,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -6712,14 +6704,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -6851,14 +6843,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -6908,14 +6900,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -7173,14 +7165,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -7247,14 +7239,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -7267,10 +7259,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -7340,14 +7331,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -7605,14 +7596,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -7744,14 +7735,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -7943,14 +7934,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -8017,14 +8008,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -8037,10 +8028,9 @@ "measures": [ { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "metric": "absolute_stream_l1", - "scope": "whole_result" + "failure_probability": 0.001 }, "k": 10, "kind": "top_k" @@ -8110,14 +8100,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -8233,14 +8223,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -8390,14 +8380,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -8529,14 +8519,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -8586,14 +8576,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -8709,14 +8699,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -8994,14 +8984,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -9117,14 +9107,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -9274,14 +9264,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -9413,14 +9403,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -9470,14 +9460,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -9755,14 +9745,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -9878,14 +9868,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -10035,14 +10025,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -10174,14 +10164,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -10437,14 +10427,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -10702,14 +10692,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -10841,14 +10831,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -10898,14 +10888,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -11021,14 +11011,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -11306,14 +11296,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -11571,14 +11561,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -11710,14 +11700,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -11767,14 +11757,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -12052,14 +12042,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -12175,14 +12165,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -12332,14 +12322,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -12471,14 +12461,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -12528,14 +12518,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -12914,14 +12904,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -13037,14 +13027,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -13194,14 +13184,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -13333,14 +13323,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -13653,14 +13643,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -13918,14 +13908,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -14057,14 +14047,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -14114,14 +14104,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -14500,14 +14490,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -14765,14 +14755,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -14904,14 +14894,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -15224,14 +15214,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -15347,14 +15337,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -15504,14 +15494,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -15643,14 +15633,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -15700,14 +15690,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -15823,14 +15813,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -16108,14 +16098,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -16231,14 +16221,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -16388,14 +16378,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -16527,14 +16517,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -16584,14 +16574,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -16869,14 +16859,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -16992,14 +16982,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -17149,14 +17139,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -17288,14 +17278,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -17551,14 +17541,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -17816,14 +17806,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -17955,14 +17945,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -18012,14 +18002,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -18135,14 +18125,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -18420,14 +18410,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -18685,14 +18675,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -18824,14 +18814,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -18881,14 +18871,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -19166,14 +19156,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -19289,14 +19279,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -19446,14 +19436,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -19585,14 +19575,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -19642,14 +19632,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -20028,14 +20018,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -20151,14 +20141,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -20308,14 +20298,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -20447,14 +20437,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -20767,14 +20757,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -21032,14 +21022,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -21171,14 +21161,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -21228,14 +21218,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -21614,14 +21604,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -21879,14 +21869,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -22018,14 +22008,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "materialization": "NotMaterialized", @@ -22943,14 +22933,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -23069,14 +23059,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -23232,14 +23222,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -23377,14 +23367,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -23437,14 +23427,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -23563,14 +23553,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -23726,14 +23716,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -23803,14 +23793,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -23892,14 +23882,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -23974,14 +23964,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -24658,14 +24648,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -24784,14 +24774,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -24947,14 +24937,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -25092,14 +25082,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -25152,14 +25142,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -25315,14 +25305,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 8, @@ -25392,14 +25382,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -25481,14 +25471,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -25563,14 +25553,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -26192,14 +26182,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -26318,14 +26308,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -26481,14 +26471,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -26626,14 +26616,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -26767,14 +26757,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -26844,14 +26834,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 8, @@ -26933,14 +26923,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -27015,14 +27005,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -27702,14 +27692,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -27976,14 +27966,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -28121,14 +28111,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -28181,14 +28171,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -28307,14 +28297,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -28470,14 +28460,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -28547,14 +28537,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -28636,14 +28626,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -28718,14 +28708,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -29405,14 +29395,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -29681,14 +29671,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -29826,14 +29816,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -29886,14 +29876,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -30012,14 +30002,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -30175,14 +30165,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -30252,14 +30242,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -30341,14 +30331,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -30423,14 +30413,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -31110,14 +31100,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -31384,14 +31374,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -31529,14 +31519,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -31589,14 +31579,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -31752,14 +31742,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 8, @@ -31829,14 +31819,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -31918,14 +31908,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -32000,14 +31990,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -32687,14 +32677,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -32963,14 +32953,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -33108,14 +33098,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -33168,14 +33158,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -33294,14 +33284,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -33457,14 +33447,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -33534,14 +33524,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -33623,14 +33613,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -33705,14 +33695,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -34392,14 +34382,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -34518,14 +34508,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -34681,14 +34671,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -34826,14 +34816,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -34886,14 +34876,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -35160,14 +35150,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -35237,14 +35227,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -35326,14 +35316,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -35408,14 +35398,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -36095,14 +36085,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -36221,14 +36211,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -36384,14 +36374,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -36529,14 +36519,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -36589,14 +36579,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -36865,14 +36855,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -36942,14 +36932,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -37031,14 +37021,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -37113,14 +37103,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -37800,14 +37790,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -37926,14 +37916,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -38089,14 +38079,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -38234,14 +38224,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -38442,14 +38432,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 8, @@ -38519,14 +38509,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -38608,14 +38598,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -38690,14 +38680,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -39377,14 +39367,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -39503,14 +39493,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -39666,14 +39656,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -39811,14 +39801,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -39871,14 +39861,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -40147,14 +40137,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -40224,14 +40214,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -40313,14 +40303,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -40395,14 +40385,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -41085,14 +41075,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -41359,14 +41349,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -41504,14 +41494,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -41564,14 +41554,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -41838,14 +41828,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -41915,14 +41905,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -42004,14 +41994,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -42086,14 +42076,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -42776,14 +42766,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -43050,14 +43040,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -43195,14 +43185,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -43255,14 +43245,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -43531,14 +43521,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -43608,14 +43598,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -43697,14 +43687,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -43779,14 +43769,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -44469,14 +44459,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -44745,14 +44735,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -44890,14 +44880,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -44950,14 +44940,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -45224,14 +45214,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -45301,14 +45291,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -45390,14 +45380,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -45472,14 +45462,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -46162,14 +46152,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -46438,14 +46428,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -46583,14 +46573,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -46643,14 +46633,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -46919,14 +46909,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -46996,14 +46986,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -47085,14 +47075,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -47167,14 +47157,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -47857,14 +47847,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -48131,14 +48121,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -48276,14 +48266,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -48484,14 +48474,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 8, @@ -48561,14 +48551,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -48650,14 +48640,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -48732,14 +48722,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -49422,14 +49412,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -49696,14 +49686,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -49841,14 +49831,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -49901,14 +49891,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -50177,14 +50167,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -50254,14 +50244,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -50343,14 +50333,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -50425,14 +50415,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -51115,14 +51105,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -51391,14 +51381,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -51536,14 +51526,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -51596,14 +51586,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -51870,14 +51860,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -51947,14 +51937,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -52036,14 +52026,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -52118,14 +52108,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 12, @@ -52808,14 +52798,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -53084,14 +53074,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -53229,14 +53219,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -53439,14 +53429,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 8, @@ -53516,14 +53506,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 9, @@ -53605,14 +53595,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 10, @@ -53687,14 +53677,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 11, @@ -54237,14 +54227,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -54363,14 +54353,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -54526,14 +54516,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -54671,14 +54661,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -54731,14 +54721,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -54857,14 +54847,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -55617,14 +55607,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -55743,14 +55733,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -55906,14 +55896,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -56051,14 +56041,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -56111,14 +56101,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -56816,14 +56806,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -56942,14 +56932,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -57105,14 +57095,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -57250,14 +57240,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -57991,14 +57981,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -58265,14 +58255,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -58410,14 +58400,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -58470,14 +58460,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -58596,14 +58586,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -59359,14 +59349,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -59635,14 +59625,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -59780,14 +59770,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -59840,14 +59830,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -59966,14 +59956,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -60729,14 +60719,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -61003,14 +60993,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -61148,14 +61138,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -61208,14 +61198,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -61971,14 +61961,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -62247,14 +62237,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -62392,14 +62382,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -62452,14 +62442,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -62578,14 +62568,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -63334,14 +63324,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -63460,14 +63450,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -63623,14 +63613,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -63768,14 +63758,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -63828,14 +63818,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -64688,14 +64678,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -64814,14 +64804,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -64977,14 +64967,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -65122,14 +65112,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -65182,14 +65172,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -66044,14 +66034,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -66170,14 +66160,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -66333,14 +66323,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -66478,14 +66468,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -67272,14 +67262,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -67398,14 +67388,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -67561,14 +67551,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -67706,14 +67696,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -67766,14 +67756,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -68631,14 +68621,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -68905,14 +68895,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -69050,14 +69040,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -69110,14 +69100,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -69973,14 +69963,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -70247,14 +70237,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -70392,14 +70382,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -70452,14 +70442,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -71317,14 +71307,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -71593,14 +71583,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -71738,14 +71728,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -71798,14 +71788,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -72661,14 +72651,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -72937,14 +72927,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -73082,14 +73072,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -73142,14 +73132,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -74007,14 +73997,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -74281,14 +74271,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -74426,14 +74416,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -75223,14 +75213,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -75497,14 +75487,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -75642,14 +75632,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -75702,14 +75692,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -76567,14 +76557,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -76843,14 +76833,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -76988,14 +76978,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -77048,14 +77038,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -77911,14 +77901,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -78187,14 +78177,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -78332,14 +78322,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -79132,14 +79122,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -79258,14 +79248,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -79421,14 +79411,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -79566,14 +79556,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -79626,14 +79616,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -79752,14 +79742,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -80512,14 +80502,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -80638,14 +80628,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -80801,14 +80791,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -80946,14 +80936,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -81006,14 +80996,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -81711,14 +81701,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -81837,14 +81827,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -82000,14 +81990,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -82145,14 +82135,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -82886,14 +82876,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -83160,14 +83150,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -83305,14 +83295,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -83365,14 +83355,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -83491,14 +83481,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -84254,14 +84244,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -84530,14 +84520,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -84675,14 +84665,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -84735,14 +84725,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -84861,14 +84851,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -85624,14 +85614,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -85898,14 +85888,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -86043,14 +86033,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -86103,14 +86093,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -86866,14 +86856,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -87142,14 +87132,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -87287,14 +87277,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -87347,14 +87337,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -87473,14 +87463,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 7, @@ -88229,14 +88219,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -88355,14 +88345,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -88518,14 +88508,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -88663,14 +88653,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -88723,14 +88713,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -89583,14 +89573,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -89709,14 +89699,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -89872,14 +89862,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -90017,14 +90007,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -90077,14 +90067,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -90939,14 +90929,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -91065,14 +91055,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -91228,14 +91218,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -91373,14 +91363,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -92167,14 +92157,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -92293,14 +92283,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 1, @@ -92456,14 +92446,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -92601,14 +92591,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -92661,14 +92651,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -93526,14 +93516,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -93800,14 +93790,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -93945,14 +93935,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -94005,14 +93995,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -94868,14 +94858,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -95142,14 +95132,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -95287,14 +95277,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -95347,14 +95337,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -96212,14 +96202,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -96488,14 +96478,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -96633,14 +96623,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -96693,14 +96683,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -97556,14 +97546,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -97832,14 +97822,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -97977,14 +97967,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -98037,14 +98027,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -98902,14 +98892,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -99176,14 +99166,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -99321,14 +99311,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -100118,14 +100108,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -100392,14 +100382,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -100537,14 +100527,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -100597,14 +100587,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -101462,14 +101452,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -101738,14 +101728,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -101883,14 +101873,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -101943,14 +101933,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 6, @@ -102806,14 +102796,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 0, @@ -103082,14 +103072,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 3, @@ -103227,14 +103217,14 @@ } ], "spec": { + "accuracy_metric": "absolute", + "accuracy_scope": "per_value", "error": { "op": "zero" }, "failure_probability": { "op": "zero" - }, - "metric": "absolute", - "scope": "per_value" + } } }, "id": 5, @@ -105160,192 +105150,192 @@ "rejected": [ { "id": "P10", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P11", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P12", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P13", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P13-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P14", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P14-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P15", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P15-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P16", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P16-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P17", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P17-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P17-m2", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P17-m3", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P18", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P18-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P18-m2", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P18-m3", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P19", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P20", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P21", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P22", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P22-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P23", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P23-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P24", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P24-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P25", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P25-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P26", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P26-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P26-m2", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P26-m3", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P27", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P27-m1", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P27-m2", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { "id": "P27-m3", - "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { metric: None, error: 0.01, failure_probability: Some(0.001), scope: WholeResult }", + "reason": "q2: the output has no guarantee, and the deployment has no evidence that it meets target AccuracyTarget { accuracy_metric: None, error: 0.01, failure_probability: Some(0.001), accuracy_scope: WholeResult }", "valid": false }, { @@ -105448,9 +105438,9 @@ "language": "promql", "requirements": { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.0, - "failure_probability": 0.0, - "scope": "whole_result" + "failure_probability": 0.0 }, "repeat_interval_ms": 10000 }, @@ -105461,9 +105451,9 @@ "language": "promql", "requirements": { "accuracy": { + "accuracy_scope": "whole_result", "error": 0.01, - "failure_probability": 0.001, - "scope": "whole_result" + "failure_probability": 0.001 }, "default_metric": "absolute_stream_l1", "max_latency_ms": 100.0, diff --git a/tools/dag-viewer/stages.js b/tools/dag-viewer/stages.js index 6c7962e9b..c4a73880e 100644 --- a/tools/dag-viewer/stages.js +++ b/tools/dag-viewer/stages.js @@ -249,22 +249,22 @@ function validateStagePipeline(doc) { return validateSelection(stage3, physicalById); } -// A query's accuracy target (`AccuracyTarget` JSON: `metric`, `error`, -// `failure_probability`, `scope`) as one line, e.g. "ε=0.01, δ=0.001 +// A query's accuracy target (`AccuracyTarget` JSON: `accuracy_metric`, +// `error`, `failure_probability`, `accuracy_scope`) as one line, e.g. "ε=0.01, δ=0.001 // (rank)" or "ε=0.01 (metric not set; default relative)"; "exact" for -// zero error with certainty; "" without a target. `defaultMetric` is the +// zero error with certainty; "" for a malformed target. `defaultMetric` is the // query's default metric the document shows for a target without one. function accuracyText(target, defaultMetric) { if (!target || typeof target !== 'object') return ''; if (target.error === 0 && target.failure_probability === 0) return 'exact'; const delta = target.failure_probability !== undefined && target.failure_probability !== null ? `, δ=${target.failure_probability}` : ''; - const metric = target.metric || `metric not set${defaultMetric ? `; default ${defaultMetric}` : ''}`; + const metric = target.accuracy_metric || `metric not set${defaultMetric ? `; default ${defaultMetric}` : ''}`; return `ε=${target.error}${delta} (${metric})`; } // What Stage 3 accepted for a valid candidate's queries (`accepted[id]`: -// one entry per query, null without a target), as "q1: …; q2: …"; null +// one entry per query), as "q1: …; q2: …"; null // when the document has none. function acceptedText(queries) { if (!Array.isArray(queries)) return null; diff --git a/tools/dag-viewer/test_server.py b/tools/dag-viewer/test_server.py index a890dae51..3e8a447f0 100644 --- a/tools/dag-viewer/test_server.py +++ b/tools/dag-viewer/test_server.py @@ -250,12 +250,12 @@ def test_a_quantile_query_plans_with_or_without_a_metric(self): way a plan is selected, and Stage 3 reports the metric it accepted.""" query = {"name": "p99", "language": "promql", "text": "quantile_over_time(0.99, m[1m])"} named = post_plan({"queries": [query], "epsilon": 0.01, "metric": "rank"}) - self.assertEqual(named["workload"]["queries"][0]["requirements"]["accuracy"]["metric"], "rank") + self.assertEqual(named["workload"]["queries"][0]["requirements"]["accuracy"]["accuracy_metric"], "rank") selected = named["stage3_plan_selection"]["selected"] self.assertIsNotNone(selected) self.assertTrue(named["stage3_plan_selection"]["accepted"][selected][0]["summary"].startswith("metric rank; accepted ")) unnamed = post_plan({"queries": [query], "epsilon": 0.01}) - self.assertNotIn("metric", unnamed["workload"]["queries"][0]["requirements"]["accuracy"]) + self.assertNotIn("accuracy_metric", unnamed["workload"]["queries"][0]["requirements"]["accuracy"]) selected = unnamed["stage3_plan_selection"]["selected"] self.assertIsNotNone(selected) self.assertTrue(unnamed["stage3_plan_selection"]["accepted"][selected][0]["summary"].startswith("metric not set; accepted ")) diff --git a/tools/dag-viewer/test_viewer.py b/tools/dag-viewer/test_viewer.py index 47b995aed..34f9fe692 100644 --- a/tools/dag-viewer/test_viewer.py +++ b/tools/dag-viewer/test_viewer.py @@ -192,9 +192,9 @@ def test_valid_rows_show_the_accepted_metric(self): def test_accuracy_text_reads_the_target_json(self): text = lambda target: self.js.call("accuracyText", target) - self.assertEqual(text({"error": 0.0, "failure_probability": 0.0, "scope": "whole_result"}), "exact") - self.assertEqual(text({"error": 0.01, "scope": "whole_result"}), "ε=0.01 (metric not set)") - self.assertEqual(text({"metric": "rank", "error": 0.01, "failure_probability": 0.001, "scope": "whole_result"}), "ε=0.01, δ=0.001 (rank)") + self.assertEqual(text({"error": 0.0, "failure_probability": 0.0, "accuracy_scope": "whole_result"}), "exact") + self.assertEqual(text({"error": 0.01, "accuracy_scope": "whole_result"}), "ε=0.01 (metric not set)") + self.assertEqual(text({"accuracy_metric": "rank", "error": 0.01, "failure_probability": 0.001, "accuracy_scope": "whole_result"}), "ε=0.01, δ=0.001 (rank)") self.assertEqual(self.js.call("accuracyText", {"error": 0.01}, "relative"), "ε=0.01 (metric not set; default relative)") self.assertEqual(text(None), "")