1use std::collections::HashMap;
56use std::fmt;
57use std::hash::{Hash, Hasher};
58use std::sync::Arc;
59
60use datafusion::arrow::array::{
61 Array, ArrayBuilder, ArrayRef, Int64Array, Int64Builder, ListArray, UInt32Array,
62};
63use datafusion::arrow::buffer::OffsetBuffer;
64use datafusion::arrow::compute::{concat, take};
65use datafusion::arrow::datatypes::{DataType, Field, Schema, SchemaRef};
66use datafusion::arrow::record_batch::RecordBatch;
67use datafusion::common::{DFSchema, DFSchemaRef};
68use datafusion::error::{DataFusionError, Result};
69use datafusion::logical_expr::{Expr, LogicalPlan, UserDefinedLogicalNodeCore};
70use ursa_core::{IdMap, Topology};
71
72use super::node::{check_vertices, endpoint_id_type};
73use super::pattern::{Direction, Mode, Quantifier, Selector};
74
75pub const START: &str = "__start";
77pub const END: &str = "__end";
79pub const LENGTH: &str = "__length";
81pub const VERTICES: &str = "__vertices";
83pub const EDGES: &str = "__edges";
85pub const ELEMENTS: &str = "__element_id";
87
88pub const FUNCTIONS: &[(&str, &str)] = &[
90 ("path_length", LENGTH),
91 ("vertices", VERTICES),
92 ("edges", EDGES),
93 ("element_id", ELEMENTS),
94];
95
96pub const MAX_PATHS: usize = 10_000_000;
98
99const BATCH_ROWS: usize = 8192;
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd)]
104pub struct Search {
105 pub direction: Direction,
106 pub quantifier: Quantifier,
107 pub selector: Selector,
108 pub mode: Mode,
109}
110
111impl fmt::Display for Search {
112 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113 match self.selector {
114 Selector::All => {}
115 Selector::Shortest(1) => write!(f, "ANY SHORTEST ")?,
116 Selector::Shortest(k) => write!(f, "SHORTEST {k} ")?,
117 Selector::AllShortest => write!(f, "ALL SHORTEST ")?,
118 }
119 let mode = match self.mode {
120 Mode::Walk => "WALK",
121 Mode::Trail => "TRAIL",
122 Mode::Simple => "SIMPLE",
123 Mode::Acyclic => "ACYCLIC",
124 };
125 let arrow = match self.direction {
126 Direction::Right => "-[]->",
127 Direction::Left => "<-[]-",
128 Direction::Any => "-[]-",
129 };
130 let max = self
131 .quantifier
132 .max
133 .map(|m| m.to_string())
134 .unwrap_or_default();
135 write!(f, "{mode} {arrow}{{{},{max}}}", self.quantifier.min)
136 }
137}
138
139#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd)]
141pub struct PathColumns {
142 pub vertices: bool,
143 pub edges: bool,
144 pub elements: bool,
145}
146
147pub fn schema(id_type: &DataType, columns: PathColumns) -> Schema {
149 let list = |t: DataType| DataType::List(Arc::new(Field::new_list_field(t, false)));
150 let mut fields = vec![
151 Field::new(START, id_type.clone(), false),
152 Field::new(END, id_type.clone(), false),
153 Field::new(LENGTH, DataType::Int64, false),
154 ];
155 if columns.vertices {
156 fields.push(Field::new(VERTICES, list(id_type.clone()), false));
157 }
158 if columns.edges {
159 fields.push(Field::new(EDGES, list(DataType::Int64), false));
160 }
161 if columns.elements {
162 fields.push(Field::new(ELEMENTS, list(DataType::Int64), false));
163 }
164 Schema::new(fields)
165}
166
167#[derive(Debug, Clone)]
173pub struct PathSearchNode {
174 pub graph: String,
175 pub search: Search,
176 pub columns: PathColumns,
177 pub input: Arc<LogicalPlan>,
178 pub vertices: Option<Arc<LogicalPlan>>,
181 pub starts: Arc<LogicalPlan>,
182 pub source: Expr,
183 pub destination: Expr,
184 pub id_type: DataType,
185 schema: DFSchemaRef,
186}
187
188impl PathSearchNode {
189 #[allow(clippy::too_many_arguments)]
190 pub fn try_new(
191 graph: String,
192 search: Search,
193 columns: PathColumns,
194 input: Arc<LogicalPlan>,
195 vertices: Option<Arc<LogicalPlan>>,
196 starts: Arc<LogicalPlan>,
197 source: Expr,
198 destination: Expr,
199 ) -> Result<Self> {
200 let id_type = endpoint_id_type(&graph, &input, &source, &destination)?;
201 check_vertices(&graph, vertices.as_deref())?;
202 if columns.elements && id_type != DataType::Int64 {
203 return Err(DataFusionError::Plan(format!(
204 "in GRAPH_TABLE: element_id(p) lists node keys and edge rows together, so it needs integer node keys; graph '{graph}' has {id_type} keys: use vertices(p) and edges(p)"
205 )));
206 }
207 let schema = Arc::new(DFSchema::try_from(schema(&id_type, columns))?);
208 Ok(Self {
209 graph,
210 search,
211 columns,
212 input,
213 vertices,
214 starts,
215 source,
216 destination,
217 id_type,
218 schema,
219 })
220 }
221
222 pub(crate) fn endpoint_indices(&self) -> Result<(usize, usize)> {
223 let index = |e: &Expr| -> Result<usize> {
224 match e {
225 Expr::Column(c) => self.input.schema().index_of_column(c),
226 other => Err(DataFusionError::Internal(format!(
227 "a path search's endpoint must be a column; got {other}"
228 ))),
229 }
230 };
231 Ok((index(&self.source)?, index(&self.destination)?))
232 }
233}
234
235impl PartialEq for PathSearchNode {
236 fn eq(&self, other: &Self) -> bool {
237 self.graph == other.graph
238 && self.search == other.search
239 && self.columns == other.columns
240 && self.input == other.input
241 && self.vertices == other.vertices
242 && self.starts == other.starts
243 && self.source == other.source
244 && self.destination == other.destination
245 }
246}
247
248impl Eq for PathSearchNode {}
249
250impl Hash for PathSearchNode {
251 fn hash<H: Hasher>(&self, state: &mut H) {
252 self.graph.hash(state);
253 self.search.hash(state);
254 self.columns.hash(state);
255 self.input.hash(state);
256 self.vertices.hash(state);
257 self.starts.hash(state);
258 }
259}
260
261impl PartialOrd for PathSearchNode {
262 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
263 (&self.graph, &self.search, &self.columns)
264 .partial_cmp(&(&other.graph, &other.search, &other.columns))
265 .filter(|o| o.is_ne())
266 }
267}
268
269impl UserDefinedLogicalNodeCore for PathSearchNode {
270 fn name(&self) -> &str {
271 "PathSearch"
272 }
273
274 fn inputs(&self) -> Vec<&LogicalPlan> {
275 let mut inputs = vec![self.input.as_ref()];
276 inputs.extend(self.vertices.as_deref());
277 inputs.push(&self.starts);
278 inputs
279 }
280
281 fn schema(&self) -> &DFSchemaRef {
282 &self.schema
283 }
284
285 fn expressions(&self) -> Vec<Expr> {
286 vec![]
287 }
288
289 fn fmt_for_explain(&self, f: &mut fmt::Formatter) -> fmt::Result {
290 write!(
291 f,
292 "PathSearch: graph={}, search={}",
293 self.graph, self.search
294 )
295 }
296
297 fn with_exprs_and_inputs(&self, _exprs: Vec<Expr>, inputs: Vec<LogicalPlan>) -> Result<Self> {
298 let expected = 2 + usize::from(self.vertices.is_some());
299 if inputs.len() != expected {
300 return Err(DataFusionError::Internal(format!(
301 "PathSearch has {expected} inputs; got {}",
302 inputs.len()
303 )));
304 }
305 let mut inputs = inputs.into_iter().map(Arc::new);
306 let input = inputs.next().expect("checked above");
307 let vertices = self.vertices.as_ref().and_then(|_| inputs.next());
308 let starts = inputs.next().expect("checked above");
309 Ok(Self {
310 input,
311 vertices,
312 starts,
313 ..self.clone()
314 })
315 }
316
317 fn necessary_children_exprs(&self, _output_columns: &[usize]) -> Option<Vec<Vec<usize>>> {
320 let (source, destination) = self.endpoint_indices().ok()?;
321 let mut children = vec![vec![source, destination]];
322 if self.vertices.is_some() {
323 children.push(vec![0]);
324 }
325 children.push(vec![0]);
326 Some(children)
327 }
328
329 fn prevent_predicate_push_down_columns(&self) -> std::collections::HashSet<String> {
331 self.schema
332 .fields()
333 .iter()
334 .map(|f| f.name().clone())
335 .collect()
336 }
337}
338
339#[derive(Debug, Clone, Copy)]
342struct Entry {
343 node: u32,
344 parent: u32,
345 edge: u32,
346 len: u32,
347}
348
349const ROOT: u32 = u32::MAX;
350
351enum States {
354 Dense {
355 seen: Vec<(u32, u32)>,
356 touched: Vec<usize>,
357 width: usize,
358 },
359 Sparse(HashMap<(u32, u32), (u32, u32)>),
360}
361
362impl States {
363 fn new(n_nodes: usize, min: u32) -> Self {
364 let width = min as usize + 1;
365 match n_nodes.checked_mul(width) {
366 Some(n) if n <= 1 << 26 => States::Dense {
367 seen: vec![(0, 0); n],
368 touched: Vec::new(),
369 width,
370 },
371 _ => States::Sparse(HashMap::new()),
372 }
373 }
374
375 fn get(&mut self, node: u32, c: u32) -> &mut (u32, u32) {
376 match self {
377 States::Dense {
378 seen,
379 touched,
380 width,
381 } => {
382 let i = node as usize * *width + c as usize;
383 if seen[i].0 == 0 {
384 touched.push(i);
385 }
386 &mut seen[i]
387 }
388 States::Sparse(map) => map.entry((node, c)).or_insert((0, 0)),
389 }
390 }
391
392 fn clear(&mut self) {
393 match self {
394 States::Dense { seen, touched, .. } => {
395 for i in touched.drain(..) {
396 seen[i] = (0, 0);
397 }
398 }
399 States::Sparse(map) => map.clear(),
400 }
401 }
402}
403
404fn steps(topology: &Topology, direction: Direction, u: u32, out: &mut Vec<(u32, u32)>) {
407 out.clear();
408 fn pairs(a: &ursa_core::Adjacency, u: u32) -> impl Iterator<Item = (u32, u32)> + '_ {
409 a.neighbors(u)
410 .iter()
411 .copied()
412 .zip(a.edge_ids(u).iter().copied())
413 }
414 match direction {
415 Direction::Right => out.extend(pairs(topology.out(), u)),
416 Direction::Left => out.extend(pairs(topology.incoming(), u)),
417 Direction::Any => {
418 let forward: Vec<(u32, u32)> = pairs(topology.out(), u).collect();
419 let backward: Vec<(u32, u32)> = pairs(topology.incoming(), u)
421 .filter(|&(w, _)| w != u)
422 .collect();
423 let (mut i, mut j) = (0, 0);
424 while i < forward.len() || j < backward.len() {
425 let take_forward =
426 j == backward.len() || (i < forward.len() && forward[i].1 < backward[j].1);
427 if take_forward {
428 out.push(forward[i]);
429 i += 1;
430 } else {
431 out.push(backward[j]);
432 j += 1;
433 }
434 }
435 }
436 }
437}
438
439fn allowed(entries: &[Entry], at: u32, w: u32, edge: u32, mode: Mode, start: u32) -> bool {
442 let chain = || {
443 let mut i = at;
444 std::iter::from_fn(move || {
445 if i == ROOT {
446 return None;
447 }
448 let e = entries[i as usize];
449 i = e.parent;
450 Some(e)
451 })
452 };
453 match mode {
454 Mode::Walk => true,
455 Mode::Trail => chain().all(|e| e.parent == ROOT || e.edge != edge),
456 Mode::Acyclic => chain().all(|e| e.node != w),
457 Mode::Simple => {
458 let last = entries[at as usize];
459 if last.len > 0 && last.node == start {
460 return false; }
462 w == start && chain().all(|e| e.node != w || e.parent == ROOT)
463 || chain().all(|e| e.node != w)
464 }
465 }
466}
467
468struct Rows<'a> {
470 starts: &'a ArrayRef,
471 columns: PathColumns,
472 schema: SchemaRef,
473 start_rows: Vec<u32>,
474 ends: Vec<u32>,
475 lengths: Vec<i64>,
476 vertex_offsets: Vec<i32>,
477 vertex_values: Vec<u32>,
478 edge_offsets: Vec<i32>,
479 edge_values: Vec<i64>,
480 batches: Vec<RecordBatch>,
481 values: ArrayRef,
484 edge_row: &'a dyn Fn(u32) -> u64,
486}
487
488impl<'a> Rows<'a> {
489 fn new(
490 ids: &'a IdMap,
491 starts: &'a ArrayRef,
492 columns: PathColumns,
493 schema: SchemaRef,
494 edge_row: &'a dyn Fn(u32) -> u64,
495 ) -> Result<Self> {
496 let values = if ids.is_empty() {
497 Arc::clone(starts)
498 } else {
499 concat(&[starts.as_ref(), ids.user_id_array().as_ref()])?
500 };
501 Ok(Self {
502 starts,
503 columns,
504 schema,
505 start_rows: Vec::new(),
506 ends: Vec::new(),
507 lengths: Vec::new(),
508 vertex_offsets: vec![0],
509 vertex_values: Vec::new(),
510 edge_offsets: vec![0],
511 edge_values: Vec::new(),
512 batches: Vec::new(),
513 values,
514 edge_row,
515 })
516 }
517
518 fn dense(&self, d: u32) -> u32 {
520 (self.starts.len() as u32) + d
521 }
522
523 fn push(&mut self, start: u32, nodes: &[u32], edges: &[u32]) -> Result<()> {
526 self.start_rows.push(start);
527 self.ends.push(*nodes.last().expect("a path has a node"));
528 self.lengths.push(edges.len() as i64);
529 if self.columns.vertices || self.columns.elements {
530 self.vertex_values.extend_from_slice(nodes);
531 self.vertex_offsets.push(self.vertex_values.len() as i32);
532 }
533 if self.columns.edges || self.columns.elements {
534 let edge_row = self.edge_row;
535 self.edge_values
536 .extend(edges.iter().map(|&e| edge_row(e) as i64));
537 self.edge_offsets.push(self.edge_values.len() as i32);
538 }
539 if self.start_rows.len() >= BATCH_ROWS {
540 self.flush()?;
541 }
542 Ok(())
543 }
544
545 fn flush(&mut self) -> Result<()> {
546 if self.start_rows.is_empty() {
547 return Ok(());
548 }
549 let start = take(
550 self.starts.as_ref(),
551 &UInt32Array::from(std::mem::take(&mut self.start_rows)),
552 None,
553 )?;
554 let end = take(
555 self.values.as_ref(),
556 &UInt32Array::from(std::mem::take(&mut self.ends)),
557 None,
558 )?;
559 let mut columns: Vec<ArrayRef> = vec![
560 start,
561 end,
562 Arc::new(Int64Array::from(std::mem::take(&mut self.lengths))),
563 ];
564 let vertex_offsets = std::mem::replace(&mut self.vertex_offsets, vec![0]);
565 let vertex_values = take(
566 self.values.as_ref(),
567 &UInt32Array::from(std::mem::take(&mut self.vertex_values)),
568 None,
569 )?;
570 let edge_offsets = std::mem::replace(&mut self.edge_offsets, vec![0]);
571 let edge_values = std::mem::take(&mut self.edge_values);
572 let field = |i: usize| match self.schema.field(i).data_type() {
573 DataType::List(f) => Arc::clone(f),
574 _ => unreachable!("a list column"),
575 };
576 let mut i = 3;
577 if self.columns.vertices {
578 columns.push(Arc::new(ListArray::try_new(
579 field(i),
580 OffsetBuffer::new(vertex_offsets.clone().into()),
581 Arc::clone(&vertex_values),
582 None,
583 )?));
584 i += 1;
585 }
586 if self.columns.edges {
587 columns.push(Arc::new(ListArray::try_new(
588 field(i),
589 OffsetBuffer::new(edge_offsets.clone().into()),
590 Arc::new(Int64Array::from(edge_values.clone())),
591 None,
592 )?));
593 i += 1;
594 }
595 if self.columns.elements {
596 let keys = vertex_values
597 .as_any()
598 .downcast_ref::<Int64Array>()
599 .ok_or_else(|| DataFusionError::Internal("element_id needs Int64 keys".into()))?;
600 let mut values = Int64Builder::with_capacity(keys.len() + edge_values.len());
601 let mut offsets = vec![0i32];
602 for p in 0..vertex_offsets.len() - 1 {
603 let (v0, v1) = (vertex_offsets[p] as usize, vertex_offsets[p + 1] as usize);
604 let e0 = edge_offsets[p] as usize;
605 for k in v0..v1 {
606 values.append_value(keys.value(k));
607 if k + 1 < v1 {
608 values.append_value(edge_values[e0 + (k - v0)]);
609 }
610 }
611 offsets.push(values.len() as i32);
612 }
613 columns.push(Arc::new(ListArray::try_new(
614 field(i),
615 OffsetBuffer::new(offsets.into()),
616 Arc::new(values.finish()),
617 None,
618 )?));
619 }
620 self.batches
621 .push(RecordBatch::try_new(Arc::clone(&self.schema), columns)?);
622 Ok(())
623 }
624}
625
626pub fn run(
631 topology: &Topology,
632 ids: &IdMap,
633 edge_row: &dyn Fn(u32) -> u64,
634 search: &Search,
635 starts: &ArrayRef,
636 columns: PathColumns,
637 schema: SchemaRef,
638) -> Result<Vec<RecordBatch>> {
639 let dense = ids
640 .dense_from_array(starts.as_ref())
641 .map_err(|e| DataFusionError::Execution(format!("in GRAPH_TABLE: {e}")))?;
642 let mut rows = Rows::new(ids, starts, columns, schema, edge_row)?;
643 let Quantifier { min, max } = search.quantifier;
644 let restricted = search.mode != Mode::Walk;
645 let pruned = !restricted
646 || (matches!(
647 search.selector,
648 Selector::Shortest(1) | Selector::AllShortest
649 ) && min <= 1
650 && !(search.mode == Mode::Trail && search.direction == Direction::Any));
651 let mut states = States::new(topology.n_nodes(), min);
652 let mut targets: HashMap<u32, (u32, u32)> = HashMap::new();
654 let mut entries: Vec<Entry> = Vec::new();
655 let mut next = Vec::new();
656 let (mut nodes, mut edges) = (Vec::new(), Vec::new());
657 let mut total = 0usize;
658 for (row, start) in dense.iter().enumerate() {
659 if starts.is_null(row) {
660 continue;
661 }
662 let row = row as u32;
663 let Some(start) = *start else {
664 if min == 0 {
666 rows.push(row, &[row], &[])?;
667 }
668 continue;
669 };
670 states.clear();
671 targets.clear();
672 entries.clear();
673 entries.push(Entry {
674 node: start,
675 parent: ROOT,
676 edge: 0,
677 len: 0,
678 });
679 *states.get(start, 0) = (1, 0);
680 let mut head = 0;
681 while head < entries.len() {
682 let at = head as u32;
683 let entry = entries[head];
684 head += 1;
685 let c = entry.len.min(min);
686 if c == min {
687 let keep = if pruned {
688 true
689 } else {
690 let t = targets.entry(entry.node).or_insert((0, entry.len));
691 let keep = match search.selector {
692 Selector::All => true,
693 Selector::Shortest(k) => t.0 < k,
694 Selector::AllShortest => t.1 == entry.len,
695 };
696 if keep {
697 t.0 += 1;
698 }
699 keep
700 };
701 let closed = search.mode == Mode::Acyclic && entry.len > 0 && entry.node == start;
703 if keep && !closed {
704 nodes.clear();
705 edges.clear();
706 let mut i = at;
707 while i != ROOT {
708 let e = entries[i as usize];
709 nodes.push(rows.dense(e.node));
710 if e.parent != ROOT {
711 edges.push(e.edge);
712 }
713 i = e.parent;
714 }
715 nodes.reverse();
716 edges.reverse();
717 rows.push(row, &nodes, &edges)?;
718 total += 1;
719 }
720 }
721 if max.is_some_and(|max| entry.len >= max) {
722 continue;
723 }
724 steps(topology, search.direction, entry.node, &mut next);
725 for &(w, edge) in &next {
726 if restricted && !allowed(&entries, at, w, edge, search.mode, start) {
727 continue;
728 }
729 let len = entry.len + 1;
730 if pruned {
731 let state = states.get(w, len.min(min));
732 let accept = match search.selector {
733 Selector::All => true,
734 Selector::Shortest(k) => state.0 < k,
735 Selector::AllShortest => state.0 == 0 || state.1 == len,
736 };
737 if !accept {
738 continue;
739 }
740 if state.0 == 0 {
741 state.1 = len;
742 }
743 state.0 += 1;
744 }
745 entries.push(Entry {
746 node: w,
747 parent: at,
748 edge,
749 len,
750 });
751 if entries.len() + total > MAX_PATHS {
752 return Err(DataFusionError::Execution(format!(
753 "in GRAPH_TABLE: the path search reached {MAX_PATHS} paths; give the quantified edge a lower upper bound, or keep fewer paths with ANY SHORTEST"
754 )));
755 }
756 }
757 }
758 }
759 rows.flush()?;
760 Ok(rows.batches)
761}
762
763#[cfg(test)]
764mod tests {
765 use datafusion::arrow::array::{AsArray, Int64Array};
766 use datafusion::arrow::datatypes::Int64Type;
767
768 use super::*;
769
770 fn index(edges: &[(i64, i64)]) -> (Topology, IdMap) {
772 let src: ArrayRef = Arc::new(Int64Array::from(
773 edges.iter().map(|e| e.0).collect::<Vec<_>>(),
774 ));
775 let dst: ArrayRef = Arc::new(Int64Array::from(
776 edges.iter().map(|e| e.1).collect::<Vec<_>>(),
777 ));
778 let mut interner = ursa_core::EdgeInterner::with_capacity(edges.len());
779 interner.push(src.as_ref(), dst.as_ref()).unwrap();
780 let (ids, s, d) = interner.finish();
781 (Topology::build(ids.len(), s, d), ids)
782 }
783
784 fn search(
785 direction: Direction,
786 min: u32,
787 max: Option<u32>,
788 selector: Selector,
789 mode: Mode,
790 ) -> Search {
791 Search {
792 direction,
793 quantifier: Quantifier { min, max },
794 selector,
795 mode,
796 }
797 }
798
799 fn paths(edges: &[(i64, i64)], starts: &[i64], s: Search) -> Vec<(Vec<i64>, Vec<i64>)> {
801 let (topology, ids) = index(edges);
802 let columns = PathColumns {
803 vertices: true,
804 edges: true,
805 elements: true,
806 };
807 let schema = Arc::new(super::schema(&DataType::Int64, columns));
808 let starts: ArrayRef = Arc::new(Int64Array::from(starts.to_vec()));
809 let batches = run(
810 &topology,
811 &ids,
812 &|e| u64::from(e),
813 &s,
814 &starts,
815 columns,
816 schema,
817 )
818 .unwrap();
819 let mut out = Vec::new();
820 for b in batches {
821 let v = b.column(3).as_list::<i32>();
822 let e = b.column(4).as_list::<i32>();
823 let el = b.column(5).as_list::<i32>();
824 for r in 0..b.num_rows() {
825 let vs: Vec<i64> = v.value(r).as_primitive::<Int64Type>().values().to_vec();
826 let es: Vec<i64> = e.value(r).as_primitive::<Int64Type>().values().to_vec();
827 let els: Vec<i64> = el.value(r).as_primitive::<Int64Type>().values().to_vec();
828 assert_eq!(els.len(), vs.len() + es.len());
829 assert_eq!(
830 b.column(2).as_primitive::<Int64Type>().value(r),
831 es.len() as i64
832 );
833 out.push((vs, es));
834 }
835 }
836 out
837 }
838
839 const KNOW: &[(i64, i64)] = &[
841 (0, 1),
842 (0, 2),
843 (0, 3),
844 (3, 0),
845 (1, 2),
846 (1, 3),
847 (2, 3),
848 (4, 3),
849 ];
850
851 #[test]
852 fn any_shortest_takes_the_first_edges_and_can_return_to_the_start() {
853 let found = paths(
854 KNOW,
855 &[4],
856 search(
857 Direction::Right,
858 1,
859 Some(3),
860 Selector::Shortest(1),
861 Mode::Walk,
862 ),
863 );
864 assert_eq!(
865 found,
866 vec![
867 (vec![4, 3], vec![7]),
868 (vec![4, 3, 0], vec![7, 3]),
869 (vec![4, 3, 0, 1], vec![7, 3, 0]),
870 (vec![4, 3, 0, 2], vec![7, 3, 1]),
871 ]
872 );
873 let found = paths(
875 KNOW,
876 &[0],
877 search(
878 Direction::Right,
879 1,
880 Some(3),
881 Selector::Shortest(1),
882 Mode::Walk,
883 ),
884 );
885 assert!(found.contains(&(vec![0, 3, 0], vec![2, 3])));
886 let found = paths(
888 KNOW,
889 &[0],
890 search(
891 Direction::Right,
892 1,
893 Some(3),
894 Selector::Shortest(1),
895 Mode::Acyclic,
896 ),
897 );
898 assert!(found.iter().all(|(v, _)| v.last() != Some(&0)));
899 }
900
901 #[test]
902 fn walks_trails_and_counts() {
903 let found = paths(
905 KNOW,
906 &[3],
907 search(Direction::Right, 1, Some(2), Selector::All, Mode::Walk),
908 );
909 assert_eq!(found.len(), 4);
910 let acyclic = paths(
912 KNOW,
913 &[3],
914 search(Direction::Right, 1, None, Selector::All, Mode::Acyclic),
915 );
916 assert!(acyclic.iter().all(|(v, _)| {
917 let mut s = v.clone();
918 s.sort();
919 s.dedup();
920 s.len() == v.len()
921 }));
922 assert_eq!(acyclic.len(), 4);
924 let found = paths(
926 KNOW,
927 &[0],
928 search(Direction::Right, 1, None, Selector::Shortest(2), Mode::Walk),
929 );
930 let to3: Vec<_> = found.iter().filter(|(v, _)| v.last() == Some(&3)).collect();
931 assert_eq!(
932 to3,
933 vec![&(vec![0, 3], vec![2]), &(vec![0, 1, 3], vec![0, 5])]
934 );
935 let found = paths(
937 KNOW,
938 &[1],
939 search(Direction::Right, 1, None, Selector::AllShortest, Mode::Walk),
940 );
941 let to0: Vec<_> = found.iter().filter(|(v, _)| v.last() == Some(&0)).collect();
942 assert_eq!(to0, vec![&(vec![1, 3, 0], vec![5, 3])]);
943 let found = paths(
944 KNOW,
945 &[4],
946 search(
947 Direction::Right,
948 3,
949 Some(3),
950 Selector::AllShortest,
951 Mode::Walk,
952 ),
953 );
954 let to2: Vec<_> = found.iter().filter(|(v, _)| v.last() == Some(&2)).collect();
955 assert_eq!(to2, vec![&(vec![4, 3, 0, 2], vec![7, 3, 1])]);
956 }
957
958 #[test]
959 fn zero_length_undirected_and_trails() {
960 let found = paths(
962 KNOW,
963 &[9, 0],
964 search(Direction::Right, 0, Some(0), Selector::All, Mode::Walk),
965 );
966 assert_eq!(found, vec![(vec![9], vec![]), (vec![0], vec![])]);
967 let found = paths(
969 &[(1, 1), (1, 2)],
970 &[1],
971 search(Direction::Any, 1, Some(1), Selector::All, Mode::Walk),
972 );
973 assert_eq!(found, vec![(vec![1, 1], vec![0]), (vec![1, 2], vec![1])]);
974 let found = paths(
977 &[(1, 2), (2, 1)],
978 &[1],
979 search(Direction::Any, 1, Some(2), Selector::All, Mode::Trail),
980 );
981 assert_eq!(
982 found,
983 vec![
984 (vec![1, 2], vec![0]),
985 (vec![1, 2], vec![1]),
986 (vec![1, 2, 1], vec![0, 1]),
987 (vec![1, 2, 1], vec![1, 0]),
988 ]
989 );
990 let found = paths(
992 KNOW,
993 &[3],
994 search(Direction::Left, 1, Some(1), Selector::All, Mode::Walk),
995 );
996 assert_eq!(
997 found,
998 vec![
999 (vec![3, 0], vec![2]),
1000 (vec![3, 1], vec![5]),
1001 (vec![3, 2], vec![6]),
1002 (vec![3, 4], vec![7])
1003 ]
1004 );
1005 let found = paths(
1007 KNOW,
1008 &[0],
1009 search(Direction::Right, 2, Some(2), Selector::All, Mode::Simple),
1010 );
1011 assert!(found.contains(&(vec![0, 3, 0], vec![2, 3])));
1012 }
1013}