From aa59c3bc2c7ae4c810ded2c019e2df51dadaf348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lecrenier?= Date: Thu, 16 Mar 2023 11:52:51 +0100 Subject: [PATCH] Replace EdgeCondition with an Option<..> + other code cleanup --- milli/src/search/new/distinct.rs | 22 +++-- .../search/new/graph_based_ranking_rule.rs | 87 +++++++++---------- milli/src/search/new/interner.rs | 38 ++++---- milli/src/search/new/logger/detailed.rs | 26 +++--- milli/src/search/new/logger/mod.rs | 8 +- milli/src/search/new/mod.rs | 6 ++ .../search/new/ranking_rule_graph/build.rs | 6 +- .../new/ranking_rule_graph/cheapest_paths.rs | 44 +++++----- ...ids_cache.rs => condition_docids_cache.rs} | 27 +++--- ..._paths_cache.rs => dead_end_path_cache.rs} | 26 +++--- .../src/search/new/ranking_rule_graph/mod.rs | 74 +++++++--------- .../search/new/ranking_rule_graph/path_set.rs | 2 +- .../new/ranking_rule_graph/proximity/build.rs | 15 ++-- .../new/ranking_rule_graph/proximity/mod.rs | 10 +-- .../search/new/ranking_rule_graph/typo/mod.rs | 14 +-- milli/src/search/new/small_bitmap.rs | 1 + 16 files changed, 202 insertions(+), 204 deletions(-) rename milli/src/search/new/ranking_rule_graph/{edge_docids_cache.rs => condition_docids_cache.rs} (60%) rename milli/src/search/new/ranking_rule_graph/{empty_paths_cache.rs => dead_end_path_cache.rs} (78%) diff --git a/milli/src/search/new/distinct.rs b/milli/src/search/new/distinct.rs index b29f0e8c3..10657f210 100644 --- a/milli/src/search/new/distinct.rs +++ b/milli/src/search/new/distinct.rs @@ -20,10 +20,17 @@ pub struct DistinctOutput { pub excluded: RoaringBitmap, } +/// Return a [`DistinctOutput`] containing: +/// - `remaining`: a set of docids built such that exactly one element from `candidates` +/// is kept for each distinct value inside the given field. If the field does not exist, it +/// is considered unique. +/// - `excluded`: the set of document ids that contain a value for the given field that occurs +/// in the given candidates. pub fn apply_distinct_rule<'ctx>( ctx: &mut SearchContext<'ctx>, field_id: u16, candidates: &RoaringBitmap, + // TODO: add a universe here, such that the `excluded` are a subset of the universe? ) -> Result { let mut excluded = RoaringBitmap::new(); let mut remaining = RoaringBitmap::new(); @@ -37,6 +44,7 @@ pub fn apply_distinct_rule<'ctx>( Ok(DistinctOutput { remaining, excluded }) } +/// Apply the distinct rule defined by [`apply_distinct_rule`] for a single document id. fn distinct_single_docid( index: &Index, txn: &RoTxn, @@ -69,6 +77,7 @@ fn distinct_single_docid( Ok(()) } +/// Return all the docids containing the given value in the given field fn facet_value_docids( database: Database, FacetGroupValueCodec>, txn: &RoTxn, @@ -79,13 +88,15 @@ fn facet_value_docids( .get(txn, &FacetGroupKey { field_id, level: 0, left_bound: facet_value }) .map(|opt| opt.map(|v| v.bitmap)) } + +/// Return an iterator over each number value in the given field of the given document. fn facet_number_values<'a>( - id: u32, - distinct: u16, + docid: u32, + field_id: u16, index: &Index, txn: &'a RoTxn, ) -> Result, Unit>> { - let key = facet_values_prefix_key(distinct, id); + let key = facet_values_prefix_key(field_id, docid); let iter = index .field_id_docid_facet_f64s @@ -96,13 +107,14 @@ fn facet_number_values<'a>( Ok(iter) } +/// Return an iterator over each string value in the given field of the given document. fn facet_string_values<'a>( docid: u32, - distinct: u16, + field_id: u16, index: &Index, txn: &'a RoTxn, ) -> Result, Str>> { - let key = facet_values_prefix_key(distinct, docid); + let key = facet_values_prefix_key(field_id, docid); let iter = index .field_id_docid_facet_strings diff --git a/milli/src/search/new/graph_based_ranking_rule.rs b/milli/src/search/new/graph_based_ranking_rule.rs index cfa0f059c..72c839f2a 100644 --- a/milli/src/search/new/graph_based_ranking_rule.rs +++ b/milli/src/search/new/graph_based_ranking_rule.rs @@ -45,7 +45,7 @@ use super::interner::MappedInterner; use super::logger::SearchLogger; use super::query_graph::QueryNode; use super::ranking_rule_graph::{ - DeadEndPathCache, EdgeCondition, EdgeConditionDocIdsCache, ProximityGraph, RankingRuleGraph, + DeadEndPathCache, EdgeConditionDocIdsCache, ProximityGraph, RankingRuleGraph, RankingRuleGraphTrait, TypoGraph, }; use super::small_bitmap::SmallBitmap; @@ -87,7 +87,7 @@ pub struct GraphBasedRankingRuleState { /// Cache to retrieve the docids associated with each edge edge_conditions_cache: EdgeConditionDocIdsCache, /// Cache used to optimistically discard paths that resolve to no documents. - empty_paths_cache: DeadEndPathCache, + dead_end_path_cache: DeadEndPathCache, /// A structure giving the list of possible costs from each node to the end node, /// along with a set of unavoidable edges that must be traversed to achieve that distance. all_distances: MappedInterner)>, QueryNode>, @@ -101,27 +101,23 @@ pub struct GraphBasedRankingRuleState { fn remove_empty_edges<'ctx, G: RankingRuleGraphTrait>( ctx: &mut SearchContext<'ctx>, graph: &mut RankingRuleGraph, - edge_docids_cache: &mut EdgeConditionDocIdsCache, + condition_docids_cache: &mut EdgeConditionDocIdsCache, universe: &RoaringBitmap, - empty_paths_cache: &mut DeadEndPathCache, + dead_end_path_cache: &mut DeadEndPathCache, ) -> Result<()> { for edge_id in graph.edges_store.indexes() { let Some(edge) = graph.edges_store.get(edge_id).as_ref() else { continue; }; - let condition = edge.condition; + let Some(condition) = edge.condition else { continue }; - match condition { - EdgeCondition::Unconditional => continue, - EdgeCondition::Conditional(condition) => { - let docids = edge_docids_cache.get_edge_docids(ctx, condition, graph, universe)?; - if docids.is_disjoint(universe) { - graph.remove_edges_with_condition(condition); - empty_paths_cache.add_condition(condition); - edge_docids_cache.cache.remove(&condition); - continue; - } - } + let docids = + condition_docids_cache.get_condition_docids(ctx, condition, graph, universe)?; + if docids.is_disjoint(universe) { + graph.remove_edges_with_condition(condition); + dead_end_path_cache.add_condition(condition); + condition_docids_cache.cache.remove(&condition); + continue; } } Ok(()) @@ -139,17 +135,17 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase query_graph: &QueryGraph, ) -> Result<()> { let mut graph = RankingRuleGraph::build(ctx, query_graph.clone())?; - let mut edge_docids_cache = EdgeConditionDocIdsCache::default(); - let mut empty_paths_cache = DeadEndPathCache::new(&graph.conditions_interner); + let mut condition_docids_cache = EdgeConditionDocIdsCache::default(); + let mut dead_end_path_cache = DeadEndPathCache::new(&graph.conditions_interner); - // First simplify the graph as much as possible, by computing the docids of the edges + // First simplify the graph as much as possible, by computing the docids of all the conditions // within the rule's universe and removing the edges that have no associated docids. remove_empty_edges( ctx, &mut graph, - &mut edge_docids_cache, + &mut condition_docids_cache, universe, - &mut empty_paths_cache, + &mut dead_end_path_cache, )?; // Then pre-compute the cost of all paths from each node to the end node @@ -157,8 +153,8 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase let state = GraphBasedRankingRuleState { graph, - edge_conditions_cache: edge_docids_cache, - empty_paths_cache, + edge_conditions_cache: condition_docids_cache, + dead_end_path_cache, all_distances, cur_distance_idx: 0, }; @@ -187,7 +183,7 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase &mut state.graph, &mut state.edge_conditions_cache, universe, - &mut state.empty_paths_cache, + &mut state.dead_end_path_cache, )?; // If the cur_distance_idx does not point to a valid cost in the `all_distances` @@ -208,8 +204,8 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase let GraphBasedRankingRuleState { graph, - edge_conditions_cache: edge_docids_cache, - empty_paths_cache, + edge_conditions_cache: condition_docids_cache, + dead_end_path_cache, all_distances, cur_distance_idx: _, } = &mut state; @@ -224,18 +220,18 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase // For each path of the given cost, we will compute its associated // document ids. // In case the path does not resolve to any document id, we try to figure out why - // and update the `empty_paths_cache` accordingly. + // and update the `dead_end_path_cache` accordingly. // For example, it may be that the path is empty because one of its edges is disjoint // with the universe, or because a prefix of the path is disjoint with the universe, or because // the path contains two edges that are disjoint from each other within the universe. - // Updating the empty_paths_cache helps speed up the execution of `visit_paths_of_cost` and reduces + // Updating the dead_end_path_cache helps speed up the execution of `visit_paths_of_cost` and reduces // the number of future candidate paths given by that same function. graph.visit_paths_of_cost( graph.query_graph.root_node, cost, all_distances, - empty_paths_cache, - |path, graph, empty_paths_cache| { + dead_end_path_cache, + |path, graph, dead_end_path_cache| { // Accumulate the path for logging purposes only paths.push(path.to_vec()); let mut path_docids = universe.clone(); @@ -243,47 +239,48 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase // We store the edges and their docids in vectors in case the path turns out to be // empty and we need to figure out why it was empty. let mut visited_conditions = vec![]; - let mut cached_edge_docids = vec![]; + let mut cached_condition_docids = vec![]; // graph.conditions_interner.map(|_| RoaringBitmap::new()); for &condition in path { visited_conditions.push(condition); - let edge_docids = - edge_docids_cache.get_edge_docids(ctx, condition, graph, &universe)?; + let condition_docids = condition_docids_cache + .get_condition_docids(ctx, condition, graph, &universe)?; - cached_edge_docids.push((condition, edge_docids.clone())); // .get_mut(condition) = edge_docids.clone(); + cached_condition_docids.push((condition, condition_docids.clone())); // .get_mut(condition) = condition_docids.clone(); // If the edge is empty, then the path will be empty as well, we update the graph // and caches accordingly and skip to the next candidate path. - if edge_docids.is_disjoint(&universe) { + if condition_docids.is_disjoint(&universe) { // 1. Store in the cache that this edge is empty for this universe - empty_paths_cache.add_condition(condition); + dead_end_path_cache.add_condition(condition); // 2. remove this edge from the ranking rule graph // ouch, no! :( need to link a condition to one or more ranking rule edges graph.remove_edges_with_condition(condition); - // 3. Also remove the entry from the edge_docids_cache, since we don't need it anymore - edge_docids_cache.cache.remove(&condition); + // 3. Also remove the entry from the condition_docids_cache, since we don't need it anymore + condition_docids_cache.cache.remove(&condition); return Ok(ControlFlow::Continue(())); } - path_docids &= edge_docids; + path_docids &= condition_docids; // If the (sub)path is empty, we try to figure out why and update the caches accordingly. if path_docids.is_disjoint(&universe) { // First, we know that this path is empty, and thus any path // that is a superset of it will also be empty. - empty_paths_cache.add_prefix(&visited_conditions); + dead_end_path_cache.add_prefix(&visited_conditions); // Second, if the intersection between this edge and any // previous one is disjoint with the universe, // then we also know that any path containing the same couple of // edges will also be empty. - for (past_condition, edge_docids2) in cached_edge_docids.iter() { + for (past_condition, condition_docids2) in cached_condition_docids.iter() { if *past_condition == condition { continue; }; - let intersection = edge_docids & edge_docids2; + let intersection = condition_docids & condition_docids2; if intersection.is_disjoint(&universe) { - empty_paths_cache.add_condition_couple(*past_condition, condition); + dead_end_path_cache + .add_condition_couple(*past_condition, condition); } } // We should maybe instead try to compute: @@ -310,7 +307,7 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase G::log_state( &original_graph, &paths, - empty_paths_cache, + dead_end_path_cache, original_universe, all_distances, cost, @@ -322,8 +319,8 @@ impl<'ctx, G: RankingRuleGraphTrait> RankingRule<'ctx, QueryGraph> for GraphBase // But we only do it in case the bucket length is >1, because otherwise // we know the child ranking rule won't be called anyway let mut next_query_graph = original_graph.query_graph; - next_query_graph.simplify(); if bucket.len() > 1 { + next_query_graph.simplify(); // 1. Gather all the words and phrases used in the computation of this bucket let mut used_words = HashSet::new(); let mut used_phrases = HashSet::new(); diff --git a/milli/src/search/new/interner.rs b/milli/src/search/new/interner.rs index f4dc4a870..3b5544b68 100644 --- a/milli/src/search/new/interner.rs +++ b/milli/src/search/new/interner.rs @@ -25,6 +25,8 @@ impl Interned { /// be copied, compared, and hashed efficiently. An immutable reference to the original value /// can be retrieved using `self.get(interned)`. A set of values within the interner can be /// efficiently managed using [`SmallBitmap`](super::small_bitmap::SmallBitmap). +/// +/// A dedup-interner can contain a maximum of `u16::MAX` values. #[derive(Clone)] pub struct DedupInterner { stable_store: Vec, @@ -36,7 +38,8 @@ impl Default for DedupInterner { } } impl DedupInterner { - /// + /// Convert the dedup-interner into a fixed-size interner, such that new + /// elements cannot be added to it anymore. pub fn freeze(self) -> FixedSizeInterner { FixedSizeInterner { stable_store: self.stable_store } } @@ -46,6 +49,8 @@ impl DedupInterner where T: Clone + Eq + Hash, { + /// Insert the given value into the dedup-interner, and return + /// its index. pub fn insert(&mut self, s: T) -> Interned { if let Some(interned) = self.lookup.get(&s) { *interned @@ -57,35 +62,21 @@ where interned } } + /// Get a reference to the interned value. pub fn get(&self, interned: Interned) -> &T { &self.stable_store[interned.idx as usize] } } -#[derive(Clone)] -pub struct Interner { - stable_store: Vec, -} -impl Default for Interner { - fn default() -> Self { - Self { stable_store: Default::default() } - } -} -impl Interner { - pub fn freeze(self) -> FixedSizeInterner { - FixedSizeInterner { stable_store: self.stable_store } - } - pub fn push(&mut self, s: T) -> Interned { - assert!(self.stable_store.len() < u16::MAX as usize); - self.stable_store.push(s); - Interned::from_raw(self.stable_store.len() as u16 - 1) - } -} +/// A fixed-length store for values of type `T`, where each value is identified +/// by an index of type [`Interned`]. #[derive(Clone)] pub struct FixedSizeInterner { stable_store: Vec, } impl FixedSizeInterner { + /// Create a fixed-size interner of the given length containing + /// clones of the given value. pub fn new(length: u16, value: T) -> Self { Self { stable_store: vec![value; length as usize] } } @@ -105,7 +96,6 @@ impl FixedSizeInterner { pub fn len(&self) -> u16 { self.stable_store.len() as u16 } - pub fn map(&self, map_f: impl Fn(&T) -> U) -> MappedInterner { MappedInterner { stable_store: self.stable_store.iter().map(map_f).collect(), @@ -122,6 +112,12 @@ impl FixedSizeInterner { self.stable_store.iter_mut().enumerate().map(|(i, x)| (Interned::from_raw(i as u16), x)) } } + +/// A store of values of type `T`, each linked to a value of type `From` +/// stored in another interner. To create a mapped interner, use the +/// `map` method on [`FixedSizeInterner`] or [`MappedInterner`]. +/// +/// Values in this interner are indexed with [`Interned`]. #[derive(Clone)] pub struct MappedInterner { stable_store: Vec, diff --git a/milli/src/search/new/logger/detailed.rs b/milli/src/search/new/logger/detailed.rs index 752e5ce35..96f4092b1 100644 --- a/milli/src/search/new/logger/detailed.rs +++ b/milli/src/search/new/logger/detailed.rs @@ -10,7 +10,7 @@ use crate::search::new::interner::{Interned, MappedInterner}; use crate::search::new::query_graph::QueryNodeData; use crate::search::new::query_term::{LocatedQueryTerm, QueryTerm}; use crate::search::new::ranking_rule_graph::{ - DeadEndPathCache, Edge, EdgeCondition, ProximityCondition, ProximityGraph, RankingRuleGraph, + DeadEndPathCache, Edge, ProximityCondition, ProximityGraph, RankingRuleGraph, RankingRuleGraphTrait, TypoEdge, TypoGraph, }; use crate::search::new::small_bitmap::SmallBitmap; @@ -44,7 +44,7 @@ pub enum SearchEvents { ProximityState { graph: RankingRuleGraph, paths: Vec>>, - empty_paths_cache: DeadEndPathCache, + dead_end_path_cache: DeadEndPathCache, universe: RoaringBitmap, distances: MappedInterner)>, QueryNode>, cost: u16, @@ -52,7 +52,7 @@ pub enum SearchEvents { TypoState { graph: RankingRuleGraph, paths: Vec>>, - empty_paths_cache: DeadEndPathCache, + dead_end_path_cache: DeadEndPathCache, universe: RoaringBitmap, distances: MappedInterner)>, QueryNode>, cost: u16, @@ -170,7 +170,7 @@ impl SearchLogger for DetailedSearchLogger { &mut self, query_graph: &RankingRuleGraph, paths_map: &[Vec>], - empty_paths_cache: &DeadEndPathCache, + dead_end_path_cache: &DeadEndPathCache, universe: &RoaringBitmap, distances: &MappedInterner)>, QueryNode>, cost: u16, @@ -178,7 +178,7 @@ impl SearchLogger for DetailedSearchLogger { self.events.push(SearchEvents::ProximityState { graph: query_graph.clone(), paths: paths_map.to_vec(), - empty_paths_cache: empty_paths_cache.clone(), + dead_end_path_cache: dead_end_path_cache.clone(), universe: universe.clone(), distances: distances.clone(), cost, @@ -189,7 +189,7 @@ impl SearchLogger for DetailedSearchLogger { &mut self, query_graph: &RankingRuleGraph, paths_map: &[Vec>], - empty_paths_cache: &DeadEndPathCache, + dead_end_path_cache: &DeadEndPathCache, universe: &RoaringBitmap, distances: &MappedInterner)>, QueryNode>, cost: u16, @@ -197,7 +197,7 @@ impl SearchLogger for DetailedSearchLogger { self.events.push(SearchEvents::TypoState { graph: query_graph.clone(), paths: paths_map.to_vec(), - empty_paths_cache: empty_paths_cache.clone(), + dead_end_path_cache: dead_end_path_cache.clone(), universe: universe.clone(), distances: distances.clone(), cost, @@ -358,7 +358,7 @@ results.{random} {{ SearchEvents::ProximityState { graph, paths, - empty_paths_cache, + dead_end_path_cache, universe, distances, cost, @@ -374,7 +374,7 @@ results.{random} {{ ctx, graph, paths, - empty_paths_cache, + dead_end_path_cache, distances.clone(), &mut new_file, ); @@ -391,7 +391,7 @@ results.{random} {{ SearchEvents::TypoState { graph, paths, - empty_paths_cache, + dead_end_path_cache, universe, distances, cost, @@ -407,7 +407,7 @@ results.{random} {{ ctx, graph, paths, - empty_paths_cache, + dead_end_path_cache, distances.clone(), &mut new_file, ); @@ -547,11 +547,11 @@ shape: class" let Edge { source_node, dest_node, condition: details, cost } = edge; match &details { - EdgeCondition::Unconditional => { + None => { writeln!(file, "{source_node} -> {dest_node} : \"always cost {cost}\"",) .unwrap(); } - EdgeCondition::Conditional(condition) => { + Some(condition) => { // let condition = graph.conditions_interner.get(*condition); writeln!( file, diff --git a/milli/src/search/new/logger/mod.rs b/milli/src/search/new/logger/mod.rs index e6c3931ed..696f65443 100644 --- a/milli/src/search/new/logger/mod.rs +++ b/milli/src/search/new/logger/mod.rs @@ -66,7 +66,7 @@ pub trait SearchLogger { &mut self, query_graph: &RankingRuleGraph, paths: &[Vec>], - empty_paths_cache: &DeadEndPathCache, + dead_end_path_cache: &DeadEndPathCache, universe: &RoaringBitmap, distances: &MappedInterner)>, QueryNode>, cost: u16, @@ -77,7 +77,7 @@ pub trait SearchLogger { &mut self, query_graph: &RankingRuleGraph, paths: &[Vec>], - empty_paths_cache: &DeadEndPathCache, + dead_end_path_cache: &DeadEndPathCache, universe: &RoaringBitmap, distances: &MappedInterner)>, QueryNode>, cost: u16, @@ -137,7 +137,7 @@ impl SearchLogger for DefaultSearchLogger { &mut self, _query_graph: &RankingRuleGraph, _paths_map: &[Vec>], - _empty_paths_cache: &DeadEndPathCache, + _dead_end_path_cache: &DeadEndPathCache, _universe: &RoaringBitmap, _distances: &MappedInterner)>, QueryNode>, _cost: u16, @@ -148,7 +148,7 @@ impl SearchLogger for DefaultSearchLogger { &mut self, _query_graph: &RankingRuleGraph, _paths: &[Vec>], - _empty_paths_cache: &DeadEndPathCache, + _dead_end_path_cache: &DeadEndPathCache, _universe: &RoaringBitmap, _distances: &MappedInterner)>, QueryNode>, _cost: u16, diff --git a/milli/src/search/new/mod.rs b/milli/src/search/new/mod.rs index 92e00df11..8ff832de4 100644 --- a/milli/src/search/new/mod.rs +++ b/milli/src/search/new/mod.rs @@ -36,6 +36,7 @@ use crate::search::new::query_term::located_query_terms_from_string; use crate::search::new::words::Words; use crate::{Filter, Index, Result, TermsMatchingStrategy}; +/// A structure used throughout the execution of a search query. pub struct SearchContext<'ctx> { pub index: &'ctx Index, pub txn: &'ctx RoTxn<'ctx>, @@ -59,6 +60,7 @@ impl<'ctx> SearchContext<'ctx> { } } +/// Apply the [`TermsMatchingStrategy`] to the query graph and resolve it. #[allow(clippy::too_many_arguments)] fn resolve_maximally_reduced_query_graph<'ctx>( ctx: &mut SearchContext<'ctx>, @@ -100,6 +102,8 @@ fn resolve_maximally_reduced_query_graph<'ctx>( Ok(docids) } + +/// Return the list of initialised ranking rules to be used for a placeholder search. fn get_ranking_rules_for_placeholder_search<'ctx>( ctx: &SearchContext<'ctx>, ) -> Result>>> { @@ -123,6 +127,8 @@ fn get_ranking_rules_for_placeholder_search<'ctx>( } Ok(ranking_rules) } + +/// Return the list of initialised ranking rules to be used for a query graph search. fn get_ranking_rules_for_query_graph_search<'ctx>( ctx: &SearchContext<'ctx>, terms_matching_strategy: TermsMatchingStrategy, diff --git a/milli/src/search/new/ranking_rule_graph/build.rs b/milli/src/search/new/ranking_rule_graph/build.rs index 7ab08aceb..cd622a7ba 100644 --- a/milli/src/search/new/ranking_rule_graph/build.rs +++ b/milli/src/search/new/ranking_rule_graph/build.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use super::{Edge, RankingRuleGraph, RankingRuleGraphTrait}; -use crate::search::new::interner::{DedupInterner, Interner}; +use crate::search::new::interner::DedupInterner; use crate::search::new::small_bitmap::SmallBitmap; use crate::search::new::{QueryGraph, SearchContext}; use crate::Result; @@ -19,7 +19,7 @@ impl RankingRuleGraph { let mut conditions_interner = DedupInterner::default(); - let mut edges_store = Interner::default(); + let mut edges_store = DedupInterner::default(); let mut edges_of_node = query_graph.nodes.map(|_| HashSet::new()); for (source_id, source_node) in graph_nodes.iter() { @@ -33,7 +33,7 @@ impl RankingRuleGraph { } for (cost, condition) in edges { - let new_edge_id = edges_store.push(Some(Edge { + let new_edge_id = edges_store.insert(Some(Edge { source_node: source_id, dest_node: dest_idx, cost, diff --git a/milli/src/search/new/ranking_rule_graph/cheapest_paths.rs b/milli/src/search/new/ranking_rule_graph/cheapest_paths.rs index 52f199ca9..a86f1fcb0 100644 --- a/milli/src/search/new/ranking_rule_graph/cheapest_paths.rs +++ b/milli/src/search/new/ranking_rule_graph/cheapest_paths.rs @@ -4,8 +4,8 @@ use std::collections::btree_map::Entry; use std::collections::{BTreeMap, VecDeque}; use std::ops::ControlFlow; -use super::empty_paths_cache::DeadEndPathCache; -use super::{EdgeCondition, RankingRuleGraph, RankingRuleGraphTrait}; +use super::dead_end_path_cache::DeadEndPathCache; +use super::{RankingRuleGraph, RankingRuleGraphTrait}; use crate::search::new::interner::{Interned, MappedInterner}; use crate::search::new::query_graph::QueryNode; use crate::search::new::small_bitmap::SmallBitmap; @@ -23,7 +23,7 @@ impl RankingRuleGraph { from: Interned, cost: u16, all_distances: &MappedInterner)>, QueryNode>, - empty_paths_cache: &mut DeadEndPathCache, + dead_end_path_cache: &mut DeadEndPathCache, mut visit: impl FnMut( &[Interned], &mut Self, @@ -34,11 +34,11 @@ impl RankingRuleGraph { from, cost, all_distances, - empty_paths_cache, + dead_end_path_cache, &mut visit, &mut vec![], &mut SmallBitmap::for_interned_values_in(&self.conditions_interner), - &mut empty_paths_cache.conditions.clone(), + &mut dead_end_path_cache.conditions.clone(), )?; Ok(()) } @@ -47,7 +47,7 @@ impl RankingRuleGraph { from: Interned, cost: u16, all_distances: &MappedInterner)>, QueryNode>, - empty_paths_cache: &mut DeadEndPathCache, + dead_end_path_cache: &mut DeadEndPathCache, visit: &mut impl FnMut( &[Interned], &mut Self, @@ -66,10 +66,10 @@ impl RankingRuleGraph { continue; } let next_any_valid = match edge.condition { - EdgeCondition::Unconditional => { + None => { if edge.dest_node == self.query_graph.end_node { any_valid = true; - let control_flow = visit(prev_conditions, self, empty_paths_cache)?; + let control_flow = visit(prev_conditions, self, dead_end_path_cache)?; match control_flow { ControlFlow::Continue(_) => {} ControlFlow::Break(_) => return Ok(true), @@ -80,7 +80,7 @@ impl RankingRuleGraph { edge.dest_node, cost - edge.cost as u16, all_distances, - empty_paths_cache, + dead_end_path_cache, visit, prev_conditions, cur_path, @@ -88,7 +88,7 @@ impl RankingRuleGraph { )? } } - EdgeCondition::Conditional(condition) => { + Some(condition) => { if forbidden_conditions.contains(condition) || !all_distances.get(edge.dest_node).iter().any( |(next_cost, necessary_conditions)| { @@ -104,8 +104,8 @@ impl RankingRuleGraph { let mut new_forbidden_conditions = forbidden_conditions.clone(); new_forbidden_conditions - .union(empty_paths_cache.condition_couples.get(condition)); - empty_paths_cache.prefixes.final_edges_after_prefix( + .union(dead_end_path_cache.condition_couples.get(condition)); + dead_end_path_cache.prefixes.final_edges_after_prefix( prev_conditions, &mut |x| { new_forbidden_conditions.insert(x); @@ -113,7 +113,7 @@ impl RankingRuleGraph { ); let next_any_valid = if edge.dest_node == self.query_graph.end_node { any_valid = true; - let control_flow = visit(prev_conditions, self, empty_paths_cache)?; + let control_flow = visit(prev_conditions, self, dead_end_path_cache)?; match control_flow { ControlFlow::Continue(_) => {} ControlFlow::Break(_) => return Ok(true), @@ -124,7 +124,7 @@ impl RankingRuleGraph { edge.dest_node, cost - edge.cost as u16, all_distances, - empty_paths_cache, + dead_end_path_cache, visit, prev_conditions, cur_path, @@ -139,15 +139,15 @@ impl RankingRuleGraph { any_valid |= next_any_valid; if next_any_valid { - if empty_paths_cache.path_is_dead_end(prev_conditions, cur_path) { + if dead_end_path_cache.path_is_dead_end(prev_conditions, cur_path) { return Ok(any_valid); } - forbidden_conditions.union(&empty_paths_cache.conditions); + forbidden_conditions.union(&dead_end_path_cache.conditions); for prev_condition in prev_conditions.iter() { forbidden_conditions - .union(empty_paths_cache.condition_couples.get(*prev_condition)); + .union(dead_end_path_cache.condition_couples.get(*prev_condition)); } - empty_paths_cache.prefixes.final_edges_after_prefix(prev_conditions, &mut |x| { + dead_end_path_cache.prefixes.final_edges_after_prefix(prev_conditions, &mut |x| { forbidden_conditions.insert(x); }); } @@ -178,16 +178,14 @@ impl RankingRuleGraph { let cur_node_edges = &self.edges_of_node.get(cur_node); for edge_idx in cur_node_edges.iter() { let edge = self.edges_store.get(edge_idx).as_ref().unwrap(); - let condition = match edge.condition { - EdgeCondition::Unconditional => None, - EdgeCondition::Conditional(condition) => Some(condition), - }; let succ_node = edge.dest_node; let succ_distances = distances_to_end.get(succ_node); for (succ_distance, succ_necessary_conditions) in succ_distances { let mut potential_necessary_edges = SmallBitmap::for_interned_values_in(&self.conditions_interner); - for condition in condition.into_iter().chain(succ_necessary_conditions.iter()) { + for condition in + edge.condition.into_iter().chain(succ_necessary_conditions.iter()) + { potential_necessary_edges.insert(condition); } diff --git a/milli/src/search/new/ranking_rule_graph/edge_docids_cache.rs b/milli/src/search/new/ranking_rule_graph/condition_docids_cache.rs similarity index 60% rename from milli/src/search/new/ranking_rule_graph/edge_docids_cache.rs rename to milli/src/search/new/ranking_rule_graph/condition_docids_cache.rs index b3426619b..9766cfaa3 100644 --- a/milli/src/search/new/ranking_rule_graph/edge_docids_cache.rs +++ b/milli/src/search/new/ranking_rule_graph/condition_docids_cache.rs @@ -9,44 +9,43 @@ use crate::search::new::SearchContext; use crate::Result; /// A cache storing the document ids associated with each ranking rule edge -pub struct EdgeConditionDocIdsCache { +pub struct ConditionDocIdsCache { // TODO: should be FxHashMap, RoaringBitmap> - pub cache: FxHashMap, RoaringBitmap>, + pub cache: FxHashMap, RoaringBitmap>, _phantom: PhantomData, } -impl Default for EdgeConditionDocIdsCache { +impl Default for ConditionDocIdsCache { fn default() -> Self { Self { cache: Default::default(), _phantom: Default::default() } } } -impl EdgeConditionDocIdsCache { +impl ConditionDocIdsCache { /// Retrieve the document ids for the given edge condition. /// /// If the cache does not yet contain these docids, they are computed /// and inserted in the cache. - pub fn get_edge_docids<'s, 'ctx>( + pub fn get_condition_docids<'s, 'ctx>( &'s mut self, ctx: &mut SearchContext<'ctx>, - // TODO: should be Interned - interned_edge_condition: Interned, + interned_condition: Interned, graph: &RankingRuleGraph, // TODO: maybe universe doesn't belong here universe: &RoaringBitmap, ) -> Result<&'s RoaringBitmap> { - if self.cache.contains_key(&interned_edge_condition) { + if self.cache.contains_key(&interned_condition) { // TODO: should we update the bitmap in the cache if the new universe // reduces it? // TODO: maybe have a generation: u32 to track every time the universe was // reduced. Then only attempt to recompute the intersection when there is a chance - // that edge_docids & universe changed - return Ok(&self.cache[&interned_edge_condition]); + // that condition_docids & universe changed + return Ok(&self.cache[&interned_condition]); } // TODO: maybe universe doesn't belong here - let edge_condition = graph.conditions_interner.get(interned_edge_condition); + let condition = graph.conditions_interner.get(interned_condition); // TODO: faster way to do this? - let docids = universe & G::resolve_edge_condition(ctx, edge_condition, universe)?; - let _ = self.cache.insert(interned_edge_condition, docids); - let docids = &self.cache[&interned_edge_condition]; + let docids = universe & G::resolve_condition(ctx, condition, universe)?; + let _ = self.cache.insert(interned_condition, docids); + let docids = &self.cache[&interned_condition]; Ok(docids) } } diff --git a/milli/src/search/new/ranking_rule_graph/empty_paths_cache.rs b/milli/src/search/new/ranking_rule_graph/dead_end_path_cache.rs similarity index 78% rename from milli/src/search/new/ranking_rule_graph/empty_paths_cache.rs rename to milli/src/search/new/ranking_rule_graph/dead_end_path_cache.rs index 15346e929..b4af625d6 100644 --- a/milli/src/search/new/ranking_rule_graph/empty_paths_cache.rs +++ b/milli/src/search/new/ranking_rule_graph/dead_end_path_cache.rs @@ -9,11 +9,11 @@ use crate::search::new::{ /// universe. pub struct DeadEndPathCache { /// The set of edge conditions that resolve to no documents. - pub conditions: SmallBitmap, + pub conditions: SmallBitmap, /// A set of path prefixes that resolve to no documents. - pub prefixes: PathSet, + pub prefixes: PathSet, /// A set of empty couples of edge conditions that resolve to no documents. - pub condition_couples: MappedInterner, G::EdgeCondition>, + pub condition_couples: MappedInterner, G::Condition>, } impl Clone for DeadEndPathCache { fn clone(&self) -> Self { @@ -27,17 +27,17 @@ impl Clone for DeadEndPathCache { impl DeadEndPathCache { /// Create a new cache for a ranking rule graph containing at most `all_edges_len` edges. - pub fn new(all_edge_conditions: &FixedSizeInterner) -> Self { + pub fn new(all_conditions: &FixedSizeInterner) -> Self { Self { - conditions: SmallBitmap::for_interned_values_in(all_edge_conditions), + conditions: SmallBitmap::for_interned_values_in(all_conditions), prefixes: PathSet::default(), - condition_couples: all_edge_conditions - .map(|_| SmallBitmap::for_interned_values_in(all_edge_conditions)), + condition_couples: all_conditions + .map(|_| SmallBitmap::for_interned_values_in(all_conditions)), } } /// Store in the cache that every path containing the given edge resolves to no documents. - pub fn add_condition(&mut self, condition: Interned) { + pub fn add_condition(&mut self, condition: Interned) { self.conditions.insert(condition); self.condition_couples.get_mut(condition).clear(); self.prefixes.remove_edge(condition); @@ -46,7 +46,7 @@ impl DeadEndPathCache { } } /// Store in the cache that every path containing the given prefix resolves to no documents. - pub fn add_prefix(&mut self, prefix: &[Interned]) { + pub fn add_prefix(&mut self, prefix: &[Interned]) { // TODO: typed PathSet self.prefixes.insert(prefix.iter().copied()); } @@ -54,8 +54,8 @@ impl DeadEndPathCache { /// Store in the cache that every path containing the two given edges resolves to no documents. pub fn add_condition_couple( &mut self, - edge1: Interned, - edge2: Interned, + edge1: Interned, + edge2: Interned, ) { self.condition_couples.get_mut(edge1).insert(edge2); } @@ -63,8 +63,8 @@ impl DeadEndPathCache { /// Returns true if the cache can determine that the given path resolves to no documents. pub fn path_is_dead_end( &self, - path: &[Interned], - path_bitmap: &SmallBitmap, + path: &[Interned], + path_bitmap: &SmallBitmap, ) -> bool { if path_bitmap.intersects(&self.conditions) { return true; diff --git a/milli/src/search/new/ranking_rule_graph/mod.rs b/milli/src/search/new/ranking_rule_graph/mod.rs index 03e7c14bc..fcb9ade3f 100644 --- a/milli/src/search/new/ranking_rule_graph/mod.rs +++ b/milli/src/search/new/ranking_rule_graph/mod.rs @@ -7,8 +7,8 @@ the same but the edges are replaced. mod build; mod cheapest_paths; -mod edge_docids_cache; -mod empty_paths_cache; +mod condition_docids_cache; +mod dead_end_path_cache; mod path_set; /// Implementation of the `proximity` ranking rule @@ -19,8 +19,8 @@ mod typo; use std::collections::HashSet; use std::hash::Hash; -pub use edge_docids_cache::EdgeConditionDocIdsCache; -pub use empty_paths_cache::DeadEndPathCache; +pub use condition_docids_cache::EdgeConditionDocIdsCache; +pub use dead_end_path_cache::DeadEndPathCache; pub use proximity::{ProximityCondition, ProximityGraph}; use roaring::RoaringBitmap; pub use typo::{TypoEdge, TypoGraph}; @@ -32,31 +32,6 @@ use super::small_bitmap::SmallBitmap; use super::{QueryGraph, QueryNode, SearchContext}; use crate::Result; -/// The condition that is associated with an edge in the ranking rule graph. -/// -/// Some edges are unconditional, which means that traversing them does not reduce -/// the set of candidates. -/// -/// Most edges, however, have a condition attached to them. For example, for the -/// proximity ranking rule, the condition could be that a word is N-close to another one. -/// When the edge is traversed, some database operations are executed to retrieve the set -/// of documents that satisfy the condition, which reduces the list of candidate document ids. -pub enum EdgeCondition { - Unconditional, - Conditional(Interned), -} - -impl Copy for EdgeCondition {} - -impl Clone for EdgeCondition { - fn clone(&self) -> Self { - match self { - Self::Unconditional => Self::Unconditional, - Self::Conditional(arg0) => Self::Conditional(*arg0), - } - } -} - /// An edge in the ranking rule graph. /// /// It contains: @@ -68,7 +43,27 @@ pub struct Edge { pub source_node: Interned, pub dest_node: Interned, pub cost: u8, - pub condition: EdgeCondition, + pub condition: Option>, +} + +impl Hash for Edge { + fn hash(&self, state: &mut H) { + self.source_node.hash(state); + self.dest_node.hash(state); + self.cost.hash(state); + self.condition.hash(state); + } +} + +impl Eq for Edge {} + +impl PartialEq for Edge { + fn eq(&self, other: &Self) -> bool { + self.source_node == other.source_node + && self.dest_node == other.dest_node + && self.cost == other.cost + && self.condition == other.condition + } } /// A trait to be implemented by a marker type to build a graph-based ranking rule. @@ -113,12 +108,12 @@ pub trait RankingRuleGraphTrait: Sized { conditions_interner: &mut DedupInterner, source_node: &QueryNode, dest_node: &QueryNode, - ) -> Result)>>; + ) -> Result>)>>; fn log_state( graph: &RankingRuleGraph, paths: &[Vec>], - empty_paths_cache: &DeadEndPathCache, + dead_end_path_cache: &DeadEndPathCache, universe: &RoaringBitmap, distances: &MappedInterner)>, QueryNode>, cost: u16, @@ -151,15 +146,12 @@ impl RankingRuleGraph { pub fn remove_edges_with_condition(&mut self, condition_to_remove: Interned) { for (edge_id, edge_opt) in self.edges_store.iter_mut() { let Some(edge) = edge_opt.as_mut() else { continue }; - match edge.condition { - EdgeCondition::Unconditional => continue, - EdgeCondition::Conditional(condition) => { - if condition == condition_to_remove { - let (source_node, _dest_node) = (edge.source_node, edge.dest_node); - *edge_opt = None; - self.edges_of_node.get_mut(source_node).remove(edge_id); - } - } + let Some(condition) = edge.condition else { continue }; + + if condition == condition_to_remove { + let (source_node, _dest_node) = (edge.source_node, edge.dest_node); + *edge_opt = None; + self.edges_of_node.get_mut(source_node).remove(edge_id); } } } diff --git a/milli/src/search/new/ranking_rule_graph/path_set.rs b/milli/src/search/new/ranking_rule_graph/path_set.rs index 04e6c9116..19c7d012e 100644 --- a/milli/src/search/new/ranking_rule_graph/path_set.rs +++ b/milli/src/search/new/ranking_rule_graph/path_set.rs @@ -4,7 +4,7 @@ use crate::search::new::interner::Interned; -/// A set of [`Path`] +/// A set of `Vec>`. pub struct PathSet { nodes: Vec<(Interned, Self)>, is_end: bool, diff --git a/milli/src/search/new/ranking_rule_graph/proximity/build.rs b/milli/src/search/new/ranking_rule_graph/proximity/build.rs index 8ae634fbf..5e38a1879 100644 --- a/milli/src/search/new/ranking_rule_graph/proximity/build.rs +++ b/milli/src/search/new/ranking_rule_graph/proximity/build.rs @@ -7,7 +7,6 @@ use crate::search::new::interner::{DedupInterner, Interned}; use crate::search::new::query_graph::QueryNodeData; use crate::search::new::query_term::{LocatedQueryTerm, Phrase, QueryTerm}; use crate::search::new::ranking_rule_graph::proximity::WordPair; -use crate::search::new::ranking_rule_graph::EdgeCondition; use crate::search::new::{QueryNode, SearchContext}; use crate::Result; use heed::RoTxn; @@ -40,7 +39,7 @@ pub fn build_edges<'ctx>( conditions_interner: &mut DedupInterner, from_node: &QueryNode, to_node: &QueryNode, -) -> Result)>> { +) -> Result>)>> { let SearchContext { index, txn, @@ -52,7 +51,7 @@ pub fn build_edges<'ctx>( } = ctx; let right_term = match &to_node.data { - QueryNodeData::End => return Ok(vec![(0, EdgeCondition::Unconditional)]), + QueryNodeData::End => return Ok(vec![(0, None)]), QueryNodeData::Deleted | QueryNodeData::Start => return Ok(vec![]), QueryNodeData::Term(term) => term, }; @@ -70,7 +69,7 @@ pub fn build_edges<'ctx>( QueryNodeData::Start => { return Ok(vec![( (right_ngram_length - 1) as u8, - EdgeCondition::Conditional( + Some( conditions_interner .insert(ProximityCondition::Term { term: *right_term_interned }), ), @@ -88,7 +87,7 @@ pub fn build_edges<'ctx>( // but `sun` and `are` have no proximity condition between them return Ok(vec![( (right_ngram_length - 1) as u8, - EdgeCondition::Conditional( + Some( conditions_interner.insert(ProximityCondition::Term { term: *right_term_interned }), ), )]); @@ -140,7 +139,7 @@ pub fn build_edges<'ctx>( .map(|(cost, word_pairs)| { ( cost, - EdgeCondition::Conditional( + Some( conditions_interner .insert(ProximityCondition::Pairs { pairs: word_pairs.into_boxed_slice() }), ), @@ -149,9 +148,7 @@ pub fn build_edges<'ctx>( .collect::>(); new_edges.push(( 8 + (right_ngram_length - 1) as u8, - EdgeCondition::Conditional( - conditions_interner.insert(ProximityCondition::Term { term: *right_term_interned }), - ), + Some(conditions_interner.insert(ProximityCondition::Term { term: *right_term_interned })), )); Ok(new_edges) } diff --git a/milli/src/search/new/ranking_rule_graph/proximity/mod.rs b/milli/src/search/new/ranking_rule_graph/proximity/mod.rs index 5a5c5cff8..d5655e949 100644 --- a/milli/src/search/new/ranking_rule_graph/proximity/mod.rs +++ b/milli/src/search/new/ranking_rule_graph/proximity/mod.rs @@ -6,8 +6,8 @@ use std::iter::FromIterator; use roaring::RoaringBitmap; -use super::empty_paths_cache::DeadEndPathCache; -use super::{EdgeCondition, RankingRuleGraph, RankingRuleGraphTrait}; +use super::dead_end_path_cache::DeadEndPathCache; +use super::{RankingRuleGraph, RankingRuleGraphTrait}; use crate::search::new::interner::{DedupInterner, Interned, MappedInterner}; use crate::search::new::logger::SearchLogger; use crate::search::new::query_term::{Phrase, QueryTerm}; @@ -60,20 +60,20 @@ impl RankingRuleGraphTrait for ProximityGraph { conditions_interner: &mut DedupInterner, source_node: &QueryNode, dest_node: &QueryNode, - ) -> Result)>> { + ) -> Result>)>> { build::build_edges(ctx, conditions_interner, source_node, dest_node) } fn log_state( graph: &RankingRuleGraph, paths: &[Vec>], - empty_paths_cache: &DeadEndPathCache, + dead_end_path_cache: &DeadEndPathCache, universe: &RoaringBitmap, distances: &MappedInterner)>, QueryNode>, cost: u16, logger: &mut dyn SearchLogger, ) { - logger.log_proximity_state(graph, paths, empty_paths_cache, universe, distances, cost); + logger.log_proximity_state(graph, paths, dead_end_path_cache, universe, distances, cost); } fn label_for_edge_condition<'ctx>( diff --git a/milli/src/search/new/ranking_rule_graph/typo/mod.rs b/milli/src/search/new/ranking_rule_graph/typo/mod.rs index d0b18ddc8..b41787ac2 100644 --- a/milli/src/search/new/ranking_rule_graph/typo/mod.rs +++ b/milli/src/search/new/ranking_rule_graph/typo/mod.rs @@ -1,7 +1,7 @@ use roaring::RoaringBitmap; -use super::empty_paths_cache::DeadEndPathCache; -use super::{EdgeCondition, RankingRuleGraph, RankingRuleGraphTrait}; +use super::dead_end_path_cache::DeadEndPathCache; +use super::{RankingRuleGraph, RankingRuleGraphTrait}; use crate::search::new::interner::{DedupInterner, Interned, MappedInterner}; use crate::search::new::logger::SearchLogger; use crate::search::new::query_graph::QueryNodeData; @@ -58,7 +58,7 @@ impl RankingRuleGraphTrait for TypoGraph { conditions_interner: &mut DedupInterner, _from_node: &QueryNode, to_node: &QueryNode, - ) -> Result)>> { + ) -> Result>)>> { let SearchContext { term_interner, .. } = ctx; match &to_node.data { QueryNodeData::Term(LocatedQueryTerm { value, positions }) => { @@ -121,7 +121,7 @@ impl RankingRuleGraphTrait for TypoGraph { if !new_term.is_empty() { edges.push(( nbr_typos as u8 + base_cost, - EdgeCondition::Conditional(conditions_interner.insert(TypoEdge { + Some(conditions_interner.insert(TypoEdge { term: term_interner.insert(new_term), nbr_typos: nbr_typos as u8, })), @@ -130,7 +130,7 @@ impl RankingRuleGraphTrait for TypoGraph { } Ok(edges) } - QueryNodeData::End => Ok(vec![(0, EdgeCondition::Unconditional)]), + QueryNodeData::End => Ok(vec![(0, None)]), QueryNodeData::Deleted | QueryNodeData::Start => panic!(), } } @@ -138,13 +138,13 @@ impl RankingRuleGraphTrait for TypoGraph { fn log_state( graph: &RankingRuleGraph, paths: &[Vec>], - empty_paths_cache: &DeadEndPathCache, + dead_end_path_cache: &DeadEndPathCache, universe: &RoaringBitmap, distances: &MappedInterner)>, QueryNode>, cost: u16, logger: &mut dyn SearchLogger, ) { - logger.log_typo_state(graph, paths, empty_paths_cache, universe, distances, cost); + logger.log_typo_state(graph, paths, dead_end_path_cache, universe, distances, cost); } fn label_for_edge_condition<'ctx>( diff --git a/milli/src/search/new/small_bitmap.rs b/milli/src/search/new/small_bitmap.rs index cb1c64ec2..975c5343f 100644 --- a/milli/src/search/new/small_bitmap.rs +++ b/milli/src/search/new/small_bitmap.rs @@ -2,6 +2,7 @@ use std::marker::PhantomData; use super::interner::{FixedSizeInterner, Interned}; +/// A compact set of [`Interned`] pub struct SmallBitmap { internal: SmallBitmapInternal, _phantom: PhantomData,