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    /// Sequential allocation order contains the same component more than once.
65    DuplicateAllocationComponent,
66    /// Shapley exact component limit must be ≥ 1.
67    NonPositiveShapleyLimit,
68    /// Approximate Shapley sample / permutation count must be ≥ 1.
69    NonPositiveShapleySamples,
70    /// Change attribution `max_components` must be ≥ 1.
71    NonPositiveComponentLimit,
72    /// Mechanism-change query has no targets.
73    EmptyMechanismChangeTargets,
74    /// Significance level must be in (0, 1).
75    InvalidSignificanceLevel,
76    /// Interventional distribution query has no outcomes.
77    EmptyDistributionOutcomes,
78    /// Path enumeration `max_paths` / `max_len` must be ≥ 1.
79    NonPositivePathLimit,
80    /// Path node overlaps treatment or outcome.
81    PathNodeOverlapsTreatmentOrOutcome,
82    /// Distribution conditioning overlaps an outcome or intervention target.
83    ConditioningOverlapsOutcomeOrIntervention,
84    /// Named / custom population requires a [`super::PopulationRegistry`].
85    PopulationRegistryRequired,
86    /// Named predicate is not bound in the registry.
87    UnknownPredicateName {
88        /// Predicate key.
89        name: std::sync::Arc<str>,
90    },
91    /// Custom distribution handle is not bound in the registry.
92    UnknownDistributionRef {
93        /// Raw distribution id.
94        id: u32,
95    },
96    /// Treated / untreated population requires a treatment column.
97    PopulationNeedsTreatment,
98    /// Treatment column is not binary 0/1.
99    PopulationNonBinaryTreatment,
100    /// Keep-mask / weight length mismatch.
101    PopulationLengthMismatch {
102        /// Expected length.
103        expected: usize,
104        /// Actual length.
105        actual: usize,
106    },
107    /// Predicate row index ≥ `n`.
108    PopulationRowOutOfRange {
109        /// Offending row.
110        row: usize,
111        /// Population size.
112        n: usize,
113    },
114    /// Environment-restricted populations need multi-env data (not resolved here).
115    PopulationEnvironmentUnsupported,
116    /// Distribution weights contain negatives or non-finite values.
117    InvalidPopulationWeights,
118}
119
120impl core::fmt::Display for QueryError {
121    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122        match self {
123            Self::TreatmentEqualsOutcome { id } => {
124                write!(f, "treatment and outcome are the same variable {id}")
125            }
126            Self::InterventionVariableMismatch { expected, got } => {
127                write!(f, "intervention targets {got}, expected treatment {expected}")
128            }
129            Self::AmbiguousInterventionTarget => {
130                write!(f, "intervention does not have a unique target variable")
131            }
132            Self::ModifierOverlapsTreatmentOrOutcome => {
133                write!(f, "effect modifier overlaps treatment or outcome")
134            }
135            Self::InvalidTemporalWindow { from, until } => {
136                write!(f, "invalid temporal window [{from}, {until}]")
137            }
138            Self::NonPositiveHorizon => write!(f, "horizon_steps must be >= 1"),
139            Self::InvalidIntervention(msg) => write!(f, "invalid intervention: {msg}"),
140            Self::EmptyCounterfactualOutcomes => {
141                write!(f, "counterfactual query requires at least one outcome")
142            }
143            Self::EmptyAnomalyTargets => write!(f, "anomaly attribution requires targets"),
144            Self::NonPositiveAnomalyLimit => write!(f, "anomaly max_units must be >= 1"),
145            Self::EmptyMediators => write!(f, "mediation query requires mediators"),
146            Self::MediatorOverlapsTreatmentOrOutcome => {
147                write!(f, "mediator overlaps treatment or outcome")
148            }
149            Self::EmptyEffectModifiers => {
150                write!(f, "conditional effect requires non-empty effect modifiers")
151            }
152            Self::EmptyPopulationRows => write!(f, "population selector has no rows"),
153            Self::EmptyPredicateName => write!(f, "predicate name must be non-empty"),
154            Self::DynamicPolicyHasNoTreatmentOffset => {
155                write!(f, "TemporalPolicy::Dynamic has no single treatment offset")
156            }
157            Self::InvalidPopulationTimeRange { start, end } => {
158                write!(f, "invalid population time range [{start}, {end})")
159            }
160            Self::EmptyAllocationOrder => write!(f, "sequential allocation order is empty"),
161            Self::DuplicateAllocationComponent => {
162                write!(f, "sequential allocation order contains duplicate components")
163            }
164            Self::NonPositiveShapleyLimit => {
165                write!(f, "Shapley max_exact_components must be >= 1")
166            }
167            Self::NonPositiveShapleySamples => {
168                write!(f, "Shapley sample / permutation count must be >= 1")
169            }
170            Self::NonPositiveComponentLimit => {
171                write!(f, "max_components / max_targets must be >= 1")
172            }
173            Self::EmptyMechanismChangeTargets => {
174                write!(f, "mechanism-change detection requires targets")
175            }
176            Self::InvalidSignificanceLevel => {
177                write!(f, "significance level must be in (0, 1)")
178            }
179            Self::EmptyDistributionOutcomes => {
180                write!(f, "interventional distribution requires at least one outcome")
181            }
182            Self::NonPositivePathLimit => {
183                write!(f, "path max_paths / max_len must be >= 1")
184            }
185            Self::PathNodeOverlapsTreatmentOrOutcome => {
186                write!(f, "path node overlaps treatment or outcome")
187            }
188            Self::ConditioningOverlapsOutcomeOrIntervention => {
189                write!(f, "distribution conditioning overlaps outcome or intervention")
190            }
191            Self::PopulationRegistryRequired => {
192                write!(f, "named predicate / custom distribution requires a PopulationRegistry")
193            }
194            Self::UnknownPredicateName { name } => {
195                write!(f, "unknown predicate name `{name}`")
196            }
197            Self::UnknownDistributionRef { id } => {
198                write!(f, "unknown DistributionRef({id})")
199            }
200            Self::PopulationNeedsTreatment => {
201                write!(f, "Treated/Untreated population requires a treatment column")
202            }
203            Self::PopulationNonBinaryTreatment => {
204                write!(f, "Treated/Untreated population requires binary 0/1 treatment")
205            }
206            Self::PopulationLengthMismatch { expected, actual } => {
207                write!(f, "population length mismatch: expected {expected}, got {actual}")
208            }
209            Self::PopulationRowOutOfRange { row, n } => {
210                write!(f, "population row {row} out of range for n={n}")
211            }
212            Self::PopulationEnvironmentUnsupported => {
213                write!(f, "Environment target population is not resolved by PopulationRegistry")
214            }
215            Self::InvalidPopulationWeights => {
216                write!(f, "custom distribution weights must be finite and non-negative")
217            }
218        }
219    }
220}
221
222impl std::error::Error for QueryError {}