Skip to main content

antecedent_validate/
validator.rs

1//! Validator contract.
2//!
3//! Effect refuters (§18.2) implement this trait. The trait is named `Validator`
4//! (not `Refuter`) per DESIGN.
5//!
6//! SPDX-License-Identifier: MIT OR Apache-2.0
7
8use antecedent_core::ExecutionContext;
9use antecedent_estimate::EstimationWorkspace;
10
11use crate::bootstrap_refute::BootstrapRefute;
12use crate::common::{RefutationProblem, RefutationReport};
13use crate::data_subset::DataSubsetRefuter;
14use crate::dummy_outcome::DummyOutcome;
15use crate::error::ValidationError;
16use crate::evalue::EValue;
17use crate::graph_refute::GraphRefuter;
18use crate::overlap::OverlapRefuter;
19use crate::overlap_rule::OverlapRuleRefuter;
20use crate::panel_slice::PanelSliceTemplate;
21use crate::placebo::PlaceboTreatment;
22use crate::rcc::RandomCommonCause;
23use crate::riesz::RieszSensitivity;
24use crate::sensitivity::{LinearSensitivity, NonparametricSensitivity, PartialLinearSensitivity};
25use crate::unobserved_common_cause::UnobservedCommonCause;
26
27mod sealed {
28    pub trait Sealed {}
29}
30
31/// Validation / refutation algorithm over artifact type `A` .
32///
33/// `validate` also takes an [`EstimationWorkspace`] because effect refuters in
34/// this crate refit estimators; DESIGN's sketch omits it.
35///
36/// This trait is sealed: only types in this crate may implement it.
37pub trait Validator<A>: sealed::Sealed {
38    /// Prepared artifact produced by [`Self::prepare`].
39    type Prepared;
40    /// Report produced by [`Self::validate`].
41    type Report;
42
43    /// Compile `artifact` into a reusable prepared form.
44    ///
45    /// # Errors
46    ///
47    /// Incompatible artifact or missing prerequisites.
48    fn prepare(
49        &self,
50        artifact: &A,
51        ctx: &ExecutionContext,
52    ) -> Result<Self::Prepared, ValidationError>;
53
54    /// Run the check on a prepared artifact.
55    ///
56    /// # Errors
57    ///
58    /// Data, estimation, or applicability failures.
59    fn validate(
60        &self,
61        prepared: &mut Self::Prepared,
62        workspace: &mut EstimationWorkspace,
63        ctx: &ExecutionContext,
64    ) -> Result<Self::Report, ValidationError>;
65}
66
67/// Prepared effect-refutation problem: borrowed inputs plus a per-unit panel
68/// slice plan when `problem.temporal` targets a panel.
69#[derive(Clone, Debug)]
70pub struct PreparedRefutation<'a> {
71    /// Underlying refutation problem.
72    pub problem: RefutationProblem<'a>,
73    /// Per-unit slice template compiled from the original stacked table.
74    pub panel: Option<PanelSliceTemplate<'a>>,
75}
76
77impl<'a> PreparedRefutation<'a> {
78    /// Compile reusable panel-slice state for `problem`.
79    ///
80    /// # Errors
81    ///
82    /// Stacked row count disagrees with the panel, or a unit's column count
83    /// disagrees with the panel schema.
84    pub fn compile(problem: &RefutationProblem<'a>) -> Result<Self, ValidationError> {
85        let panel = match problem.temporal.and_then(|t| t.panel) {
86            Some(p) => Some(PanelSliceTemplate::from_panel(p, problem.data)?),
87            None => None,
88        };
89        Ok(Self { problem: *problem, panel })
90    }
91}
92
93/// Run `validator` end-to-end (prepare → validate) for suite dispatch.
94///
95/// # Errors
96///
97/// Propagates prepare/validate failures.
98pub fn run_validator<'a, V>(
99    validator: &V,
100    problem: &RefutationProblem<'a>,
101    workspace: &mut EstimationWorkspace,
102    ctx: &ExecutionContext,
103) -> Result<RefutationReport, ValidationError>
104where
105    V: Validator<
106            RefutationProblem<'a>,
107            Prepared = PreparedRefutation<'a>,
108            Report = RefutationReport,
109        >,
110{
111    let mut prepared = validator.prepare(problem, ctx)?;
112    validator.validate(&mut prepared, workspace, ctx)
113}
114
115macro_rules! impl_effect_validator {
116    ($ty:ty, $call:expr) => {
117        impl crate::validator::sealed::Sealed for $ty {}
118        impl<'a> Validator<RefutationProblem<'a>> for $ty {
119            type Prepared = PreparedRefutation<'a>;
120            type Report = RefutationReport;
121
122            fn prepare(
123                &self,
124                artifact: &RefutationProblem<'a>,
125                _ctx: &ExecutionContext,
126            ) -> Result<Self::Prepared, ValidationError> {
127                PreparedRefutation::compile(artifact)
128            }
129
130            fn validate(
131                &self,
132                prepared: &mut Self::Prepared,
133                workspace: &mut EstimationWorkspace,
134                ctx: &ExecutionContext,
135            ) -> Result<Self::Report, ValidationError> {
136                let problem = prepared.problem.with_panel_slices(prepared.panel.as_ref());
137                ($call)(self, &problem, workspace, ctx)
138            }
139        }
140    };
141}
142
143impl_effect_validator!(PlaceboTreatment, |this: &PlaceboTreatment, p, ws, ctx| {
144    this.refute(p, ws, ctx)
145});
146impl_effect_validator!(RandomCommonCause, |this: &RandomCommonCause, p, ws, ctx| {
147    this.refute(p, ws, ctx)
148});
149impl_effect_validator!(BootstrapRefute, |this: &BootstrapRefute, p, ws, ctx| {
150    this.refute(p, ws, ctx)
151});
152impl_effect_validator!(UnobservedCommonCause, |this: &UnobservedCommonCause, p, ws, ctx| {
153    this.refute(p, ws, ctx)
154});
155impl_effect_validator!(DataSubsetRefuter, |this: &DataSubsetRefuter, p, ws, ctx| {
156    this.refute(p, ws, ctx)
157});
158impl_effect_validator!(DummyOutcome, |this: &DummyOutcome, p, ws, ctx| { this.refute(p, ws, ctx) });
159impl_effect_validator!(GraphRefuter, |this: &GraphRefuter, p, ws, ctx| { this.refute(p, ws, ctx) });
160impl_effect_validator!(LinearSensitivity, |this: &LinearSensitivity, p, ws, ctx| {
161    this.refute(p, ws, ctx)
162});
163impl_effect_validator!(PartialLinearSensitivity, |this: &PartialLinearSensitivity, p, ws, ctx| {
164    this.refute(p, ws, ctx)
165});
166impl_effect_validator!(NonparametricSensitivity, |this: &NonparametricSensitivity, p, ws, ctx| {
167    this.refute(p, ws, ctx)
168});
169impl_effect_validator!(RieszSensitivity, |this: &RieszSensitivity, p, ws, ctx| {
170    this.refute(p, ws, ctx)
171});
172
173/// Validators whose `refute` does not need an estimation workspace.
174macro_rules! impl_stateless_effect_validator {
175    ($ty:ty, $call:expr) => {
176        impl crate::validator::sealed::Sealed for $ty {}
177        impl<'a> Validator<RefutationProblem<'a>> for $ty {
178            type Prepared = PreparedRefutation<'a>;
179            type Report = RefutationReport;
180
181            fn prepare(
182                &self,
183                artifact: &RefutationProblem<'a>,
184                _ctx: &ExecutionContext,
185            ) -> Result<Self::Prepared, ValidationError> {
186                PreparedRefutation::compile(artifact)
187            }
188
189            fn validate(
190                &self,
191                prepared: &mut Self::Prepared,
192                _workspace: &mut EstimationWorkspace,
193                _ctx: &ExecutionContext,
194            ) -> Result<Self::Report, ValidationError> {
195                let problem = prepared.problem.with_panel_slices(prepared.panel.as_ref());
196                ($call)(self, &problem)
197            }
198        }
199    };
200}
201
202impl_stateless_effect_validator!(OverlapRefuter, |this: &OverlapRefuter, p| this.refute(p));
203impl_stateless_effect_validator!(OverlapRuleRefuter, |this: &OverlapRuleRefuter, p| {
204    this.refute(p)
205});
206impl_stateless_effect_validator!(EValue, |this: &EValue, p| this.refute(p));