Skip to main content

molframe_query/execution/
eval_query.rs

1//! Public query compilation and evaluation entry points.
2
3use super::{Evaluation, Groups, Query};
4use crate::builder::Builder;
5use crate::execution::QueryFingerprint;
6use crate::plan::{LogicalPlan, PhysicalQuery};
7use crate::spatial::SpatialResolver;
8use molframe_core::contract::AnalysisPolicy;
9use molframe_core::diagnostic::Diagnostic;
10use molframe_core::selection::AtomSelection;
11use molframe_core::structure::Structure;
12use std::ops::{BitAnd, BitOr, Not};
13
14impl Query {
15    /// Compiles textual selection syntax into a reusable typed plan.
16    ///
17    /// # Errors
18    ///
19    /// Returns registered diagnostics for lexical, syntax and type errors.
20    pub fn compile(source: &str) -> Result<Self, Vec<Diagnostic>> {
21        let parsed = crate::parser::parse(source)?;
22        Ok(Self {
23            expr: parsed.expr,
24            warnings: parsed.warnings,
25            source: source.into(),
26        })
27    }
28
29    /// Creates a reusable query from the typed builder surface.
30    #[must_use]
31    pub fn from_builder(builder: Builder) -> Self {
32        let (expr, source) = builder.into_parts();
33        Self {
34            expr,
35            warnings: Vec::new(),
36            source,
37        }
38    }
39
40    /// Canonical parser input for this immutable query.
41    #[must_use]
42    pub fn source(&self) -> &str {
43        &self.source
44    }
45
46    /// Stable identity of the normalized typed query plan.
47    #[must_use]
48    pub fn fingerprint(&self) -> QueryFingerprint {
49        QueryFingerprint::of(&self.expr)
50    }
51
52    /// Selects atoms within `radius` of `target`.
53    #[must_use]
54    pub fn within(radius: f32, target: Self) -> Self {
55        let source = format!("within {radius} of ({})", target.source);
56        Self {
57            expr: crate::ast::Expr::Geometric(crate::ast::GeometricExpr::Within {
58                radius,
59                target: Box::new(target.expr),
60            }),
61            warnings: target.warnings,
62            source: source.into(),
63        }
64    }
65
66    /// Expands the selected atoms to their complete residues.
67    #[must_use]
68    pub fn by_residue(target: Self) -> Self {
69        let source = format!("byres ({})", target.source);
70        Self {
71            expr: crate::ast::Expr::ByResidue(Box::new(target.expr)),
72            warnings: target.warnings,
73            source: source.into(),
74        }
75    }
76
77    /// Selects complete residues within `radius` of `target`.
78    #[must_use]
79    pub fn residues_within(radius: f32, target: Self) -> Self {
80        Self::by_residue(Self::within(radius, target))
81    }
82
83    /// Returns the storage-independent plan produced by either input surface.
84    #[must_use]
85    pub fn logical_plan(&self) -> LogicalPlan {
86        LogicalPlan(self.expr.clone())
87    }
88
89    /// Binds this query to a structure dictionary and analysis policy.
90    #[must_use]
91    pub fn plan(&self, structure: &Structure, policy: &AnalysisPolicy) -> PhysicalQuery {
92        crate::plan::bind_expr(&self.expr, structure, policy, self.warnings.clone())
93    }
94
95    /// Evaluates against the whole first-model topology.
96    ///
97    /// # Errors
98    ///
99    /// Returns diagnostics when required data or a spatial resolver is absent.
100    pub fn evaluate(
101        &self,
102        structure: &Structure,
103        policy: &AnalysisPolicy,
104        groups: &Groups,
105        spatial: Option<&dyn SpatialResolver>,
106    ) -> Result<Evaluation, Vec<Diagnostic>> {
107        self.evaluate_in(
108            structure,
109            &AtomSelection::All(structure.atom_count()),
110            policy,
111            groups,
112            spatial,
113        )
114    }
115
116    /// Evaluates inside a current view; `global` escapes to the whole topology.
117    ///
118    /// # Errors
119    ///
120    /// Returns the same diagnostics as [`Query::evaluate`].
121    pub fn evaluate_in(
122        &self,
123        structure: &Structure,
124        universe: &AtomSelection,
125        policy: &AnalysisPolicy,
126        groups: &Groups,
127        spatial: Option<&dyn SpatialResolver>,
128    ) -> Result<Evaluation, Vec<Diagnostic>> {
129        self.plan(structure, policy)
130            .evaluate_in(structure, universe, policy, groups, spatial)
131    }
132}
133
134impl From<Builder> for Query {
135    fn from(builder: Builder) -> Self {
136        Self::from_builder(builder)
137    }
138}
139
140impl BitAnd for Query {
141    type Output = Self;
142
143    fn bitand(self, right: Self) -> Self::Output {
144        let source = format!("({}) and ({})", self.source, right.source);
145        Self {
146            expr: crate::ast::Expr::And(Box::new(self.expr), Box::new(right.expr)),
147            warnings: merged_warnings(self.warnings, right.warnings),
148            source: source.into(),
149        }
150    }
151}
152
153impl BitOr for Query {
154    type Output = Self;
155
156    fn bitor(self, right: Self) -> Self::Output {
157        let source = format!("({}) or ({})", self.source, right.source);
158        Self {
159            expr: crate::ast::Expr::Or(Box::new(self.expr), Box::new(right.expr)),
160            warnings: merged_warnings(self.warnings, right.warnings),
161            source: source.into(),
162        }
163    }
164}
165
166impl Not for Query {
167    type Output = Self;
168
169    fn not(self) -> Self::Output {
170        let source = format!("not ({})", self.source);
171        Self {
172            expr: crate::ast::Expr::Not(Box::new(self.expr)),
173            warnings: self.warnings,
174            source: source.into(),
175        }
176    }
177}
178
179fn merged_warnings(mut left: Vec<Diagnostic>, right: Vec<Diagnostic>) -> Vec<Diagnostic> {
180    left.extend(right);
181    left.sort_by_key(Diagnostic::code);
182    left
183}