Skip to main content

antecedent_core/query/
mod.rs

1//! Typed causal queries.
2//!
3//! Hot paths bind [`VariableId`](crate::ids::VariableId)s; names are resolved only at API boundaries.
4//!
5//! SPDX-License-Identifier: MIT OR Apache-2.0
6
7mod attribution;
8mod average;
9mod counterfactual;
10mod distribution;
11mod error;
12mod interference;
13mod mediation;
14mod population;
15mod response;
16mod target;
17mod temporal;
18mod transport;
19
20pub use crate::intervention::TemporalPolicy;
21
22pub use attribution::{
23    AllocationMethod, AnomalyAttributionQuery, AttributionComponents, ChangeAttributionQuery,
24    MechanismChangeQuery, OrderedFloatBits, PopulationSelector, ShapleyConfig, ShapleyMode,
25    UnitChangeQuery,
26};
27pub use average::AverageEffectQuery;
28pub use counterfactual::CounterfactualQuery;
29pub use distribution::{InterventionalDistributionQuery, PathSpecificEffectQuery};
30pub use error::QueryError;
31pub use interference::{
32    AssignmentDesign, EXPOSURE_LEVEL_TOLERANCE, ExposureLevel, ExposureMapping,
33    InterferenceFunctional, InterferenceQuery,
34};
35pub use mediation::{ConditionalEffectQuery, MediationContrast, MediationQuery};
36pub use population::{PopulationRegistry, PopulationSelection};
37pub use response::{
38    ContinuousDomain, DerivativeScale, DerivativeWeighting, GridSpec,
39    MAX_NONPARAMETRIC_RESPONSE_DIM, ObservationAssumption, ObservationSpec, ResponseFunctional,
40    ResponseQuery,
41};
42pub use target::{PredicateExpr, TargetPopulation};
43pub use temporal::TemporalEffectQuery;
44pub use transport::TransportQuery;
45
46/// Top-level causal query enum.
47#[derive(Clone, Debug, PartialEq)]
48#[non_exhaustive]
49pub enum CausalQuery {
50    /// Average / population effect (static).
51    AverageEffect(AverageEffectQuery),
52    /// Temporal effect over a discrete horizon.
53    TemporalEffect(TemporalEffectQuery),
54    /// Counterfactual / unit-level what-if query .
55    Counterfactual(CounterfactualQuery),
56    /// Anomaly attribution for one or more units .
57    AnomalyAttribution(AnomalyAttributionQuery),
58    /// Distribution / population change attribution .
59    ChangeAttribution(ChangeAttributionQuery),
60    /// Mechanism-change detection — not attribution .
61    MechanismChange(MechanismChangeQuery),
62    /// Per-unit change attribution .
63    UnitChange(UnitChangeQuery),
64    /// Mediation (direct / mediated / natural effects).
65    Mediation(MediationQuery),
66    /// Conditional average effect given modifiers.
67    ConditionalEffect(ConditionalEffectQuery),
68    /// Interventional distribution P(Y | do(...)).
69    Distribution(InterventionalDistributionQuery),
70    /// Path-specific effect / contribution.
71    PathSpecific(PathSpecificEffectQuery),
72    /// Continuous response, derivative, policy response, or Jacobian.
73    Response(ResponseQuery),
74    /// Structurally transported response between populations.
75    Transport(TransportQuery),
76    /// Randomization-based causal effect under interference.
77    Interference(InterferenceQuery),
78}
79
80impl CausalQuery {
81    /// Construct an average-effect query.
82    #[must_use]
83    pub fn average_effect(query: AverageEffectQuery) -> Self {
84        Self::AverageEffect(query)
85    }
86
87    /// Construct a temporal-effect query.
88    #[must_use]
89    pub fn temporal_effect(query: TemporalEffectQuery) -> Self {
90        Self::TemporalEffect(query)
91    }
92
93    /// Construct a counterfactual query.
94    #[must_use]
95    pub fn counterfactual(query: CounterfactualQuery) -> Self {
96        Self::Counterfactual(query)
97    }
98
99    /// Construct an anomaly attribution query.
100    #[must_use]
101    pub fn anomaly_attribution(query: AnomalyAttributionQuery) -> Self {
102        Self::AnomalyAttribution(query)
103    }
104
105    /// Construct a change attribution query.
106    #[must_use]
107    pub fn change_attribution(query: ChangeAttributionQuery) -> Self {
108        Self::ChangeAttribution(query)
109    }
110
111    /// Construct a mechanism-change detection query.
112    #[must_use]
113    pub fn mechanism_change(query: MechanismChangeQuery) -> Self {
114        Self::MechanismChange(query)
115    }
116
117    /// Construct a unit-change attribution query.
118    #[must_use]
119    pub fn unit_change(query: UnitChangeQuery) -> Self {
120        Self::UnitChange(query)
121    }
122
123    /// Construct a mediation query.
124    #[must_use]
125    pub fn mediation(query: MediationQuery) -> Self {
126        Self::Mediation(query)
127    }
128
129    /// Construct a conditional-effect query.
130    #[must_use]
131    pub fn conditional_effect(query: ConditionalEffectQuery) -> Self {
132        Self::ConditionalEffect(query)
133    }
134
135    /// Construct an interventional-distribution query.
136    #[must_use]
137    pub fn distribution(query: InterventionalDistributionQuery) -> Self {
138        Self::Distribution(query)
139    }
140
141    /// Construct a path-specific effect query.
142    #[must_use]
143    pub fn path_specific(query: PathSpecificEffectQuery) -> Self {
144        Self::PathSpecific(query)
145    }
146
147    /// Construct a continuous-response query.
148    #[must_use]
149    pub fn response(query: ResponseQuery) -> Self {
150        Self::Response(query)
151    }
152
153    /// Construct a transportability query.
154    #[must_use]
155    pub fn transport(query: TransportQuery) -> Self {
156        Self::Transport(query)
157    }
158
159    /// Construct an interference query.
160    #[must_use]
161    pub fn interference(query: InterferenceQuery) -> Self {
162        Self::Interference(query)
163    }
164}
165
166impl From<AverageEffectQuery> for CausalQuery {
167    fn from(query: AverageEffectQuery) -> Self {
168        Self::AverageEffect(query)
169    }
170}
171
172impl From<TemporalEffectQuery> for CausalQuery {
173    fn from(query: TemporalEffectQuery) -> Self {
174        Self::TemporalEffect(query)
175    }
176}
177
178impl From<CounterfactualQuery> for CausalQuery {
179    fn from(query: CounterfactualQuery) -> Self {
180        Self::Counterfactual(query)
181    }
182}
183
184impl From<MediationQuery> for CausalQuery {
185    fn from(query: MediationQuery) -> Self {
186        Self::Mediation(query)
187    }
188}
189
190impl From<ConditionalEffectQuery> for CausalQuery {
191    fn from(query: ConditionalEffectQuery) -> Self {
192        Self::ConditionalEffect(query)
193    }
194}
195
196impl From<InterventionalDistributionQuery> for CausalQuery {
197    fn from(query: InterventionalDistributionQuery) -> Self {
198        Self::Distribution(query)
199    }
200}
201
202impl From<PathSpecificEffectQuery> for CausalQuery {
203    fn from(query: PathSpecificEffectQuery) -> Self {
204        Self::PathSpecific(query)
205    }
206}
207
208impl From<AnomalyAttributionQuery> for CausalQuery {
209    fn from(query: AnomalyAttributionQuery) -> Self {
210        Self::AnomalyAttribution(query)
211    }
212}
213
214impl From<ChangeAttributionQuery> for CausalQuery {
215    fn from(query: ChangeAttributionQuery) -> Self {
216        Self::ChangeAttribution(query)
217    }
218}
219
220impl From<MechanismChangeQuery> for CausalQuery {
221    fn from(query: MechanismChangeQuery) -> Self {
222        Self::MechanismChange(query)
223    }
224}
225
226impl From<UnitChangeQuery> for CausalQuery {
227    fn from(query: UnitChangeQuery) -> Self {
228        Self::UnitChange(query)
229    }
230}
231
232impl From<ResponseQuery> for CausalQuery {
233    fn from(query: ResponseQuery) -> Self {
234        Self::Response(query)
235    }
236}
237
238impl From<TransportQuery> for CausalQuery {
239    fn from(query: TransportQuery) -> Self {
240        Self::Transport(query)
241    }
242}
243
244impl From<InterferenceQuery> for CausalQuery {
245    fn from(query: InterferenceQuery) -> Self {
246        Self::Interference(query)
247    }
248}
249
250impl CausalQuery {
251    /// Whether this query is the static ATE path.
252    #[must_use]
253    pub const fn is_static_ate(&self) -> bool {
254        matches!(self, Self::AverageEffect(_))
255    }
256
257    /// Whether this query is a temporal effect.
258    #[must_use]
259    pub const fn is_temporal_effect(&self) -> bool {
260        matches!(self, Self::TemporalEffect(_))
261    }
262
263    /// Whether this query is counterfactual.
264    #[must_use]
265    pub const fn is_counterfactual(&self) -> bool {
266        matches!(self, Self::Counterfactual(_))
267    }
268
269    /// Whether this query is mediation.
270    #[must_use]
271    pub const fn is_mediation(&self) -> bool {
272        matches!(self, Self::Mediation(_))
273    }
274
275    /// Whether this query is a conditional effect.
276    #[must_use]
277    pub const fn is_conditional_effect(&self) -> bool {
278        matches!(self, Self::ConditionalEffect(_))
279    }
280
281    /// Whether this query is an interventional distribution.
282    #[must_use]
283    pub const fn is_distribution(&self) -> bool {
284        matches!(self, Self::Distribution(_))
285    }
286
287    /// Whether this query is path-specific.
288    #[must_use]
289    pub const fn is_path_specific(&self) -> bool {
290        matches!(self, Self::PathSpecific(_))
291    }
292
293    /// Validate the inner query.
294    ///
295    /// # Errors
296    ///
297    /// Propagates inner [`QueryError`].
298    pub fn validate(&self) -> Result<(), QueryError> {
299        match self {
300            Self::AverageEffect(q) => q.validate(),
301            Self::TemporalEffect(q) => q.validate(),
302            Self::Counterfactual(q) => q.validate(),
303            Self::AnomalyAttribution(q) => q.validate(),
304            Self::ChangeAttribution(q) => q.validate(),
305            Self::MechanismChange(q) => q.validate(),
306            Self::UnitChange(q) => q.validate(),
307            Self::Mediation(q) => q.validate(),
308            Self::ConditionalEffect(q) => q.validate(),
309            Self::Distribution(q) => q.validate(),
310            Self::PathSpecific(q) => q.validate(),
311            Self::Response(q) => q.validate(),
312            Self::Transport(q) => q.validate(),
313            Self::Interference(q) => q.validate(),
314        }
315    }
316}
317
318#[cfg(test)]
319#[path = "tests.rs"]
320mod tests;