2023-02-21 09:46:00 +01:00
|
|
|
pub mod build;
|
|
|
|
pub mod cheapest_paths;
|
|
|
|
pub mod edge_docids_cache;
|
|
|
|
pub mod empty_paths_cache;
|
|
|
|
pub mod paths_map;
|
|
|
|
pub mod proximity;
|
|
|
|
pub mod resolve_paths;
|
|
|
|
|
2023-02-21 12:55:44 +01:00
|
|
|
use std::collections::{BTreeSet, HashSet};
|
2023-02-21 09:46:00 +01:00
|
|
|
use std::ops::ControlFlow;
|
|
|
|
|
|
|
|
use heed::RoTxn;
|
|
|
|
use roaring::RoaringBitmap;
|
|
|
|
|
|
|
|
use super::db_cache::DatabaseCache;
|
2023-02-21 12:55:44 +01:00
|
|
|
use super::{QueryGraph, QueryNode};
|
2023-02-21 09:46:00 +01:00
|
|
|
use crate::{Index, Result};
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum EdgeDetails<E> {
|
|
|
|
Unconditional,
|
|
|
|
Data(E),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct Edge<E> {
|
2023-02-21 12:55:44 +01:00
|
|
|
from_node: u32,
|
|
|
|
to_node: u32,
|
2023-02-21 09:46:00 +01:00
|
|
|
cost: u8,
|
|
|
|
details: EdgeDetails<E>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct EdgePointer<'graph, E> {
|
2023-02-21 12:55:44 +01:00
|
|
|
pub index: u32,
|
2023-02-21 09:46:00 +01:00
|
|
|
pub edge: &'graph Edge<E>,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait RankingRuleGraphTrait {
|
2023-02-21 12:33:32 +01:00
|
|
|
/// The details of an edge connecting two query nodes. These details
|
|
|
|
/// should be sufficient to compute the edge's cost and associated document ids
|
|
|
|
/// in [`compute_docids`](RankingRuleGraphTrait).
|
2023-02-21 09:46:00 +01:00
|
|
|
type EdgeDetails: Sized;
|
2023-02-21 12:33:32 +01:00
|
|
|
|
2023-02-21 09:46:00 +01:00
|
|
|
type BuildVisitedFromNode;
|
|
|
|
|
2023-02-21 12:33:32 +01:00
|
|
|
/// Return the label of the given edge details, to be used when visualising
|
|
|
|
/// the ranking rule graph using GraphViz.
|
|
|
|
fn graphviz_edge_details_label(edge: &Self::EdgeDetails) -> String;
|
2023-02-21 09:46:00 +01:00
|
|
|
|
2023-02-21 12:33:32 +01:00
|
|
|
/// Compute the document ids associated with the given edge.
|
2023-02-21 09:46:00 +01:00
|
|
|
fn compute_docids<'transaction>(
|
|
|
|
index: &Index,
|
|
|
|
txn: &'transaction RoTxn,
|
|
|
|
db_cache: &mut DatabaseCache<'transaction>,
|
|
|
|
edge_details: &Self::EdgeDetails,
|
|
|
|
) -> Result<RoaringBitmap>;
|
|
|
|
|
2023-02-21 12:33:32 +01:00
|
|
|
/// Prepare to build the edges outgoing from `from_node`.
|
|
|
|
///
|
|
|
|
/// This call is followed by zero, one or more calls to [`build_visit_to_node`](RankingRuleGraphTrait::build_visit_to_node),
|
|
|
|
/// which builds the actual edges.
|
2023-02-21 09:46:00 +01:00
|
|
|
fn build_visit_from_node<'transaction>(
|
|
|
|
index: &Index,
|
|
|
|
txn: &'transaction RoTxn,
|
|
|
|
db_cache: &mut DatabaseCache<'transaction>,
|
|
|
|
from_node: &QueryNode,
|
|
|
|
) -> Result<Option<Self::BuildVisitedFromNode>>;
|
|
|
|
|
2023-02-21 12:33:32 +01:00
|
|
|
/// Return the cost and details of the edges going from the previously visited node
|
|
|
|
/// (with [`build_visit_from_node`](RankingRuleGraphTrait::build_visit_from_node)) to `to_node`.
|
2023-02-21 09:46:00 +01:00
|
|
|
fn build_visit_to_node<'from_data, 'transaction: 'from_data>(
|
|
|
|
index: &Index,
|
|
|
|
txn: &'transaction RoTxn,
|
|
|
|
db_cache: &mut DatabaseCache<'transaction>,
|
|
|
|
to_node: &QueryNode,
|
|
|
|
from_node_data: &'from_data Self::BuildVisitedFromNode,
|
2023-02-21 12:33:32 +01:00
|
|
|
) -> Result<Vec<(u8, EdgeDetails<Self::EdgeDetails>)>>;
|
2023-02-21 09:46:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct RankingRuleGraph<G: RankingRuleGraphTrait> {
|
|
|
|
pub query_graph: QueryGraph,
|
|
|
|
// pub edges: Vec<HashMap<usize, Vec<Edge<G::EdgeDetails>>>>,
|
|
|
|
pub all_edges: Vec<Option<Edge<G::EdgeDetails>>>,
|
2023-02-21 12:33:32 +01:00
|
|
|
|
|
|
|
pub node_edges: Vec<RoaringBitmap>,
|
|
|
|
|
|
|
|
pub successors: Vec<RoaringBitmap>,
|
|
|
|
// to get the edges between two nodes:
|
|
|
|
// 1. get node_outgoing_edges[from]
|
|
|
|
// 2. get node_incoming_edges[to]
|
|
|
|
// 3. take intersection betweem the two
|
|
|
|
|
|
|
|
// TODO: node edges could be different I guess
|
|
|
|
// something like:
|
|
|
|
// pub node_edges: Vec<BitSet>
|
|
|
|
// where each index is the result of:
|
|
|
|
// the successor index in the top 16 bits, the edge index in the bottom 16 bits
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
// node_successors?
|
|
|
|
|
2023-02-21 12:55:44 +01:00
|
|
|
// pub removed_edges: HashSet<u32>,
|
|
|
|
// pub tmp_removed_edges: HashSet<u32>,
|
2023-02-21 09:46:00 +01:00
|
|
|
}
|
|
|
|
impl<G: RankingRuleGraphTrait> RankingRuleGraph<G> {
|
2023-02-21 12:33:32 +01:00
|
|
|
// Visit all edges between the two given nodes in order of increasing cost.
|
2023-02-21 09:46:00 +01:00
|
|
|
pub fn visit_edges<'graph, O>(
|
|
|
|
&'graph self,
|
2023-02-21 12:55:44 +01:00
|
|
|
from: u32,
|
|
|
|
to: u32,
|
|
|
|
mut visit: impl FnMut(u32, &'graph Edge<G::EdgeDetails>) -> ControlFlow<O>,
|
2023-02-21 09:46:00 +01:00
|
|
|
) -> Option<O> {
|
2023-02-21 12:55:44 +01:00
|
|
|
let from_edges = &self.node_edges[from as usize];
|
2023-02-21 12:33:32 +01:00
|
|
|
for edge_idx in from_edges {
|
|
|
|
let edge = self.all_edges[edge_idx as usize].as_ref().unwrap();
|
2023-02-21 09:46:00 +01:00
|
|
|
if edge.to_node == to {
|
2023-02-21 12:55:44 +01:00
|
|
|
let cf = visit(edge_idx, edge);
|
2023-02-21 09:46:00 +01:00
|
|
|
match cf {
|
|
|
|
ControlFlow::Continue(_) => continue,
|
|
|
|
ControlFlow::Break(o) => return Some(o),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2023-02-21 12:55:44 +01:00
|
|
|
fn remove_edge(&mut self, edge_index: u32) {
|
|
|
|
let edge_opt = &mut self.all_edges[edge_index as usize];
|
2023-02-21 12:33:32 +01:00
|
|
|
let Some(edge) = &edge_opt else { return };
|
|
|
|
let (from_node, to_node) = (edge.from_node, edge.to_node);
|
|
|
|
*edge_opt = None;
|
2023-02-21 09:46:00 +01:00
|
|
|
|
2023-02-21 12:55:44 +01:00
|
|
|
let from_node_edges = &mut self.node_edges[from_node as usize];
|
|
|
|
from_node_edges.remove(edge_index);
|
2023-02-21 09:46:00 +01:00
|
|
|
|
2023-02-21 12:33:32 +01:00
|
|
|
let mut new_successors_from_node = RoaringBitmap::new();
|
2023-02-21 12:55:44 +01:00
|
|
|
for from_node_edge in from_node_edges.iter() {
|
|
|
|
let Edge { to_node, .. } = &self.all_edges[from_node_edge as usize].as_ref().unwrap();
|
|
|
|
new_successors_from_node.insert(*to_node);
|
2023-02-21 09:46:00 +01:00
|
|
|
}
|
2023-02-21 12:55:44 +01:00
|
|
|
self.successors[from_node as usize] = new_successors_from_node;
|
2023-02-21 09:46:00 +01:00
|
|
|
}
|
2023-02-21 12:33:32 +01:00
|
|
|
// pub fn remove_nodes(&mut self, nodes: &[usize]) {
|
|
|
|
// for &node in nodes {
|
|
|
|
// let edge_indices = &mut self.node_edges[node];
|
|
|
|
// for edge_index in edge_indices.iter() {
|
|
|
|
// self.all_edges[*edge_index] = None;
|
|
|
|
// }
|
|
|
|
// edge_indices.clear();
|
|
|
|
|
|
|
|
// let preds = &self.query_graph.edges[node].incoming;
|
|
|
|
// for pred in preds {
|
|
|
|
// let edge_indices = &mut self.node_edges[*pred];
|
|
|
|
// for edge_index in edge_indices.iter() {
|
|
|
|
// let edge_opt = &mut self.all_edges[*edge_index];
|
|
|
|
// let Some(edge) = edge_opt else { continue; };
|
|
|
|
// if edge.to_node == node {
|
|
|
|
// *edge_opt = None;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// panic!("remove nodes is incorrect at the moment");
|
|
|
|
// edge_indices.clear();
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// self.query_graph.remove_nodes(nodes);
|
|
|
|
// }
|
|
|
|
// pub fn simplify(&mut self) {
|
|
|
|
// loop {
|
|
|
|
// let mut nodes_to_remove = vec![];
|
|
|
|
// for (node_idx, node) in self.query_graph.nodes.iter().enumerate() {
|
|
|
|
// if !matches!(node, QueryNode::End | QueryNode::Deleted)
|
|
|
|
// && self.node_edges[node_idx].is_empty()
|
|
|
|
// {
|
|
|
|
// nodes_to_remove.push(node_idx);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// if nodes_to_remove.is_empty() {
|
|
|
|
// break;
|
|
|
|
// } else {
|
|
|
|
// self.remove_nodes(&nodes_to_remove);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
2023-02-21 12:55:44 +01:00
|
|
|
// fn is_removed_edge(&self, edge: u32) -> bool {
|
2023-02-21 09:46:00 +01:00
|
|
|
// self.removed_edges.contains(&edge) || self.tmp_removed_edges.contains(&edge)
|
|
|
|
// }
|
|
|
|
|
|
|
|
pub fn graphviz(&self) -> String {
|
|
|
|
let mut desc = String::new();
|
|
|
|
desc.push_str("digraph G {\nrankdir = LR;\nnode [shape = \"record\"]\n");
|
|
|
|
|
|
|
|
for (node_idx, node) in self.query_graph.nodes.iter().enumerate() {
|
|
|
|
if matches!(node, QueryNode::Deleted) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
desc.push_str(&format!("{node_idx} [label = {:?}]", node));
|
2023-02-21 12:55:44 +01:00
|
|
|
if node_idx == self.query_graph.root_node as usize {
|
2023-02-21 09:46:00 +01:00
|
|
|
desc.push_str("[color = blue]");
|
2023-02-21 12:55:44 +01:00
|
|
|
} else if node_idx == self.query_graph.end_node as usize {
|
2023-02-21 09:46:00 +01:00
|
|
|
desc.push_str("[color = red]");
|
|
|
|
}
|
|
|
|
desc.push_str(";\n");
|
|
|
|
}
|
|
|
|
for edge in self.all_edges.iter().flatten() {
|
|
|
|
let Edge { from_node, to_node, cost, details } = edge;
|
|
|
|
|
|
|
|
match &details {
|
|
|
|
EdgeDetails::Unconditional => {
|
|
|
|
desc.push_str(&format!(
|
|
|
|
"{from_node} -> {to_node} [label = \"always cost {cost}\"];\n",
|
|
|
|
cost = edge.cost,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
EdgeDetails::Data(details) => {
|
|
|
|
desc.push_str(&format!(
|
|
|
|
"{from_node} -> {to_node} [label = \"cost {cost} {edge_label}\"];\n",
|
|
|
|
cost = edge.cost,
|
2023-02-21 12:33:32 +01:00
|
|
|
edge_label = G::graphviz_edge_details_label(details)
|
2023-02-21 09:46:00 +01:00
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
desc.push('}');
|
|
|
|
desc
|
|
|
|
}
|
|
|
|
}
|