1use num_rational::BigRational;
17use oxiz_math::polynomial::{Polynomial, Var};
18use rustc_hash::FxHashMap;
19use std::collections::{VecDeque, hash_map::DefaultHasher};
20use std::hash::{Hash, Hasher};
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct RootHint {
25 pub lower: BigRational,
27 pub upper: BigRational,
29 pub refinement_level: u32,
31}
32
33impl RootHint {
34 pub fn new(lower: BigRational, upper: BigRational, refinement_level: u32) -> Self {
36 Self {
37 lower,
38 upper,
39 refinement_level,
40 }
41 }
42
43 pub fn width(&self) -> BigRational {
45 &self.upper - &self.lower
46 }
47
48 pub fn midpoint(&self) -> BigRational {
50 (&self.lower + &self.upper) / BigRational::from_integer(2.into())
51 }
52
53 pub fn contains(&self, value: &BigRational) -> bool {
55 &self.lower <= value && value <= &self.upper
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct PolynomialRootHints {
62 #[allow(dead_code)]
64 poly_hash: u64,
65 #[allow(dead_code)]
67 variable: Var,
68 hints: Vec<RootHint>,
70 use_count: u64,
72}
73
74impl PolynomialRootHints {
75 pub fn new(poly_hash: u64, variable: Var, hints: Vec<RootHint>) -> Self {
77 Self {
78 poly_hash,
79 variable,
80 hints,
81 use_count: 0,
82 }
83 }
84
85 pub fn num_roots(&self) -> usize {
87 self.hints.len()
88 }
89
90 pub fn get_hint(&self, index: usize) -> Option<&RootHint> {
92 self.hints.get(index)
93 }
94
95 pub fn all_hints(&self) -> &[RootHint] {
97 &self.hints
98 }
99
100 fn mark_used(&mut self) {
102 self.use_count += 1;
103 }
104}
105
106#[derive(Debug, Clone, Default)]
108pub struct RootHintsStats {
109 pub num_hints_stored: u64,
111 pub num_cache_hits: u64,
113 pub num_cache_misses: u64,
115 pub num_refinements: u64,
117}
118
119impl RootHintsStats {
120 pub fn hit_rate(&self) -> f64 {
122 let total = self.num_cache_hits + self.num_cache_misses;
123 if total == 0 {
124 0.0
125 } else {
126 self.num_cache_hits as f64 / total as f64
127 }
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct RootHintsConfig {
134 pub max_polynomials: usize,
136 pub max_hints_per_poly: usize,
138 pub enable_refinement: bool,
140}
141
142impl Default for RootHintsConfig {
143 fn default() -> Self {
144 Self {
145 max_polynomials: 1000,
146 max_hints_per_poly: 20,
147 enable_refinement: true,
148 }
149 }
150}
151
152pub struct RootHintsCache {
154 config: RootHintsConfig,
156 cache: FxHashMap<u64, PolynomialRootHints>,
158 lru: VecDeque<u64>,
160 stats: RootHintsStats,
162}
163
164impl RootHintsCache {
165 pub fn new() -> Self {
167 Self::with_config(RootHintsConfig::default())
168 }
169
170 pub fn with_config(config: RootHintsConfig) -> Self {
172 Self {
173 config,
174 cache: FxHashMap::default(),
175 lru: VecDeque::new(),
176 stats: RootHintsStats::default(),
177 }
178 }
179
180 pub fn store_hints(&mut self, poly: &Polynomial, variable: Var, hints: Vec<RootHint>) {
182 let hash = Self::hash_polynomial(poly, variable);
183
184 let limited_hints: Vec<_> = hints
186 .into_iter()
187 .take(self.config.max_hints_per_poly)
188 .collect();
189
190 let poly_hints = PolynomialRootHints::new(hash, variable, limited_hints);
191
192 if self.cache.len() >= self.config.max_polynomials
194 && !self.cache.contains_key(&hash)
195 && let Some(old_hash) = self.lru.pop_back()
196 {
197 self.cache.remove(&old_hash);
198 }
199
200 self.cache.insert(hash, poly_hints);
201 self.lru.retain(|&h| h != hash);
202 self.lru.push_front(hash);
203
204 self.stats.num_hints_stored += 1;
205 }
206
207 pub fn lookup_hints(&mut self, poly: &Polynomial, variable: Var) -> Option<&[RootHint]> {
209 let hash = Self::hash_polynomial(poly, variable);
210
211 if let Some(hints) = self.cache.get_mut(&hash) {
212 self.stats.num_cache_hits += 1;
213 hints.mark_used();
214
215 self.lru.retain(|&h| h != hash);
217 self.lru.push_front(hash);
218
219 Some(hints.all_hints())
220 } else {
221 self.stats.num_cache_misses += 1;
222 None
223 }
224 }
225
226 pub fn refine_hint(
228 &mut self,
229 poly: &Polynomial,
230 variable: Var,
231 root_index: usize,
232 new_lower: BigRational,
233 new_upper: BigRational,
234 ) -> bool {
235 if !self.config.enable_refinement {
236 return false;
237 }
238
239 let hash = Self::hash_polynomial(poly, variable);
240
241 if let Some(hints) = self.cache.get_mut(&hash)
242 && let Some(hint) = hints.hints.get_mut(root_index)
243 {
244 hint.lower = new_lower;
245 hint.upper = new_upper;
246 hint.refinement_level += 1;
247 self.stats.num_refinements += 1;
248 return true;
249 }
250
251 false
252 }
253
254 pub fn num_roots(&mut self, poly: &Polynomial, variable: Var) -> Option<usize> {
256 let hash = Self::hash_polynomial(poly, variable);
257 self.cache.get(&hash).map(|h| h.num_roots())
258 }
259
260 fn hash_polynomial(poly: &Polynomial, variable: Var) -> u64 {
262 let mut hasher = DefaultHasher::new();
263 format!("{:?}", poly).hash(&mut hasher);
264 variable.hash(&mut hasher);
265 hasher.finish()
266 }
267
268 pub fn clear(&mut self) {
270 self.cache.clear();
271 self.lru.clear();
272 }
273
274 pub fn stats(&self) -> &RootHintsStats {
276 &self.stats
277 }
278
279 pub fn cache_size(&self) -> usize {
281 self.cache.len()
282 }
283}
284
285impl Default for RootHintsCache {
286 fn default() -> Self {
287 Self::new()
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use num_bigint::BigInt;
295
296 fn rat(n: i32) -> BigRational {
297 BigRational::from_integer(BigInt::from(n))
298 }
299
300 #[test]
301 fn test_root_hint_new() {
302 let hint = RootHint::new(rat(0), rat(10), 1);
303 assert_eq!(hint.lower, rat(0));
304 assert_eq!(hint.upper, rat(10));
305 assert_eq!(hint.refinement_level, 1);
306 }
307
308 #[test]
309 fn test_root_hint_width() {
310 let hint = RootHint::new(rat(0), rat(10), 1);
311 assert_eq!(hint.width(), rat(10));
312 }
313
314 #[test]
315 fn test_root_hint_midpoint() {
316 let hint = RootHint::new(rat(0), rat(10), 1);
317 assert_eq!(hint.midpoint(), rat(5));
318 }
319
320 #[test]
321 fn test_root_hint_contains() {
322 let hint = RootHint::new(rat(0), rat(10), 1);
323 assert!(hint.contains(&rat(5)));
324 assert!(hint.contains(&rat(0)));
325 assert!(hint.contains(&rat(10)));
326 assert!(!hint.contains(&rat(-1)));
327 assert!(!hint.contains(&rat(11)));
328 }
329
330 #[test]
331 fn test_cache_new() {
332 let cache = RootHintsCache::new();
333 assert_eq!(cache.cache_size(), 0);
334 }
335
336 #[test]
337 fn test_store_and_lookup() {
338 let mut cache = RootHintsCache::new();
339 let poly = Polynomial::from_var(0);
340 let hints = vec![RootHint::new(rat(0), rat(1), 1)];
341
342 cache.store_hints(&poly, 0, hints.clone());
343
344 let looked_up = cache.lookup_hints(&poly, 0);
345 assert!(looked_up.is_some());
346 assert_eq!(looked_up.expect("test operation should succeed").len(), 1);
347 }
348
349 #[test]
350 fn test_cache_miss() {
351 let mut cache = RootHintsCache::new();
352 let poly = Polynomial::from_var(0);
353
354 let result = cache.lookup_hints(&poly, 0);
355 assert!(result.is_none());
356 assert_eq!(cache.stats.num_cache_misses, 1);
357 }
358
359 #[test]
360 fn test_cache_hit() {
361 let mut cache = RootHintsCache::new();
362 let poly = Polynomial::from_var(0);
363 let hints = vec![RootHint::new(rat(0), rat(1), 1)];
364
365 cache.store_hints(&poly, 0, hints);
366 let _result = cache.lookup_hints(&poly, 0);
367
368 assert_eq!(cache.stats.num_cache_hits, 1);
369 }
370
371 #[test]
372 fn test_refine_hint() {
373 let mut cache = RootHintsCache::new();
374 let poly = Polynomial::from_var(0);
375 let hints = vec![RootHint::new(rat(0), rat(10), 1)];
376
377 cache.store_hints(&poly, 0, hints);
378
379 let success = cache.refine_hint(&poly, 0, 0, rat(2), rat(8));
380 assert!(success);
381
382 let refined = cache
383 .lookup_hints(&poly, 0)
384 .expect("test operation should succeed");
385 assert_eq!(refined[0].lower, rat(2));
386 assert_eq!(refined[0].upper, rat(8));
387 assert_eq!(refined[0].refinement_level, 2);
388 }
389
390 #[test]
391 fn test_num_roots() {
392 let mut cache = RootHintsCache::new();
393 let poly = Polynomial::from_var(0);
394 let hints = vec![
395 RootHint::new(rat(0), rat(1), 1),
396 RootHint::new(rat(5), rat(6), 1),
397 ];
398
399 cache.store_hints(&poly, 0, hints);
400
401 assert_eq!(cache.num_roots(&poly, 0), Some(2));
402 }
403
404 #[test]
405 fn test_eviction() {
406 let config = RootHintsConfig {
407 max_polynomials: 2,
408 ..Default::default()
409 };
410 let mut cache = RootHintsCache::with_config(config);
411
412 for i in 0..3 {
414 let poly = Polynomial::from_var(i);
415 let hints = vec![RootHint::new(rat(0), rat(1), 1)];
416 cache.store_hints(&poly, i, hints);
417 }
418
419 assert_eq!(cache.cache_size(), 2);
420 }
421
422 #[test]
423 fn test_hit_rate() {
424 let mut cache = RootHintsCache::new();
425 let poly = Polynomial::from_var(0);
426 let hints = vec![RootHint::new(rat(0), rat(1), 1)];
427
428 cache.store_hints(&poly, 0, hints);
429
430 let _result1 = cache.lookup_hints(&poly, 0);
432
433 let poly2 = Polynomial::from_var(1);
435 let _result2 = cache.lookup_hints(&poly2, 1);
436
437 assert_eq!(cache.stats().hit_rate(), 0.5);
438 }
439
440 #[test]
441 fn test_clear() {
442 let mut cache = RootHintsCache::new();
443 let poly = Polynomial::from_var(0);
444 let hints = vec![RootHint::new(rat(0), rat(1), 1)];
445
446 cache.store_hints(&poly, 0, hints);
447 assert!(cache.cache_size() > 0);
448
449 cache.clear();
450 assert_eq!(cache.cache_size(), 0);
451 }
452
453 #[test]
454 fn test_hints_limit() {
455 let config = RootHintsConfig {
456 max_hints_per_poly: 2,
457 ..Default::default()
458 };
459 let mut cache = RootHintsCache::with_config(config);
460
461 let poly = Polynomial::from_var(0);
462 let hints = vec![
463 RootHint::new(rat(0), rat(1), 1),
464 RootHint::new(rat(2), rat(3), 1),
465 RootHint::new(rat(4), rat(5), 1),
466 ];
467
468 cache.store_hints(&poly, 0, hints);
469
470 assert_eq!(cache.num_roots(&poly, 0), Some(2));
472 }
473}