diff --git a/cozo-core/src/runtime/hnsw.rs b/cozo-core/src/runtime/hnsw.rs index ccb29578..68be69fb 100644 --- a/cozo-core/src/runtime/hnsw.rs +++ b/cozo-core/src/runtime/hnsw.rs @@ -14,7 +14,7 @@ use crate::data::value::Vector; use crate::parse::sys::HnswDistance; use crate::runtime::relation::RelationHandle; use crate::runtime::transact::SessionTx; -use crate::{decode_tuple_from_kv, DataValue, SourceSpan}; +use crate::{DataValue, SourceSpan}; use itertools::Itertools; use miette::{bail, miette, Result}; use ordered_float::OrderedFloat; @@ -22,7 +22,7 @@ use priority_queue::PriorityQueue; use rand::Rng; use smartstring::{LazyCompact, SmartString}; use std::cmp::{max, Reverse}; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; #[derive(Debug, Clone, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)] pub(crate) struct HnswIndexManifest { @@ -50,49 +50,19 @@ impl HnswIndexManifest { // the level is the largest integer smaller than r -(r.floor() as i64) } - fn get_vector(&self, tuple: &Tuple, idx: usize, sub_idx: i32) -> Result { - let field = tuple.get(idx).unwrap(); - if sub_idx >= 0 { - match field { - DataValue::List(l) => match l.get(sub_idx as usize) { - Some(DataValue::Vec(v)) => Ok(v.clone()), - _ => bail!( - "Cannot extract vector from {} for sub index {}", - field, - sub_idx - ), - }, - _ => bail!("Cannot interpret {} as list", field), - } - } else { - match field { - DataValue::Vec(v) => Ok(v.clone()), - _ => bail!("Cannot interpret {} as vector", field), - } - } - } - fn get_distance(&self, q: &Vector, tuple: &Tuple, idx: usize, sub_idx: i32) -> Result { - let field = tuple.get(idx).unwrap(); - let target = if sub_idx >= 0 { - match field { - DataValue::List(l) => match l.get(sub_idx as usize) { - Some(DataValue::Vec(v)) => v, - _ => bail!( - "Cannot extract vector from {} for sub index {}", - field, - sub_idx - ), - }, - _ => bail!("Cannot interpret {} as list", field), - } - } else { - match field { - DataValue::Vec(v) => v, - _ => bail!("Cannot interpret {} as vector", field), - } - }; - Ok(match self.distance { - HnswDistance::L2 => match (q, target) { +} + +type CompoundKey = (Tuple, usize, i32); + +struct VectorCache { + cache: BTreeMap, + distance: HnswDistance, +} + +impl VectorCache { + fn dist(&self, v1: &Vector, v2: &Vector) -> f64 { + match self.distance { + HnswDistance::L2 => match (v1, v2) { (Vector::F32(a), Vector::F32(b)) => { let diff = a - b; diff.dot(&diff) as f64 @@ -101,13 +71,9 @@ impl HnswIndexManifest { let diff = a - b; diff.dot(&diff) } - _ => bail!( - "Cannot compute L2 distance between {:?} and {:?}", - q, - target - ), + _ => panic!("Cannot compute L2 distance between {:?} and {:?}", v1, v2), }, - HnswDistance::Cosine => match (q, target) { + HnswDistance::Cosine => match (v1, v2) { (Vector::F32(a), Vector::F32(b)) => { let a_norm = a.dot(a) as f64; let b_norm = b.dot(b) as f64; @@ -120,13 +86,12 @@ impl HnswIndexManifest { let dot = a.dot(b); 1.0 - dot / (a_norm * b_norm).sqrt() } - _ => bail!( + _ => panic!( "Cannot compute cosine distance between {:?} and {:?}", - q, - target + v1, v2 ), }, - HnswDistance::InnerProduct => match (q, target) { + HnswDistance::InnerProduct => match (v1, v2) { (Vector::F32(a), Vector::F32(b)) => { let dot = a.dot(b); 1. - dot as f64 @@ -135,13 +100,51 @@ impl HnswIndexManifest { let dot = a.dot(b); 1. - dot as f64 } - _ => bail!( - "Cannot compute inner product between {:?} and {:?}", - q, - target - ), + _ => panic!("Cannot compute inner product between {:?} and {:?}", v1, v2), }, - }) + } + } + fn v_dist(&self, v: &Vector, key: &CompoundKey) -> f64 { + let v2 = self.cache.get(key).unwrap(); + self.dist(v, v2) + } + fn k_dist(&self, k1: &CompoundKey, k2: &CompoundKey) -> f64 { + let v1 = self.cache.get(k1).unwrap(); + let v2 = self.cache.get(k2).unwrap(); + self.dist(v1, v2) + } + fn get_key(&self, key: &CompoundKey) -> &Vector { + self.cache.get(key).unwrap() + } + fn ensure_key( + &mut self, + key: &CompoundKey, + handle: &RelationHandle, + tx: &SessionTx<'_>, + ) -> Result<()> { + if !self.cache.contains_key(key) { + match handle.get(tx, &key.0)? { + Some(tuple) => { + let mut field = &tuple[key.1]; + if key.2 >= 0 { + match field { + DataValue::List(l) => { + field = &l[key.2 as usize]; + } + _ => bail!("Cannot interpret {} as list", field), + } + } + match field { + DataValue::Vec(v) => { + self.cache.insert(key.clone(), v.clone()); + } + _ => bail!("Cannot interpret {} as vector", field), + } + } + None => bail!("Cannot find tuple {:?}", key.0), + } + } + Ok(()) } } @@ -156,7 +159,12 @@ impl<'a> SessionTx<'a> { orig_table: &RelationHandle, idx_table: &RelationHandle, ) -> Result<()> { + let mut vec_cache = VectorCache { + cache: BTreeMap::new(), + distance: manifest.distance, + }; let tuple_key = &tuple[..orig_table.metadata.keys.len()]; + vec_cache.cache.insert((tuple_key.to_vec(), idx, subidx), q.clone()); let hash = q.get_hash(); let mut canary_tuple = vec![DataValue::from(0)]; for _ in 0..2 { @@ -185,13 +193,15 @@ impl<'a> SessionTx<'a> { let ep = ep?; // bottom level since we are going up let bottom_level = ep[0].get_int().unwrap(); - let ep_key = ep[1..orig_table.metadata.keys.len() + 1].to_vec(); + let ep_t_key = ep[1..orig_table.metadata.keys.len() + 1].to_vec(); let ep_idx = ep[orig_table.metadata.keys.len() + 1].get_int().unwrap() as usize; let ep_subidx = ep[orig_table.metadata.keys.len() + 2].get_int().unwrap() as i32; - let ep_distance = - self.hnsw_compare_vector(q, &ep_key, idx, subidx, manifest, orig_table)?; + let ep_key = (ep_t_key, ep_idx, ep_subidx); + vec_cache.ensure_key(&ep_key, orig_table, self)?; + let ep_distance = vec_cache.v_dist(q, &ep_key); + // max queue let mut found_nn = PriorityQueue::new(); - found_nn.push((ep_key, ep_idx, ep_subidx), OrderedFloat(ep_distance)); + found_nn.push(ep_key, OrderedFloat(ep_distance)); let target_level = manifest.get_random_level(); if target_level < bottom_level { // this becomes the entry point @@ -211,10 +221,10 @@ impl<'a> SessionTx<'a> { q, 1, current_level, - manifest, orig_table, idx_table, &mut found_nn, + &mut vec_cache, )?; } let mut self_tuple_key = Vec::with_capacity(orig_table.metadata.keys.len() * 2 + 5); @@ -239,10 +249,10 @@ impl<'a> SessionTx<'a> { q, manifest.ef_construction, current_level, - manifest, orig_table, idx_table, &mut found_nn, + &mut vec_cache, )?; // add bidirectional links to the nearest neighbors let neighbours = self.hnsw_select_neighbours_heuristic( @@ -253,6 +263,7 @@ impl<'a> SessionTx<'a> { manifest, idx_table, orig_table, + &mut vec_cache, )?; // add self-link self_tuple_key[0] = DataValue::from(current_level); @@ -336,6 +347,7 @@ impl<'a> SessionTx<'a> { manifest, idx_table, orig_table, + &mut vec_cache, )?; } // update degree @@ -373,24 +385,16 @@ impl<'a> SessionTx<'a> { manifest: &HnswIndexManifest, idx_table: &RelationHandle, orig_table: &RelationHandle, + vec_cache: &mut VectorCache, ) -> Result { - let orig_key = orig_table.encode_key_for_store(target, Default::default())?; - let orig_val = match self.store_tx.get(&orig_key, false)? { - Some(bytes) => bytes, - None => { - bail!("Indexed vector not found, this signifies a bug in the index implementation") - } - }; - let orig_tuple = decode_tuple_from_kv(&orig_key, &orig_val); - let vec = manifest.get_vector(&orig_tuple, idx, sub_idx)?; + let c_key = (target.to_vec(), idx, sub_idx); + vec_cache.ensure_key(&c_key, orig_table, self)?; + let vec = vec_cache.get_key(&c_key).clone(); let mut candidates = PriorityQueue::new(); - for neighbour in + for (neighbour_key, neighbour_dist) in self.hnsw_get_neighbours(target.to_vec(), idx, sub_idx, level, idx_table, false)? { - candidates.push( - (neighbour.0, neighbour.1, neighbour.2), - OrderedFloat(neighbour.3), - ); + candidates.push(neighbour_key, OrderedFloat(neighbour_dist)); } let new_candidates = self.hnsw_select_neighbours_heuristic( &vec, @@ -400,6 +404,7 @@ impl<'a> SessionTx<'a> { manifest, idx_table, orig_table, + vec_cache, )?; let mut old_candidate_set = BTreeSet::new(); for (old, _) in &candidates { @@ -467,35 +472,30 @@ impl<'a> SessionTx<'a> { Ok(new_degree) } - fn hnsw_compare_vector( - &self, - q: &Vector, - target_key: &[DataValue], - target_idx: usize, - target_subidx: i32, - manifest: &HnswIndexManifest, - orig_table: &RelationHandle, - ) -> Result { - let target_key_bytes = orig_table.encode_key_for_store(target_key, Default::default())?; - let bytes = match self.store_tx.get(&target_key_bytes, false)? { - Some(bytes) => bytes, - None => bail!("Indexed data not found, this signifies a bug in the index."), - }; - let target_tuple = decode_tuple_from_kv(&target_key_bytes, &bytes); - manifest.get_distance(q, &target_tuple, target_idx, target_subidx) - } fn hnsw_select_neighbours_heuristic( &self, q: &Vector, - found: &PriorityQueue<(Tuple, usize, i32), OrderedFloat>, + found: &PriorityQueue>, m: usize, level: i64, manifest: &HnswIndexManifest, idx_table: &RelationHandle, orig_table: &RelationHandle, - ) -> Result>>> { + vec_cache: &mut VectorCache, + ) -> Result>>> { let mut candidates = PriorityQueue::new(); - let mut ret: PriorityQueue<_, Reverse>> = PriorityQueue::new(); + // Simple non-heuristic selection + // let mut temp = found.clone(); + // while temp.len() > m { + // temp.pop(); + // } + // for (item, dist) in temp.iter() { + // candidates.push(item.clone(), Reverse(*dist)); + // } + // return Ok(candidates); + // End of simple non-heuristic selection + + let mut ret: PriorityQueue>> = PriorityQueue::new(); let mut discarded: PriorityQueue<_, Reverse>> = PriorityQueue::new(); for (item, dist) in found.iter() { // Add to candidates @@ -504,7 +504,7 @@ impl<'a> SessionTx<'a> { if manifest.extend_candidates { for (item, _) in found.iter() { // Extend by neighbours - for neighbour in self.hnsw_get_neighbours( + for (neighbour_key, _) in self.hnsw_get_neighbours( item.0.clone(), item.1, item.2, @@ -512,35 +512,32 @@ impl<'a> SessionTx<'a> { idx_table, false, )? { - let dist = self.hnsw_compare_vector( - q, - &neighbour.0, - neighbour.1, - neighbour.2, - manifest, - orig_table, - )?; + vec_cache.ensure_key(&neighbour_key, orig_table, self)?; + let dist = vec_cache.v_dist(q, &neighbour_key); candidates.push( - (neighbour.0, neighbour.1, neighbour.2), + (neighbour_key.0, neighbour_key.1, neighbour_key.2), Reverse(OrderedFloat(dist)), ); } } } while !candidates.is_empty() && ret.len() < m { - let (nearest_triple, Reverse(OrderedFloat(nearest_dist))) = candidates.pop().unwrap(); - match ret.peek() { - Some((_, Reverse(OrderedFloat(dist)))) => { - if nearest_dist < *dist { - ret.push(nearest_triple, Reverse(OrderedFloat(nearest_dist))); - } else if manifest.keep_pruned_connections { - discarded.push(nearest_triple, Reverse(OrderedFloat(nearest_dist))); - } - } - None => { - ret.push(nearest_triple, Reverse(OrderedFloat(nearest_dist))); + let (cand_key, Reverse(OrderedFloat(cand_dist_to_q))) = candidates.pop().unwrap(); + let mut should_add = true; + for (existing, _) in ret.iter() { + vec_cache.ensure_key(&cand_key, orig_table, self)?; + vec_cache.ensure_key(&existing, orig_table, self)?; + let dist_to_existing = vec_cache.k_dist(existing, &cand_key); + if dist_to_existing < cand_dist_to_q { + should_add = false; + break; } } + if should_add { + ret.push(cand_key, Reverse(OrderedFloat(cand_dist_to_q))); + } else if manifest.keep_pruned_connections { + discarded.push(cand_key, Reverse(OrderedFloat(cand_dist_to_q))); + } } if manifest.keep_pruned_connections { while !discarded.is_empty() && ret.len() < m { @@ -556,13 +553,14 @@ impl<'a> SessionTx<'a> { q: &Vector, ef: usize, cur_level: i64, - manifest: &HnswIndexManifest, orig_table: &RelationHandle, idx_table: &RelationHandle, - found_nn: &mut PriorityQueue<(Tuple, usize, i32), OrderedFloat>, + found_nn: &mut PriorityQueue>, + vec_cache: &mut VectorCache, ) -> Result<()> { - let mut visited: BTreeSet<(Tuple, usize, i32)> = BTreeSet::new(); - let mut candidates: PriorityQueue<(Tuple, usize, i32), Reverse>> = + let mut visited: BTreeSet = BTreeSet::new(); + // min queue + let mut candidates: PriorityQueue>> = PriorityQueue::new(); for item in found_nn.iter() { @@ -577,7 +575,7 @@ impl<'a> SessionTx<'a> { break; } // loop over each of the candidate's neighbors - for neighbour_tetra in self.hnsw_get_neighbours( + for (neighbour_key, _) in self.hnsw_get_neighbours( candidate.0, candidate.1, candidate.2, @@ -585,30 +583,20 @@ impl<'a> SessionTx<'a> { idx_table, false, )? { - let neighbour_triple = (neighbour_tetra.0, neighbour_tetra.1, neighbour_tetra.2); - if visited.contains(&neighbour_triple) { + if visited.contains(&neighbour_key) { continue; } - let neighbour_dist = self.hnsw_compare_vector( - q, - &neighbour_triple.0, - neighbour_triple.1, - neighbour_triple.2, - manifest, - orig_table, - )?; + vec_cache.ensure_key(&neighbour_key, orig_table, self)?; + let neighbour_dist = vec_cache.v_dist(q, &neighbour_key); let (_, OrderedFloat(cand_furtherest_dist)) = found_nn.peek().unwrap(); if found_nn.len() < ef || neighbour_dist < *cand_furtherest_dist { - candidates.push( - neighbour_triple.clone(), - Reverse(OrderedFloat(neighbour_dist)), - ); - found_nn.push(neighbour_triple.clone(), OrderedFloat(neighbour_dist)); + candidates.push(neighbour_key.clone(), Reverse(OrderedFloat(neighbour_dist))); + found_nn.push(neighbour_key.clone(), OrderedFloat(neighbour_dist)); if found_nn.len() > ef { found_nn.pop(); } } - visited.insert(neighbour_triple); + visited.insert(neighbour_key); } } @@ -622,7 +610,7 @@ impl<'a> SessionTx<'a> { level: i64, idx_handle: &RelationHandle, include_deleted: bool, - ) -> Result + 'b> { + ) -> Result + 'b> { let mut start_tuple = Vec::with_capacity(cand_key.len() + 3); start_tuple.push(DataValue::from(level)); start_tuple.extend_from_slice(&cand_key); @@ -642,9 +630,7 @@ impl<'a> SessionTx<'a> { } else { if include_deleted { return Some(( - key_slice, - key_idx, - key_subidx, + (key_slice, key_idx, key_subidx), tuple[2 * key_len + 5].get_float().unwrap(), )); } @@ -653,9 +639,7 @@ impl<'a> SessionTx<'a> { None } else { Some(( - key_slice, - key_idx, - key_subidx, + (key_slice, key_idx, key_subidx), tuple[2 * key_len + 5].get_float().unwrap(), )) } @@ -801,22 +785,22 @@ impl<'a> SessionTx<'a> { .hnsw_get_neighbours(tuple_key.to_vec(), idx, subidx, layer, idx_table, true)? .collect_vec(); encountered_singletons |= neigbours.is_empty(); - for (neighbour_key, neighbour_idx, neighbour_subidx, _) in neigbours { + for (neighbour_key, _) in neigbours { // REMARK: this still has some probability of disconnecting the graph. // Should we accept that as a consequence of the probabilistic nature of the algorithm? let mut out_key = vec![DataValue::from(layer)]; out_key.extend_from_slice(tuple_key); out_key.push(DataValue::from(idx as i64)); out_key.push(DataValue::from(subidx as i64)); - out_key.extend_from_slice(&neighbour_key); - out_key.push(DataValue::from(neighbour_idx as i64)); - out_key.push(DataValue::from(neighbour_subidx as i64)); + out_key.extend_from_slice(&neighbour_key.0); + out_key.push(DataValue::from(neighbour_key.1 as i64)); + out_key.push(DataValue::from(neighbour_key.2 as i64)); let out_key_bytes = idx_table.encode_key_for_store(&out_key, Default::default())?; self.store_tx.del(&out_key_bytes)?; let mut in_key = vec![DataValue::from(layer)]; - in_key.extend_from_slice(&neighbour_key); - in_key.push(DataValue::from(neighbour_idx as i64)); - in_key.push(DataValue::from(neighbour_subidx as i64)); + in_key.extend_from_slice(&neighbour_key.0); + in_key.push(DataValue::from(neighbour_key.1 as i64)); + in_key.push(DataValue::from(neighbour_key.2 as i64)); in_key.extend_from_slice(tuple_key); in_key.push(DataValue::from(idx as i64)); in_key.push(DataValue::from(subidx as i64)); @@ -824,9 +808,9 @@ impl<'a> SessionTx<'a> { self.store_tx.del(&in_key_bytes)?; let mut neighbour_self_key = vec![DataValue::from(layer)]; for _ in 0..2 { - neighbour_self_key.extend_from_slice(&neighbour_key); - neighbour_self_key.push(DataValue::from(neighbour_idx as i64)); - neighbour_self_key.push(DataValue::from(neighbour_subidx as i64)); + neighbour_self_key.extend_from_slice(&neighbour_key.0); + neighbour_self_key.push(DataValue::from(neighbour_key.1 as i64)); + neighbour_self_key.push(DataValue::from(neighbour_key.2 as i64)); } let neighbour_val_bytes = self .store_tx @@ -903,6 +887,11 @@ impl<'a> SessionTx<'a> { (Vector::F64(v), VecElementType::F32) => Vector::F32(v.mapv(|x| x as f32)), }; + let mut vec_cache = VectorCache { + cache: Default::default(), + distance: config.manifest.distance, + }; + let ep_res = config .idx_handle .scan_bounded_prefix( @@ -915,42 +904,37 @@ impl<'a> SessionTx<'a> { if let Some(ep) = ep_res { let ep = ep?; let bottom_level = ep[0].get_int().unwrap(); - let ep_key = ep[1..config.base_handle.metadata.keys.len() + 1].to_vec(); + let ep_t_key = ep[1..config.base_handle.metadata.keys.len() + 1].to_vec(); let ep_idx = ep[config.base_handle.metadata.keys.len() + 1] .get_int() .unwrap() as usize; let ep_subidx = ep[config.base_handle.metadata.keys.len() + 2] .get_int() .unwrap() as i32; - let ep_distance = self.hnsw_compare_vector( - &q, - &ep_key, - ep_idx, - ep_subidx, - &config.manifest, - &config.base_handle, - )?; + let ep_key = (ep_t_key, ep_idx, ep_subidx); + vec_cache.ensure_key(&ep_key, &config.base_handle, self)?; + let ep_distance = vec_cache.v_dist(&q, &ep_key); let mut found_nn = PriorityQueue::new(); - found_nn.push((ep_key, ep_idx, ep_subidx), OrderedFloat(ep_distance)); + found_nn.push(ep_key, OrderedFloat(ep_distance)); for current_level in bottom_level..0 { self.hnsw_search_level( &q, 1, current_level, - &config.manifest, &config.base_handle, &config.idx_handle, &mut found_nn, + &mut vec_cache, )?; } self.hnsw_search_level( &q, config.ef, 0, - &config.manifest, &config.base_handle, &config.idx_handle, &mut found_nn, + &mut vec_cache, )?; if found_nn.is_empty() { return Ok(vec![]); @@ -964,9 +948,7 @@ impl<'a> SessionTx<'a> { let mut ret = vec![]; - while let Some(((cand_tuple, cand_idx, cand_subidx), OrderedFloat(distance))) = - found_nn.pop() - { + while let Some((cand_key, OrderedFloat(distance))) = found_nn.pop() { if let Some(r) = config.radius { if distance > r { continue; @@ -975,40 +957,40 @@ impl<'a> SessionTx<'a> { let mut cand_tuple = config .base_handle - .get(self, &cand_tuple)? + .get(self, &cand_key.0)? .ok_or_else(|| miette!("corrupted index"))?; if config.bind_field.is_some() { - let field = if cand_idx as usize >= config.base_handle.metadata.keys.len() { - config.base_handle.metadata.keys[cand_idx as usize] + let field = if cand_key.1 as usize >= config.base_handle.metadata.keys.len() { + config.base_handle.metadata.keys[cand_key.1 as usize] .name .clone() } else { config.base_handle.metadata.non_keys - [cand_idx as usize - config.base_handle.metadata.keys.len()] + [cand_key.1 as usize - config.base_handle.metadata.keys.len()] .name .clone() }; cand_tuple.push(DataValue::Str(field)); } if config.bind_field_idx.is_some() { - cand_tuple.push(if cand_subidx < 0 { + cand_tuple.push(if cand_key.2 < 0 { DataValue::Null } else { - DataValue::from(cand_subidx as i64) + DataValue::from(cand_key.2 as i64) }); } if config.bind_distance.is_some() { cand_tuple.push(DataValue::from(distance)); } if config.bind_vector.is_some() { - let vec = if cand_subidx < 0 { - match &cand_tuple[cand_idx] { - DataValue::List(v) => v[cand_subidx as usize].clone(), + let vec = if cand_key.2 < 0 { + match &cand_tuple[cand_key.1] { + DataValue::List(v) => v[cand_key.2 as usize].clone(), _ => bail!("corrupted index"), } } else { - cand_tuple[cand_idx].clone() + cand_tuple[cand_key.1].clone() }; cand_tuple.push(vec); } diff --git a/cozo-core/src/runtime/tests.rs b/cozo-core/src/runtime/tests.rs index 64e09914..50a351a1 100644 --- a/cozo-core/src/runtime/tests.rs +++ b/cozo-core/src/runtime/tests.rs @@ -863,7 +863,7 @@ fn test_insertions() { db.run_script(r"?[k, v] := *a{k, v}", Default::default()) .unwrap(); db.run_script( - r"::hnsw create a:i {fields: [v], dim: 100, ef: 200, m: 50}", + r"::hnsw create a:i {fields: [v], dim: 100, ef: 16, m: 32}", Default::default(), ) .unwrap(); @@ -871,10 +871,10 @@ fn test_insertions() { .unwrap(); db.run_script(r"?[k] <- [[1]] :put a {k}", Default::default()) .unwrap(); - db.run_script(r"?[k] := k in int_range(1000) :put a {k}", Default::default()).unwrap(); + db.run_script(r"?[k] := k in int_range(10000) :put a {k}", Default::default()).unwrap(); let res = db .run_script( - r"?[dist, k] := ~a:i{k | query: v, bind_distance: dist, k:10, ef: 200}, *a{k: 333, v}", + r"?[dist, k] := ~a:i{k | query: v, bind_distance: dist, k:10, ef: 5}, *a{k: 8888, v}", Default::default(), ) .unwrap();