Skip to main content

molframe_query/execution/
fingerprint.rs

1//! Stable semantic identity for normalized query expressions.
2
3use crate::ast::{Expr, GeometricExpr};
4use std::fmt;
5use std::hash::{Hash, Hasher};
6
7const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
8const PRIME: u64 = 0x0000_0100_0000_01b3;
9
10/// Stable identity of a normalized molecular query.
11#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
12pub struct QueryFingerprint(u64);
13
14impl QueryFingerprint {
15    pub(super) fn of(expression: &Expr) -> Self {
16        let mut encoder = Encoder(OFFSET);
17        encoder.expression(expression);
18        Self(encoder.finish())
19    }
20
21    /// Raw fingerprint value.
22    #[must_use]
23    pub const fn get(self) -> u64 {
24        self.0
25    }
26}
27
28impl fmt::Display for QueryFingerprint {
29    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30        write!(formatter, "query-fnv1a64:{:016x}", self.0)
31    }
32}
33
34impl fmt::Debug for QueryFingerprint {
35    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36        fmt::Display::fmt(self, formatter)
37    }
38}
39
40struct Encoder(u64);
41
42impl Encoder {
43    fn expression(&mut self, expression: &Expr) {
44        match expression {
45            Expr::All => self.tag(0),
46            Expr::None => self.tag(1),
47            Expr::And(left, right) => self.binary(2, left, right),
48            Expr::Or(left, right) => self.binary(3, left, right),
49            Expr::Not(value) => self.unary(4, value),
50            Expr::Global(value) => self.unary(5, value),
51            Expr::ByResidue(value) => self.unary(6, value),
52            Expr::Same { key, target } => {
53                self.tag(7);
54                key.hash(self);
55                self.expression(target);
56            }
57            Expr::Bonded { depth, target } => {
58                self.tag(8);
59                self.write_u32(*depth);
60                self.expression(target);
61            }
62            Expr::Geometric(value) => self.geometric(value),
63            Expr::Comparison {
64                column,
65                operator,
66                value,
67                absolute,
68            } => {
69                self.tag(10);
70                column.hash(self);
71                operator.hash(self);
72                self.write_u64(value.to_bits());
73                self.write_u8(u8::from(*absolute));
74            }
75            Expr::Membership { column, values } => {
76                self.tag(11);
77                column.hash(self);
78                self.write_usize(values.len());
79                for value in values {
80                    self.text(value);
81                }
82            }
83            Expr::Group(value) => self.tagged_text(12, value),
84            Expr::Atom {
85                segment,
86                residue,
87                name,
88            } => {
89                self.tag(13);
90                self.text(segment);
91                self.write_i32(*residue);
92                self.text(name);
93            }
94            Expr::Macro(value) => {
95                self.tag(14);
96                value.hash(self);
97            }
98            Expr::Chirality(value) => self.tagged_text(15, value),
99            Expr::Smarts { pattern: value, .. } => {
100                self.tag(16);
101                value.hash(self);
102            }
103        }
104    }
105
106    fn geometric(&mut self, expression: &GeometricExpr) {
107        self.tag(9);
108        match expression {
109            GeometricExpr::Within { radius, target } => self.radius_target(0, *radius, target),
110            GeometricExpr::Beyond { radius, target } => self.radius_target(1, *radius, target),
111            GeometricExpr::Around { radius, target } => self.radius_target(2, *radius, target),
112            GeometricExpr::SphereZone { radius, target } => {
113                self.radius_target(3, *radius, target);
114            }
115            GeometricExpr::SphereLayer {
116                inner,
117                outer,
118                target,
119            } => self.layer_target(4, [*inner, *outer], target),
120            GeometricExpr::IsoLayer {
121                inner,
122                outer,
123                target,
124            } => self.layer_target(5, [*inner, *outer], target),
125            GeometricExpr::CylinderZone {
126                radius,
127                z_max,
128                z_min,
129                target,
130            } => self.layer_target(6, [*radius, *z_max, *z_min], target),
131            GeometricExpr::CylinderLayer {
132                inner,
133                outer,
134                z_max,
135                z_min,
136                target,
137            } => self.layer_target(7, [*inner, *outer, *z_max, *z_min], target),
138            GeometricExpr::Point { point, radius } => {
139                self.tag(8);
140                self.floats(point);
141                self.write_u32(radius.to_bits());
142            }
143        }
144    }
145
146    fn unary(&mut self, tag: u8, value: &Expr) {
147        self.tag(tag);
148        self.expression(value);
149    }
150
151    fn binary(&mut self, tag: u8, left: &Expr, right: &Expr) {
152        self.tag(tag);
153        self.expression(left);
154        self.expression(right);
155    }
156
157    fn radius_target(&mut self, tag: u8, radius: f32, target: &Expr) {
158        self.tag(tag);
159        self.write_u32(radius.to_bits());
160        self.expression(target);
161    }
162
163    fn layer_target<const N: usize>(&mut self, tag: u8, values: [f32; N], target: &Expr) {
164        self.tag(tag);
165        self.floats(&values);
166        self.expression(target);
167    }
168
169    fn floats(&mut self, values: &[f32]) {
170        for value in values {
171            self.write_u32(value.to_bits());
172        }
173    }
174
175    fn tagged_text(&mut self, tag: u8, value: &str) {
176        self.tag(tag);
177        self.text(value);
178    }
179
180    fn text(&mut self, value: &str) {
181        self.write_usize(value.len());
182        self.write(value.as_bytes());
183    }
184
185    fn tag(&mut self, tag: u8) {
186        self.write_u8(tag);
187    }
188}
189
190impl Hasher for Encoder {
191    fn finish(&self) -> u64 {
192        self.0
193    }
194
195    fn write(&mut self, bytes: &[u8]) {
196        for byte in bytes {
197            self.0 ^= u64::from(*byte);
198            self.0 = self.0.wrapping_mul(PRIME);
199        }
200    }
201}