use std::sync::Arc;
use crate::ids::VariableId;
use crate::intervention::Intervention;
use crate::value::Value;
use super::TargetPopulation;
use super::error::QueryError;
#[derive(Clone, Debug, PartialEq)]
pub struct InterventionalDistributionQuery {
pub outcomes: Arc<[VariableId]>,
pub interventions: Arc<[Intervention]>,
pub conditioning: Arc<[VariableId]>,
pub target_population: TargetPopulation,
}
impl InterventionalDistributionQuery {
#[must_use]
pub fn new(outcome: VariableId, interventions: impl Into<Arc<[Intervention]>>) -> Self {
Self {
outcomes: Arc::from([outcome]),
interventions: interventions.into(),
conditioning: Arc::from([]),
target_population: TargetPopulation::AllObserved,
}
}
#[must_use]
pub fn with_outcomes(mut self, outcomes: impl Into<Arc<[VariableId]>>) -> Self {
self.outcomes = outcomes.into();
self
}
#[must_use]
pub fn with_conditioning(mut self, conditioning: impl Into<Arc<[VariableId]>>) -> Self {
self.conditioning = conditioning.into();
self
}
#[must_use]
pub fn with_target_population(mut self, population: TargetPopulation) -> Self {
self.target_population = population;
self
}
pub fn validate(&self) -> Result<(), QueryError> {
if self.outcomes.is_empty() {
return Err(QueryError::EmptyDistributionOutcomes);
}
for iv in self.interventions.iter() {
iv.validate().map_err(|e| QueryError::InvalidIntervention(e.to_string()))?;
}
for &z in self.conditioning.iter() {
if self.outcomes.iter().any(|&y| y == z) {
return Err(QueryError::ConditioningOverlapsOutcomeOrIntervention);
}
if self.interventions.iter().any(|iv| iv.primary_variable() == Some(z)) {
return Err(QueryError::ConditioningOverlapsOutcomeOrIntervention);
}
}
self.target_population.validate()?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PathSpecificEffectQuery {
pub treatment: VariableId,
pub outcome: VariableId,
pub path_nodes: Arc<[VariableId]>,
pub control: Intervention,
pub active: Intervention,
pub target_population: TargetPopulation,
pub max_paths: usize,
pub max_len: usize,
}
impl PathSpecificEffectQuery {
#[must_use]
pub fn binary(treatment: VariableId, outcome: VariableId) -> Self {
Self {
treatment,
outcome,
path_nodes: Arc::from([]),
control: Intervention::set(treatment, Value::f64(0.0)),
active: Intervention::set(treatment, Value::f64(1.0)),
target_population: TargetPopulation::AllObserved,
max_paths: 64,
max_len: 16,
}
}
#[must_use]
pub fn with_path_nodes(mut self, nodes: impl Into<Arc<[VariableId]>>) -> Self {
self.path_nodes = nodes.into();
self
}
#[must_use]
pub const fn with_max_paths(mut self, max_paths: usize) -> Self {
self.max_paths = max_paths;
self
}
#[must_use]
pub const fn with_max_len(mut self, max_len: usize) -> Self {
self.max_len = max_len;
self
}
#[must_use]
pub fn with_target_population(mut self, population: TargetPopulation) -> Self {
self.target_population = population;
self
}
pub fn validate(&self) -> Result<(), QueryError> {
if self.treatment == self.outcome {
return Err(QueryError::TreatmentEqualsOutcome { id: self.treatment });
}
if self.max_paths == 0 {
return Err(QueryError::NonPositivePathLimit);
}
if self.max_len == 0 {
return Err(QueryError::NonPositivePathLimit);
}
if self.path_nodes.iter().any(|&n| n == self.treatment || n == self.outcome) {
return Err(QueryError::PathNodeOverlapsTreatmentOrOutcome);
}
let control_var =
self.control.primary_variable().ok_or(QueryError::AmbiguousInterventionTarget)?;
if control_var != self.treatment {
return Err(QueryError::InterventionVariableMismatch {
expected: self.treatment,
got: control_var,
});
}
let active_var =
self.active.primary_variable().ok_or(QueryError::AmbiguousInterventionTarget)?;
if active_var != self.treatment {
return Err(QueryError::InterventionVariableMismatch {
expected: self.treatment,
got: active_var,
});
}
self.target_population.validate()?;
Ok(())
}
}