1use crate::bitmap::Postings;
7use crate::tokenql::TokenStore;
8use std::collections::HashMap;
9
10pub struct InfonIndex<B: Postings> {
11 symbol_table: HashMap<String, B>,
12 universe: B,
13 n: u32,
14 numbers: HashMap<String, Vec<(u32, f64)>>,
16 polarity_neg: HashMap<String, B>,
23 polarity_weak: HashMap<String, B>,
24}
25
26impl<B: Postings> InfonIndex<B> {
27 pub fn from_postings(raw: HashMap<String, Vec<u32>>, n: u32) -> Self {
29 let mut symbol_table = HashMap::with_capacity(raw.len());
30 for (tok, ids) in raw {
31 symbol_table.insert(tok, B::from_sorted(&ids));
32 }
33 let universe = B::from_sorted(&(0..n).collect::<Vec<_>>());
34 InfonIndex { symbol_table, universe, n, numbers: HashMap::new(), polarity_neg: HashMap::new(), polarity_weak: HashMap::new() }
35 }
36
37 pub fn add_infon_polar(&mut self, sid: u32, token: &str, level: f32) {
41 self.symbol_table.entry(token.to_string()).or_insert_with(B::empty).insert(sid);
42 if level < 0.0 {
43 self.polarity_neg.entry(token.to_string()).or_insert_with(B::empty).insert(sid);
44 }
45 if level.abs() < 0.75 {
46 self.polarity_weak.entry(token.to_string()).or_insert_with(B::empty).insert(sid);
47 }
48 }
49
50 pub fn signed_mass(&self, token: &str, scope: &B) -> f64 {
54 let Some(post) = self.symbol_table.get(token) else { return 0.0 };
55 let base = post.and(scope);
56 let neg = self.polarity_neg.get(token).map(|n| base.and(n)).unwrap_or_else(B::empty);
57 let weak = self.polarity_weak.get(token).map(|w| base.and(w)).unwrap_or_else(B::empty);
58 let pos = base.and_not(&neg); let pos_weak = pos.and(&weak);
60 let neg_weak = neg.and(&weak);
61 let pos_strong = pos.len() - pos_weak.len();
62 let neg_strong = neg.len() - neg_weak.len();
63 pos_strong as f64 + 0.5 * pos_weak.len() as f64 - 0.5 * neg_weak.len() as f64 - neg_strong as f64
64 }
65
66 pub fn evidence_set(&self, token: &str, min_bel: f64) -> B {
69 let Some(post) = self.symbol_table.get(token) else { return B::empty() };
70 let neg = self.polarity_neg.get(token);
71 let pos = match neg {
72 Some(n) => post.and_not(n), None => post.clone(),
74 };
75 if min_bel >= 0.75 {
76 match self.polarity_weak.get(token) {
78 Some(w) => pos.and_not(w),
79 None => pos,
80 }
81 } else if min_bel >= 0.25 {
82 pos } else {
84 post.clone() }
86 }
87
88 pub fn belief_interval(&self, token: &str, scope: &B) -> (f64, f64) {
92 let n = scope.len();
93 if n == 0 {
94 return (0.0, 0.0);
95 }
96 let Some(post) = self.symbol_table.get(token) else { return (0.0, 1.0) };
97 let base = post.and(scope);
98 let neg = self.polarity_neg.get(token).map(|x| base.and(x)).unwrap_or_else(B::empty);
99 let weak = self.polarity_weak.get(token).map(|x| base.and(x)).unwrap_or_else(B::empty);
100 let pos = base.and_not(&neg);
101 let strong_for = pos.len() - pos.and(&weak).len();
102 let strong_against = neg.len() - neg.and(&weak).len();
103 let bel = strong_for as f64 / n as f64;
104 let pl = 1.0 - (strong_against as f64 / n as f64);
105 (bel, pl)
106 }
107
108 pub fn add_number(&mut self, sid: u32, field: &str, value: f64) {
110 self.numbers.entry(field.to_string()).or_default().push((sid, value));
111 }
112
113 pub fn numeric_fields(&self) -> impl Iterator<Item = &String> {
115 self.numbers.keys()
116 }
117
118 pub fn add(&mut self, tokens: &[String]) -> u32 {
121 let sid = self.n;
122 for t in tokens {
123 self.symbol_table.entry(t.clone()).or_insert_with(B::empty).insert(sid);
124 }
125 self.universe.insert(sid);
126 self.n += 1;
127 sid
128 }
129
130 pub fn vocab_size(&self) -> usize {
131 self.symbol_table.len()
132 }
133 pub fn tokens(&self) -> impl Iterator<Item = &String> {
134 self.symbol_table.keys()
135 }
136 pub fn post(&self, token: &str) -> B {
139 self.symbol_table.get(token).cloned().unwrap_or_else(B::empty)
140 }
141 pub fn post_len(&self, token: &str) -> usize {
142 self.symbol_table.get(token).map(|b| b.len()).unwrap_or(0)
143 }
144 pub fn facet_members(&self, facet: &str) -> Vec<&String> {
147 self.symbol_table.keys().filter(|t| t.split('/').next() == Some(facet)).collect()
148 }
149 pub fn tokens_in_facet(&self, facet: &str, limit: usize) -> Vec<(String, usize)> {
152 let mut v: Vec<(String, usize)> = self
153 .symbol_table
154 .iter()
155 .filter(|(t, _)| t.split('/').next() == Some(facet))
156 .map(|(t, b)| (t.clone(), b.len()))
157 .collect();
158 v.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
159 v.truncate(limit);
160 v
161 }
162 pub fn postings(&self) -> impl Iterator<Item = (&String, &B)> {
164 self.symbol_table.iter()
165 }
166 pub fn situations(&self) -> u32 {
167 self.n
168 }
169
170 pub fn postings_native_bytes(&self) -> usize {
172 self.symbol_table.values().map(|b| b.native_bytes()).sum()
173 }
174 pub fn postings_deltagap_bytes(&self) -> usize {
176 self.symbol_table.values().map(|b| b.serialize_deltagap().len()).sum()
177 }
178}
179
180fn glob_match(pat: &str, s: &str) -> bool {
182 let (p, t) = (pat.as_bytes(), s.as_bytes());
184 let (mut pi, mut ti) = (0usize, 0usize);
185 let (mut star, mut mark) = (usize::MAX, 0usize);
186 while ti < t.len() {
187 if pi < p.len() && (p[pi] == b'?' || p[pi] == t[ti]) {
188 pi += 1;
189 ti += 1;
190 } else if pi < p.len() && p[pi] == b'*' {
191 star = pi;
192 mark = ti;
193 pi += 1;
194 } else if star != usize::MAX {
195 pi = star + 1;
196 mark += 1;
197 ti = mark;
198 } else {
199 return false;
200 }
201 }
202 while pi < p.len() && p[pi] == b'*' {
203 pi += 1;
204 }
205 pi == p.len()
206}
207
208fn is_glob(pat: &str) -> bool {
209 pat.contains('*') || pat.contains('?')
210}
211
212impl<B: Postings> InfonIndex<B> {
213 pub fn s_path_tokens(&self, a: &str, b: &str, s: usize) -> Option<Vec<String>> {
223 let s = s.max(1);
224 if !self.symbol_table.contains_key(a) || !self.symbol_table.contains_key(b) {
225 return None;
226 }
227 if a == b {
228 return Some(vec![a.to_string()]);
229 }
230 let mut prev: std::collections::HashMap<&str, Option<&str>> = std::collections::HashMap::new();
231 prev.insert(a, None);
232 let mut queue = std::collections::VecDeque::from([a]);
233 while let Some(cur) = queue.pop_front() {
234 let cur_post = match self.symbol_table.get(cur) {
235 Some(p) => p,
236 None => continue,
237 };
238 for (tok, post) in &self.symbol_table {
239 if tok.as_str() == cur || prev.contains_key(tok.as_str()) {
240 continue;
241 }
242 if cur_post.and(post).len() < s {
243 continue;
244 }
245 prev.insert(tok.as_str(), Some(cur));
246 if tok.as_str() == b {
247 let mut chain = vec![b.to_string()];
249 let mut node = b;
250 while let Some(Some(p)) = prev.get(node) {
251 chain.push((*p).to_string());
252 node = p;
253 }
254 chain.reverse();
255 return Some(chain);
256 }
257 queue.push_back(tok.as_str());
258 }
259 }
260 None
261 }
262}
263
264impl<B: Postings> TokenStore<B> for InfonIndex<B> {
265 fn atom(&self, pattern: &str) -> B {
266 if is_glob(pattern) {
267 let mut acc = B::empty();
268 for (tok, post) in &self.symbol_table {
269 if glob_match(pattern, tok) {
270 acc.or_inplace(post);
271 }
272 }
273 acc
274 } else {
275 self.symbol_table.get(pattern).cloned().unwrap_or_else(B::empty)
276 }
277 }
278 fn universe(&self) -> B {
279 self.universe.clone()
280 }
281 fn evidence(&self, token: &str, min_bel: f64) -> B {
285 self.evidence_set(token, min_bel)
286 }
287 fn s_path(&self, a: &str, b: &str, s: usize) -> Option<B> {
290 let Some(chain) = self.s_path_tokens(a, b, s) else { return Some(B::empty()) };
294 let mut out = B::empty();
295 for tok in &chain {
296 if let Some(post) = self.symbol_table.get(tok) {
297 out.or_inplace(post);
298 }
299 }
300 Some(out)
301 }
302 fn numeric(&self, field: &str, op: &str, value: f64) -> B {
303 match self.numbers.get(field) {
304 Some(vals) => {
305 let sids: Vec<u32> = vals.iter().filter(|(_, v)| crate::units::cmp_op(*v, op, value)).map(|(sid, _)| *sid).collect();
306 B::from_sorted(&sids)
308 }
309 None => B::empty(),
310 }
311 }
312}