diff --git a/experiments/voxel/args-voxel.json b/experiments/voxel/args-voxel.json index 61c233567..78c1ebfde 100644 --- a/experiments/voxel/args-voxel.json +++ b/experiments/voxel/args-voxel.json @@ -1,8 +1,18 @@ { "cmd": "rofi-voxel {init} {goal} --init-format {init_fmt} --goal-format {goal_fmt} --alg {alg} --repr {repr} -v", - "args": [ - { "alg": "bfs", "repr": ["matrix", "map"] }, - { "alg": "astar-zero", "repr": "map" }, - { "alg": "astar-naive", "repr": ["matrix", "map", "sortvec"] } - ] + "args": { + "alg": [ + "bfs", + "astar-zero", + "astar-naive", + "astar-naive-opt", + "astar-assg-posvoxel", + "astar-assg-posvoxel-opt", + "astar-assg-pos", + "astar-assg-pos-opt", + "astar-assg-voxel", + "astar-assg-voxel-opt" + ], + "repr": ["matrix", "map", "sortvec"] + } } diff --git a/experiments/voxel/full_traversal.Dockerfile b/experiments/voxel/full_traversal.Dockerfile index c808f2a1a..0cfd5ad25 100644 --- a/experiments/voxel/full_traversal.Dockerfile +++ b/experiments/voxel/full_traversal.Dockerfile @@ -2,6 +2,8 @@ FROM ghcr.io/paradise-fi/rofi.debian:latest SHELL ["/bin/bash", "-c"] +RUN apt-get install -y --no-install-recommends time jq moreutils + RUN cd /; \ git clone https://github.com/paradise-fi/RoFI.git --depth 1 --branch master; \ cd RoFI; \ @@ -15,29 +17,27 @@ RUN echo $'#!/usr/bin/env bash\n\ $@\n' > /bin/rofiInvoke; \ chmod +x /bin/rofiInvoke -RUN export RUSTFLAGS="-C debug-assertions -C overflow-checks" - WORKDIR /RoFI ENTRYPOINT ["rofiInvoke"] -# Name: RoFI Voxel - full traversal bfs -# Memory limit: 2048 MB +# Name: Voxel - full traversal bfs +# Memory limit: 32768 MB # Tasks: # [ # "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m1_snake.json --log-counters /artefact/results.json -vv", # "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m2_snake.json --log-counters /artefact/results.json -vv", # "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m3_snake.json --log-counters /artefact/results.json -vv", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m4_snake.json --log-counters /artefact/results.json -vv --max 8", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m5_snake.json --log-counters /artefact/results.json -vv --max 7", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m6_snake.json --log-counters /artefact/results.json -vv --max 6", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m7_snake.json --log-counters /artefact/results.json -vv --max 6", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m8_snake.json --log-counters /artefact/results.json -vv --max 5", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m9_snake.json --log-counters /artefact/results.json -vv --max 5", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m10_snake.json --log-counters /artefact/results.json -vv --max 5", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m11_snake.json --log-counters /artefact/results.json -vv --max 4", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m12_snake.json --log-counters /artefact/results.json -vv --max 4", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m13_snake.json --log-counters /artefact/results.json -vv --max 4", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m14_snake.json --log-counters /artefact/results.json -vv --max 4", -# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m15_snake.json --log-counters /artefact/results.json -vv --max 4" +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m4_snake.json --log-counters /artefact/results.json -vv --max 10", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m5_snake.json --log-counters /artefact/results.json -vv --max 8", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m6_snake.json --log-counters /artefact/results.json -vv --max 7", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m7_snake.json --log-counters /artefact/results.json -vv --max 7", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m8_snake.json --log-counters /artefact/results.json -vv --max 6", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m9_snake.json --log-counters /artefact/results.json -vv --max 6", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m10_snake.json --log-counters /artefact/results.json -vv --max 6", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m11_snake.json --log-counters /artefact/results.json -vv --max 6", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m12_snake.json --log-counters /artefact/results.json -vv --max 5", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m13_snake.json --log-counters /artefact/results.json -vv --max 5", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m14_snake.json --log-counters /artefact/results.json -vv --max 5", +# "rofi-voxel_full_traversal_bfs data/configurations/voxel/snake/m15_snake.json --log-counters /artefact/results.json -vv --max 5" # ] diff --git a/experiments/voxel/utils.py b/experiments/voxel/utils.py index 70cc3baed..a2c11c8c1 100644 --- a/experiments/voxel/utils.py +++ b/experiments/voxel/utils.py @@ -52,10 +52,13 @@ def parse_generic_json(cls: type[T], json_value: Any) -> T: assert isinstance(json_value, cls), f"Type {cls} is dict with no type args" return json_value key_cls, value_cls = cls_args - return { - parse_json(key_cls, key): parse_json(value_cls, value) - for key, value in json_value.items() - } # type: ignore + try: + return { + parse_json(key_cls, key): parse_json(value_cls, value) + for key, value in json_value.items() + } # type: ignore + except ValueError as e: + raise ValueError(f"Error parsing {cls}: {e}") from e if cls_origin is list: if not isinstance(json_value, list): @@ -64,7 +67,10 @@ def parse_generic_json(cls: type[T], json_value: Any) -> T: assert isinstance(json_value, cls), f"Type {cls} is list with no type args" return json_value (arg_cls,) = cls_args - return [parse_json(arg_cls, value) for value in json_value] # type: ignore + try: + return [parse_json(arg_cls, value) for value in json_value] # type: ignore + except ValueError as e: + raise ValueError(f"Error parsing {cls}: {e}") from e if cls_origin is types.UnionType or cls_origin is Union: for arg_cls in cls_args: @@ -99,10 +105,19 @@ def parse_json_dataclass(cls: type[T], json_value: Any) -> T: raise ValueError(f"Cannot parse dataclass key from type {type(key)}") field_types = {field.name: field.type for field in dataclasses.fields(cls)} - field_values = { - key: parse_json(field_types[key], value) if key in field_types else value - for key, value in json_value.items() - } + + def parse_field(json_key: str, json_value: Any): + try: + if json_key in field_types: + return parse_json(field_types[json_key], json_value) + else: + return json_value + except ValueError as e: + raise ValueError( + f"Error parsing field {json_key!r} from {cls.__name__}: {e}" + ) from e + + field_values = {key: parse_field(key, value) for key, value in json_value.items()} result = cls(**field_values) assert isinstance(result, cls) diff --git a/softwareComponents/voxelReconfig/Cargo.toml b/softwareComponents/voxelReconfig/Cargo.toml index d49c868df..019da6ff7 100644 --- a/softwareComponents/voxelReconfig/Cargo.toml +++ b/softwareComponents/voxelReconfig/Cargo.toml @@ -22,12 +22,18 @@ educe = "0.4" enum-iterator = "1.2" iter_fixed = "0.3" itertools = "0.11" +lapjv = "0.2" litemap = "0.7" log = "0.4" modular-bitfield = "0.11" +ndarray = "0.13" # In sync with crate lapjv num = "0.4" +ordered-float = "4.1" rs-graph = "0.21" rustc-hash = "1.1" serde = { version = "1.0", features = ["derive"] } smallvec = "1.10" static_assertions = "1.1" + +[dev-dependencies] +multicode = { path = "multicode" } diff --git a/softwareComponents/voxelReconfig/multicode/.gitignore b/softwareComponents/voxelReconfig/multicode/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/softwareComponents/voxelReconfig/multicode/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/softwareComponents/voxelReconfig/multicode/Cargo.toml b/softwareComponents/voxelReconfig/multicode/Cargo.toml new file mode 100644 index 000000000..5cfd9f74f --- /dev/null +++ b/softwareComponents/voxelReconfig/multicode/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "multicode" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +syn = { version = "2.0", features = ["full"] } +quote = "1.0" diff --git a/softwareComponents/voxelReconfig/multicode/src/lib.rs b/softwareComponents/voxelReconfig/multicode/src/lib.rs new file mode 100644 index 000000000..29d20fcf4 --- /dev/null +++ b/softwareComponents/voxelReconfig/multicode/src/lib.rs @@ -0,0 +1,8 @@ +mod multicode_impl; + +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn multicode(attr: TokenStream, item: TokenStream) -> TokenStream { + multicode_impl::multicode_impl(attr, item) +} diff --git a/softwareComponents/voxelReconfig/multicode/src/multicode_impl.rs b/softwareComponents/voxelReconfig/multicode/src/multicode_impl.rs new file mode 100644 index 000000000..e4b54bad6 --- /dev/null +++ b/softwareComponents/voxelReconfig/multicode/src/multicode_impl.rs @@ -0,0 +1,124 @@ +use std::str::FromStr; + +use proc_macro2::{ TokenStream, TokenTree, Group, Delimiter }; +use quote::{ ToTokens, TokenStreamExt }; +use syn::{ bracketed, parse_macro_input, Ident, Token }; +use syn::punctuated::Punctuated; +use syn::parse::{ Parse, ParseStream }; +use syn::token; + +macro_rules! outer_sep { + () => { Token![;] }; +} + +struct Replacement { + mod_name: Ident, + _sep: Token![:], + replacement: TokenStream, +} +impl std::fmt::Debug for Replacement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { mod_name, _sep, replacement } = self; + f.debug_struct("Replacement") + .field("mod_name", mod_name) + .field("replacement", replacement) + .finish() + } +} +impl Parse for Replacement { + fn parse(input: ParseStream) -> syn::Result { + Ok(Replacement { + mod_name: input.parse()?, + _sep: input.parse()?, + replacement: { + let mut repl = TokenStream::new(); + while !input.is_empty() && !input.peek(outer_sep!()) { + repl.append(input.parse::()?); + } + repl + }, + }) + } +} +impl Replacement { + fn add_mod_with_repl( + &self, + out_stream: &mut TokenStream, + repl_ident: &Ident, + inner_stream: TokenStream + ) { + Ident::new("mod", self.mod_name.span()).to_tokens(out_stream); + self.mod_name.to_tokens(out_stream); + + let pub_use_all = TokenStream::from_str("pub use super::*;").unwrap(); + let repl_stream = replace_ident(inner_stream, repl_ident, &self.replacement); + Group::new( + Delimiter::Brace, + TokenStream::from_iter(pub_use_all.into_iter().chain(repl_stream)) + ).to_tokens(out_stream); + } +} + +fn replace_ident(input: TokenStream, repl_ident: &Ident, replacement: &TokenStream) -> TokenStream { + input + .into_iter() + .map(|token| { + match token { + TokenTree::Ident(ident) => { + if ident.to_string() == repl_ident.to_string() { + replacement.clone() + } else { + TokenTree::Ident(ident).into_token_stream() + } + } + TokenTree::Group(group) => { + let new_stream = replace_ident(group.stream(), repl_ident, replacement); + let mut new_group = Group::new(group.delimiter(), new_stream); + new_group.set_span(group.span()); + TokenTree::Group(new_group).into_token_stream() + } + token => token.into_token_stream(), + } + }) + .collect() +} +struct MulticodeArgs { + ident: Ident, + _sep: Token![,], + _repl_bracket: token::Bracket, + replacements: Punctuated, +} +impl std::fmt::Debug for MulticodeArgs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { ident, _sep, _repl_bracket, replacements } = self; + f.debug_struct("MulticodeArgs") + .field("ident", ident) + .field("replacements", &replacements.iter().collect::>()) + .finish() + } +} +impl Parse for MulticodeArgs { + fn parse(input: ParseStream) -> syn::Result { + let repl_content; + Ok(MulticodeArgs { + ident: input.parse()?, + _sep: input.parse()?, + _repl_bracket: bracketed!(repl_content in input), + replacements: Punctuated::parse_terminated(&repl_content)?, + }) + } +} + +pub fn multicode_impl( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream +) -> proc_macro::TokenStream { + let args = parse_macro_input!(attr as MulticodeArgs); + let item = TokenStream::from(item); + + let mut result = TokenStream::new(); + for repl in &args.replacements { + repl.add_mod_with_repl(&mut result, &args.ident, item.clone()); + } + result.into() +} diff --git a/softwareComponents/voxelReconfig/src/algs/astar.rs b/softwareComponents/voxelReconfig/src/algs/astar.rs new file mode 100644 index 000000000..e1d5af8ab --- /dev/null +++ b/softwareComponents/voxelReconfig/src/algs/astar.rs @@ -0,0 +1,101 @@ +//! Compute any path using the [A* search algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm) with early return. + +use super::{AlgInfo, ParentMap, StateGraph}; +use crate::reconfig::metric::cost::{Cost, CostHolder}; +use crate::reconfig::metric::Metric; +use std::cmp::Reverse; +use std::collections::BinaryHeap; +use std::rc::Rc; + +pub struct AstarAlgInfo, const OPTIMAL: bool = false> { + metric: TMetric, + states_to_visit: BinaryHeap, TMetric::EstimatedCost>>>, +} + +impl AlgInfo + for AstarAlgInfo +where + TGraph: StateGraph, + TMetric: Metric, +{ + type NodeInfo = Cost; + const EARLY_CHECK: bool = !OPTIMAL; + + fn new(init: Rc, goal: &TGraph::StateType) -> Self { + let mut metric = TMetric::new(goal); + debug_assert!(metric.get_potential(init.as_ref()) >= metric.get_potential(goal)); + Self { + metric, + states_to_visit: BinaryHeap::from([Reverse(CostHolder { + estimated_cost: TMetric::EstimatedCost::default(), + state: init, + })]), + } + } + + fn visit_next_state( + &mut self, + parent_map: &ParentMap, Self::NodeInfo>, + ) -> Option<(Rc, Self::NodeInfo)> { + let Some(Reverse(CostHolder { + estimated_cost: current_estimated_cost, + state: current, + })) = self.states_to_visit.pop() + else { + return None; + }; + TGraph::debug_check_state(¤t); + + let &(_, cost) = parent_map + .get(¤t) + .expect("Visiting state not in parent map"); + + { + let estimated_cost = TMetric::estimated_cost(cost); + if current_estimated_cost > estimated_cost { + // There was a shorter way + return >::visit_next_state(self, parent_map); + } + // This is the shortest way + debug_assert_eq!( + current_estimated_cost, estimated_cost, + "The lower estimated cost should have been taken first" + ); + } + + Some((current, cost)) + } + + fn get_node_info( + &mut self, + parent_cost: &Self::NodeInfo, + new_state: &TGraph::StateType, + parent_map: &ParentMap, Self::NodeInfo>, + ) -> Option { + let real_cost = parent_cost.real_cost + 1; + + if let Some((key, &(_, old_cost))) = parent_map.get_key_value(new_state) { + TGraph::debug_check_state(&key); + + if real_cost >= old_cost.real_cost { + debug_assert!(TGraph::equivalent_states(&new_state).all(|eq_state| { + parent_map + .get(&eq_state) + .is_some_and(|(_, c)| c.real_cost == old_cost.real_cost) + })); + return Default::default(); + } + + Some(Cost::new(real_cost, old_cost.potential)) + } else { + Some(Cost::new(real_cost, self.metric.get_potential(&new_state))) + } + } + + fn add_state_to_visit(&mut self, state: Rc, node_info: Self::NodeInfo) { + self.states_to_visit.push(Reverse(CostHolder::new( + state, + TMetric::estimated_cost(node_info), + ))); + } +} diff --git a/softwareComponents/voxelReconfig/src/algs/astar/mod.rs b/softwareComponents/voxelReconfig/src/algs/astar/mod.rs deleted file mode 100644 index 3fcd4d460..000000000 --- a/softwareComponents/voxelReconfig/src/algs/astar/mod.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! Compute any path using the [A* search algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm) with early return. - -pub mod opt; - -use crate::algs::{reconstruct_path_to, Error, StateGraph}; -use crate::counters::Counter; -use crate::reconfig::metric::cost::{Cost, CostHolder}; -use crate::reconfig::metric::Metric; -use rustc_hash::FxHashMap; -use std::cmp::Reverse; -use std::collections::BinaryHeap; -use std::rc::Rc; - -type ParentMap = FxHashMap, (Option>, Cost)>; - -/// metric will be called only once on each equivalent state -pub fn compute_path( - init: &TGraph::StateType, - goal: &TGraph::StateType, -) -> Result>, Error> -where - TGraph: StateGraph, - TMetric: Metric, -{ - TGraph::debug_check_state(init); - TGraph::debug_check_state(goal); - - if !TGraph::init_check(init, goal) { - return Err(Error::InitCheckError); - } - - let mut metric = TMetric::new(goal); - debug_assert!(metric.get_potential(init) >= metric.get_potential(goal)); - - let parent_map = compute_parents::(init, goal, metric).ok_or(Error::PathNotFound)?; - - let goal = parent_map - .get_key_value(goal) - .expect("Parent map has to contain goal") - .0 - .clone(); - - Ok(reconstruct_path_to(goal, parent_map, |p| p.0)) -} - -fn compute_parents( - init: &TGraph::StateType, - goal: &TGraph::StateType, - mut metric: TMetric, -) -> Option> -where - TGraph: StateGraph, - TMetric: Metric, -{ - TGraph::debug_check_state(init); - TGraph::debug_check_state(goal); - - let mut init_states = TGraph::equivalent_states(init).map(Rc::new).peekable(); - let init = init_states.peek().expect("No equivalent state").clone(); - let mut parent_map = init_states - .map(|init| (init, (None, Cost::new(0, TMetric::Potential::default())))) - .collect::>(); - let mut states_to_visit = BinaryHeap::from([Reverse(CostHolder { - estimated_cost: TMetric::EstimatedCost::default(), - state: init, - })]); - - while let Some(Reverse(CostHolder { - estimated_cost, - state: current, - })) = states_to_visit.pop() - { - TGraph::debug_check_state(¤t); - - let &(_, cost) = parent_map.get(¤t).unwrap(); - - { - let old_estimated_cost = TMetric::estimated_cost(cost); - if estimated_cost > old_estimated_cost { - // There was a shorter way - continue; - } - assert_eq!( - estimated_cost, old_estimated_cost, - "The old estimated cost should be taken first" - ); - } - - for new_state in TGraph::next_states(¤t) { - TGraph::debug_check_state(&new_state); - - let new_real_cost = cost.real_cost + 1; - - let new_state_rc; - let new_cost; - if let Some((key, &(_, old_cost))) = parent_map.get_key_value(&new_state) { - if new_real_cost >= old_cost.real_cost { - debug_assert!(TGraph::equivalent_states(&new_state).all(|eq_state| { - parent_map - .get(&eq_state) - .is_some_and(|(_, c)| c.real_cost == old_cost.real_cost) - })); - continue; - } - new_state_rc = key.clone(); - new_cost = Cost::new(new_real_cost, old_cost.potential); - for eq_state in TGraph::equivalent_states(&new_state) { - let (eq_old_parent, eq_old_cost) = parent_map - .get_mut(&eq_state) - .expect("Parent map contains a state, but not all of its eq variants"); - debug_assert!(eq_old_parent.is_some(), "Cannot get better path to init"); - debug_assert_eq!(eq_old_cost, &old_cost); - *eq_old_parent = Some(current.clone()); - *eq_old_cost = new_cost; - } - } else { - let mut eq_states = TGraph::equivalent_states(&new_state) - .map(Rc::new) - .peekable(); - new_state_rc = eq_states.peek().expect("No equivalent state").clone(); - new_cost = Cost::new(new_real_cost, metric.get_potential(&new_state_rc)); - - debug_assert!(TGraph::equivalent_states(&new_state_rc) - .all(|eq_state| !parent_map.contains_key(&eq_state))); - - Counter::saved_new_unique_state(); - parent_map.extend( - eq_states.map(|eq_state| (eq_state, (Some(current.clone()), new_cost))), - ); - - if parent_map.contains_key(goal) { - return Some(parent_map); - } - } - TGraph::debug_check_state(&new_state_rc); - - states_to_visit.push(Reverse(CostHolder::new( - new_state_rc, - TMetric::estimated_cost(new_cost), - ))); - } - } - None -} diff --git a/softwareComponents/voxelReconfig/src/algs/astar/opt.rs b/softwareComponents/voxelReconfig/src/algs/astar/opt.rs deleted file mode 100644 index 635970107..000000000 --- a/softwareComponents/voxelReconfig/src/algs/astar/opt.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! Compute a shortest path (or all shorted paths) using the [A* search algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm). - -use crate::algs::{reconstruct_path_to, Error, StateGraph}; -use crate::counters::Counter; -use crate::reconfig::metric::cost::{Cost, CostHolder}; -use crate::reconfig::metric::Metric; -use rustc_hash::FxHashMap; -use std::cmp::Reverse; -use std::collections::BinaryHeap; -use std::rc::Rc; - -type ParentMap = FxHashMap, (Option>, Cost)>; - -/// metric will be called only once on each equivalent state -pub fn compute_path( - init: &TGraph::StateType, - goal: &TGraph::StateType, -) -> Result>, Error> -where - TGraph: StateGraph, - TMetric: Metric, -{ - TGraph::debug_check_state(init); - TGraph::debug_check_state(goal); - - if !TGraph::init_check(init, goal) { - return Err(Error::InitCheckError); - } - - let mut metric = TMetric::new(goal); - debug_assert!(metric.get_potential(init) >= metric.get_potential(goal)); - - let is_goal = - |state: &TGraph::StateType| TGraph::equivalent_states(state).any(|state| &state == goal); - - let parent_map = - compute_parents::(init, is_goal, metric).ok_or(Error::PathNotFound)?; - - let goal = parent_map - .get_key_value(goal) - .expect("Parent map has to contain goal") - .0 - .clone(); - - Ok(reconstruct_path_to(goal, parent_map, |p| p.0)) -} - -fn compute_parents( - init: &TGraph::StateType, - is_goal: impl Fn(&TGraph::StateType) -> bool, - mut metric: TMetric, -) -> Option> -where - TGraph: StateGraph, - TMetric: Metric, -{ - TGraph::debug_check_state(init); - - let mut init_states = TGraph::equivalent_states(init).map(Rc::new).peekable(); - let init = init_states.peek().expect("No equivalent state").clone(); - let mut parent_map = init_states - .map(|init| (init, (None, Cost::new(0, TMetric::Potential::default())))) - .collect::>(); - let mut states_to_visit = BinaryHeap::from([Reverse(CostHolder { - estimated_cost: TMetric::EstimatedCost::default(), - state: init, - })]); - - while let Some(Reverse(CostHolder { - estimated_cost, - state: current, - })) = states_to_visit.pop() - { - TGraph::debug_check_state(¤t); - - if is_goal(¤t) { - return Some(parent_map); - } - let &(_, cost) = parent_map.get(¤t).unwrap(); - - { - let old_estimated_cost = TMetric::estimated_cost(cost); - if estimated_cost > old_estimated_cost { - // There was a shorter way - continue; - } - assert_eq!( - estimated_cost, old_estimated_cost, - "The old estimated cost should be taken first" - ); - } - - for new_state in TGraph::next_states(¤t) { - TGraph::debug_check_state(&new_state); - - let new_real_cost = cost.real_cost + 1; - - let new_state_rc; - let new_cost; - if let Some((key, &(_, old_cost))) = parent_map.get_key_value(&new_state) { - if new_real_cost >= old_cost.real_cost { - debug_assert!(TGraph::equivalent_states(&new_state).all(|eq_state| { - parent_map - .get(&eq_state) - .is_some_and(|(_, c)| c.real_cost == old_cost.real_cost) - })); - continue; - } - new_state_rc = key.clone(); - new_cost = Cost::new(new_real_cost, old_cost.potential); - for eq_state in TGraph::equivalent_states(&new_state) { - let (eq_old_parent, eq_old_cost) = parent_map - .get_mut(&eq_state) - .expect("Parent map contains a state, but not all of its eq variants"); - debug_assert!(eq_old_parent.is_some(), "Cannot get better path to init"); - debug_assert_eq!(eq_old_cost, &old_cost); - *eq_old_parent = Some(current.clone()); - *eq_old_cost = new_cost; - } - } else { - let mut eq_states = TGraph::equivalent_states(&new_state) - .map(Rc::new) - .peekable(); - new_state_rc = eq_states.peek().expect("No equivalent state").clone(); - new_cost = Cost::new(new_real_cost, metric.get_potential(&new_state_rc)); - - debug_assert!(TGraph::equivalent_states(&new_state_rc) - .all(|eq_state| !parent_map.contains_key(&eq_state))); - - Counter::saved_new_unique_state(); - parent_map.extend( - eq_states.map(|eq_state| (eq_state, (Some(current.clone()), new_cost))), - ); - } - TGraph::debug_check_state(&new_state_rc); - - states_to_visit.push(Reverse(CostHolder::new( - new_state_rc, - TMetric::estimated_cost(new_cost), - ))); - } - } - None -} diff --git a/softwareComponents/voxelReconfig/src/algs/bfs.rs b/softwareComponents/voxelReconfig/src/algs/bfs.rs index cbe885dba..3d92acdba 100644 --- a/softwareComponents/voxelReconfig/src/algs/bfs.rs +++ b/softwareComponents/voxelReconfig/src/algs/bfs.rs @@ -1,88 +1,54 @@ //! Compute a shortest path using the [breadth-first search algorithm](https://en.wikipedia.org/wiki/Breadth-first_search). -use super::{reconstruct_path_to, Error, StateGraph}; -use crate::counters::Counter; -use rustc_hash::FxHashMap; +use super::{AlgInfo, ParentMap, StateGraph}; use std::collections::VecDeque; use std::rc::Rc; -type ParentMap = FxHashMap, Option>>; - -pub fn compute_path( - init: &TGraph::StateType, - goal: &TGraph::StateType, -) -> Result>, Error> { - TGraph::debug_check_state(init); - TGraph::debug_check_state(goal); - - if !TGraph::init_check(init, goal) { - return Err(Error::InitCheckError); - } - - let parent_map = compute_parents::(init, goal).ok_or(Error::PathNotFound)?; - - let goal = parent_map - .get_key_value(goal) - .expect("Parent map has to contain goal") - .0 - .clone(); - - Ok(reconstruct_path_to(goal, parent_map, |p| p)) +pub struct BfsAlgInfo { + states_to_visit: VecDeque>, } -fn compute_parents( - init: &TGraph::StateType, - goal: &TGraph::StateType, -) -> Option> { - TGraph::debug_check_state(init); - TGraph::debug_check_state(goal); +impl AlgInfo for BfsAlgInfo { + type NodeInfo = (); + const EARLY_CHECK: bool = true; - let mut init_states = TGraph::equivalent_states(init).map(Rc::new).peekable(); - let init = init_states.peek().expect("No equivalent state").clone(); - let mut parent_map = init_states - .map(|init| (init, None)) - .collect::>(); - let mut states_to_visit = VecDeque::from([init]); - - if parent_map.contains_key(goal) { - return Some(parent_map); + fn new(init: Rc, _goal: &TGraph::StateType) -> Self { + Self { + states_to_visit: VecDeque::from([init]), + } } - while let Some(current) = states_to_visit.pop_front() { - TGraph::debug_check_state(¤t); - for new_state in TGraph::next_states(¤t) { - TGraph::debug_check_state(&new_state); - - if parent_map.contains_key(&new_state) { - debug_assert!( - TGraph::equivalent_states(&new_state) - .all(|eq_state| parent_map.contains_key(&eq_state)), - "Parent map contains a state, but not all of its eq variants" - ); - continue; - } + fn visit_next_state( + &mut self, + _parent_map: &ParentMap, Self::NodeInfo>, + ) -> Option<(Rc, Self::NodeInfo)> { + self.states_to_visit.pop_front().map(|s| (s, ())) + } + fn get_node_info( + &mut self, + _parent_node_info: &Self::NodeInfo, + new_state: &TGraph::StateType, + parent_map: &ParentMap, Self::NodeInfo>, + ) -> Option { + if parent_map.contains_key(new_state) { debug_assert!( - TGraph::equivalent_states(&new_state) - .all(|eq_state| !parent_map.contains_key(&eq_state)), - "Parent map contains eq variant of a state but not the state itself" + TGraph::equivalent_states(new_state) + .all(|eq_state| parent_map.contains_key(&eq_state)), + "Parent map contains a state, but not all of its eq variants" ); + return None; + } - let mut eq_states = TGraph::equivalent_states(&new_state) - .map(Rc::new) - .peekable(); - let new_state = eq_states.peek().expect("No equivalent state").clone(); - TGraph::debug_check_state(&new_state); - - Counter::saved_new_unique_state(); - parent_map.extend(eq_states.map(|eq_state| (eq_state, Some(current.clone())))); - - if parent_map.contains_key(goal) { - return Some(parent_map); - } + debug_assert!( + TGraph::equivalent_states(new_state) + .all(|eq_state| !parent_map.contains_key(&eq_state)), + "Parent map contains eq variant of a state but not the state itself" + ); + Some(()) + } - states_to_visit.push_back(new_state); - } + fn add_state_to_visit(&mut self, state: Rc, _node_info: Self::NodeInfo) { + self.states_to_visit.push_back(state); } - None } diff --git a/softwareComponents/voxelReconfig/src/algs/bidir.rs b/softwareComponents/voxelReconfig/src/algs/bidir.rs new file mode 100644 index 000000000..c5af6f8fd --- /dev/null +++ b/softwareComponents/voxelReconfig/src/algs/bidir.rs @@ -0,0 +1,87 @@ +//! [Bidirectional path finding](https://en.wikipedia.org/wiki/Bidirectional_search) using traits to accomplish generality. + +use super::common::{ + compute_next_step, initialize_parent_map, reconstruct_path_to, NextStepResult, +}; +use super::{AlgInfo, Error, ParentMap, StateGraph}; +use std::rc::Rc; + +struct BidirectionalResult { + parent_map: ParentMap, + rev_parent_map: ParentMap, + mid_state: TItem, +} + +pub fn compute_path>( + init: &TGraph::StateType, + goal: &TGraph::StateType, +) -> Result>, Error> { + TGraph::debug_check_state(init); + TGraph::debug_check_state(goal); + + if !TGraph::init_check(init, goal) { + return Err(Error::InitCheckError); + } + + let parent_maps = compute_parents::(init, goal).ok_or(Error::PathNotFound)?; + + let init_to_mid = + reconstruct_path_to(parent_maps.mid_state.clone(), parent_maps.parent_map, |p| { + p.0 + }); + let goal_to_mid = + reconstruct_path_to(parent_maps.mid_state, parent_maps.rev_parent_map, |p| p.0); + + let mut path = init_to_mid; + path.extend(goal_to_mid.into_iter().rev().skip(1)); + Ok(path) +} + +fn compute_parents>( + init: &TGraph::StateType, + goal: &TGraph::StateType, +) -> Option, TAlgInfo::NodeInfo>> { + let (init, mut parent_map) = initialize_parent_map::(init); + let (goal, mut rev_parent_map) = initialize_parent_map::(goal); + + if parent_map.contains_key(&goal) { + return Some(BidirectionalResult { + parent_map, + rev_parent_map, + mid_state: goal, + }); + } + + let mut alg_info = TAlgInfo::new(init.clone(), &goal); + let mut rev_alg_info = TAlgInfo::new(goal, &init); + + loop { + match compute_next_step(&mut alg_info, &mut parent_map, |s| { + rev_parent_map.contains_key(s) + }) { + NextStepResult::Continue => {} + NextStepResult::NoNextState => return None, + NextStepResult::GoalFound(mid_state) => { + return Some(BidirectionalResult { + parent_map, + rev_parent_map, + mid_state, + }) + } + } + + match compute_next_step(&mut rev_alg_info, &mut rev_parent_map, |s| { + parent_map.contains_key(s) + }) { + NextStepResult::Continue => {} + NextStepResult::NoNextState => return None, + NextStepResult::GoalFound(mid_state) => { + return Some(BidirectionalResult { + parent_map, + rev_parent_map, + mid_state, + }) + } + } + } +} diff --git a/softwareComponents/voxelReconfig/src/algs/common.rs b/softwareComponents/voxelReconfig/src/algs/common.rs new file mode 100644 index 000000000..a57d4ff77 --- /dev/null +++ b/softwareComponents/voxelReconfig/src/algs/common.rs @@ -0,0 +1,159 @@ +use super::{AlgInfo, ParentMap, StateGraph}; +use crate::counters::Counter; +use std::collections::HashMap; +use std::hash::BuildHasher; +use std::rc::Rc; + +pub fn initialize_parent_map( + init: &TGraph::StateType, +) -> ( + Rc, + ParentMap, TNodeInfo>, +) { + let mut init_states = TGraph::equivalent_states(init).map(Rc::new).peekable(); + let init = init_states.peek().expect("No equivalent state").clone(); + let parent_map = init_states.map(|init| (init, Default::default())).collect(); + (init, parent_map) +} + +fn add_to_parent_map( + new_state: &TGraph::StateType, + new_node_info: &TNodeInfo, + parent_state: &Rc, + parent_map: &mut ParentMap, TNodeInfo>, +) -> Rc { + let mut eq_states = TGraph::equivalent_states(&new_state) + .map(Rc::new) + .peekable(); + let new_state = eq_states.peek().expect("No equivalent state").clone(); + TGraph::debug_check_state(&new_state); + + parent_map.extend(eq_states.map(|eq_state| { + ( + eq_state, + (Some(parent_state.clone()), new_node_info.clone()), + ) + })); + + new_state +} + +fn update_parent_map( + new_state: &TGraph::StateType, + new_node_info: &TNodeInfo, + parent_state: &Rc, + parent_map: &mut ParentMap, TNodeInfo>, +) { + for eq_state in TGraph::equivalent_states(new_state) { + let eq_state_parent_info = parent_map + .get_mut(&eq_state) + .expect("Parent map contains a state, but not all of its eq variants"); + debug_assert!( + eq_state_parent_info.0.is_some(), + "Cannot get better path to init" + ); + *eq_state_parent_info = (Some(parent_state.clone()), new_node_info.clone()); + } +} + +pub enum NextStepResult { + Continue, + GoalFound(TState), + NoNextState, +} + +pub fn compute_next_step>( + alg_info: &mut TAlgInfo, + parent_map: &mut ParentMap, TAlgInfo::NodeInfo>, + mut is_goal: impl FnMut(&TGraph::StateType) -> bool, +) -> NextStepResult> { + let Some((current, current_node_info)) = alg_info.visit_next_state(&parent_map) else { + return NextStepResult::NoNextState; + }; + TGraph::debug_check_state(¤t); + + if !TAlgInfo::EARLY_CHECK && is_goal(¤t) { + return NextStepResult::GoalFound(current); + } + + for new_state in TGraph::next_states(¤t) { + TGraph::debug_check_state(&new_state); + + let Some(new_node_info) = + alg_info.get_node_info(¤t_node_info, &new_state, &parent_map) + else { + continue; + }; + + if let Some((new_state, _)) = parent_map.get_key_value(&new_state) { + let new_state = new_state.clone(); + update_parent_map::(&new_state, &new_node_info, ¤t, parent_map); + alg_info.add_state_to_visit(new_state, new_node_info); + } else { + debug_assert!(TGraph::equivalent_states(&new_state) + .all(|eq_state| !parent_map.contains_key(&eq_state))); + + Counter::saved_new_unique_state(); + + let new_state = + add_to_parent_map::(&new_state, &new_node_info, ¤t, parent_map); + + if TAlgInfo::EARLY_CHECK && is_goal(&new_state) { + return NextStepResult::GoalFound(new_state); + } + + alg_info.add_state_to_visit(new_state, new_node_info); + } + } + NextStepResult::Continue +} + +pub fn reconstruct_path_to( + goal: TItem, + mut parent_map: HashMap, + mut get_parent: impl FnMut(TParentInfo) -> Option, +) -> Vec { + assert!(parent_map.contains_key(&goal), "Missing goal parent"); + let mut path = Vec::new(); + let mut parent = goal; + loop { + if let Some(new_parent_info) = parent_map.remove(&parent) { + if let Some(new_parent) = get_parent(new_parent_info) { + path.push(std::mem::replace(&mut parent, new_parent)); + } else { + path.push(parent); + break; + } + } else { + if path.contains(&parent) { + panic!("Cyclic dependency in parent graph"); + } else { + panic!("Missing parent info"); + } + } + } + + path.reverse(); + path +} + +#[allow(unused)] +pub fn reconstruct_path_to_noconsume( + goal: TItem, + parent_map: &HashMap, + mut get_parent: impl FnMut(&TParentInfo) -> Option, +) -> Vec { + assert!(parent_map.contains_key(&goal), "Missing goal parent"); + let mut parent = Some(goal); + let mut path = std::iter::from_fn(|| { + let new_parent = parent_map + .get(parent.as_ref()?) + .map(&mut get_parent) + .expect("Missing prev node"); + std::mem::replace(&mut parent, new_parent) + }) + .collect::>(); + + path.reverse(); + path +} diff --git a/softwareComponents/voxelReconfig/src/algs/mod.rs b/softwareComponents/voxelReconfig/src/algs/mod.rs index 4cdf0d639..d280e98c7 100644 --- a/softwareComponents/voxelReconfig/src/algs/mod.rs +++ b/softwareComponents/voxelReconfig/src/algs/mod.rs @@ -1,9 +1,16 @@ -use std::collections::HashMap; -use std::hash::BuildHasher; +mod common; pub mod astar; pub mod bfs; +pub mod bidir; +pub mod onedir; + +use rustc_hash::FxHashMap; +use std::rc::Rc; + +pub type ParentMap = FxHashMap, TNodeInfo)>; + #[derive(Debug, Clone, Copy, amplify::Display, amplify::Error)] #[display(doc_comments)] pub enum Error { @@ -30,52 +37,23 @@ pub trait StateGraph { fn next_states(state: &Self::StateType) -> Self::NextStatesIter<'_>; } -fn reconstruct_path_to( - goal: TItem, - mut parent_map: HashMap, - mut get_parent: impl FnMut(TParentInfo) -> Option, -) -> Vec { - assert!(parent_map.contains_key(&goal), "Missing goal parent"); - let mut path = Vec::new(); - let mut parent = goal; - loop { - if let Some(new_parent_info) = parent_map.remove(&parent) { - if let Some(new_parent) = get_parent(new_parent_info) { - path.push(std::mem::replace(&mut parent, new_parent)); - } else { - path.push(parent); - break; - } - } else { - if path.contains(&parent) { - panic!("Cyclic dependency in parent graph"); - } else { - panic!("Missing parent info"); - } - } - } +pub trait AlgInfo { + type NodeInfo: Default + Clone = (); + const EARLY_CHECK: bool; - path.reverse(); - path -} - -#[allow(unused)] -fn reconstruct_path_to_noconsume( - goal: TItem, - parent_map: &HashMap, - mut get_parent: impl FnMut(&TParentInfo) -> Option, -) -> Vec { - assert!(parent_map.contains_key(&goal), "Missing goal parent"); - let mut parent = Some(goal); - let mut path = std::iter::from_fn(|| { - let new_parent = parent_map - .get(parent.as_ref()?) - .map(&mut get_parent) - .expect("Missing prev node"); - std::mem::replace(&mut parent, new_parent) - }) - .collect::>(); - - path.reverse(); - path + fn new(init: Rc, goal: &TGraph::StateType) -> Self + where + Self: Sized; + + fn visit_next_state( + &mut self, + parent_map: &ParentMap, Self::NodeInfo>, + ) -> Option<(Rc, Self::NodeInfo)>; + fn get_node_info( + &mut self, + parent_node_info: &Self::NodeInfo, + new_state: &TGraph::StateType, + parent_map: &ParentMap, Self::NodeInfo>, + ) -> Option; + fn add_state_to_visit(&mut self, state: Rc, node_info: Self::NodeInfo); } diff --git a/softwareComponents/voxelReconfig/src/algs/onedir.rs b/softwareComponents/voxelReconfig/src/algs/onedir.rs new file mode 100644 index 000000000..5ed2789f2 --- /dev/null +++ b/softwareComponents/voxelReconfig/src/algs/onedir.rs @@ -0,0 +1,52 @@ +//! One directional path finding using traits to accomplish generality. + +use super::common::{ + compute_next_step, initialize_parent_map, reconstruct_path_to, NextStepResult, +}; +use super::{AlgInfo, Error, ParentMap, StateGraph}; +use smallvec::SmallVec; +use std::rc::Rc; + +pub fn compute_path>( + init: &TGraph::StateType, + goal: &TGraph::StateType, +) -> Result>, Error> { + TGraph::debug_check_state(init); + TGraph::debug_check_state(goal); + + if !TGraph::init_check(init, goal) { + return Err(Error::InitCheckError); + } + + let parent_map = compute_parents::(init, goal).ok_or(Error::PathNotFound)?; + + let goal = parent_map + .get_key_value(goal) + .expect("Parent map has to contain goal") + .0 + .clone(); + + Ok(reconstruct_path_to(goal, parent_map, |p| p.0)) +} + +fn compute_parents>( + init: &TGraph::StateType, + goal: &TGraph::StateType, +) -> Option, TAlgInfo::NodeInfo>> { + let (init, mut parent_map) = initialize_parent_map::(init); + + let goal_states = TGraph::equivalent_states(goal).collect::>(); + if goal_states.contains(&init) { + return Some(parent_map); + } + + let mut alg_info = TAlgInfo::new(init, goal); + + loop { + match compute_next_step(&mut alg_info, &mut parent_map, |s| goal_states.contains(s)) { + NextStepResult::Continue => {} + NextStepResult::GoalFound(_) => return Some(parent_map), + NextStepResult::NoNextState => return None, + } + } +} diff --git a/softwareComponents/voxelReconfig/src/lib.rs b/softwareComponents/voxelReconfig/src/lib.rs index 31485d628..50d9fb414 100644 --- a/softwareComponents/voxelReconfig/src/lib.rs +++ b/softwareComponents/voxelReconfig/src/lib.rs @@ -3,7 +3,8 @@ assert_matches, associated_type_defaults, map_try_insert, - impl_trait_in_assoc_type + impl_trait_in_assoc_type, + return_position_impl_trait_in_trait )] pub mod atoms; diff --git a/softwareComponents/voxelReconfig/src/reconfig/metric/assignment.rs b/softwareComponents/voxelReconfig/src/reconfig/metric/assignment.rs new file mode 100644 index 000000000..cf3bd78c3 --- /dev/null +++ b/softwareComponents/voxelReconfig/src/reconfig/metric/assignment.rs @@ -0,0 +1,184 @@ +//! Metric based on best assignment algorithm. +//! This limits the metric to evaluating only a pair of modules. + +use super::centered_by_pos_median; +use super::potential_fn::{FSqrtSum, PotentialFn}; +use crate::module_repr::{get_all_module_reprs, get_other_body, is_module_repr}; +use crate::pos::Pos; +use crate::voxel::{JointPosition, PosVoxel, Voxel}; +use crate::voxel_world::normalized_eq_worlds; +use crate::voxel_world::{CenteredVoxelWorld, NormVoxelWorld, VoxelWorld}; +use iter_fixed::IntoIteratorFixed; +use ndarray::Array2; +use num::ToPrimitive; +use ordered_float::OrderedFloat; +use std::marker::PhantomData; + +fn voxel_joint_diff(goal: Voxel, other: Voxel) -> N { + match (goal.joint_pos(), other.joint_pos()) { + (JointPosition::Zero, JointPosition::Zero) => num::zero(), + (JointPosition::Zero, _) | (_, JointPosition::Zero) => num::one(), + _ => num::zero(), + } +} +pub fn joint_diff(goal: [Voxel; 2], other: [Voxel; 2]) -> N { + let [goal_a, goal_b] = goal; + let [other_a, other_b] = other; + + let goal_gamma_rot = goal_a.shoe_rotated() != goal_b.shoe_rotated(); + let other_gamma_rot = other_a.shoe_rotated() != other_b.shoe_rotated(); + let gamma_different = goal_gamma_rot != other_gamma_rot; + let gamma_diff = if gamma_different { N::one() } else { N::zero() }; + + let goal_same_shoe_rot = match (goal_a.joint_pos(), goal_b.joint_pos()) { + (JointPosition::Zero, _) | (_, JointPosition::Zero) => None, + _ => Some(goal_a.joint_pos() == goal_b.joint_pos()), + }; + let other_same_shoe_rot = match (goal_a.joint_pos(), goal_b.joint_pos()) { + (JointPosition::Zero, _) | (_, JointPosition::Zero) => None, + _ => Some(goal_a.joint_pos() == goal_b.joint_pos()), + }; + let opposite_shoe_rot_penalty = match (goal_same_shoe_rot, other_same_shoe_rot) { + (None, _) | (_, None) => N::zero(), + _ if goal_same_shoe_rot == other_same_shoe_rot => N::zero(), + _ => N::one() + N::one(), + }; + + voxel_joint_diff::(goal_a, other_a) + + voxel_joint_diff::(goal_b, other_b) + + opposite_shoe_rot_penalty + + gamma_diff +} + +fn pos_cost(lhs: Pos, rhs: Pos) -> TIndex { + lhs.as_array() + .into_iter_fixed() + .zip(rhs.as_array()) + .map(|(lhs, rhs)| num::abs_sub(lhs, rhs)) + .into_iter() + .fold(num::zero(), TIndex::add) +} + +fn compute_best_mapping_cost(other: &TWorld, goal: &TGoalWorld) -> f32 +where + TWorld: VoxelWorld, + TGoalWorld: VoxelWorld, + TWorld::IndexType: ToPrimitive, + TCostFn: CostFn, +{ + let other_modules_count = get_all_module_reprs(other).count(); + let goal_modules_count = get_all_module_reprs(goal).count(); + assert_eq!(other_modules_count, goal_modules_count); + + let mut costs = Array2::zeros((other_modules_count, goal_modules_count)); + for (other_body_a, i) in get_all_module_reprs(other).zip(0..) { + let other_body_b = get_other_body(other_body_a, other).unwrap(); + for (goal_body_a, j) in get_all_module_reprs(goal).zip(0..) { + let goal_body_b = get_other_body(goal_body_a, goal).unwrap(); + + costs[(i, j)] = + TCostFn::compute_cost([other_body_a, other_body_b], [goal_body_a, goal_body_b]); + } + } + + let best_mapping = lapjv::lapjv(&costs).unwrap(); + lapjv::cost(&costs, &best_mapping.0) +} + +pub trait CostFn { + fn compute_cost(lhs_module: [PosVoxel; 2], rhs_module: [PosVoxel; 2]) -> f32; +} + +pub struct PosCostFn; +impl CostFn for PosCostFn +where + TIndex: num::Signed + ToPrimitive + Copy, +{ + fn compute_cost(lhs_module: [PosVoxel; 2], rhs_module: [PosVoxel; 2]) -> f32 { + debug_assert!(is_module_repr(lhs_module[0].1)); + debug_assert!(is_module_repr(rhs_module[0].1)); + // Comparing shoeA with shoeB will always have equal or higher pos cost + let pos_cost = + pos_cost(lhs_module[0].0, rhs_module[0].0) + pos_cost(lhs_module[1].0, rhs_module[1].0); + ToPrimitive::to_f32(&pos_cost).expect("Invalid position cost") + } +} + +pub struct JointCostFn; +impl CostFn for JointCostFn +where + TIndex: num::Signed, +{ + fn compute_cost(lhs_module: [PosVoxel; 2], rhs_module: [PosVoxel; 2]) -> f32 { + f32::min( + joint_diff( + [lhs_module[0].1, lhs_module[1].1], + [rhs_module[0].1, rhs_module[1].1], + ), + joint_diff( + [lhs_module[1].1, lhs_module[0].1], + [rhs_module[0].1, rhs_module[1].1], + ), + ) + } +} + +pub struct PosJointCostFn; +impl CostFn for PosJointCostFn +where + TIndex: num::Signed + ToPrimitive + Copy, +{ + fn compute_cost(lhs_module: [PosVoxel; 2], rhs_module: [PosVoxel; 2]) -> f32 { + let pos_cost = PosCostFn::compute_cost(lhs_module, rhs_module); + let joint_cost = JointCostFn::compute_cost(lhs_module, rhs_module); + (pos_cost + joint_cost) / 2. + } +} + +pub struct AssignmentPotentialFn +where + TWorld: VoxelWorld, + TCostFn: CostFn, +{ + goals: Vec>, + __phantom: PhantomData, +} + +impl PotentialFn for AssignmentPotentialFn +where + TWorld: NormVoxelWorld, + TWorld::IndexType: ToPrimitive, + TCostFn: CostFn, +{ + type Potential = OrderedFloat; + + fn new(goal: &TWorld) -> Self { + let goals = normalized_eq_worlds(goal) + .map(centered_by_pos_median) + .collect(); + Self { + goals, + __phantom: Default::default(), + } + } + + fn get_potential(&mut self, state: &TWorld) -> Self::Potential { + let state = centered_by_pos_median::(state); + + assert!(!self.goals.is_empty()); + self.goals + .iter() + .map(|goal| compute_best_mapping_cost::<_, _, TCostFn>(&state, goal)) + .map(OrderedFloat) + .min() + .unwrap() + } +} + +pub type PosAssgPotFn = AssignmentPotentialFn; +pub type JointAssgPotFn = AssignmentPotentialFn; +pub type PosJointAssgPotFn = AssignmentPotentialFn; + +pub type PosAssgMetric = FSqrtSum>; +pub type JointAssgMetric = FSqrtSum>; +pub type PosJointAssgMetric = FSqrtSum>; diff --git a/softwareComponents/voxelReconfig/src/reconfig/metric/cost.rs b/softwareComponents/voxelReconfig/src/reconfig/metric/cost.rs index 44e7fa7e4..0716ad7b2 100644 --- a/softwareComponents/voxelReconfig/src/reconfig/metric/cost.rs +++ b/softwareComponents/voxelReconfig/src/reconfig/metric/cost.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Cost { /// Cost from init pub real_cost: usize, diff --git a/softwareComponents/voxelReconfig/src/reconfig/metric/mod.rs b/softwareComponents/voxelReconfig/src/reconfig/metric/mod.rs index 8184b1cb4..ff956cde9 100644 --- a/softwareComponents/voxelReconfig/src/reconfig/metric/mod.rs +++ b/softwareComponents/voxelReconfig/src/reconfig/metric/mod.rs @@ -1,7 +1,11 @@ +pub mod assignment; pub mod cost; pub mod naive; +pub mod potential_fn; use self::cost::Cost; +use crate::pos::Pos; +use crate::voxel_world::{CenteredVoxelWorld, VoxelWorld}; pub trait Metric { type Potential: std::cmp::Ord + Default + Copy + std::fmt::Debug = usize; @@ -21,10 +25,7 @@ impl Metric for ZeroMetric { type Potential = (); type EstimatedCost = usize; - fn new(_goal: &TState) -> Self - where - Self: Sized, - { + fn new(_goal: &TState) -> Self { Self } fn get_potential(&mut self, _state: &TState) -> Self::Potential {} @@ -32,3 +33,25 @@ impl Metric for ZeroMetric { cost.real_cost } } + +fn get_median_pos(mut positions: Vec>) -> Pos { + positions.sort_by_key(|pos| pos.x); + let x_median = positions[positions.len() / 2].x; + positions.sort_by_key(|pos| pos.y); + let y_median = positions[positions.len() / 2].y; + positions.sort_by_key(|pos| pos.z); + let z_median = positions[positions.len() / 2].z; + + Pos::from([x_median, y_median, z_median]) +} + +fn centered_by_pos_median( + world: TWorldRef, +) -> CenteredVoxelWorld +where + TWorld: VoxelWorld, + TWorldRef: std::borrow::Borrow, +{ + let pos_median = get_median_pos(world.borrow().all_voxels().map(|(pos, _)| pos).collect()); + CenteredVoxelWorld::new(world, pos_median) +} diff --git a/softwareComponents/voxelReconfig/src/reconfig/metric/naive.rs b/softwareComponents/voxelReconfig/src/reconfig/metric/naive.rs index 70e7e3407..2d681ffa7 100644 --- a/softwareComponents/voxelReconfig/src/reconfig/metric/naive.rs +++ b/softwareComponents/voxelReconfig/src/reconfig/metric/naive.rs @@ -1,22 +1,25 @@ //! Metric based on finding mappings by going through all mappings //! and evaluating the difference of worlds fixed to these mappings. +//! +//! Cannot use the best assignment algorith since it compares +//! the connection graphs of the configurations as well. use self::graph::{Graph, Mapping, Node}; -use super::{cost::Cost, Metric}; +use super::potential_fn::{ISqrtSum, PotentialFn}; use crate::voxel::{JointPosition, Voxel}; use crate::voxel_world::NormVoxelWorld; -use num::integer::Roots; use std::marker::PhantomData; -pub struct NaiveMetric +pub struct NaivePotential where TWorld: NormVoxelWorld, { goal: Graph, __phantom: PhantomData, } +pub type NaiveMetric = ISqrtSum>; -impl NaiveMetric +impl NaivePotential where TWorld: NormVoxelWorld, { @@ -80,7 +83,7 @@ where } } -impl Metric for NaiveMetric +impl PotentialFn for NaivePotential where TWorld: NormVoxelWorld, { @@ -99,10 +102,6 @@ where fn get_potential(&mut self, state: &TWorld) -> Self::Potential { self.compute_best_potential(state) } - - fn estimated_cost(cost: Cost) -> Self::EstimatedCost { - cost.potential + cost.real_cost.sqrt() - } } pub mod graph { diff --git a/softwareComponents/voxelReconfig/src/reconfig/metric/potential_fn.rs b/softwareComponents/voxelReconfig/src/reconfig/metric/potential_fn.rs new file mode 100644 index 000000000..d246628fc --- /dev/null +++ b/softwareComponents/voxelReconfig/src/reconfig/metric/potential_fn.rs @@ -0,0 +1,97 @@ +use super::Metric; +use num::integer::Roots; +use num::traits::real::Real; +use num::FromPrimitive; +use std::marker::PhantomData; + +pub trait PotentialFn { + type Potential: std::cmp::Ord + Default + Copy + std::fmt::Debug = usize; + + fn new(goal: &TState) -> Self + where + Self: Sized; + + fn get_potential(&mut self, state: &TState) -> Self::Potential; +} + +// Uses the `potential + real_cost` for the estimated cost +pub struct Sum(pub TPotentialFn, PhantomData) +where + TPotentialFn: PotentialFn; + +impl Metric for Sum +where + TPotentialFn: PotentialFn, + TPotentialFn::Potential: FromPrimitive + num::Num, +{ + type Potential = TPotentialFn::Potential; + type EstimatedCost = Self::Potential; + + fn new(goal: &TState) -> Self { + Self(PotentialFn::new(goal), Default::default()) + } + + fn get_potential(&mut self, state: &TState) -> Self::Potential { + self.0.get_potential(state) + } + + fn estimated_cost(cost: super::cost::Cost) -> Self::EstimatedCost { + cost.potential + + FromPrimitive::from_usize(cost.real_cost).expect("Cannot convert real cost") + } +} + +// Uses the `potential + integer_sqrt(real_cost)` for the estimated cost +pub struct ISqrtSum(pub TPotentialFn, PhantomData) +where + TPotentialFn: PotentialFn; + +impl Metric for ISqrtSum +where + TPotentialFn: PotentialFn, + TPotentialFn::Potential: num::Integer + FromPrimitive, +{ + type Potential = TPotentialFn::Potential; + type EstimatedCost = TPotentialFn::Potential; + + fn new(goal: &TState) -> Self { + Self(PotentialFn::new(goal), Default::default()) + } + + fn get_potential(&mut self, state: &TState) -> Self::Potential { + self.0.get_potential(state) + } + + fn estimated_cost(cost: super::cost::Cost) -> Self::EstimatedCost { + cost.potential + + FromPrimitive::from_usize(cost.real_cost.sqrt()).expect("Cannot convert real cost") + } +} + +// Uses the `potential + sqrt(real_cost)` for the estimated cost +pub struct FSqrtSum(pub TPotentialFn, PhantomData) +where + TPotentialFn: PotentialFn; + +impl Metric for FSqrtSum +where + TPotentialFn: PotentialFn, + TPotentialFn::Potential: FromPrimitive + Real, +{ + type Potential = TPotentialFn::Potential; + type EstimatedCost = TPotentialFn::Potential; + + fn new(goal: &TState) -> Self { + Self(PotentialFn::new(goal), Default::default()) + } + + fn get_potential(&mut self, state: &TState) -> Self::Potential { + self.0.get_potential(state) + } + + fn estimated_cost(cost: super::cost::Cost) -> Self::EstimatedCost { + let real_cost = + Self::EstimatedCost::from_usize(cost.real_cost).expect("Cannot convert real cost"); + cost.potential + real_cost.sqrt() + } +} diff --git a/softwareComponents/voxelReconfig/src/reconfig/test/astar_opt_test.rs b/softwareComponents/voxelReconfig/src/reconfig/test/astar_opt_test.rs deleted file mode 100644 index a0b2e1dea..000000000 --- a/softwareComponents/voxelReconfig/src/reconfig/test/astar_opt_test.rs +++ /dev/null @@ -1,129 +0,0 @@ -use super::{validate_norm_voxel_world, validate_reconfig_path}; -use crate::algs::astar::opt::compute_path; -use crate::atoms::{Axis, Direction}; -use crate::reconfig::metric::{naive::NaiveMetric, ZeroMetric}; -use crate::reconfig::voxel_worlds_graph::VoxelWorldsGraph; -use crate::voxel::{JointPosition, Voxel}; -use crate::voxel_world::as_one_of_norm_eq_world; -use crate::voxel_world::impls::{MapVoxelWorld, MatrixVoxelWorld, SortvecVoxelWorld}; -use crate::voxel_world::NormVoxelWorld; -use std::rc::Rc; - -#[test] -pub fn test_reconfig_no_steps_map() { - test_reconfig_no_steps::>(); -} -#[test] -pub fn test_reconfig_no_steps_matrix() { - test_reconfig_no_steps::>(); -} -#[test] -pub fn test_reconfig_no_steps_sortvec() { - test_reconfig_no_steps::>(); -} -fn test_reconfig_no_steps() -where - TWorld: NormVoxelWorld + Eq + std::hash::Hash + Clone + std::fmt::Debug, - TWorld::IndexType: num::Integer + std::hash::Hash, -{ - let (world, _) = TWorld::from_voxels([ - ( - [num::zero(); 3].into(), - Voxel::new_with( - Direction::new_with(Axis::X, true), - true, - JointPosition::Plus90, - ), - ), - ( - [num::one(), num::zero(), num::zero()].into(), - Voxel::new_with( - Direction::new_with(Axis::X, false), - false, - JointPosition::Minus90, - ), - ), - ]) - .unwrap(); - validate_norm_voxel_world(&world); - - let world = Rc::new(as_one_of_norm_eq_world(world)); - let expected_path = &[world.clone()]; - - let result = compute_path::, ZeroMetric>(&world, &world).unwrap(); - validate_reconfig_path(&result, expected_path); - - let result = compute_path::, NaiveMetric<_>>(&world, &world).unwrap(); - validate_reconfig_path(&result, expected_path); -} - -#[test] -pub fn test_reconfig_one_step_map() { - test_reconfig_one_step::>(); -} -#[test] -pub fn test_reconfig_one_step_matrix() { - test_reconfig_one_step::>(); -} -#[test] -pub fn test_reconfig_one_step_sortvec() { - test_reconfig_one_step::>(); -} -pub fn test_reconfig_one_step() -where - TWorld: NormVoxelWorld + Eq + std::hash::Hash + Clone + std::fmt::Debug, - TWorld::IndexType: num::Integer + std::hash::Hash, -{ - let (init_world, _) = TWorld::from_voxels([ - ( - [num::zero(); 3].into(), - Voxel::new_with( - Direction::new_with(Axis::X, true), - true, - JointPosition::Plus90, - ), - ), - ( - [num::one(), num::zero(), num::zero()].into(), - Voxel::new_with( - Direction::new_with(Axis::X, false), - false, - JointPosition::Minus90, - ), - ), - ]) - .unwrap(); - validate_norm_voxel_world(&init_world); - let (goal_world, _) = TWorld::from_voxels([ - ( - [num::zero(); 3].into(), - Voxel::new_with( - Direction::new_with(Axis::X, true), - true, - JointPosition::Plus90, - ), - ), - ( - [num::one(), num::zero(), num::zero()].into(), - Voxel::new_with( - Direction::new_with(Axis::X, false), - true, - JointPosition::Minus90, - ), - ), - ]) - .unwrap(); - validate_norm_voxel_world(&goal_world); - - let init_world = Rc::new(as_one_of_norm_eq_world(init_world)); - let goal_world = Rc::new(as_one_of_norm_eq_world(goal_world)); - let expected_path = &[init_world.clone(), goal_world.clone()]; - - let result = - compute_path::, ZeroMetric>(&init_world, &goal_world).unwrap(); - validate_reconfig_path(&result, expected_path); - - let result = - compute_path::, NaiveMetric<_>>(&init_world, &goal_world).unwrap(); - validate_reconfig_path(&result, expected_path); -} diff --git a/softwareComponents/voxelReconfig/src/reconfig/test/astar_test.rs b/softwareComponents/voxelReconfig/src/reconfig/test/astar_test.rs deleted file mode 100644 index a08a94712..000000000 --- a/softwareComponents/voxelReconfig/src/reconfig/test/astar_test.rs +++ /dev/null @@ -1,129 +0,0 @@ -use super::{validate_norm_voxel_world, validate_reconfig_path}; -use crate::algs::astar::compute_path; -use crate::atoms::{Axis, Direction}; -use crate::reconfig::metric::{naive::NaiveMetric, ZeroMetric}; -use crate::reconfig::voxel_worlds_graph::VoxelWorldsGraph; -use crate::voxel::{JointPosition, Voxel}; -use crate::voxel_world::as_one_of_norm_eq_world; -use crate::voxel_world::impls::{MapVoxelWorld, MatrixVoxelWorld, SortvecVoxelWorld}; -use crate::voxel_world::NormVoxelWorld; -use std::rc::Rc; - -#[test] -pub fn test_reconfig_no_steps_map() { - test_reconfig_no_steps::>(); -} -#[test] -pub fn test_reconfig_no_steps_matrix() { - test_reconfig_no_steps::>(); -} -#[test] -pub fn test_reconfig_no_steps_sortvec() { - test_reconfig_no_steps::>(); -} -fn test_reconfig_no_steps() -where - TWorld: NormVoxelWorld + Eq + std::hash::Hash + Clone + std::fmt::Debug, - TWorld::IndexType: num::Integer + std::hash::Hash, -{ - let (world, _) = TWorld::from_voxels([ - ( - [num::zero(); 3].into(), - Voxel::new_with( - Direction::new_with(Axis::X, true), - true, - JointPosition::Plus90, - ), - ), - ( - [num::one(), num::zero(), num::zero()].into(), - Voxel::new_with( - Direction::new_with(Axis::X, false), - false, - JointPosition::Minus90, - ), - ), - ]) - .unwrap(); - validate_norm_voxel_world(&world); - - let world = Rc::new(as_one_of_norm_eq_world(world)); - let expected_path = &[world.clone()]; - - let result = compute_path::, ZeroMetric>(&world, &world).unwrap(); - validate_reconfig_path(&result, expected_path); - - let result = compute_path::, NaiveMetric<_>>(&world, &world).unwrap(); - validate_reconfig_path(&result, expected_path); -} - -#[test] -pub fn test_reconfig_one_step_map() { - test_reconfig_one_step::>(); -} -#[test] -pub fn test_reconfig_one_step_matrix() { - test_reconfig_one_step::>(); -} -#[test] -pub fn test_reconfig_one_step_sortvec() { - test_reconfig_one_step::>(); -} -pub fn test_reconfig_one_step() -where - TWorld: NormVoxelWorld + Eq + std::hash::Hash + Clone + std::fmt::Debug, - TWorld::IndexType: num::Integer + std::hash::Hash, -{ - let (init_world, _) = TWorld::from_voxels([ - ( - [num::zero(); 3].into(), - Voxel::new_with( - Direction::new_with(Axis::X, true), - true, - JointPosition::Plus90, - ), - ), - ( - [num::one(), num::zero(), num::zero()].into(), - Voxel::new_with( - Direction::new_with(Axis::X, false), - false, - JointPosition::Minus90, - ), - ), - ]) - .unwrap(); - validate_norm_voxel_world(&init_world); - let (goal_world, _) = TWorld::from_voxels([ - ( - [num::zero(); 3].into(), - Voxel::new_with( - Direction::new_with(Axis::X, true), - true, - JointPosition::Plus90, - ), - ), - ( - [num::one(), num::zero(), num::zero()].into(), - Voxel::new_with( - Direction::new_with(Axis::X, false), - true, - JointPosition::Minus90, - ), - ), - ]) - .unwrap(); - validate_norm_voxel_world(&goal_world); - - let init_world = Rc::new(as_one_of_norm_eq_world(init_world)); - let goal_world = Rc::new(as_one_of_norm_eq_world(goal_world)); - let expected_path = &[init_world.clone(), goal_world.clone()]; - - let result = - compute_path::, ZeroMetric>(&init_world, &goal_world).unwrap(); - validate_reconfig_path(&result, expected_path); - - let result = - compute_path::, NaiveMetric<_>>(&init_world, &goal_world).unwrap(); - validate_reconfig_path(&result, expected_path); -} diff --git a/softwareComponents/voxelReconfig/src/reconfig/test/bfs_test.rs b/softwareComponents/voxelReconfig/src/reconfig/test/bfs_test.rs deleted file mode 100644 index b6656b271..000000000 --- a/softwareComponents/voxelReconfig/src/reconfig/test/bfs_test.rs +++ /dev/null @@ -1,126 +0,0 @@ -use super::validate_norm_voxel_world; -use crate::algs::bfs::compute_path; -use crate::atoms::{Axis, Direction}; -use crate::reconfig::voxel_worlds_graph::VoxelWorldsGraph; -use crate::voxel::{JointPosition, Voxel}; -use crate::voxel_world::impls::{MapVoxelWorld, MatrixVoxelWorld, SortvecVoxelWorld}; -use crate::voxel_world::{as_one_of_norm_eq_world, NormVoxelWorld}; -use std::rc::Rc; - -#[test] -pub fn test_reconfig_no_steps_map() { - test_reconfig_no_steps::>(); -} -#[test] -pub fn test_reconfig_no_steps_matrix() { - test_reconfig_no_steps::>(); -} -#[test] -pub fn test_reconfig_no_steps_sortvec() { - test_reconfig_no_steps::>(); -} -fn test_reconfig_no_steps() -where - TWorld: NormVoxelWorld + Eq + std::hash::Hash + Clone + std::fmt::Debug, - TWorld::IndexType: num::Integer + std::hash::Hash, -{ - let (world, _) = TWorld::from_voxels([ - ( - [num::zero(); 3].into(), - Voxel::new_with( - Direction::new_with(Axis::X, true), - true, - JointPosition::Plus90, - ), - ), - ( - [num::one(), num::zero(), num::zero()].into(), - Voxel::new_with( - Direction::new_with(Axis::X, false), - false, - JointPosition::Minus90, - ), - ), - ]) - .unwrap(); - let world = as_one_of_norm_eq_world(world); - validate_norm_voxel_world(&world); - - let result = compute_path::>(&world, &world).unwrap(); - assert_eq!(result.len(), 1); - result - .iter() - .map(AsRef::as_ref) - .for_each(validate_norm_voxel_world); - assert_eq!(&result, &vec![Rc::new(world)]); -} - -#[test] -pub fn test_reconfig_one_step_map() { - test_reconfig_one_step::>(); -} -#[test] -pub fn test_reconfig_one_step_matrix() { - test_reconfig_one_step::>(); -} -#[test] -pub fn test_reconfig_one_step_sortvec() { - test_reconfig_one_step::>(); -} -pub fn test_reconfig_one_step() -where - TWorld: NormVoxelWorld + Eq + std::hash::Hash + Clone + std::fmt::Debug, - TWorld::IndexType: num::Integer + std::hash::Hash, -{ - let (init_world, _) = TWorld::from_voxels([ - ( - [num::zero(); 3].into(), - Voxel::new_with( - Direction::new_with(Axis::X, true), - true, - JointPosition::Plus90, - ), - ), - ( - [num::one(), num::zero(), num::zero()].into(), - Voxel::new_with( - Direction::new_with(Axis::X, false), - false, - JointPosition::Minus90, - ), - ), - ]) - .unwrap(); - let (goal_world, _) = TWorld::from_voxels([ - ( - [num::zero(); 3].into(), - Voxel::new_with( - Direction::new_with(Axis::X, true), - true, - JointPosition::Plus90, - ), - ), - ( - [num::one(), num::zero(), num::zero()].into(), - Voxel::new_with( - Direction::new_with(Axis::X, false), - true, - JointPosition::Minus90, - ), - ), - ]) - .unwrap(); - - let init_world = as_one_of_norm_eq_world(init_world); - let goal_world = as_one_of_norm_eq_world(goal_world); - validate_norm_voxel_world(&init_world); - validate_norm_voxel_world(&goal_world); - - let result = compute_path::>(&init_world, &goal_world).unwrap(); - assert_eq!(result.len(), 2); - result - .iter() - .map(AsRef::::as_ref) - .for_each(validate_norm_voxel_world); - assert_eq!(&result, &vec![Rc::new(init_world), Rc::new(goal_world)]); -} diff --git a/softwareComponents/voxelReconfig/src/reconfig/test/mod.rs b/softwareComponents/voxelReconfig/src/reconfig/test/mod.rs index 8d8a378b3..27fc7e82c 100644 --- a/softwareComponents/voxelReconfig/src/reconfig/test/mod.rs +++ b/softwareComponents/voxelReconfig/src/reconfig/test/mod.rs @@ -1,11 +1,20 @@ -mod astar_opt_test; -mod astar_test; -mod bfs_test; +mod test_paths; +use crate::algs; +use crate::algs::{astar::AstarAlgInfo, bfs::BfsAlgInfo}; use crate::pos::{minimal_pos_hull, SizeRanges}; -use crate::voxel_world::{NormVoxelWorld, VoxelWorld}; +use crate::reconfig::metric::{naive::NaiveMetric, ZeroMetric}; +use crate::reconfig::voxel_worlds_graph::VoxelWorldsGraph; +use crate::voxel::PosVoxel; +use crate::voxel_world::impls::{MapVoxelWorld, MatrixVoxelWorld, SortvecVoxelWorld}; +use crate::voxel_world::{ + as_one_of_norm_eq_world_with_rot, NormVoxelWorld, VoxelWorld, WorldRotation, +}; use iter_fixed::IntoIteratorFixed; +use multicode::multicode; +use std::assert_matches::assert_matches; use std::rc::Rc; +use test_paths::{no_steps_path, one_step_path}; pub fn validate_voxel_world(world: &impl VoxelWorld) { for (pos, voxel) in world.all_voxels() { @@ -43,3 +52,55 @@ where .for_each(validate_norm_voxel_world); assert_eq!(path, expected_path); } + +pub fn test_path_exact( + compute_path: impl FnOnce(&TWorld, &TWorld) -> Result>, algs::Error>, + path: IWorlds, +) where + TWorld: NormVoxelWorld + std::fmt::Debug + Eq, + IWorlds: IntoIterator>>, +{ + let path = path + .into_iter() + .map(|voxels| { + let world = TWorld::from_voxels(voxels); + assert_matches!(world, Ok(_)); + let (world, origin) = world.unwrap(); + assert_eq!(origin.as_array(), [num::zero(); 3]); + validate_norm_voxel_world(&world); + let (world, rotation) = as_one_of_norm_eq_world_with_rot(world); + assert_eq!(rotation, WorldRotation::identity()); + let world = Rc::new(world); + validate_norm_voxel_world(world.as_ref()); + world + }) + .collect::>(); + assert!(!path.is_empty()); + + let result = compute_path(path.first().unwrap(), path.last().unwrap()); + assert_matches!(result, Ok(_)); + validate_reconfig_path(&result.unwrap(), &path); +} + +#[multicode(TAlgInfo, [ + bfs: BfsAlgInfo<_>; + astar_zero: AstarAlgInfo<_, ZeroMetric>; + astar_naive: AstarAlgInfo<_, NaiveMetric<_>>; +])] +#[multicode(COMPUTE_PATH, [ + onedir: algs::onedir::compute_path; + bidir: algs::bidir::compute_path; +])] +#[multicode(TWorld, [ + matrix_i8: MatrixVoxelWorld; + map_i8: MapVoxelWorld; + sortvec_i8: SortvecVoxelWorld; +])] +#[multicode(PATH, [ + no_steps: no_steps_path(); + one_step: one_step_path(); +])] +#[test] +fn test_exact() { + test_path_exact(COMPUTE_PATH::, TAlgInfo>, PATH); +} diff --git a/softwareComponents/voxelReconfig/src/reconfig/test/test_paths.rs b/softwareComponents/voxelReconfig/src/reconfig/test/test_paths.rs new file mode 100644 index 000000000..609096b99 --- /dev/null +++ b/softwareComponents/voxelReconfig/src/reconfig/test/test_paths.rs @@ -0,0 +1,70 @@ +use crate::atoms::{Axis, Direction}; +use crate::voxel::{JointPosition, PosVoxel, Voxel}; + +pub fn no_steps_path() -> impl IntoIterator>> +where + TIndex: num::Num + Copy, +{ + [vec![ + ( + [num::zero(); 3].into(), + Voxel::new_with( + Direction::new_with(Axis::X, true), + true, + JointPosition::Plus90, + ), + ), + ( + [num::one(), num::zero(), num::zero()].into(), + Voxel::new_with( + Direction::new_with(Axis::X, false), + false, + JointPosition::Minus90, + ), + ), + ]] +} + +pub fn one_step_path() -> impl IntoIterator>> +where + TIndex: num::Num + Copy, +{ + [ + vec![ + ( + [num::zero(); 3].into(), + Voxel::new_with( + Direction::new_with(Axis::X, true), + true, + JointPosition::Plus90, + ), + ), + ( + [num::one(), num::zero(), num::zero()].into(), + Voxel::new_with( + Direction::new_with(Axis::X, false), + false, + JointPosition::Minus90, + ), + ), + ], + vec![ + ( + [num::zero(); 3].into(), + Voxel::new_with( + Direction::new_with(Axis::X, true), + true, + JointPosition::Plus90, + ), + ), + ( + [num::one(), num::zero(), num::zero()].into(), + Voxel::new_with( + Direction::new_with(Axis::X, false), + true, + JointPosition::Minus90, + ), + ), + ], + ] +} diff --git a/softwareComponents/voxelReconfig/src/voxel_world/mod.rs b/softwareComponents/voxelReconfig/src/voxel_world/mod.rs index 01b6038b2..bfc78190d 100644 --- a/softwareComponents/voxelReconfig/src/voxel_world/mod.rs +++ b/softwareComponents/voxelReconfig/src/voxel_world/mod.rs @@ -11,13 +11,13 @@ pub use centered::CenteredVoxelWorld; pub use rotated::{rotate_voxel, RotatedVoxelWorld}; pub use subworld::VoxelSubworld; pub use traits::{NormVoxelWorld, VoxelWorld}; +pub use world_rotation::WorldRotation; use crate::module_repr::get_other_body; use crate::pos::{minimal_pos_hull, Pos, SizeRanges, Sizes}; use crate::voxel::{get_other_body_pos, PosVoxel}; use iter_fixed::IntoIteratorFixed; use std::assert_matches::debug_assert_matches; -use world_rotation::WorldRotation; #[derive(Debug, Clone, amplify::Error)] pub enum InvalidVoxelWorldError { diff --git a/tools/voxel/Cargo.lock b/tools/voxel/Cargo.lock index e40db20c0..8e66ffc37 100644 --- a/tools/voxel/Cargo.lock +++ b/tools/voxel/Cargo.lock @@ -394,6 +394,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lapjv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7093f0ea0bab1f9b0cd14e68ed428d1bf8efa4fab86597e9490ccc98d9eec83" +dependencies = [ + "log", + "ndarray", + "num-traits", +] + [[package]] name = "libc" version = "0.2.148" @@ -463,6 +474,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "matrixmultiply" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" +dependencies = [ + "rawpointer", +] + [[package]] name = "modular-bitfield" version = "0.11.2" @@ -484,6 +504,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ndarray" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac06db03ec2f46ee0ecdca1a1c34a99c0d188a0d83439b84bf0cb4b386e4ab09" +dependencies = [ + "matrixmultiply", + "num-complex 0.2.4", + "num-integer", + "num-traits", + "rawpointer", +] + [[package]] name = "num" version = "0.4.1" @@ -491,7 +524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" dependencies = [ "num-bigint", - "num-complex", + "num-complex 0.4.4", "num-integer", "num-iter", "num-rational", @@ -509,6 +542,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.4" @@ -575,6 +618,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-float" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a540f3e3b3d7929c884e46d093d344e4e5bdeed54d08bf007df50c93cc85d5" +dependencies = [ + "num-traits", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -616,6 +668,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -661,10 +719,13 @@ dependencies = [ "enum-iterator", "iter_fixed", "itertools", + "lapjv", "litemap", "log", "modular-bitfield", + "ndarray", "num", + "ordered-float 4.1.0", "rs-graph", "rustc-hash", "serde", @@ -717,7 +778,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ - "ordered-float", + "ordered-float 2.10.0", "serde", ] diff --git a/tools/voxel/rofi-voxel b/tools/voxel/rofi-voxel index 8ccd826d2..5fc2a1735 100755 --- a/tools/voxel/rofi-voxel +++ b/tools/voxel/rofi-voxel @@ -246,9 +246,14 @@ def check_input_files(files: List[str]): [ "bfs", "astar-zero", - "astar-zero-opt", "astar-naive", "astar-naive-opt", + "astar-assg-posjoint", + "astar-assg-posjoint-opt", + "astar-assg-pos", + "astar-assg-pos-opt", + "astar-assg-joint", + "astar-assg-joint-opt", ] ), default="bfs", @@ -263,6 +268,7 @@ def check_input_files(files: List[str]): show_default=True, help="Use connections with specified representation", ) +@click.option("--bidir", "-b", is_flag=True, help="Use bidirectional path finding") @click.option( "--log-counters", "-l", @@ -309,6 +315,7 @@ def rofi_voxel( repr: str, alg: str, connections: Optional[str], + bidir: bool, log_counters: Optional[str], short: bool, verbose: int, @@ -333,6 +340,7 @@ def rofi_voxel( format if goal_format is None else goal_format, args=["--repr", repr, "--alg", alg] + ([] if connections is None else ["--connections", connections]) + + ([] if not bidir else ["--bidir"]) + ([] if log_counters is None else ["--log-counters", log_counters]) + ([] if not short else ["--short"]), output=output, diff --git a/tools/voxel/src/bin/rofi-voxel_full_traversal_bfs.rs b/tools/voxel/src/bin/rofi-voxel_full_traversal_bfs.rs index f9f6b56df..3a5cb0609 100644 --- a/tools/voxel/src/bin/rofi-voxel_full_traversal_bfs.rs +++ b/tools/voxel/src/bin/rofi-voxel_full_traversal_bfs.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use rofi_voxel_cli::{FileErrOutput, FileInput, LogArgs}; use rofi_voxel_reconfig::counters::Counter; use rofi_voxel_reconfig::reconfig::all_next_worlds_norm; -use rofi_voxel_reconfig::voxel_world::impls::MapVoxelWorld; +use rofi_voxel_reconfig::voxel_world::impls::SortvecVoxelWorld; use rofi_voxel_reconfig::voxel_world::NormVoxelWorld; use rofi_voxel_reconfig::voxel_world::{as_one_of_norm_eq_world, normalized_eq_worlds}; use rofi_voxel_reconfig::voxel_world::{check_voxel_world, is_normalized}; @@ -229,7 +229,7 @@ fn main() -> Result<()> { }; let world = args.get_world()?; - let (world, _min_pos) = world.to_world_and_min_pos::>()?; + let (world, _min_pos) = world.to_world_and_min_pos::>()?; assert_matches!(check_voxel_world(&world), Ok(())); let results_data = compute_steps(world, args.max())?; diff --git a/tools/voxel/src/bin/rofi-voxel_main.rs b/tools/voxel/src/bin/rofi-voxel_main.rs index c7f952fd2..4498cc55d 100644 --- a/tools/voxel/src/bin/rofi-voxel_main.rs +++ b/tools/voxel/src/bin/rofi-voxel_main.rs @@ -3,7 +3,13 @@ use clap::Parser; use rofi_voxel_cli::{FileErrOutput, FileInput, LogArgs}; use rofi_voxel_reconfig::algs; use rofi_voxel_reconfig::counters::Counter; -use rofi_voxel_reconfig::reconfig::metric::{naive::NaiveMetric, ZeroMetric}; +use rofi_voxel_reconfig::reconfig::metric::assignment::{ + JointAssgMetric, JointAssgPotFn, PosAssgMetric, PosAssgPotFn, PosJointAssgMetric, + PosJointAssgPotFn, +}; +use rofi_voxel_reconfig::reconfig::metric::naive::{NaiveMetric, NaivePotential}; +use rofi_voxel_reconfig::reconfig::metric::potential_fn::Sum; +use rofi_voxel_reconfig::reconfig::metric::ZeroMetric; use rofi_voxel_reconfig::reconfig::voxel_worlds_graph::VoxelWorldsGraph; use rofi_voxel_reconfig::voxel_world::as_one_of_norm_eq_world; use rofi_voxel_reconfig::voxel_world::impls::{MapVoxelWorld, MatrixVoxelWorld, SortvecVoxelWorld}; @@ -38,8 +44,13 @@ enum AlgorithmType { Bfs, AstarZero, AstarNaive, - AstarZeroOpt, AstarNaiveOpt, + AstarAssgPosjoint, + AstarAssgPosjointOpt, + AstarAssgPos, + AstarAssgPosOpt, + AstarAssgJoint, + AstarAssgJointOpt, } /// Compute RoFI reconfiguration from init to goal by using voxels @@ -55,6 +66,9 @@ struct Cli { /// Algorithm for reconfiguration #[arg(short, long, default_value_t = AlgorithmType::Bfs)] alg: AlgorithmType, + /// Use bidirectional path finding + #[arg(short, long, default_value_t = false)] + bidir: bool, /// Use connections with specified representation #[arg(short, long)] connections: Option, @@ -95,26 +109,117 @@ fn run_reconfig_alg( init: &TWorld, goal: &TWorld, alg_type: AlgorithmType, + bidir: bool, +) -> Result>, algs::Error> +where + TWorld: NormVoxelWorld + Eq + std::hash::Hash, + TWorld::IndexType: num::Integer + std::hash::Hash + num::ToPrimitive, +{ + if bidir { + run_reconfig_alg_bidir(init, goal, alg_type) + } else { + run_reconfig_alg_onedir(init, goal, alg_type) + } +} +fn run_reconfig_alg_onedir( + init: &TWorld, + goal: &TWorld, + alg_type: AlgorithmType, +) -> Result>, algs::Error> +where + TWorld: NormVoxelWorld + Eq + std::hash::Hash, + TWorld::IndexType: num::Integer + std::hash::Hash + num::ToPrimitive, +{ + use algs::onedir::compute_path; + type StateGraph = VoxelWorldsGraph; + use algs::{astar::AstarAlgInfo, bfs::BfsAlgInfo}; + + match alg_type { + AlgorithmType::Bfs => compute_path::, BfsAlgInfo<_>>(init, goal), + + AlgorithmType::AstarZero => { + compute_path::, AstarAlgInfo<_, ZeroMetric>>(init, goal) + } + AlgorithmType::AstarNaive => { + compute_path::, AstarAlgInfo<_, NaiveMetric<_>>>(init, goal) + } + AlgorithmType::AstarAssgPos => { + compute_path::, AstarAlgInfo<_, PosAssgMetric<_>>>(init, goal) + } + AlgorithmType::AstarAssgJoint => { + compute_path::, AstarAlgInfo<_, JointAssgMetric<_>>>(init, goal) + } + AlgorithmType::AstarAssgPosjoint => { + compute_path::, AstarAlgInfo<_, PosJointAssgMetric<_>>>(init, goal) + } + + AlgorithmType::AstarNaiveOpt => compute_path::< + StateGraph<_>, + AstarAlgInfo<_, Sum<_, NaivePotential<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgPosOpt => compute_path::< + StateGraph<_>, + AstarAlgInfo<_, Sum<_, PosAssgPotFn<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgJointOpt => compute_path::< + StateGraph<_>, + AstarAlgInfo<_, Sum<_, JointAssgPotFn<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgPosjointOpt => compute_path::< + StateGraph<_>, + AstarAlgInfo<_, Sum<_, PosJointAssgPotFn<_>>, true>, + >(init, goal), + } +} + +fn run_reconfig_alg_bidir( + init: &TWorld, + goal: &TWorld, + alg_type: AlgorithmType, ) -> Result>, algs::Error> where TWorld: NormVoxelWorld + Eq + std::hash::Hash, - TWorld::IndexType: num::Integer + std::hash::Hash, + TWorld::IndexType: num::Integer + std::hash::Hash + num::ToPrimitive, { + use algs::bidir::compute_path; type StateGraph = VoxelWorldsGraph; + use algs::{astar::AstarAlgInfo, bfs::BfsAlgInfo}; + match alg_type { - AlgorithmType::Bfs => algs::bfs::compute_path::>(init, goal), + AlgorithmType::Bfs => compute_path::, BfsAlgInfo<_>>(init, goal), + AlgorithmType::AstarZero => { - algs::astar::compute_path::, ZeroMetric>(init, goal) + compute_path::, AstarAlgInfo<_, ZeroMetric>>(init, goal) } AlgorithmType::AstarNaive => { - algs::astar::compute_path::, NaiveMetric<_>>(init, goal) + compute_path::, AstarAlgInfo<_, NaiveMetric<_>>>(init, goal) } - AlgorithmType::AstarZeroOpt => { - algs::astar::opt::compute_path::, ZeroMetric>(init, goal) + AlgorithmType::AstarAssgPos => { + compute_path::, AstarAlgInfo<_, PosAssgMetric<_>>>(init, goal) } - AlgorithmType::AstarNaiveOpt => { - algs::astar::opt::compute_path::, NaiveMetric<_>>(init, goal) + AlgorithmType::AstarAssgJoint => { + compute_path::, AstarAlgInfo<_, JointAssgMetric<_>>>(init, goal) } + AlgorithmType::AstarAssgPosjoint => { + compute_path::, AstarAlgInfo<_, PosJointAssgMetric<_>>>(init, goal) + } + + AlgorithmType::AstarNaiveOpt => compute_path::< + StateGraph<_>, + AstarAlgInfo<_, Sum<_, NaivePotential<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgPosOpt => compute_path::< + StateGraph<_>, + AstarAlgInfo<_, Sum<_, PosAssgPotFn<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgJointOpt => compute_path::< + StateGraph<_>, + AstarAlgInfo<_, Sum<_, JointAssgPotFn<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgPosjointOpt => compute_path::< + StateGraph<_>, + AstarAlgInfo<_, Sum<_, PosJointAssgPotFn<_>>, true>, + >(init, goal), } } @@ -122,30 +227,129 @@ fn run_reconfig_alg_with_connections( init: &VoxelWorldWithConnections, goal: &VoxelWorldWithConnections, alg_type: AlgorithmType, + bidir: bool, +) -> Result>>, algs::Error> +where + TWorld: NormVoxelWorld + Eq + std::hash::Hash, + TWorld::IndexType: num::Integer + std::hash::Hash + num::ToPrimitive, + TConnections: Connections + Eq + std::hash::Hash + Clone, + TWorld: 'static, + TConnections: 'static, +{ + if bidir { + run_reconfig_alg_with_connections_bidir(init, goal, alg_type) + } else { + run_reconfig_alg_with_connections_onedir(init, goal, alg_type) + } +} +fn run_reconfig_alg_with_connections_onedir( + init: &VoxelWorldWithConnections, + goal: &VoxelWorldWithConnections, + alg_type: AlgorithmType, ) -> Result>>, algs::Error> where TWorld: NormVoxelWorld + Eq + std::hash::Hash, - TWorld::IndexType: num::Integer + std::hash::Hash, + TWorld::IndexType: num::Integer + std::hash::Hash + num::ToPrimitive, TConnections: Connections + Eq + std::hash::Hash + Clone, TWorld: 'static, TConnections: 'static, { - type StateGraph = VoxelWorldsWithConnectionsGraph; + use algs::onedir::compute_path; + type StateGraph = VoxelWorldsWithConnectionsGraph; + use algs::bfs::BfsAlgInfo; + type AstarAlgInfo = + algs::astar::AstarAlgInfo, OPTIMAL>; + match alg_type { - AlgorithmType::Bfs => algs::bfs::compute_path::>(init, goal), + AlgorithmType::Bfs => compute_path::, BfsAlgInfo<_>>(init, goal), + AlgorithmType::AstarZero => { - algs::astar::compute_path::, ZeroMetric>(init, goal) + compute_path::, AstarAlgInfo<_, ZeroMetric>>(init, goal) + } + AlgorithmType::AstarNaive => { + compute_path::, AstarAlgInfo<_, NaiveMetric<_>>>(init, goal) + } + AlgorithmType::AstarAssgPos => { + compute_path::, AstarAlgInfo<_, PosAssgMetric<_>>>(init, goal) + } + AlgorithmType::AstarAssgJoint => { + compute_path::, AstarAlgInfo<_, JointAssgMetric<_>>>(init, goal) + } + AlgorithmType::AstarAssgPosjoint => { + compute_path::, AstarAlgInfo<_, PosJointAssgMetric<_>>>(init, goal) } - AlgorithmType::AstarNaive => algs::astar::compute_path::< + + AlgorithmType::AstarNaiveOpt => compute_path::< + StateGraph<_, _>, + AstarAlgInfo<_, Sum<_, NaivePotential<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgPosOpt => compute_path::< + StateGraph<_, _>, + AstarAlgInfo<_, Sum<_, PosAssgPotFn<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgJointOpt => compute_path::< + StateGraph<_, _>, + AstarAlgInfo<_, Sum<_, JointAssgPotFn<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgPosjointOpt => compute_path::< StateGraph<_, _>, - WorldMetricWrapper>, + AstarAlgInfo<_, Sum<_, PosJointAssgPotFn<_>>, true>, >(init, goal), - AlgorithmType::AstarZeroOpt => { - algs::astar::opt::compute_path::, ZeroMetric>(init, goal) + } +} + +fn run_reconfig_alg_with_connections_bidir( + init: &VoxelWorldWithConnections, + goal: &VoxelWorldWithConnections, + alg_type: AlgorithmType, +) -> Result>>, algs::Error> +where + TWorld: NormVoxelWorld + Eq + std::hash::Hash, + TWorld::IndexType: num::Integer + std::hash::Hash + num::ToPrimitive, + TConnections: Connections + Eq + std::hash::Hash + Clone, + TWorld: 'static, + TConnections: 'static, +{ + use algs::bidir::compute_path; + type StateGraph = VoxelWorldsWithConnectionsGraph; + use algs::bfs::BfsAlgInfo; + type AstarAlgInfo = + algs::astar::AstarAlgInfo, OPTIMAL>; + + match alg_type { + AlgorithmType::Bfs => compute_path::, BfsAlgInfo<_>>(init, goal), + + AlgorithmType::AstarZero => { + compute_path::, AstarAlgInfo<_, ZeroMetric>>(init, goal) + } + AlgorithmType::AstarNaive => { + compute_path::, AstarAlgInfo<_, NaiveMetric<_>>>(init, goal) } - AlgorithmType::AstarNaiveOpt => algs::astar::opt::compute_path::< + AlgorithmType::AstarAssgPos => { + compute_path::, AstarAlgInfo<_, PosAssgMetric<_>>>(init, goal) + } + AlgorithmType::AstarAssgJoint => { + compute_path::, AstarAlgInfo<_, JointAssgMetric<_>>>(init, goal) + } + AlgorithmType::AstarAssgPosjoint => { + compute_path::, AstarAlgInfo<_, PosJointAssgMetric<_>>>(init, goal) + } + + AlgorithmType::AstarNaiveOpt => compute_path::< StateGraph<_, _>, - WorldMetricWrapper>, + AstarAlgInfo<_, Sum<_, NaivePotential<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgPosOpt => compute_path::< + StateGraph<_, _>, + AstarAlgInfo<_, Sum<_, PosAssgPotFn<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgJointOpt => compute_path::< + StateGraph<_, _>, + AstarAlgInfo<_, Sum<_, JointAssgPotFn<_>>, true>, + >(init, goal), + AlgorithmType::AstarAssgPosjointOpt => compute_path::< + StateGraph<_, _>, + AstarAlgInfo<_, Sum<_, PosJointAssgPotFn<_>>, true>, >(init, goal), } } @@ -155,10 +359,11 @@ fn run_voxel_reconfig( goal: rofi_voxel_reconfig::serde::VoxelWorld, connections_repr: Option, alg_type: AlgorithmType, + bidir: bool, ) -> Result>> where TWorld: NormVoxelWorld + Eq + std::hash::Hash, - TWorld::IndexType: 'static + num::Integer + std::hash::Hash + Send + Sync, + TWorld::IndexType: 'static + num::Integer + std::hash::Hash + num::ToPrimitive + Send + Sync, TWorld: 'static, { let (init, _min_pos) = init.to_world_and_min_pos()?; @@ -169,7 +374,7 @@ where match connections_repr { None => { - let reconfig_sequence = run_reconfig_alg::(&init, &goal, alg_type)?; + let reconfig_sequence = run_reconfig_alg::(&init, &goal, alg_type, bidir)?; Ok(reconfig_sequence .iter() .map(|world| rofi_voxel_reconfig::serde::VoxelWorld::from_world(world.as_ref())) @@ -181,6 +386,7 @@ where &VoxelWorldWithConnections::new_all_connected(init), &VoxelWorldWithConnections::new_all_connected(goal), alg_type, + bidir, )?; Ok(reconfig_sequence .iter() @@ -193,6 +399,7 @@ where &VoxelWorldWithConnections::new_all_connected(init), &VoxelWorldWithConnections::new_all_connected(goal), alg_type, + bidir, )?; Ok(reconfig_sequence .iter() @@ -220,15 +427,27 @@ fn main() -> Result<()> { let InputWorlds { init, goal } = args.get_worlds()?; let reconfig_sequence = match args.repr { - VoxelWorldRepr::Map => { - run_voxel_reconfig::>(init, goal, args.connections, args.alg)? - } - VoxelWorldRepr::Matrix => { - run_voxel_reconfig::>(init, goal, args.connections, args.alg)? - } - VoxelWorldRepr::Sortvec => { - run_voxel_reconfig::>(init, goal, args.connections, args.alg)? - } + VoxelWorldRepr::Map => run_voxel_reconfig::>( + init, + goal, + args.connections, + args.alg, + args.bidir, + )?, + VoxelWorldRepr::Matrix => run_voxel_reconfig::>( + init, + goal, + args.connections, + args.alg, + args.bidir, + )?, + VoxelWorldRepr::Sortvec => run_voxel_reconfig::>( + init, + goal, + args.connections, + args.alg, + args.bidir, + )?, }; if args.short {