Skip to main content

antecedent_core/query/
error.rs

1//! Query submodule.
2//!
3//! SPDX-License-Identifier: MIT OR Apache-2.0
4
5use crate::ids::VariableId;
6
7#[derive(Clone, Debug, Eq, PartialEq)]
8/// Errors from query construction or validation.
9pub enum QueryError {
10    /// Treatment and outcome are the same variable.
11    TreatmentEqualsOutcome {
12        /// Shared id.
13        id: VariableId,
14    },
15    /// Intervention does not target the declared treatment.
16    InterventionVariableMismatch {
17        /// Expected treatment id.
18        expected: VariableId,
19        /// Actual intervention target.
20        got: VariableId,
21    },
22    /// Intervention sequence has no unique target variable.
23    AmbiguousInterventionTarget,
24    /// Effect modifier overlaps treatment or outcome.
25    ModifierOverlapsTreatmentOrOutcome,
26    /// Sustained window has `until < from`.
27    InvalidTemporalWindow {
28        /// Window start.
29        from: i32,
30        /// Window end.
31        until: i32,
32    },
33    /// Horizon must be at least one time step.
34    NonPositiveHorizon,
35    /// Nested intervention failed validation.
36    InvalidIntervention(String),
37    /// Counterfactual query has no outcomes.
38    EmptyCounterfactualOutcomes,
39    /// Anomaly query has no targets.
40    EmptyAnomalyTargets,
41    /// Anomaly `max_units` must be ≥ 1.
42    NonPositiveAnomalyLimit,
43    /// Mediation query has no mediators.
44    EmptyMediators,
45    /// Mediator overlaps treatment or outcome.
46    MediatorOverlapsTreatmentOrOutcome,
47    /// Conditional effect requires non-empty modifiers.
48    EmptyEffectModifiers,
49    /// Population selector has no rows.
50    EmptyPopulationRows,
51    /// Named [`crate::query::PredicateExpr`] has an empty registry key.
52    EmptyPredicateName,
53    /// [`crate::intervention::TemporalPolicy::Dynamic`] has no single treatment origin.
54    DynamicPolicyHasNoTreatmentOffset,
55    /// Time-range population has `end <= start`.
56    InvalidPopulationTimeRange {
57        /// Start.
58        start: usize,
59        /// End.
60        end: usize,
61    },
62    /// Sequential allocation order is empty.
63    EmptyAllocationOrder,
64    /// Shapley exact component limit must be ≥ 1.
65    NonPositiveShapleyLimit,
66    /// Approximate Shapley sample / permutation count must be ≥ 1.
67    NonPositiveShapleySamples,
68    /// Change attribution `max_components` must be ≥ 1.
69    NonPositiveComponentLimit,
70    /// Mechanism-change query has no targets.
71    EmptyMechanismChangeTargets,
72    /// Significance level must be in (0, 1).
73    InvalidSignificanceLevel,
74    /// Interventional distribution query has no outcomes.
75    EmptyDistributionOutcomes,
76    /// Path enumeration `max_paths` / `max_len` must be ≥ 1.
77    NonPositivePathLimit,
78    /// Path node overlaps treatment or outcome.
79    PathNodeOverlapsTreatmentOrOutcome,
80    /// Distribution conditioning overlaps an outcome or intervention target.
81    ConditioningOverlapsOutcomeOrIntervention,
82    /// Named / custom population requires a [`super::PopulationRegistry`].
83    PopulationRegistryRequired,
84    /// Named predicate is not bound in the registry.
85    UnknownPredicateName {
86        /// Predicate key.
87        name: std::sync::Arc<str>,
88    },
89    /// Custom distribution handle is not bound in the registry.
90    UnknownDistributionRef {
91        /// Raw distribution id.
92        id: u32,
93    },
94    /// Treated / untreated population requires a treatment column.
95    PopulationNeedsTreatment,
96    /// Treatment column is not binary 0/1.
97    PopulationNonBinaryTreatment,
98    /// Keep-mask / weight length mismatch.
99    PopulationLengthMismatch {
100        /// Expected length.
101        expected: usize,
102        /// Actual length.
103        actual: usize,
104    },
105    /// Predicate row index ≥ `n`.
106    PopulationRowOutOfRange {
107        /// Offending row.
108        row: usize,
109        /// Population size.
110        n: usize,
111    },
112    /// Environment-restricted populations need multi-env data (not resolved here).
113    PopulationEnvironmentUnsupported,
114    /// Distribution weights contain negatives or non-finite values.
115    InvalidPopulationWeights,
116}
117
118impl core::fmt::Display for QueryError {
119    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
120        match self {
121            Self::TreatmentEqualsOutcome { id } => {
122                write!(f, "treatment and outcome are the same variable {id}")
123            }
124            Self::InterventionVariableMismatch { expected, got } => {
125                write!(f, "intervention targets {got}, expected treatment {expected}")
126            }
127            Self::AmbiguousInterventionTarget => {
128                write!(f, "intervention does not have a unique target variable")
129            }
130            Self::ModifierOverlapsTreatmentOrOutcome => {
131                write!(f, "effect modifier overlaps treatment or outcome")
132            }
133            Self::InvalidTemporalWindow { from, until } => {
134                write!(f, "invalid temporal window [{from}, {until}]")
135            }
136            Self::NonPositiveHorizon => write!(f, "horizon_steps must be >= 1"),
137            Self::InvalidIntervention(msg) => write!(f, "invalid intervention: {msg}"),
138            Self::EmptyCounterfactualOutcomes => {
139                write!(f, "counterfactual query requires at least one outcome")
140            }
141            Self::EmptyAnomalyTargets => write!(f, "anomaly attribution requires targets"),
142            Self::NonPositiveAnomalyLimit => write!(f, "anomaly max_units must be >= 1"),
143            Self::EmptyMediators => write!(f, "mediation query requires mediators"),
144            Self::MediatorOverlapsTreatmentOrOutcome => {
145                write!(f, "mediator overlaps treatment or outcome")
146            }
147            Self::EmptyEffectModifiers => {
148                write!(f, "conditional effect requires non-empty effect modifiers")
149            }
150            Self::EmptyPopulationRows => write!(f, "population selector has no rows"),
151            Self::EmptyPredicateName => write!(f, "predicate name must be non-empty"),
152            Self::DynamicPolicyHasNoTreatmentOffset => {
153                write!(f, "TemporalPolicy::Dynamic has no single treatment offset")
154            }
155            Self::InvalidPopulationTimeRange { start, end } => {
156                write!(f, "invalid population time range [{start}, {end})")
157            }
158            Self::EmptyAllocationOrder => write!(f, "sequential allocation order is empty"),
159            Self::NonPositiveShapleyLimit => {
160                write!(f, "Shapley max_exact_components must be >= 1")
161            }
162            Self::NonPositiveShapleySamples => {
163                write!(f, "Shapley sample / permutation count must be >= 1")
164            }
165            Self::NonPositiveComponentLimit => {
166                write!(f, "max_components / max_targets must be >= 1")
167            }
168            Self::EmptyMechanismChangeTargets => {
169                write!(f, "mechanism-change detection requires targets")
170            }
171            Self::InvalidSignificanceLevel => {
172                write!(f, "significance level must be in (0, 1)")
173            }
174            Self::EmptyDistributionOutcomes => {
175                write!(f, "interventional distribution requires at least one outcome")
176            }
177            Self::NonPositivePathLimit => {
178                write!(f, "path max_paths / max_len must be >= 1")
179            }
180            Self::PathNodeOverlapsTreatmentOrOutcome => {
181                write!(f, "path node overlaps treatment or outcome")
182            }
183            Self::ConditioningOverlapsOutcomeOrIntervention => {
184                write!(f, "distribution conditioning overlaps outcome or intervention")
185            }
186            Self::PopulationRegistryRequired => {
187                write!(f, "named predicate / custom distribution requires a PopulationRegistry")
188            }
189            Self::UnknownPredicateName { name } => {
190                write!(f, "unknown predicate name `{name}`")
191            }
192            Self::UnknownDistributionRef { id } => {
193                write!(f, "unknown DistributionRef({id})")
194            }
195            Self::PopulationNeedsTreatment => {
196                write!(f, "Treated/Untreated population requires a treatment column")
197            }
198            Self::PopulationNonBinaryTreatment => {
199                write!(f, "Treated/Untreated population requires binary 0/1 treatment")
200            }
201            Self::PopulationLengthMismatch { expected, actual } => {
202                write!(f, "population length mismatch: expected {expected}, got {actual}")
203            }
204            Self::PopulationRowOutOfRange { row, n } => {
205                write!(f, "population row {row} out of range for n={n}")
206            }
207            Self::PopulationEnvironmentUnsupported => {
208                write!(f, "Environment target population is not resolved by PopulationRegistry")
209            }
210            Self::InvalidPopulationWeights => {
211                write!(f, "custom distribution weights must be finite and non-negative")
212            }
213        }
214    }
215}
216
217impl std::error::Error for QueryError {}