From 7d03cdf9c1d864f84d4837d1c612acf8b5afe346 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 23 Jul 2023 10:55:04 +0200 Subject: [PATCH 01/39] Add iterator traits --- rust/src/iterator.rs | 121 ++++++++++++++++++++++++++++++++++++ rust/src/lib.rs | 4 +- rust/src/pcube/raw_pcube.rs | 6 +- 3 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 rust/src/iterator.rs diff --git a/rust/src/iterator.rs b/rust/src/iterator.rs new file mode 100644 index 0000000..97d2eeb --- /dev/null +++ b/rust/src/iterator.rs @@ -0,0 +1,121 @@ +use std::collections::{HashMap, HashSet}; + +use crate::pcube::RawPCube; + +/// An iterator over polycubes +pub trait PolycubeIterator: Iterator +where + Self: Sized, +{ + /// Returns true if all polycubes returned are in _some_ canonical + /// form. No guarantee is provided about the "type" of canonicality, + /// but if this returns `true` it is guaranteed that all cubes returned + /// by this iterator are in a form that can be used for comparison. + fn is_canonical(&self) -> bool; +} + +/// A trait for converting a [`PolycubeIterator`] into a [`UniquePolycubeIterator`]. +pub trait IntoUniquePolycubeIterator +where + Self: Sized + PolycubeIterator, +{ + fn into_unique(self) -> Option> { + UniquePolycubes::new(self) + } +} + +impl IntoUniquePolycubeIterator for T where T: PolycubeIterator {} + +/// An iterator over at least one variant of all unique polycubes +/// of size [`n`](AllPolycubeIterator::n). +/// +/// Iterators that implement this trait guarantee that they yield +/// at least one copy of all polycubes for size `n`, but do not guarantee +/// anything about the orientation of those cubes, nor about the amount +/// of times each copy of that polycubes occurs. +pub trait AllPolycubeIterator: PolycubeIterator { + /// The size of the polycubes returned by this + /// iterator. + fn n(&self) -> usize; +} + +/// An iterator over unique polycubes. +/// +/// Unique, in this context, means that no two items yielded by this +/// iterator have the same canonical form. +pub trait UniquePolycubeIterator: PolycubeIterator {} + +/// An iterator over all unique polycubes of size [`n`](AllPolycubeIterator::n). +pub trait AllUniquePolycubeIterator: UniquePolycubeIterator + AllPolycubeIterator {} + +/// A struct that yields only unique Polycubes. +pub struct UniquePolycubes { + stored: HashMap<(u8, u8, u8), HashSet>>, + inner: T, +} + +impl UniquePolycubes +where + T: PolycubeIterator, +{ + pub fn new(inner: T) -> Option { + if inner.is_canonical() { + Some(Self { + inner, + stored: HashMap::new(), + }) + } else { + None + } + } +} + +impl Iterator for UniquePolycubes +where + T: Iterator, +{ + type Item = RawPCube; + + fn next(&mut self) -> Option { + while let Some(v) = self.inner.next() { + let entry = self.stored.entry(v.dims()).or_default(); + + // No need to canonicalize, as a `UniquePolycubes` can only be constructed + // from a `PolycubeIterator` that is canonical. + + if entry.contains(v.data()) { + continue; + } + + if entry.insert(v.data().to_vec()) { + return Some(v); + } + } + + None + } +} + +impl PolycubeIterator for UniquePolycubes +where + T: PolycubeIterator, +{ + fn is_canonical(&self) -> bool { + let is_canonical = self.inner.is_canonical(); + assert!(is_canonical); + is_canonical + } +} + +impl UniquePolycubeIterator for UniquePolycubes where T: PolycubeIterator {} + +impl AllPolycubeIterator for UniquePolycubes +where + T: AllPolycubeIterator, +{ + fn n(&self) -> usize { + self.inner.n() + } +} + +impl AllUniquePolycubeIterator for UniquePolycubes where T: AllPolycubeIterator {} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 86c3040..e060410 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod test; -pub mod pcube; - pub mod naive_polycube; +pub mod pcube; +pub mod iterator; diff --git a/rust/src/pcube/raw_pcube.rs b/rust/src/pcube/raw_pcube.rs index 65f8863..768fa7c 100644 --- a/rust/src/pcube/raw_pcube.rs +++ b/rust/src/pcube/raw_pcube.rs @@ -33,10 +33,14 @@ impl RawPCube { (self.dim_1, self.dim_2, self.dim_3) } - pub fn data(&self) -> &[u8] { + pub fn data(&self) -> &Vec { &self.data } + pub fn into_data(self) -> Vec { + self.data + } + pub fn new(dim_1: u8, dim_2: u8, dim_3: u8, data: Vec) -> Option { let len = (dim_1 as usize) * (dim_2 as usize) * (dim_3 as usize); let byte_len = (len + 7) / 8; From 4cb5cee4668fdb6bd4f29f9a02c6038cce126915 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 23 Jul 2023 11:39:38 +0200 Subject: [PATCH 02/39] Update docs & fix iterators --- rust/src/iterator.rs | 7 ++-- rust/src/pcube/mod.rs | 94 ++++++++++++++++++++++++++++++------------- 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/rust/src/iterator.rs b/rust/src/iterator.rs index 97d2eeb..02f40eb 100644 --- a/rust/src/iterator.rs +++ b/rust/src/iterator.rs @@ -8,9 +8,10 @@ where Self: Sized, { /// Returns true if all polycubes returned are in _some_ canonical - /// form. No guarantee is provided about the "type" of canonicality, - /// but if this returns `true` it is guaranteed that all cubes returned - /// by this iterator are in a form that can be used for comparison. + /// form. No guarantee is provided about the type of canonicality, nor + /// about uniqueness. However, if this returns `true` it is guaranteed + /// that all cubes returned by this iterator are in a form that can be + /// used directly to check for uniqueness. fn is_canonical(&self) -> bool; } diff --git a/rust/src/pcube/mod.rs b/rust/src/pcube/mod.rs index 1d73b18..5cc6e23 100644 --- a/rust/src/pcube/mod.rs +++ b/rust/src/pcube/mod.rs @@ -13,6 +13,8 @@ mod compression; pub use compression::Compression; use compression::{Reader, Writer}; +use crate::iterator::PolycubeIterator; + const MAGIC: [u8; 4] = [0xCB, 0xEC, 0xCB, 0xEC]; /// A pcube file. @@ -22,7 +24,6 @@ pub struct PCubeFile where T: Read, { - had_error: bool, input: Reader, len: Option, cubes_read: usize, @@ -44,32 +45,7 @@ where } fn next(&mut self) -> Option { - if self.had_error { - return None; - } - - let next_cube = RawPCube::unpack(&mut self.input); - - let next_cube = match (next_cube, self.len) { - (Err(_), None) => return None, - (Err(e), Some(expected)) => { - if expected == self.cubes_read { - return None; - } else { - self.had_error = true; - let msg = format!( - "Expected {expected} cubes, but failed to read after {} cubes. Error: {e}", - self.cubes_read - ); - return Some(Err(std::io::Error::new(ErrorKind::InvalidData, msg))); - } - } - (Ok(c), _) => c, - }; - - self.cubes_read += 1; - - Some(Ok(next_cube)) + self.next() } } @@ -155,9 +131,35 @@ where len, cubes_read: 0, cubes_are_canonical: canonicalized, - had_error: false, }) } + + pub fn next(&mut self) -> Option> { + let next_cube = RawPCube::unpack(&mut self.input); + + match (next_cube, self.len) { + (Ok(c), _) => { + self.cubes_read += 1; + Some(Ok(c)) + } + (Err(_), None) => return None, + (Err(e), Some(expected)) => { + if expected == self.cubes_read { + return None; + } else { + let msg = format!( + "Expected {expected} cubes, but failed to read after {} cubes. Error: {e}", + self.cubes_read + ); + return Some(Err(std::io::Error::new(ErrorKind::InvalidData, msg))); + } + } + } + } + + pub fn into_iter(self) -> impl PolycubeIterator { + IgnoreErrorIter::new(self) + } } impl PCubeFile { @@ -269,3 +271,39 @@ impl PCubeFile { Ok(()) } } + +struct IgnoreErrorIter +where + T: Read, +{ + inner: PCubeFile, +} + +impl IgnoreErrorIter +where + T: Read, +{ + pub fn new(inner: PCubeFile) -> Self { + Self { inner } + } +} + +impl Iterator for IgnoreErrorIter +where + T: Read, +{ + type Item = RawPCube; + + fn next(&mut self) -> Option { + self.inner.next().map(|v| v.ok()).flatten() + } +} + +impl PolycubeIterator for IgnoreErrorIter +where + T: Read, +{ + fn is_canonical(&self) -> bool { + self.inner.canonical() + } +} From 704ee4f9ac37928f2206004c8b183429ccc9c1a0 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 23 Jul 2023 12:18:56 +0200 Subject: [PATCH 03/39] Add AllUnique iterator for pcube file --- rust/src/iterator.rs | 14 +++---- rust/src/pcube/mod.rs | 86 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/rust/src/iterator.rs b/rust/src/iterator.rs index 02f40eb..3474c0a 100644 --- a/rust/src/iterator.rs +++ b/rust/src/iterator.rs @@ -20,7 +20,7 @@ pub trait IntoUniquePolycubeIterator where Self: Sized + PolycubeIterator, { - fn into_unique(self) -> Option> { + fn into_unique(self) -> UniquePolycubes { UniquePolycubes::new(self) } } @@ -59,14 +59,10 @@ impl UniquePolycubes where T: PolycubeIterator, { - pub fn new(inner: T) -> Option { - if inner.is_canonical() { - Some(Self { - inner, - stored: HashMap::new(), - }) - } else { - None + pub fn new(inner: T) -> Self { + Self { + inner, + stored: HashMap::new(), } } } diff --git a/rust/src/pcube/mod.rs b/rust/src/pcube/mod.rs index 5cc6e23..8d9ce50 100644 --- a/rust/src/pcube/mod.rs +++ b/rust/src/pcube/mod.rs @@ -3,6 +3,7 @@ use std::{ fs::File, io::{ErrorKind, Read, Seek, Write}, + iter::Peekable, path::Path, }; @@ -13,7 +14,9 @@ mod compression; pub use compression::Compression; use compression::{Reader, Writer}; -use crate::iterator::PolycubeIterator; +use crate::iterator::{ + AllPolycubeIterator, AllUniquePolycubeIterator, PolycubeIterator, UniquePolycubeIterator, +}; const MAGIC: [u8; 4] = [0xCB, 0xEC, 0xCB, 0xEC]; @@ -160,6 +163,11 @@ where pub fn into_iter(self) -> impl PolycubeIterator { IgnoreErrorIter::new(self) } + + /// This is by no means guaranteed, but makes life a bit easier + pub fn assume_all_unique(self) -> impl AllUniquePolycubeIterator { + AllUnique::new(self) + } } impl PCubeFile { @@ -307,3 +315,79 @@ where self.inner.canonical() } } + +struct AllUnique +where + T: Read, +{ + n: usize, + canonical: bool, + inner: Peekable>, +} + +impl AllUnique +where + T: Read, +{ + pub fn new(inner: PCubeFile) -> Self { + let canonical = inner.canonical(); + let mut peekable = IgnoreErrorIter::new(inner).peekable(); + + let n = if let Some(peek) = peekable.peek() { + let mut n = 0; + let (x, y, z) = peek.dims(); + for x in 0..x { + for y in 0..y { + for z in 0..z { + if peek.get(x, y, z) { + n += 1; + } + } + } + } + + n + } else { + 0 + }; + + Self { + n, + canonical, + inner: peekable, + } + } +} + +impl Iterator for AllUnique +where + T: Read, +{ + type Item = RawPCube; + + fn next(&mut self) -> Option { + self.inner.next() + } +} + +impl PolycubeIterator for AllUnique +where + T: Read, +{ + fn is_canonical(&self) -> bool { + self.canonical + } +} + +impl UniquePolycubeIterator for AllUnique where T: Read {} + +impl AllPolycubeIterator for AllUnique +where + T: Read, +{ + fn n(&self) -> usize { + self.n + } +} + +impl AllUniquePolycubeIterator for AllUnique where T: Read {} From 041dc1070a334d1f2e42a8555f6fcaa2b56b3d84 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 23 Jul 2023 14:04:54 +0200 Subject: [PATCH 04/39] Completely trait-ified expansion for NaivePolyCube --- rust/src/cli.rs | 105 ++++++++++++++++++++-------- rust/src/iterator.rs | 73 ++++++++++++++++++- rust/src/naive_polycube/expander.rs | 6 +- rust/src/naive_polycube/mod.rs | 90 +++++++++++++++++++----- rust/src/pcube/mod.rs | 8 +++ 5 files changed, 232 insertions(+), 50 deletions(-) diff --git a/rust/src/cli.rs b/rust/src/cli.rs index 31b5a45..0f1d053 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -8,6 +8,10 @@ use std::{ use clap::{Args, Parser, Subcommand, ValueEnum}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use opencubes::{ + iterator::{ + indicatif::PolycubeProgressBarIter, AllPolycubeIterator, AllUniquePolycubeIterator, + PolycubeIterator, UniquePolycubeIterator, + }, naive_polycube::NaivePolyCube, pcube::{PCubeFile, RawPCube}, }; @@ -357,26 +361,63 @@ fn load_cache(n: usize) -> (Vec, usize) { (current, 2) } -fn unique_expansions( - mut expansion_fn: F, +struct AllUniques { + current: std::vec::IntoIter, + n: usize, +} + +impl Iterator for AllUniques { + type Item = RawPCube; + + fn next(&mut self) -> Option { + self.current.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.current.size_hint() + } +} + +impl ExactSizeIterator for AllUniques { + fn len(&self) -> usize { + self.current.len() + } +} + +impl PolycubeIterator for AllUniques { + fn is_canonical(&self) -> bool { + false + } + + fn n_hint(&self) -> Option { + Some(self.n) + } +} + +impl AllPolycubeIterator for AllUniques {} +impl UniquePolycubeIterator for AllUniques {} +impl AllUniquePolycubeIterator for AllUniques {} + +fn unique_expansions( + expansion_fn: F, use_cache: bool, n: usize, compression: Compression, current: Vec, calculate_from: usize, -) -> Vec +) -> Vec where - F: FnMut(&ProgressBar, std::slice::Iter<'_, NaivePolyCube>) -> Vec, + F: Fn(PolycubeProgressBarIter) -> O, + O: AllUniquePolycubeIterator, { if n == 0 { return Vec::new(); } - let mut current = current - .into_iter() - .map(NaivePolyCube::from) - .map(|v| v.canonical_form()) - .collect::>(); + let mut current = AllUniques { + current: current.into_iter(), + n: calculate_from, + }; for i in calculate_from..=n { let bar = make_bar(current.len() as u64); @@ -384,7 +425,8 @@ where let start = Instant::now(); - let next = expansion_fn(&bar, current.iter()); + let with_bar = PolycubeProgressBarIter::new(bar.clone(), current); + let next: Vec = expansion_fn(with_bar).collect(); bar.set_message(format!( "Found {} unique expansions (N = {i}) in {} ms.", @@ -398,17 +440,25 @@ where let name = &format!("cubes_{i}.pcube"); if !std::fs::File::open(name).is_ok() { println!("Saving {} cubes to cache file", next.len()); - PCubeFile::write_file(false, compression.into(), next.iter().map(Into::into), name) - .unwrap(); + PCubeFile::write_file( + false, + compression.into(), + next.iter().map(Clone::clone), + name, + ) + .unwrap(); } else { println!("Cache file already exists for N = {i}. Not overwriting."); } } - current = next; + current = AllUniques { + current: next.into_iter(), + n: i + 1, + }; } - current + current.collect() } pub fn enumerate(opts: &EnumerateOpts) { @@ -437,9 +487,7 @@ pub fn enumerate(opts: &EnumerateOpts) { let cubes_len = match (opts.mode, opts.no_parallelism) { (EnumerationMode::Standard, true) => { let cubes = unique_expansions( - |bar, current: std::slice::Iter<'_, NaivePolyCube>| { - NaivePolyCube::unique_expansions(bar, current) - }, + NaivePolyCube::unique_expansions, cache, n, opts.cache_compression, @@ -449,17 +497,18 @@ pub fn enumerate(opts: &EnumerateOpts) { cubes.len() } (EnumerationMode::Standard, false) => { - let cubes = unique_expansions( - |bar, current: std::slice::Iter<'_, NaivePolyCube>| { - NaivePolyCube::unique_expansions_rayon(bar, current) - }, - cache, - n, - opts.cache_compression, - seed_list, - startn, - ); - cubes.len() + todo!() + // let cubes = unique_expansions( + // |bar, current: std::slice::Iter<'_, NaivePolyCube>| { + // NaivePolyCube::unique_expansions_rayon(bar, current) + // }, + // cache, + // n, + // opts.cache_compression, + // seed_list, + // startn, + // ); + // cubes.len() } (EnumerationMode::RotationReduced, para) => { if n > 16 { diff --git a/rust/src/iterator.rs b/rust/src/iterator.rs index 3474c0a..68ed0f1 100644 --- a/rust/src/iterator.rs +++ b/rust/src/iterator.rs @@ -13,6 +13,8 @@ where /// that all cubes returned by this iterator are in a form that can be /// used directly to check for uniqueness. fn is_canonical(&self) -> bool; + + fn n_hint(&self) -> Option; } /// A trait for converting a [`PolycubeIterator`] into a [`UniquePolycubeIterator`]. @@ -37,7 +39,12 @@ impl IntoUniquePolycubeIterator for T where T: PolycubeIterator {} pub trait AllPolycubeIterator: PolycubeIterator { /// The size of the polycubes returned by this /// iterator. - fn n(&self) -> usize; + fn n(&self) -> usize { + let n_hint = self.n_hint(); + assert!(n_hint.is_some()); + // SAFETY: we asserted that n_hint is some + unsafe { n_hint.unwrap_unchecked() } + } } /// An iterator over unique polycubes. @@ -97,6 +104,10 @@ impl PolycubeIterator for UniquePolycubes where T: PolycubeIterator, { + fn n_hint(&self) -> Option { + self.inner.n_hint() + } + fn is_canonical(&self) -> bool { let is_canonical = self.inner.is_canonical(); assert!(is_canonical); @@ -116,3 +127,63 @@ where } impl AllUniquePolycubeIterator for UniquePolycubes where T: AllPolycubeIterator {} + +// TODO: hide this behind a feature? +pub mod indicatif { + use indicatif::{ProgressBar, ProgressIterator}; + + use super::{ + AllPolycubeIterator, AllUniquePolycubeIterator, PolycubeIterator, UniquePolycubeIterator, + }; + + pub struct PolycubeProgressBarIter { + inner: indicatif::ProgressBarIter, + is_canonical: bool, + n_hint: Option, + } + + impl PolycubeProgressBarIter + where + T: PolycubeIterator, + { + pub fn new(bar: ProgressBar, inner: T) -> Self { + let is_canonical = inner.is_canonical(); + let n_hint = inner.n_hint(); + + Self { + inner: inner.progress_with(bar), + is_canonical, + n_hint, + } + } + } + + impl Iterator for PolycubeProgressBarIter + where + T: Iterator, + { + type Item = T::Item; + + fn next(&mut self) -> Option { + self.inner.next() + } + } + + impl PolycubeIterator for PolycubeProgressBarIter + where + T: PolycubeIterator, + { + fn is_canonical(&self) -> bool { + self.is_canonical + } + + fn n_hint(&self) -> Option { + self.n_hint + } + } + + impl ExactSizeIterator for PolycubeProgressBarIter where T: ExactSizeIterator {} + impl AllPolycubeIterator for PolycubeProgressBarIter where T: AllPolycubeIterator {} + impl UniquePolycubeIterator for PolycubeProgressBarIter where T: UniquePolycubeIterator {} + impl AllUniquePolycubeIterator for PolycubeProgressBarIter where T: AllUniquePolycubeIterator {} +} diff --git a/rust/src/naive_polycube/expander.rs b/rust/src/naive_polycube/expander.rs index ef3d2b7..1a9dba4 100644 --- a/rust/src/naive_polycube/expander.rs +++ b/rust/src/naive_polycube/expander.rs @@ -6,7 +6,7 @@ use super::NaivePolyCube; type Sides = std::array::IntoIter<(usize, usize, usize), 6>; #[derive(Clone)] -struct ExpansionIterator { +pub struct ExpansionIterator { dim_1: usize, dim_2: usize, dim_3: usize, @@ -87,7 +87,7 @@ impl Iterator for ExpansionIterator { let mut next_cube = self.padded_cube.clone(); next_cube.set(d1, d2, d3).unwrap(); - return Some(next_cube); + return Some(next_cube.crop()); } else { return None; } @@ -96,7 +96,7 @@ impl Iterator for ExpansionIterator { } impl NaivePolyCube { - pub fn expand(&self) -> impl Iterator + Clone { + pub fn expand(&self) -> ExpansionIterator { ExpansionIterator { dim_1: 1, dim_2: 1, diff --git a/rust/src/naive_polycube/mod.rs b/rust/src/naive_polycube/mod.rs index 502f604..bf48d1a 100644 --- a/rust/src/naive_polycube/mod.rs +++ b/rust/src/naive_polycube/mod.rs @@ -1,11 +1,16 @@ //! A rather naive polycube implementation. -use std::collections::HashSet; +use std::{collections::HashSet, iter::FusedIterator}; use indicatif::ProgressBar; use parking_lot::RwLock; -use crate::pcube::RawPCube; +use crate::{ + iterator::{ + AllPolycubeIterator, AllUniquePolycubeIterator, PolycubeIterator, UniquePolycubeIterator, + }, + pcube::RawPCube, +}; mod expander; mod rotations; @@ -360,34 +365,83 @@ impl NaivePolyCube { /// Obtain a list of [`NaivePolyCube`]s representing all unique expansions of the /// items in `from_set`. - /// - // TODO: turn this into an iterator that yield unique expansions? - pub fn unique_expansions<'a, I>(progress_bar: &ProgressBar, from_set: I) -> Vec + pub fn unique_expansions(from_set: I) -> impl AllUniquePolycubeIterator where - I: Iterator + ExactSizeIterator, + I: AllUniquePolycubeIterator + ExactSizeIterator, { - let mut this_level = HashSet::new(); + struct AllUniques { + stored: HashSet, + current_expander: Option, + from_set: T, + } - for value in from_set { - for expansion in value.expand().map(|v| v.crop()) { - // Skip expansions that are already in the list. - if this_level.contains(&expansion) { - continue; + impl AllUniques + where + T: AllUniquePolycubeIterator, + { + fn new(from_set: T) -> Self { + Self { + stored: HashSet::new(), + from_set, + current_expander: None, } + } + } - let max = expansion.canonical_form(); + impl Iterator for AllUniques + where + T: Iterator, + { + type Item = RawPCube; + + fn next(&mut self) -> Option { + loop { + if let Some(ref mut current_expander) = self.current_expander { + while let Some(next_expansion) = current_expander.next().map(|v| v.crop()) { + if self.stored.contains(&next_expansion) { + continue; + } - let missing = !this_level.contains(&max); + let canonical = next_expansion.canonical_form(); - if missing { - this_level.insert(max); + if self.stored.insert(canonical.clone()) { + return Some(canonical.into()); + } + } + } + + self.current_expander = self + .from_set + .next() + .map(|v| NaivePolyCube::from(v).expand()); + + // No more expansions + if self.current_expander.is_none() { + return None; + } } } + } + + impl PolycubeIterator for AllUniques + where + T: PolycubeIterator, + { + fn is_canonical(&self) -> bool { + self.from_set.is_canonical() + } - progress_bar.inc(1); + fn n_hint(&self) -> Option { + self.from_set.n_hint() + } } - this_level.into_iter().collect() + impl FusedIterator for AllUniques where T: Iterator {} + impl UniquePolycubeIterator for AllUniques where T: PolycubeIterator {} + impl AllPolycubeIterator for AllUniques where T: AllPolycubeIterator {} + impl AllUniquePolycubeIterator for AllUniques where T: AllUniquePolycubeIterator {} + + AllUniques::new(from_set) } /// Check whether this cube is already cropped. diff --git a/rust/src/pcube/mod.rs b/rust/src/pcube/mod.rs index 8d9ce50..0b6acd0 100644 --- a/rust/src/pcube/mod.rs +++ b/rust/src/pcube/mod.rs @@ -314,6 +314,10 @@ where fn is_canonical(&self) -> bool { self.inner.canonical() } + + fn n_hint(&self) -> Option { + None + } } struct AllUnique @@ -377,6 +381,10 @@ where fn is_canonical(&self) -> bool { self.canonical } + + fn n_hint(&self) -> Option { + Some(self.n()) + } } impl UniquePolycubeIterator for AllUnique where T: Read {} From 963fa8e52d172b3c0d1dfb93a46aa9a3f697174c Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 23 Jul 2023 14:47:05 +0200 Subject: [PATCH 05/39] Also make rayon trait based --- rust/Cargo.lock | 1 + rust/Cargo.toml | 1 + rust/src/cli.rs | 45 ++++++++-------- rust/src/iterator.rs | 14 +++-- rust/src/naive_polycube/mod.rs | 97 +++++++++++++++++++++++----------- 5 files changed, 102 insertions(+), 56 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 60604d7..908396a 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -388,6 +388,7 @@ name = "opencubes" version = "0.1.0" dependencies = [ "clap", + "crossbeam-channel", "flate2", "hashbrown", "indicatif", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 3d96406..3ae7e1d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -20,6 +20,7 @@ num_cpus = "1.16.0" parking_lot = "0.12.1" rayon = "1.7.0" hashbrown = { version = "0.14", features = ["rayon"] } +crossbeam-channel = "0.5.8" [[bin]] name = "opencubes" diff --git a/rust/src/cli.rs b/rust/src/cli.rs index 0f1d053..8e213ec 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -2,6 +2,7 @@ use std::{ collections::{BTreeMap, HashSet}, io::ErrorKind, path::PathBuf, + sync::Arc, time::{Duration, Instant}, }; @@ -361,8 +362,10 @@ fn load_cache(n: usize) -> (Vec, usize) { (current, 2) } +#[derive(Clone)] struct AllUniques { - current: std::vec::IntoIter, + current: Arc>, + offset: usize, n: usize, } @@ -370,19 +373,18 @@ impl Iterator for AllUniques { type Item = RawPCube; fn next(&mut self) -> Option { - self.current.next() + let output = self.current.get(self.offset)?.clone(); + self.offset += 1; + Some(output) } fn size_hint(&self) -> (usize, Option) { - self.current.size_hint() + let len = self.current.len() - self.offset; + (len, Some(len)) } } -impl ExactSizeIterator for AllUniques { - fn len(&self) -> usize { - self.current.len() - } -} +impl ExactSizeIterator for AllUniques {} impl PolycubeIterator for AllUniques { fn is_canonical(&self) -> bool { @@ -415,7 +417,8 @@ where } let mut current = AllUniques { - current: current.into_iter(), + current: Arc::new(current), + offset: 0, n: calculate_from, }; @@ -453,7 +456,8 @@ where } current = AllUniques { - current: next.into_iter(), + current: Arc::new(next), + offset: 0, n: i + 1, }; } @@ -497,18 +501,15 @@ pub fn enumerate(opts: &EnumerateOpts) { cubes.len() } (EnumerationMode::Standard, false) => { - todo!() - // let cubes = unique_expansions( - // |bar, current: std::slice::Iter<'_, NaivePolyCube>| { - // NaivePolyCube::unique_expansions_rayon(bar, current) - // }, - // cache, - // n, - // opts.cache_compression, - // seed_list, - // startn, - // ); - // cubes.len() + let cubes = unique_expansions( + NaivePolyCube::unique_expansions_rayon, + cache, + n, + opts.cache_compression, + seed_list, + startn, + ); + cubes.len() } (EnumerationMode::RotationReduced, para) => { if n > 16 { diff --git a/rust/src/iterator.rs b/rust/src/iterator.rs index 68ed0f1..c8eabf5 100644 --- a/rust/src/iterator.rs +++ b/rust/src/iterator.rs @@ -130,14 +130,16 @@ impl AllUniquePolycubeIterator for UniquePolycubes where T: AllPolycubeIte // TODO: hide this behind a feature? pub mod indicatif { - use indicatif::{ProgressBar, ProgressIterator}; + use indicatif::ProgressBar; use super::{ AllPolycubeIterator, AllUniquePolycubeIterator, PolycubeIterator, UniquePolycubeIterator, }; + #[derive(Clone)] pub struct PolycubeProgressBarIter { - inner: indicatif::ProgressBarIter, + bar: ProgressBar, + inner: T, is_canonical: bool, n_hint: Option, } @@ -151,7 +153,8 @@ pub mod indicatif { let n_hint = inner.n_hint(); Self { - inner: inner.progress_with(bar), + inner: inner, + bar, is_canonical, n_hint, } @@ -165,8 +168,13 @@ pub mod indicatif { type Item = T::Item; fn next(&mut self) -> Option { + self.bar.inc(1); self.inner.next() } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } } impl PolycubeIterator for PolycubeProgressBarIter diff --git a/rust/src/naive_polycube/mod.rs b/rust/src/naive_polycube/mod.rs index bf48d1a..cb0f007 100644 --- a/rust/src/naive_polycube/mod.rs +++ b/rust/src/naive_polycube/mod.rs @@ -2,7 +2,6 @@ use std::{collections::HashSet, iter::FusedIterator}; -use indicatif::ProgressBar; use parking_lot::RwLock; use crate::{ @@ -562,52 +561,88 @@ impl NaivePolyCube { impl NaivePolyCube { // TODO: turn this into an iterator that yield unique expansions? - pub fn unique_expansions_rayon<'a, I>(bar: &ProgressBar, from_set: I) -> Vec + pub fn unique_expansions_rayon(from_set: I) -> impl AllUniquePolycubeIterator where - I: Iterator + ExactSizeIterator + Clone + Send + Sync, + I: AllUniquePolycubeIterator + ExactSizeIterator + Clone + Send + Sync + 'static, { use rayon::prelude::*; - if from_set.len() == 0 { - return Vec::new(); - } + let (tx, rx) = crossbeam_channel::bounded(1024); + let next_n = from_set.n() + 1; - let available_parallelism = num_cpus::get(); + std::thread::spawn(move || { + if from_set.len() == 0 { + return; + } - let chunk_size = (from_set.len() / available_parallelism) + 1; - let chunks = (from_set.len() + chunk_size - 1) / chunk_size; + let available_parallelism = num_cpus::get(); - let chunk_iterator = (0..chunks).into_par_iter().map(|v| { - from_set - .clone() - .skip(v * chunk_size) - .take(chunk_size) - .into_iter() - }); + let chunk_size = (from_set.len() / available_parallelism) + 1; + let chunks = (from_set.len() + chunk_size - 1) / chunk_size; - let this_level = RwLock::new(HashSet::new()); + let chunk_iterator = (0..chunks).into_par_iter().map(|v| { + from_set + .clone() + .skip(v * chunk_size) + .take(chunk_size) + .into_iter() + }); - chunk_iterator.for_each(|v| { - for value in v { - for expansion in value.expand().map(|v| v.crop()) { - // Skip expansions that are already in the list. - if this_level.read().contains(&expansion) { - continue; - } + let this_level = RwLock::new(HashSet::new()); - let max = expansion.canonical_form(); + chunk_iterator.for_each(|v| { + for value in v { + for expansion in NaivePolyCube::from(value).expand().map(|v| v.crop()) { + // Skip expansions that are already in the list. + if this_level.read().contains(&expansion) { + continue; + } + + let max = expansion.canonical_form(); - let missing = !this_level.read().contains(&max); + let missing = !this_level.read().contains(&max); - if missing { - this_level.write().insert(max); + if missing { + if this_level.write().insert(max.clone()) { + tx.send(RawPCube::from(max)).unwrap(); + } + } } } + }); + }); + + struct AllUniques { + inner: crossbeam_channel::IntoIter, + n_hint: Option, + } + + impl Iterator for AllUniques { + type Item = RawPCube; - bar.inc(1); + fn next(&mut self) -> Option { + self.inner.next() + } + } + + impl PolycubeIterator for AllUniques { + fn is_canonical(&self) -> bool { + false } - }); - this_level.into_inner().into_iter().collect() + fn n_hint(&self) -> Option { + self.n_hint + } + } + + impl FusedIterator for AllUniques {} + impl UniquePolycubeIterator for AllUniques {} + impl AllPolycubeIterator for AllUniques {} + impl AllUniquePolycubeIterator for AllUniques {} + + AllUniques { + inner: rx.into_iter(), + n_hint: Some(next_n), + } } } From 3d017ce194e382d251b97da8a52e7f1442d4cce2 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 23 Jul 2023 22:31:01 +0200 Subject: [PATCH 06/39] Split it up a bit --- rust/src/iterator.rs | 2 +- rust/src/naive_polycube/mod.rs | 98 ++++++++++++++++++++++------------ 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/rust/src/iterator.rs b/rust/src/iterator.rs index c8eabf5..f55fff5 100644 --- a/rust/src/iterator.rs +++ b/rust/src/iterator.rs @@ -153,7 +153,7 @@ pub mod indicatif { let n_hint = inner.n_hint(); Self { - inner: inner, + inner, bar, is_canonical, n_hint, diff --git a/rust/src/naive_polycube/mod.rs b/rust/src/naive_polycube/mod.rs index cb0f007..128c1ee 100644 --- a/rust/src/naive_polycube/mod.rs +++ b/rust/src/naive_polycube/mod.rs @@ -362,50 +362,38 @@ impl NaivePolyCube { cube_next } - /// Obtain a list of [`NaivePolyCube`]s representing all unique expansions of the - /// items in `from_set`. - pub fn unique_expansions(from_set: I) -> impl AllUniquePolycubeIterator + pub fn expansions(from_set: I) -> impl Iterator where - I: AllUniquePolycubeIterator + ExactSizeIterator, + I: Iterator, { - struct AllUniques { - stored: HashSet, + struct AllExpansions { current_expander: Option, from_set: T, } - impl AllUniques + impl AllExpansions where - T: AllUniquePolycubeIterator, + T: Iterator, { fn new(from_set: T) -> Self { Self { - stored: HashSet::new(), from_set, current_expander: None, } } } - impl Iterator for AllUniques + impl Iterator for AllExpansions where - T: Iterator, + T: Iterator, { - type Item = RawPCube; + type Item = NaivePolyCube; fn next(&mut self) -> Option { loop { if let Some(ref mut current_expander) = self.current_expander { - while let Some(next_expansion) = current_expander.next().map(|v| v.crop()) { - if self.stored.contains(&next_expansion) { - continue; - } - - let canonical = next_expansion.canonical_form(); - - if self.stored.insert(canonical.clone()) { - return Some(canonical.into()); - } + if let Some(next_expansion) = current_expander.next().map(|v| v.crop()) { + return Some(next_expansion); } } @@ -422,25 +410,69 @@ impl NaivePolyCube { } } - impl PolycubeIterator for AllUniques - where - T: PolycubeIterator, - { + impl FusedIterator for AllExpansions where T: Iterator {} + + AllExpansions::new(from_set) + } + + /// Obtain a list of [`NaivePolyCube`]s representing all unique expansions of the + /// items in `from_set`. + pub fn unique_expansions(from_set: I) -> impl AllUniquePolycubeIterator + where + I: AllUniquePolycubeIterator, + { + let out_n = from_set.n() + 1; + let mut uniques = HashSet::new(); + + Self::expansions(from_set.map(NaivePolyCube::from)).for_each(|v| { + if uniques.contains(&v) { + return; + } + + let max = v.canonical_form(); + + uniques.insert(max); + }); + + struct AllUniques { + uniques: std::collections::hash_set::IntoIter, + n: usize, + is_canonical: bool, + } + + impl Iterator for AllUniques { + type Item = RawPCube; + + fn next(&mut self) -> Option { + self.uniques.next().map(|v| RawPCube::from(v)) + } + + fn size_hint(&self) -> (usize, Option) { + self.uniques.size_hint() + } + } + + impl PolycubeIterator for AllUniques { fn is_canonical(&self) -> bool { - self.from_set.is_canonical() + self.is_canonical } fn n_hint(&self) -> Option { - self.from_set.n_hint() + Some(self.n) } } - impl FusedIterator for AllUniques where T: Iterator {} - impl UniquePolycubeIterator for AllUniques where T: PolycubeIterator {} - impl AllPolycubeIterator for AllUniques where T: AllPolycubeIterator {} - impl AllUniquePolycubeIterator for AllUniques where T: AllUniquePolycubeIterator {} + impl FusedIterator for AllUniques {} + impl ExactSizeIterator for AllUniques {} + impl UniquePolycubeIterator for AllUniques {} + impl AllPolycubeIterator for AllUniques {} + impl AllUniquePolycubeIterator for AllUniques {} - AllUniques::new(from_set) + AllUniques { + uniques: uniques.into_iter(), + n: out_n, + is_canonical: false, + } } /// Check whether this cube is already cropped. From e3feebd3b831e10526ae0a9e5598ebc88abff378 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 23 Jul 2023 22:37:12 +0200 Subject: [PATCH 07/39] Yah yah --- rust/src/naive_polycube/mod.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/rust/src/naive_polycube/mod.rs b/rust/src/naive_polycube/mod.rs index 128c1ee..7486e05 100644 --- a/rust/src/naive_polycube/mod.rs +++ b/rust/src/naive_polycube/mod.rs @@ -375,10 +375,11 @@ impl NaivePolyCube { where T: Iterator, { - fn new(from_set: T) -> Self { + fn new(mut from_set: T) -> Self { + let current_expander = from_set.next().map(|v| v.expand()); Self { from_set, - current_expander: None, + current_expander, } } } @@ -390,7 +391,7 @@ impl NaivePolyCube { type Item = NaivePolyCube; fn next(&mut self) -> Option { - loop { + while self.current_expander.is_some() { if let Some(ref mut current_expander) = self.current_expander { if let Some(next_expansion) = current_expander.next().map(|v| v.crop()) { return Some(next_expansion); @@ -401,16 +402,14 @@ impl NaivePolyCube { .from_set .next() .map(|v| NaivePolyCube::from(v).expand()); - - // No more expansions - if self.current_expander.is_none() { - return None; - } } + + None } } impl FusedIterator for AllExpansions where T: Iterator {} + impl ExactSizeIterator for AllExpansions where T: ExactSizeIterator {} AllExpansions::new(from_set) } From 40ac94e70d5a879bc30a12df9066a9f677903382 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 23 Jul 2023 22:57:45 +0200 Subject: [PATCH 08/39] Only crop once --- rust/src/naive_polycube/expander.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/naive_polycube/expander.rs b/rust/src/naive_polycube/expander.rs index 1a9dba4..d8a3104 100644 --- a/rust/src/naive_polycube/expander.rs +++ b/rust/src/naive_polycube/expander.rs @@ -87,7 +87,7 @@ impl Iterator for ExpansionIterator { let mut next_cube = self.padded_cube.clone(); next_cube.set(d1, d2, d3).unwrap(); - return Some(next_cube.crop()); + return Some(next_cube); } else { return None; } From 3f30a3a1229d04044dbeba91e4005698f5e5883d Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 17:36:13 +0200 Subject: [PATCH 09/39] Fixup rayon impl --- rust/src/naive_polycube/mod.rs | 156 +++++++++++++-------------------- 1 file changed, 59 insertions(+), 97 deletions(-) diff --git a/rust/src/naive_polycube/mod.rs b/rust/src/naive_polycube/mod.rs index 7486e05..a8f01e4 100644 --- a/rust/src/naive_polycube/mod.rs +++ b/rust/src/naive_polycube/mod.rs @@ -14,6 +14,40 @@ use crate::{ mod expander; mod rotations; +struct AllUniques { + uniques: std::collections::hash_set::IntoIter, + n: usize, + is_canonical: bool, +} + +impl Iterator for AllUniques { + type Item = RawPCube; + + fn next(&mut self) -> Option { + self.uniques.next().map(|v| RawPCube::from(v)) + } + + fn size_hint(&self) -> (usize, Option) { + self.uniques.size_hint() + } +} + +impl PolycubeIterator for AllUniques { + fn is_canonical(&self) -> bool { + self.is_canonical + } + + fn n_hint(&self) -> Option { + Some(self.n) + } +} + +impl FusedIterator for AllUniques {} +impl ExactSizeIterator for AllUniques {} +impl UniquePolycubeIterator for AllUniques {} +impl AllPolycubeIterator for AllUniques {} +impl AllUniquePolycubeIterator for AllUniques {} + /// A polycube, represented as three dimensions and an array of booleans. /// /// The array of booleans represents the cubes and their presence (if `true`) @@ -433,40 +467,6 @@ impl NaivePolyCube { uniques.insert(max); }); - struct AllUniques { - uniques: std::collections::hash_set::IntoIter, - n: usize, - is_canonical: bool, - } - - impl Iterator for AllUniques { - type Item = RawPCube; - - fn next(&mut self) -> Option { - self.uniques.next().map(|v| RawPCube::from(v)) - } - - fn size_hint(&self) -> (usize, Option) { - self.uniques.size_hint() - } - } - - impl PolycubeIterator for AllUniques { - fn is_canonical(&self) -> bool { - self.is_canonical - } - - fn n_hint(&self) -> Option { - Some(self.n) - } - } - - impl FusedIterator for AllUniques {} - impl ExactSizeIterator for AllUniques {} - impl UniquePolycubeIterator for AllUniques {} - impl AllPolycubeIterator for AllUniques {} - impl AllUniquePolycubeIterator for AllUniques {} - AllUniques { uniques: uniques.into_iter(), n: out_n, @@ -598,82 +598,44 @@ impl NaivePolyCube { { use rayon::prelude::*; - let (tx, rx) = crossbeam_channel::bounded(1024); let next_n = from_set.n() + 1; - std::thread::spawn(move || { - if from_set.len() == 0 { - return; - } - - let available_parallelism = num_cpus::get(); - - let chunk_size = (from_set.len() / available_parallelism) + 1; - let chunks = (from_set.len() + chunk_size - 1) / chunk_size; - - let chunk_iterator = (0..chunks).into_par_iter().map(|v| { - from_set - .clone() - .skip(v * chunk_size) - .take(chunk_size) - .into_iter() - }); + let available_parallelism = num_cpus::get(); - let this_level = RwLock::new(HashSet::new()); + let chunk_size = (from_set.len() / available_parallelism) + 1; + let chunks = (from_set.len() + chunk_size - 1) / chunk_size; - chunk_iterator.for_each(|v| { - for value in v { - for expansion in NaivePolyCube::from(value).expand().map(|v| v.crop()) { - // Skip expansions that are already in the list. - if this_level.read().contains(&expansion) { - continue; - } - - let max = expansion.canonical_form(); - - let missing = !this_level.read().contains(&max); - - if missing { - if this_level.write().insert(max.clone()) { - tx.send(RawPCube::from(max)).unwrap(); - } - } - } - } - }); + let chunk_iterator = (0..chunks).into_par_iter().map(|v| { + from_set + .clone() + .skip(v * chunk_size) + .take(chunk_size) + .into_iter() }); - struct AllUniques { - inner: crossbeam_channel::IntoIter, - n_hint: Option, - } + let this_level = RwLock::new(HashSet::new()); - impl Iterator for AllUniques { - type Item = RawPCube; + chunk_iterator.for_each(|v| { + for expansion in Self::expansions(v.map(NaivePolyCube::from)).map(|v| v.crop()) { + // Skip expansions that are already in the list. + if this_level.read().contains(&expansion) { + continue; + } - fn next(&mut self) -> Option { - self.inner.next() - } - } + let max = expansion.canonical_form(); - impl PolycubeIterator for AllUniques { - fn is_canonical(&self) -> bool { - false - } + let missing = !this_level.read().contains(&max); - fn n_hint(&self) -> Option { - self.n_hint + if missing { + this_level.write().insert(max); + } } - } - - impl FusedIterator for AllUniques {} - impl UniquePolycubeIterator for AllUniques {} - impl AllPolycubeIterator for AllUniques {} - impl AllUniquePolycubeIterator for AllUniques {} + }); AllUniques { - inner: rx.into_iter(), - n_hint: Some(next_n), + uniques: this_level.into_inner().into_iter(), + n: next_n, + is_canonical: false, } } } From 923483d784c3e7ffc7c648f776430454c8fa10c5 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 17:37:49 +0200 Subject: [PATCH 10/39] Remove crossbeam-channel --- rust/Cargo.lock | 29 ++++++++++++++--------------- rust/Cargo.toml | 1 - 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 908396a..c1317c7 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -106,9 +106,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.3.12" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73" +checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", "clap_derive", @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.12" +version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd" +checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ "anstream", "anstyle", @@ -218,9 +218,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" @@ -388,7 +388,6 @@ name = "opencubes" version = "0.1.0" dependencies = [ "clap", - "crossbeam-channel", "flate2", "hashbrown", "indicatif", @@ -428,18 +427,18 @@ checksum = "edc55135a600d700580e406b4de0d59cb9ad25e344a3a091a97ded2622ec4ec6" [[package]] name = "proc-macro2" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92de25114670a878b1261c79c9f8f729fb97e95bac93f6312f583c60dd6a1dfe" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -490,9 +489,9 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "smallvec" @@ -508,9 +507,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.26" +version = "2.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" dependencies = [ "proc-macro2", "quote", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 3ae7e1d..3d96406 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -20,7 +20,6 @@ num_cpus = "1.16.0" parking_lot = "0.12.1" rayon = "1.7.0" hashbrown = { version = "0.14", features = ["rayon"] } -crossbeam-channel = "0.5.8" [[bin]] name = "opencubes" From ed49b1f4fb6f88c75c81cfcc754f10546d5b7874 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 20:13:03 +0200 Subject: [PATCH 11/39] More iterators! --- rust/src/cli.rs | 159 ++++++++++++++++++++++++------------------ rust/src/pcube/mod.rs | 4 +- 2 files changed, 93 insertions(+), 70 deletions(-) diff --git a/rust/src/cli.rs b/rust/src/cli.rs index e35a6ef..f39c3ab 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -46,7 +46,11 @@ fn unknown_bar() -> ProgressBar { "----------", ]); - ProgressBar::new(100).with_style(style) + let bar = ProgressBar::new(100).with_style(style); + + bar.enable_steady_tick(Duration::from_millis(66)); + + bar } pub fn make_bar(len: u64) -> indicatif::ProgressBar { @@ -293,71 +297,83 @@ fn load_cache_file(n: usize) -> Option { Err(e) => { if e.kind() == ErrorKind::InvalidData || e.kind() == ErrorKind::Other { println!("Enountered invalid cache file {name}. Error: {e}."); - } else { - println!("Could not load cache file '{name}'. Error: {e}"); } None } } } -/// load closes cache file to n into a vec +/// load largest findable cachefile with size <= n - 1 into a vec /// returns a vec and the next order above the found cache file -fn load_cache(n: usize) -> (Vec, usize) { - let calculate_from = 2; - - for n in (calculate_from..n).rev() { - let name = format!("cubes_{n}.pcube"); - let cache = if let Some(file) = load_cache_file(n) { - file - } else { - continue; - }; - - println!("Found cache for N = {n}. Loading data..."); +fn load_cache(n: usize) -> impl AllUniquePolycubeIterator { + enum CacheOrbase { + Cache(opencubes::pcube::AllUnique), + Base(bool), + } - if !cache.canonical() { - println!("Cached cubes are not canonical. Canonicalizing...") + impl Iterator for CacheOrbase { + type Item = RawPCube; + + fn next(&mut self) -> Option { + match self { + CacheOrbase::Cache(cache) => cache.next(), + CacheOrbase::Base(v) if v == &false => { + *v = true; + let mut base = RawPCube::new_empty(1, 1, 1); + base.set(0, 0, 0, true); + Some(base) + } + CacheOrbase::Base(_) => None, + } } - let len = cache.len(); + fn size_hint(&self) -> (usize, Option) { + match self { + CacheOrbase::Cache(c) => c.size_hint(), + CacheOrbase::Base(_) => (1, Some(1)), + } + } + } - let mut error = None; - let mut total_loaded = 0; + impl PolycubeIterator for CacheOrbase { + fn is_canonical(&self) -> bool { + match self { + CacheOrbase::Cache(c) => c.is_canonical(), + CacheOrbase::Base(_) => true, + } + } - let filter = |value| { - total_loaded += 1; - match value { - Ok(v) => Some(v), - Err(e) => { - error = Some(e); - None - } + fn n_hint(&self) -> Option { + match self { + CacheOrbase::Cache(c) => Some(c.n()), + CacheOrbase::Base(_) => Some(1), } - }; + } + } - let cached: HashSet<_> = cache.filter_map(filter).collect(); + impl UniquePolycubeIterator for CacheOrbase {} + impl AllPolycubeIterator for CacheOrbase {} + impl AllUniquePolycubeIterator for CacheOrbase {} - if let Some(e) = error { - println!("Error occured while loading {name}. Error: {e}"); - } else { - let total_len = len.unwrap_or(total_loaded); + let calculate_from = 2; - if total_len != cached.len() { - println!("There were non-unique cubes in the cache file. Continuing...") - } + for n in (calculate_from..n).rev() { + let cache = if let Some(file) = load_cache_file(n) { + file + } else { + continue; + }; - return (cached.into_iter().collect(), n + 1); - } + println!("Found cache for N = {n}."); + return CacheOrbase::Cache(cache.assume_all_unique()); } - println!("no cache file found reverting to start building from n=1"); - let mut base = RawPCube::new_empty(1, 1, 1); - base.set(0, 0, 0, true); + println!( + "No cache file found for size <= {}. Starting from N = 1", + n - 1 + ); - let current = [base.clone()].to_vec(); - //calculate from 2 because 1 is in the vec - (current, 2) + CacheOrbase::Base(false) } #[derive(Clone)] @@ -403,8 +419,7 @@ fn unique_expansions( use_cache: bool, n: usize, compression: Compression, - current: Vec, - calculate_from: usize, + current: impl AllPolycubeIterator, ) -> Vec where F: Fn(PolycubeProgressBarIter) -> O, @@ -414,6 +429,9 @@ where return Vec::new(); } + let calculate_from = current.n(); + let current = current.collect(); + let mut current = AllUniques { current: Arc::new(current), offset: 0, @@ -440,7 +458,7 @@ where if use_cache { let name = &format!("cubes_{i}.pcube"); if !std::fs::File::open(name).is_ok() { - println!("Saving {} cubes to cache file", next.len()); + bar.println(format!("Saving {} cubes to cache file", next.len())); PCubeFile::write_file( false, compression.into(), @@ -449,7 +467,9 @@ where ) .unwrap(); } else { - println!("Cache file already exists for N = {i}. Not overwriting."); + bar.println(format!( + "Cache file already exists for N = {i}. Not overwriting." + )); } } @@ -460,7 +480,7 @@ where }; } - current.collect() + Arc::into_inner(current.current).unwrap() } pub fn enumerate(opts: &EnumerateOpts) { @@ -474,17 +494,13 @@ pub fn enumerate(opts: &EnumerateOpts) { let start = Instant::now(); - let (seed_list, startn) = if cache { - load_cache(n) - } else { - let mut base = RawPCube::new_empty(1, 1, 1); - base.set(0, 0, 0, true); + let seed_list = load_cache(n); - let current = [base].to_vec(); - //calculate from 2 because 1 is in the vec - (current, 2) + let bar = if let (_, Some(max)) = seed_list.size_hint() { + make_bar(max as u64) + } else { + unknown_bar() }; - let bar = make_bar(seed_list.len() as u64); //Select enumeration function to run let cubes_len = match (opts.mode, opts.no_parallelism) { @@ -495,7 +511,6 @@ pub fn enumerate(opts: &EnumerateOpts) { n, opts.cache_compression, seed_list, - startn, ); cubes.len() } @@ -506,30 +521,38 @@ pub fn enumerate(opts: &EnumerateOpts) { n, opts.cache_compression, seed_list, - startn, ); cubes.len() } - (EnumerationMode::RotationReduced, para) => { + (EnumerationMode::RotationReduced, not_parallel) => { if n > 16 { println!("n > 16 not supported for rotation reduced"); return; } - if !para { + if !not_parallel { println!("no parallel implementation for rotation-reduced, running single threaded") } rotation_reduced::gen_polycubes(n, &bar) } - (EnumerationMode::PointList, para) => { + (EnumerationMode::PointList, not_parallel) => { if n > 16 { println!("n > 16 not supported for point-list"); return; } - let cubes = pointlist::gen_polycubes(n, cache, !para, seed_list, startn, &bar); + let startn = seed_list.n(); + let cubes = pointlist::gen_polycubes( + n, + cache, + !not_parallel, + seed_list.collect(), + startn, + &bar, + ); cubes.len() } - (EnumerationMode::Hashless, para) => { - hashless::gen_polycubes(n, !para, seed_list, startn, &bar) + (EnumerationMode::Hashless, not_parallel) => { + let startn = seed_list.n(); + hashless::gen_polycubes(n, !not_parallel, seed_list.collect(), startn, &bar) } }; diff --git a/rust/src/pcube/mod.rs b/rust/src/pcube/mod.rs index 0b6acd0..95a6add 100644 --- a/rust/src/pcube/mod.rs +++ b/rust/src/pcube/mod.rs @@ -165,7 +165,7 @@ where } /// This is by no means guaranteed, but makes life a bit easier - pub fn assume_all_unique(self) -> impl AllUniquePolycubeIterator { + pub fn assume_all_unique(self) -> AllUnique { AllUnique::new(self) } } @@ -320,7 +320,7 @@ where } } -struct AllUnique +pub struct AllUnique where T: Read, { From 5489dc8757c829b5f0e07c6572d4d9da6d9691a3 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 20:19:20 +0200 Subject: [PATCH 12/39] Loop so we don't have to copy everything that last time --- rust/src/cli.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/rust/src/cli.rs b/rust/src/cli.rs index f39c3ab..31a3d83 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -438,7 +438,8 @@ where n: calculate_from, }; - for i in calculate_from..=n { + let mut i = calculate_from; + loop { let bar = make_bar(current.len() as u64); bar.set_message(format!("base polycubes expanded for N = {i}...")); @@ -448,8 +449,9 @@ where let next: Vec = expansion_fn(with_bar).collect(); bar.set_message(format!( - "Found {} unique expansions (N = {i}) in {} ms.", + "Found {} unique expansions (N = {}) in {} ms.", next.len(), + i + 1, start.elapsed().as_millis(), )); @@ -473,14 +475,18 @@ where } } - current = AllUniques { - current: Arc::new(next), - offset: 0, - n: i + 1, - }; - } + i += 1; - Arc::into_inner(current.current).unwrap() + if i == n { + return next; + } else { + current = AllUniques { + current: Arc::new(next), + offset: 0, + n: i + 1, + }; + } + } } pub fn enumerate(opts: &EnumerateOpts) { From 0aaeb40bfb7e1a9cc2e3a558e6fe90fc278393b2 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 20:30:44 +0200 Subject: [PATCH 13/39] Just friggin par_bridge... --- rust/src/naive_polycube/mod.rs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/rust/src/naive_polycube/mod.rs b/rust/src/naive_polycube/mod.rs index a8f01e4..b5cbec6 100644 --- a/rust/src/naive_polycube/mod.rs +++ b/rust/src/naive_polycube/mod.rs @@ -600,23 +600,10 @@ impl NaivePolyCube { let next_n = from_set.n() + 1; - let available_parallelism = num_cpus::get(); - - let chunk_size = (from_set.len() / available_parallelism) + 1; - let chunks = (from_set.len() + chunk_size - 1) / chunk_size; - - let chunk_iterator = (0..chunks).into_par_iter().map(|v| { - from_set - .clone() - .skip(v * chunk_size) - .take(chunk_size) - .into_iter() - }); - let this_level = RwLock::new(HashSet::new()); - chunk_iterator.for_each(|v| { - for expansion in Self::expansions(v.map(NaivePolyCube::from)).map(|v| v.crop()) { + from_set.par_bridge().for_each(|v| { + for expansion in NaivePolyCube::from(v).expand().map(|v| v.crop()) { // Skip expansions that are already in the list. if this_level.read().contains(&expansion) { continue; From 653f0e9e4b33292763f4cd05342c450390cf475e Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 20:45:11 +0200 Subject: [PATCH 14/39] Fewer bounds plz --- rust/src/iterator.rs | 17 +++-------------- rust/src/naive_polycube/mod.rs | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/rust/src/iterator.rs b/rust/src/iterator.rs index f55fff5..4e8bde8 100644 --- a/rust/src/iterator.rs +++ b/rust/src/iterator.rs @@ -136,12 +136,9 @@ pub mod indicatif { AllPolycubeIterator, AllUniquePolycubeIterator, PolycubeIterator, UniquePolycubeIterator, }; - #[derive(Clone)] pub struct PolycubeProgressBarIter { bar: ProgressBar, inner: T, - is_canonical: bool, - n_hint: Option, } impl PolycubeProgressBarIter @@ -149,15 +146,7 @@ pub mod indicatif { T: PolycubeIterator, { pub fn new(bar: ProgressBar, inner: T) -> Self { - let is_canonical = inner.is_canonical(); - let n_hint = inner.n_hint(); - - Self { - inner, - bar, - is_canonical, - n_hint, - } + Self { inner, bar } } } @@ -182,11 +171,11 @@ pub mod indicatif { T: PolycubeIterator, { fn is_canonical(&self) -> bool { - self.is_canonical + self.inner.is_canonical() } fn n_hint(&self) -> Option { - self.n_hint + self.inner.n_hint() } } diff --git a/rust/src/naive_polycube/mod.rs b/rust/src/naive_polycube/mod.rs index b5cbec6..912125c 100644 --- a/rust/src/naive_polycube/mod.rs +++ b/rust/src/naive_polycube/mod.rs @@ -594,7 +594,7 @@ impl NaivePolyCube { // TODO: turn this into an iterator that yield unique expansions? pub fn unique_expansions_rayon(from_set: I) -> impl AllUniquePolycubeIterator where - I: AllUniquePolycubeIterator + ExactSizeIterator + Clone + Send + Sync + 'static, + I: AllUniquePolycubeIterator + ExactSizeIterator + Send + Sync + 'static, { use rayon::prelude::*; From 886d6be7746ba82f7f15f80471d098099a6733af Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 20:52:47 +0200 Subject: [PATCH 15/39] Fix --- rust/src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/src/cli.rs b/rust/src/cli.rs index 31a3d83..05c56af 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -545,7 +545,7 @@ pub fn enumerate(opts: &EnumerateOpts) { println!("n > 16 not supported for point-list"); return; } - let startn = seed_list.n(); + let startn = seed_list.n() + 1; let cubes = pointlist::gen_polycubes( n, cache, @@ -557,7 +557,7 @@ pub fn enumerate(opts: &EnumerateOpts) { cubes.len() } (EnumerationMode::Hashless, not_parallel) => { - let startn = seed_list.n(); + let startn = seed_list.n() + 1; hashless::gen_polycubes(n, !not_parallel, seed_list.collect(), startn, &bar) } }; From f88efdff6bf1c30fa29c83445e550ba234d2f47a Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 20:55:43 +0200 Subject: [PATCH 16/39] De-generify this a little --- rust/src/cli.rs | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/rust/src/cli.rs b/rust/src/cli.rs index 05c56af..8ce0168 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -414,17 +414,13 @@ impl AllPolycubeIterator for AllUniques {} impl UniquePolycubeIterator for AllUniques {} impl AllUniquePolycubeIterator for AllUniques {} -fn unique_expansions( - expansion_fn: F, +fn unique_expansions( use_cache: bool, n: usize, compression: Compression, current: impl AllPolycubeIterator, -) -> Vec -where - F: Fn(PolycubeProgressBarIter) -> O, - O: AllUniquePolycubeIterator, -{ + parallel: bool, +) -> Vec { if n == 0 { return Vec::new(); } @@ -446,7 +442,11 @@ where let start = Instant::now(); let with_bar = PolycubeProgressBarIter::new(bar.clone(), current); - let next: Vec = expansion_fn(with_bar).collect(); + let next: Vec = if parallel { + NaivePolyCube::unique_expansions_rayon(with_bar).collect() + } else { + NaivePolyCube::unique_expansions(with_bar).collect() + }; bar.set_message(format!( "Found {} unique expansions (N = {}) in {} ms.", @@ -510,24 +510,9 @@ pub fn enumerate(opts: &EnumerateOpts) { //Select enumeration function to run let cubes_len = match (opts.mode, opts.no_parallelism) { - (EnumerationMode::Standard, true) => { - let cubes = unique_expansions( - NaivePolyCube::unique_expansions, - cache, - n, - opts.cache_compression, - seed_list, - ); - cubes.len() - } - (EnumerationMode::Standard, false) => { - let cubes = unique_expansions( - NaivePolyCube::unique_expansions_rayon, - cache, - n, - opts.cache_compression, - seed_list, - ); + (EnumerationMode::Standard, no_parallelism) => { + let cubes = + unique_expansions(cache, n, opts.cache_compression, seed_list, !no_parallelism); cubes.len() } (EnumerationMode::RotationReduced, not_parallel) => { From 5a4fc9a5b422c20f19db73fadbfd5a7b0b802e53 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 21:00:31 +0200 Subject: [PATCH 17/39] save_to_cache --- rust/src/cli.rs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/rust/src/cli.rs b/rust/src/cli.rs index 8ce0168..cda5ddd 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -414,11 +414,30 @@ impl AllPolycubeIterator for AllUniques {} impl UniquePolycubeIterator for AllUniques {} impl AllUniquePolycubeIterator for AllUniques {} +fn save_to_cache( + bar: &ProgressBar, + compression: Compression, + n: usize, + // Ideally, this would be `AllUniquePolycubeIterator` but it's + // a bit unwieldy + cubes: impl Iterator + ExactSizeIterator, +) { + let name = &format!("cubes_{n}.pcube"); + if !std::fs::File::open(name).is_ok() { + bar.println(format!("Saving {} cubes to cache file", cubes.len())); + PCubeFile::write_file(false, compression.into(), cubes, name).unwrap(); + } else { + bar.println(format!( + "Cache file already exists for N = {n}. Not overwriting." + )); + } +} + fn unique_expansions( use_cache: bool, n: usize, compression: Compression, - current: impl AllPolycubeIterator, + current: impl AllUniquePolycubeIterator, parallel: bool, ) -> Vec { if n == 0 { @@ -458,21 +477,7 @@ fn unique_expansions( bar.finish(); if use_cache { - let name = &format!("cubes_{i}.pcube"); - if !std::fs::File::open(name).is_ok() { - bar.println(format!("Saving {} cubes to cache file", next.len())); - PCubeFile::write_file( - false, - compression.into(), - next.iter().map(Clone::clone), - name, - ) - .unwrap(); - } else { - bar.println(format!( - "Cache file already exists for N = {i}. Not overwriting." - )); - } + save_to_cache(&bar, compression, i + 1, next.iter().map(Clone::clone)); } i += 1; From 45d3485eb678832b9c0c3fab9844300d22d1359a Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 21:13:49 +0200 Subject: [PATCH 18/39] Fix unknown bar --- rust/src/cli.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/src/cli.rs b/rust/src/cli.rs index cda5ddd..5dfa48c 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -24,6 +24,8 @@ fn unknown_bar() -> ProgressBar { let style = ProgressStyle::with_template("[{elapsed_precise}] [{spinner:10.cyan/blue}] {msg}") .unwrap() .tick_strings(&[ + ">---------", + ">---------", "=>--------", "<=>-------", "-<=>------", @@ -34,16 +36,16 @@ fn unknown_bar() -> ProgressBar { "------<=>-", "-------<=>", "--------<=", + "---------<", + "--------<=", "-------<=>", "------<=>-", - "----<=>---", "-----<=>--", "---<=>----", "--<=>-----", "-<=>------", "<=>-------", "=>--------", - "----------", ]); let bar = ProgressBar::new(100).with_style(style); From 56ff9c8411bce2ce9d88a87ab39a3d894a950ffb Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 21:14:07 +0200 Subject: [PATCH 19/39] Fix this a bit, for now --- rust/src/cli.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/rust/src/cli.rs b/rust/src/cli.rs index 5dfa48c..3dc94a5 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -457,7 +457,8 @@ fn unique_expansions( let mut i = calculate_from; loop { - let bar = make_bar(current.len() as u64); + // let bar = make_bar(current.len() as u64); + let bar = unknown_bar(); bar.set_message(format!("base polycubes expanded for N = {i}...")); let start = Instant::now(); @@ -509,12 +510,6 @@ pub fn enumerate(opts: &EnumerateOpts) { let seed_list = load_cache(n); - let bar = if let (_, Some(max)) = seed_list.size_hint() { - make_bar(max as u64) - } else { - unknown_bar() - }; - //Select enumeration function to run let cubes_len = match (opts.mode, opts.no_parallelism) { (EnumerationMode::Standard, no_parallelism) => { @@ -530,6 +525,12 @@ pub fn enumerate(opts: &EnumerateOpts) { if !not_parallel { println!("no parallel implementation for rotation-reduced, running single threaded") } + let bar = if let (_, Some(max)) = seed_list.size_hint() { + make_bar(max as u64) + } else { + unknown_bar() + }; + rotation_reduced::gen_polycubes(n, &bar) } (EnumerationMode::PointList, not_parallel) => { @@ -537,6 +538,12 @@ pub fn enumerate(opts: &EnumerateOpts) { println!("n > 16 not supported for point-list"); return; } + let bar = if let (_, Some(max)) = seed_list.size_hint() { + make_bar(max as u64) + } else { + unknown_bar() + }; + let startn = seed_list.n() + 1; let cubes = pointlist::gen_polycubes( n, @@ -550,6 +557,12 @@ pub fn enumerate(opts: &EnumerateOpts) { } (EnumerationMode::Hashless, not_parallel) => { let startn = seed_list.n() + 1; + let bar = if let (_, Some(max)) = seed_list.size_hint() { + make_bar(max as u64) + } else { + unknown_bar() + }; + hashless::gen_polycubes(n, !not_parallel, seed_list.collect(), startn, &bar) } }; From 7fe64c8e97bb3bc76c42e101b277203ef21bbcc6 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 21:46:56 +0200 Subject: [PATCH 20/39] Let's divy this up a bit more nicely --- rust/Cargo.toml | 2 +- rust/src/{ => cli}/cli.rs | 298 +------------------------------------- rust/src/cli/enumerate.rs | 293 +++++++++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+), 295 deletions(-) rename rust/src/{ => cli}/cli.rs (61%) create mode 100644 rust/src/cli/enumerate.rs diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 1dc02ba..39123b1 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -26,4 +26,4 @@ hashbrown = { version = "0.14", features = ["rayon"] } [[bin]] name = "opencubes" -path = "./src/cli.rs" +path = "./src/cli/cli.rs" diff --git a/rust/src/cli.rs b/rust/src/cli/cli.rs similarity index 61% rename from rust/src/cli.rs rename to rust/src/cli/cli.rs index 3dc94a5..b580624 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli/cli.rs @@ -1,25 +1,17 @@ use std::{ collections::{BTreeMap, HashSet}, - io::ErrorKind, path::PathBuf, - sync::Arc, time::{Duration, Instant}, }; use clap::{Args, Parser, Subcommand, ValueEnum}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; -use opencubes::{ - hashless, - iterator::{ - indicatif::PolycubeProgressBarIter, AllPolycubeIterator, AllUniquePolycubeIterator, - PolycubeIterator, UniquePolycubeIterator, - }, - naive_polycube::NaivePolyCube, - pcube::{PCubeFile, RawPCube}, - pointlist, rotation_reduced, -}; +use opencubes::{naive_polycube::NaivePolyCube, pcube::PCubeFile}; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; +mod enumerate; +use enumerate::enumerate; + fn unknown_bar() -> ProgressBar { let style = ProgressStyle::with_template("[{elapsed_precise}] [{spinner:10.cyan/blue}] {msg}") .unwrap() @@ -291,288 +283,6 @@ pub fn validate(opts: &ValidateArgs) -> std::io::Result<()> { Ok(()) } -fn load_cache_file(n: usize) -> Option { - let name = format!("cubes_{n}.pcube"); - - match PCubeFile::new_file(&name) { - Ok(file) => Some(file), - Err(e) => { - if e.kind() == ErrorKind::InvalidData || e.kind() == ErrorKind::Other { - println!("Enountered invalid cache file {name}. Error: {e}."); - } - None - } - } -} - -/// load largest findable cachefile with size <= n - 1 into a vec -/// returns a vec and the next order above the found cache file -fn load_cache(n: usize) -> impl AllUniquePolycubeIterator { - enum CacheOrbase { - Cache(opencubes::pcube::AllUnique), - Base(bool), - } - - impl Iterator for CacheOrbase { - type Item = RawPCube; - - fn next(&mut self) -> Option { - match self { - CacheOrbase::Cache(cache) => cache.next(), - CacheOrbase::Base(v) if v == &false => { - *v = true; - let mut base = RawPCube::new_empty(1, 1, 1); - base.set(0, 0, 0, true); - Some(base) - } - CacheOrbase::Base(_) => None, - } - } - - fn size_hint(&self) -> (usize, Option) { - match self { - CacheOrbase::Cache(c) => c.size_hint(), - CacheOrbase::Base(_) => (1, Some(1)), - } - } - } - - impl PolycubeIterator for CacheOrbase { - fn is_canonical(&self) -> bool { - match self { - CacheOrbase::Cache(c) => c.is_canonical(), - CacheOrbase::Base(_) => true, - } - } - - fn n_hint(&self) -> Option { - match self { - CacheOrbase::Cache(c) => Some(c.n()), - CacheOrbase::Base(_) => Some(1), - } - } - } - - impl UniquePolycubeIterator for CacheOrbase {} - impl AllPolycubeIterator for CacheOrbase {} - impl AllUniquePolycubeIterator for CacheOrbase {} - - let calculate_from = 2; - - for n in (calculate_from..n).rev() { - let cache = if let Some(file) = load_cache_file(n) { - file - } else { - continue; - }; - - println!("Found cache for N = {n}."); - return CacheOrbase::Cache(cache.assume_all_unique()); - } - - println!( - "No cache file found for size <= {}. Starting from N = 1", - n - 1 - ); - - CacheOrbase::Base(false) -} - -#[derive(Clone)] -struct AllUniques { - current: Arc>, - offset: usize, - n: usize, -} - -impl Iterator for AllUniques { - type Item = RawPCube; - - fn next(&mut self) -> Option { - let output = self.current.get(self.offset)?.clone(); - self.offset += 1; - Some(output) - } - - fn size_hint(&self) -> (usize, Option) { - let len = self.current.len() - self.offset; - (len, Some(len)) - } -} - -impl ExactSizeIterator for AllUniques {} - -impl PolycubeIterator for AllUniques { - fn is_canonical(&self) -> bool { - false - } - - fn n_hint(&self) -> Option { - Some(self.n) - } -} - -impl AllPolycubeIterator for AllUniques {} -impl UniquePolycubeIterator for AllUniques {} -impl AllUniquePolycubeIterator for AllUniques {} - -fn save_to_cache( - bar: &ProgressBar, - compression: Compression, - n: usize, - // Ideally, this would be `AllUniquePolycubeIterator` but it's - // a bit unwieldy - cubes: impl Iterator + ExactSizeIterator, -) { - let name = &format!("cubes_{n}.pcube"); - if !std::fs::File::open(name).is_ok() { - bar.println(format!("Saving {} cubes to cache file", cubes.len())); - PCubeFile::write_file(false, compression.into(), cubes, name).unwrap(); - } else { - bar.println(format!( - "Cache file already exists for N = {n}. Not overwriting." - )); - } -} - -fn unique_expansions( - use_cache: bool, - n: usize, - compression: Compression, - current: impl AllUniquePolycubeIterator, - parallel: bool, -) -> Vec { - if n == 0 { - return Vec::new(); - } - - let calculate_from = current.n(); - let current = current.collect(); - - let mut current = AllUniques { - current: Arc::new(current), - offset: 0, - n: calculate_from, - }; - - let mut i = calculate_from; - loop { - // let bar = make_bar(current.len() as u64); - let bar = unknown_bar(); - bar.set_message(format!("base polycubes expanded for N = {i}...")); - - let start = Instant::now(); - - let with_bar = PolycubeProgressBarIter::new(bar.clone(), current); - let next: Vec = if parallel { - NaivePolyCube::unique_expansions_rayon(with_bar).collect() - } else { - NaivePolyCube::unique_expansions(with_bar).collect() - }; - - bar.set_message(format!( - "Found {} unique expansions (N = {}) in {} ms.", - next.len(), - i + 1, - start.elapsed().as_millis(), - )); - - bar.finish(); - - if use_cache { - save_to_cache(&bar, compression, i + 1, next.iter().map(Clone::clone)); - } - - i += 1; - - if i == n { - return next; - } else { - current = AllUniques { - current: Arc::new(next), - offset: 0, - n: i + 1, - }; - } - } -} - -pub fn enumerate(opts: &EnumerateOpts) { - let n = opts.n; - let cache = !opts.no_cache; - - if n < 2 { - println!("n < 2 unsuported"); - return; - } - - let start = Instant::now(); - - let seed_list = load_cache(n); - - //Select enumeration function to run - let cubes_len = match (opts.mode, opts.no_parallelism) { - (EnumerationMode::Standard, no_parallelism) => { - let cubes = - unique_expansions(cache, n, opts.cache_compression, seed_list, !no_parallelism); - cubes.len() - } - (EnumerationMode::RotationReduced, not_parallel) => { - if n > 16 { - println!("n > 16 not supported for rotation reduced"); - return; - } - if !not_parallel { - println!("no parallel implementation for rotation-reduced, running single threaded") - } - let bar = if let (_, Some(max)) = seed_list.size_hint() { - make_bar(max as u64) - } else { - unknown_bar() - }; - - rotation_reduced::gen_polycubes(n, &bar) - } - (EnumerationMode::PointList, not_parallel) => { - if n > 16 { - println!("n > 16 not supported for point-list"); - return; - } - let bar = if let (_, Some(max)) = seed_list.size_hint() { - make_bar(max as u64) - } else { - unknown_bar() - }; - - let startn = seed_list.n() + 1; - let cubes = pointlist::gen_polycubes( - n, - cache, - !not_parallel, - seed_list.collect(), - startn, - &bar, - ); - cubes.len() - } - (EnumerationMode::Hashless, not_parallel) => { - let startn = seed_list.n() + 1; - let bar = if let (_, Some(max)) = seed_list.size_hint() { - make_bar(max as u64) - } else { - unknown_bar() - }; - - hashless::gen_polycubes(n, !not_parallel, seed_list.collect(), startn, &bar) - } - }; - - let duration = start.elapsed(); - - println!("Unique polycubes found for N = {n}: {cubes_len}.",); - println!("Duration: {} ms", duration.as_millis()); -} - pub fn convert(opts: &ConvertArgs) { if opts.output_path.is_some() && opts.path.len() > 1 { println!("Cannot convert more than 1 file when output path is provided"); diff --git a/rust/src/cli/enumerate.rs b/rust/src/cli/enumerate.rs new file mode 100644 index 0000000..22ea882 --- /dev/null +++ b/rust/src/cli/enumerate.rs @@ -0,0 +1,293 @@ +use std::{io::ErrorKind, sync::Arc, time::Instant}; + +use ::indicatif::ProgressBar; +use opencubes::{ + hashless, + iterator::{indicatif::PolycubeProgressBarIter, *}, + naive_polycube::NaivePolyCube, + pcube::{PCubeFile, RawPCube}, + pointlist, rotation_reduced, +}; + +use crate::{make_bar, unknown_bar, Compression, EnumerateOpts, EnumerationMode}; + +#[derive(Clone)] +struct AllUniques { + current: Arc>, + offset: usize, + n: usize, +} + +impl Iterator for AllUniques { + type Item = RawPCube; + + fn next(&mut self) -> Option { + let output = self.current.get(self.offset)?.clone(); + self.offset += 1; + Some(output) + } + + fn size_hint(&self) -> (usize, Option) { + let len = self.current.len() - self.offset; + (len, Some(len)) + } +} + +impl ExactSizeIterator for AllUniques {} + +impl PolycubeIterator for AllUniques { + fn is_canonical(&self) -> bool { + false + } + + fn n_hint(&self) -> Option { + Some(self.n) + } +} + +impl AllPolycubeIterator for AllUniques {} +impl UniquePolycubeIterator for AllUniques {} +impl AllUniquePolycubeIterator for AllUniques {} + +fn save_to_cache( + bar: &ProgressBar, + compression: Compression, + n: usize, + // Ideally, this would be `AllUniquePolycubeIterator` but it's + // a bit unwieldy + cubes: impl Iterator + ExactSizeIterator, +) { + let name = &format!("cubes_{n}.pcube"); + if !std::fs::File::open(name).is_ok() { + bar.println(format!("Saving {} cubes to cache file", cubes.len())); + PCubeFile::write_file(false, compression.into(), cubes, name).unwrap(); + } else { + bar.println(format!( + "Cache file already exists for N = {n}. Not overwriting." + )); + } +} + +fn load_cache_file(n: usize) -> Option { + let name = format!("cubes_{n}.pcube"); + + match PCubeFile::new_file(&name) { + Ok(file) => Some(file), + Err(e) => { + if e.kind() == ErrorKind::InvalidData || e.kind() == ErrorKind::Other { + println!("Enountered invalid cache file {name}. Error: {e}."); + } + None + } + } +} + +/// load largest findable cachefile with size <= n - 1 into a vec +/// returns a vec and the next order above the found cache file +fn load_cache(n: usize) -> impl AllUniquePolycubeIterator { + enum CacheOrbase { + Cache(opencubes::pcube::AllUnique), + Base(bool), + } + + impl Iterator for CacheOrbase { + type Item = RawPCube; + + fn next(&mut self) -> Option { + match self { + CacheOrbase::Cache(cache) => cache.next(), + CacheOrbase::Base(v) if v == &false => { + *v = true; + let mut base = RawPCube::new_empty(1, 1, 1); + base.set(0, 0, 0, true); + Some(base) + } + CacheOrbase::Base(_) => None, + } + } + + fn size_hint(&self) -> (usize, Option) { + match self { + CacheOrbase::Cache(c) => c.size_hint(), + CacheOrbase::Base(_) => (1, Some(1)), + } + } + } + + impl PolycubeIterator for CacheOrbase { + fn is_canonical(&self) -> bool { + match self { + CacheOrbase::Cache(c) => c.is_canonical(), + CacheOrbase::Base(_) => true, + } + } + + fn n_hint(&self) -> Option { + match self { + CacheOrbase::Cache(c) => Some(c.n()), + CacheOrbase::Base(_) => Some(1), + } + } + } + + impl UniquePolycubeIterator for CacheOrbase {} + impl AllPolycubeIterator for CacheOrbase {} + impl AllUniquePolycubeIterator for CacheOrbase {} + + let calculate_from = 2; + + for n in (calculate_from..n).rev() { + let cache = if let Some(file) = load_cache_file(n) { + file + } else { + continue; + }; + + println!("Found cache for N = {n}."); + return CacheOrbase::Cache(cache.assume_all_unique()); + } + + println!( + "No cache file found for size <= {}. Starting from N = 1", + n - 1 + ); + + CacheOrbase::Base(false) +} + +fn unique_expansions( + use_cache: bool, + n: usize, + compression: Compression, + current: impl AllUniquePolycubeIterator, + parallel: bool, +) -> Vec { + if n == 0 { + return Vec::new(); + } + + let calculate_from = current.n(); + let current = current.collect(); + + let mut current = AllUniques { + current: Arc::new(current), + offset: 0, + n: calculate_from, + }; + + let mut i = calculate_from; + loop { + let bar = make_bar(current.len() as u64); + bar.set_message(format!("base polycubes expanded for N = {i}...")); + + let start = Instant::now(); + + let with_bar = PolycubeProgressBarIter::new(bar.clone(), current); + let next: Vec = if parallel { + NaivePolyCube::unique_expansions_rayon(with_bar).collect() + } else { + NaivePolyCube::unique_expansions(with_bar).collect() + }; + + bar.set_message(format!( + "Found {} unique expansions (N = {}) in {} ms.", + next.len(), + i + 1, + start.elapsed().as_millis(), + )); + + bar.finish(); + + if use_cache { + save_to_cache(&bar, compression, i + 1, next.iter().map(Clone::clone)); + } + + i += 1; + + if i == n { + return next; + } else { + current = AllUniques { + current: Arc::new(next), + offset: 0, + n: i + 1, + }; + } + } +} + +pub fn enumerate(opts: &EnumerateOpts) { + let n = opts.n; + let cache = !opts.no_cache; + + if n < 2 { + println!("n < 2 unsuported"); + return; + } + + let start = Instant::now(); + + let seed_list = load_cache(n); + + //Select enumeration function to run + let cubes_len = match (opts.mode, opts.no_parallelism) { + (EnumerationMode::Standard, no_parallelism) => { + let cubes = + unique_expansions(cache, n, opts.cache_compression, seed_list, !no_parallelism); + cubes.len() + } + (EnumerationMode::RotationReduced, not_parallel) => { + if n > 16 { + println!("n > 16 not supported for rotation reduced"); + return; + } + if !not_parallel { + println!("no parallel implementation for rotation-reduced, running single threaded") + } + let bar = if let (_, Some(max)) = seed_list.size_hint() { + make_bar(max as u64) + } else { + unknown_bar() + }; + + rotation_reduced::gen_polycubes(n, &bar) + } + (EnumerationMode::PointList, not_parallel) => { + if n > 16 { + println!("n > 16 not supported for point-list"); + return; + } + let bar = if let (_, Some(max)) = seed_list.size_hint() { + make_bar(max as u64) + } else { + unknown_bar() + }; + + let startn = seed_list.n() + 1; + let cubes = pointlist::gen_polycubes( + n, + cache, + !not_parallel, + seed_list.collect(), + startn, + &bar, + ); + cubes.len() + } + (EnumerationMode::Hashless, not_parallel) => { + let startn = seed_list.n() + 1; + let bar = if let (_, Some(max)) = seed_list.size_hint() { + make_bar(max as u64) + } else { + unknown_bar() + }; + + hashless::gen_polycubes(n, !not_parallel, seed_list.collect(), startn, &bar) + } + }; + + let duration = start.elapsed(); + + println!("Unique polycubes found for N = {n}: {cubes_len}.",); + println!("Duration: {} ms", duration.as_millis()); +} From 4993b57e37805dc0edcafe7ede89d7f7ae271465 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 21:51:09 +0200 Subject: [PATCH 21/39] Fixup for N <= 2 --- rust/src/cli/enumerate.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/rust/src/cli/enumerate.rs b/rust/src/cli/enumerate.rs index 22ea882..e49ca7a 100644 --- a/rust/src/cli/enumerate.rs +++ b/rust/src/cli/enumerate.rs @@ -1,6 +1,5 @@ use std::{io::ErrorKind, sync::Arc, time::Instant}; -use ::indicatif::ProgressBar; use opencubes::{ hashless, iterator::{indicatif::PolycubeProgressBarIter, *}, @@ -50,7 +49,6 @@ impl UniquePolycubeIterator for AllUniques {} impl AllUniquePolycubeIterator for AllUniques {} fn save_to_cache( - bar: &ProgressBar, compression: Compression, n: usize, // Ideally, this would be `AllUniquePolycubeIterator` but it's @@ -59,12 +57,10 @@ fn save_to_cache( ) { let name = &format!("cubes_{n}.pcube"); if !std::fs::File::open(name).is_ok() { - bar.println(format!("Saving {} cubes to cache file", cubes.len())); + println!("Saving {} cubes to cache file", cubes.len()); PCubeFile::write_file(false, compression.into(), cubes, name).unwrap(); } else { - bar.println(format!( - "Cache file already exists for N = {n}. Not overwriting." - )); + println!("Cache file already exists for N = {n}. Not overwriting."); } } @@ -149,7 +145,7 @@ fn load_cache(n: usize) -> impl AllUniquePolycubeIterator { println!( "No cache file found for size <= {}. Starting from N = 1", - n - 1 + n.saturating_sub(1) ); CacheOrbase::Base(false) @@ -176,6 +172,7 @@ fn unique_expansions( }; let mut i = calculate_from; + loop { let bar = make_bar(current.len() as u64); bar.set_message(format!("base polycubes expanded for N = {i}...")); @@ -199,12 +196,12 @@ fn unique_expansions( bar.finish(); if use_cache { - save_to_cache(&bar, compression, i + 1, next.iter().map(Clone::clone)); + save_to_cache(compression, i + 1, next.iter().map(Clone::clone)); } i += 1; - if i == n { + if n.saturating_sub(i) == 0 { return next; } else { current = AllUniques { @@ -220,11 +217,6 @@ pub fn enumerate(opts: &EnumerateOpts) { let n = opts.n; let cache = !opts.no_cache; - if n < 2 { - println!("n < 2 unsuported"); - return; - } - let start = Instant::now(); let seed_list = load_cache(n); From 53e03a73ee46c6f72cf01c643551824db7a364b3 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Mon, 24 Jul 2023 22:30:41 +0200 Subject: [PATCH 22/39] Fix behavior for cacheless --- rust/src/cli/enumerate.rs | 88 ++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/rust/src/cli/enumerate.rs b/rust/src/cli/enumerate.rs index e49ca7a..bcaf492 100644 --- a/rust/src/cli/enumerate.rs +++ b/rust/src/cli/enumerate.rs @@ -78,58 +78,58 @@ fn load_cache_file(n: usize) -> Option { } } -/// load largest findable cachefile with size <= n - 1 into a vec -/// returns a vec and the next order above the found cache file -fn load_cache(n: usize) -> impl AllUniquePolycubeIterator { - enum CacheOrbase { - Cache(opencubes::pcube::AllUnique), - Base(bool), - } +enum CacheOrbase { + Cache(opencubes::pcube::AllUnique), + Base(bool), +} + +impl Iterator for CacheOrbase { + type Item = RawPCube; - impl Iterator for CacheOrbase { - type Item = RawPCube; - - fn next(&mut self) -> Option { - match self { - CacheOrbase::Cache(cache) => cache.next(), - CacheOrbase::Base(v) if v == &false => { - *v = true; - let mut base = RawPCube::new_empty(1, 1, 1); - base.set(0, 0, 0, true); - Some(base) - } - CacheOrbase::Base(_) => None, + fn next(&mut self) -> Option { + match self { + CacheOrbase::Cache(cache) => cache.next(), + CacheOrbase::Base(v) if v == &false => { + *v = true; + let mut base = RawPCube::new_empty(1, 1, 1); + base.set(0, 0, 0, true); + Some(base) } + CacheOrbase::Base(_) => None, } + } - fn size_hint(&self) -> (usize, Option) { - match self { - CacheOrbase::Cache(c) => c.size_hint(), - CacheOrbase::Base(_) => (1, Some(1)), - } + fn size_hint(&self) -> (usize, Option) { + match self { + CacheOrbase::Cache(c) => c.size_hint(), + CacheOrbase::Base(_) => (1, Some(1)), } } +} - impl PolycubeIterator for CacheOrbase { - fn is_canonical(&self) -> bool { - match self { - CacheOrbase::Cache(c) => c.is_canonical(), - CacheOrbase::Base(_) => true, - } +impl PolycubeIterator for CacheOrbase { + fn is_canonical(&self) -> bool { + match self { + CacheOrbase::Cache(c) => c.is_canonical(), + CacheOrbase::Base(_) => true, } + } - fn n_hint(&self) -> Option { - match self { - CacheOrbase::Cache(c) => Some(c.n()), - CacheOrbase::Base(_) => Some(1), - } + fn n_hint(&self) -> Option { + match self { + CacheOrbase::Cache(c) => Some(c.n()), + CacheOrbase::Base(_) => Some(1), } } +} - impl UniquePolycubeIterator for CacheOrbase {} - impl AllPolycubeIterator for CacheOrbase {} - impl AllUniquePolycubeIterator for CacheOrbase {} +impl UniquePolycubeIterator for CacheOrbase {} +impl AllPolycubeIterator for CacheOrbase {} +impl AllUniquePolycubeIterator for CacheOrbase {} +/// load largest findable cachefile with size <= n - 1 into a vec +/// returns a vec and the next order above the found cache file +fn load_cache(n: usize) -> CacheOrbase { let calculate_from = 2; for n in (calculate_from..n).rev() { @@ -152,7 +152,7 @@ fn load_cache(n: usize) -> impl AllUniquePolycubeIterator { } fn unique_expansions( - use_cache: bool, + save_cache: bool, n: usize, compression: Compression, current: impl AllUniquePolycubeIterator, @@ -195,7 +195,7 @@ fn unique_expansions( bar.finish(); - if use_cache { + if save_cache { save_to_cache(compression, i + 1, next.iter().map(Clone::clone)); } @@ -219,7 +219,11 @@ pub fn enumerate(opts: &EnumerateOpts) { let start = Instant::now(); - let seed_list = load_cache(n); + let seed_list = if opts.no_cache { + CacheOrbase::Base(false) + } else { + load_cache(n) + }; //Select enumeration function to run let cubes_len = match (opts.mode, opts.no_parallelism) { From d2bbd73dcfdca7f4cda83795567d16ad7cf01054 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 16:54:47 +0200 Subject: [PATCH 23/39] Not sure where this came from --- rust/src/cli/cli.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/src/cli/cli.rs b/rust/src/cli/cli.rs index b580624..792b94e 100644 --- a/rust/src/cli/cli.rs +++ b/rust/src/cli/cli.rs @@ -16,7 +16,6 @@ fn unknown_bar() -> ProgressBar { let style = ProgressStyle::with_template("[{elapsed_precise}] [{spinner:10.cyan/blue}] {msg}") .unwrap() .tick_strings(&[ - ">---------", ">---------", "=>--------", "<=>-------", From 4856d7ebbfe267a3f6c8a040f75fea6d4eda0d76 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 17:00:07 +0200 Subject: [PATCH 24/39] Loosen this trait bound --- rust/src/naive_polycube/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/src/naive_polycube/mod.rs b/rust/src/naive_polycube/mod.rs index 912125c..1027907 100644 --- a/rust/src/naive_polycube/mod.rs +++ b/rust/src/naive_polycube/mod.rs @@ -452,7 +452,7 @@ impl NaivePolyCube { /// items in `from_set`. pub fn unique_expansions(from_set: I) -> impl AllUniquePolycubeIterator where - I: AllUniquePolycubeIterator, + I: AllPolycubeIterator, { let out_n = from_set.n() + 1; let mut uniques = HashSet::new(); @@ -594,7 +594,7 @@ impl NaivePolyCube { // TODO: turn this into an iterator that yield unique expansions? pub fn unique_expansions_rayon(from_set: I) -> impl AllUniquePolycubeIterator where - I: AllUniquePolycubeIterator + ExactSizeIterator + Send + Sync + 'static, + I: AllPolycubeIterator + ExactSizeIterator + Send + Sync + 'static, { use rayon::prelude::*; From 882718f1a91630e2362f03067d5b681ac6b95bff Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 18:40:41 +0200 Subject: [PATCH 25/39] hashless: move into `impl` block --- rust/src/hashless.rs | 344 ++++++++++++++++++++++--------------------- 1 file changed, 176 insertions(+), 168 deletions(-) diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index 4ce4ed2..723e795 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -11,198 +11,206 @@ use crate::{ rotations::{rot_matrix_points, to_min_rot_points, MatrixCol}, }; -/// helper function to not duplicate code for canonicalising polycubes -/// and storing them in the hashset -fn insert_map(store: &mut HashSet>, dim: &Dim, map: &CubeMapPos<32>, count: usize) { - if !store.contains(map) { - let map = to_min_rot_points(map, dim, count); - store.insert(map); - } +pub struct HashlessCubeMap { + inner: HashSet>, } -/// try expaning each cube into both x+1 and x-1, calculating new dimension -/// and ensuring x is never negative -#[inline] -fn expand_xs(dst: &mut HashSet>, seed: &CubeMapPos<32>, shape: &Dim, count: usize) { - for (i, coord) in seed.cubes[0..count].iter().enumerate() { - if !seed.cubes[(i + 1)..count].contains(&(coord + 1)) { - let mut new_shape = *shape; - let mut exp_map = *seed; +impl HashlessCubeMap { + pub fn new() -> Self { + Self { + inner: HashSet::new(), + } + } - array_insert(coord + 1, &mut exp_map.cubes[i..=count]); - new_shape.x = max(new_shape.x, ((coord + 1) & 0x1f) as usize); - insert_map(dst, &new_shape, &exp_map, count + 1) + /// helper function to not duplicate code for canonicalising polycubes + /// and storing them in the hashset + fn insert_map(&mut self, dim: &Dim, map: &CubeMapPos, count: usize) { + if !self.inner.contains(map) { + let map = to_min_rot_points(map, dim, count); + self.inner.insert(map); } - if coord & 0x1f != 0 { - if !seed.cubes[0..i].contains(&(coord - 1)) { + } + + /// try expaning each cube into both x+1 and x-1, calculating new dimension + /// and ensuring x is never negative + #[inline] + fn expand_xs(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { + for (i, coord) in seed.cubes[0..count].iter().enumerate() { + if !seed.cubes[(i + 1)..count].contains(&(coord + 1)) { + let mut new_shape = *shape; let mut exp_map = *seed; - //faster move of top half hopefully - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(coord - 1, &mut exp_map.cubes[0..=i]); - insert_map(dst, shape, &exp_map, count + 1) + + array_insert(coord + 1, &mut exp_map.cubes[i..=count]); + new_shape.x = max(new_shape.x, ((coord + 1) & 0x1f) as usize); + self.insert_map(&new_shape, &exp_map, count + 1) } - } else { - let mut new_shape = *shape; - new_shape.x += 1; - let mut exp_map = *seed; - for i in 0..count { - exp_map.cubes[i] += 1; + + if coord & 0x1f != 0 { + if !seed.cubes[0..i].contains(&(coord - 1)) { + let mut exp_map = *seed; + //faster move of top half hopefully + array_shift(&mut exp_map.cubes[i..=count]); + array_insert(coord - 1, &mut exp_map.cubes[0..=i]); + self.insert_map(shape, &exp_map, count + 1) + } + } else { + let mut new_shape = *shape; + new_shape.x += 1; + let mut exp_map = *seed; + for i in 0..count { + exp_map.cubes[i] += 1; + } + array_shift(&mut exp_map.cubes[i..=count]); + array_insert(*coord, &mut exp_map.cubes[0..=i]); + self.insert_map(&new_shape, &exp_map, count + 1) } - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(*coord, &mut exp_map.cubes[0..=i]); - insert_map(dst, &new_shape, &exp_map, count + 1) } } -} -/// try expaning each cube into both y+1 and y-1, calculating new dimension -/// and ensuring y is never negative -#[inline] -fn expand_ys(dst: &mut HashSet>, seed: &CubeMapPos<32>, shape: &Dim, count: usize) { - for (i, coord) in seed.cubes[0..count].iter().enumerate() { - if !seed.cubes[(i + 1)..count].contains(&(coord + (1 << 5))) { - let mut new_shape = *shape; - let mut exp_map = *seed; - array_insert(coord + (1 << 5), &mut exp_map.cubes[i..=count]); - new_shape.y = max(new_shape.y, (((coord >> 5) + 1) & 0x1f) as usize); - insert_map(dst, &new_shape, &exp_map, count + 1) - } - if (coord >> 5) & 0x1f != 0 { - if !seed.cubes[0..i].contains(&(coord - (1 << 5))) { + /// try expaning each cube into both y+1 and y-1, calculating new dimension + /// and ensuring y is never negative + #[inline] + fn expand_ys(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { + for (i, coord) in seed.cubes[0..count].iter().enumerate() { + if !seed.cubes[(i + 1)..count].contains(&(coord + (1 << 5))) { + let mut new_shape = *shape; let mut exp_map = *seed; - //faster move of top half hopefully - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(coord - (1 << 5), &mut exp_map.cubes[0..=i]); - insert_map(dst, shape, &exp_map, count + 1) + array_insert(coord + (1 << 5), &mut exp_map.cubes[i..=count]); + new_shape.y = max(new_shape.y, (((coord >> 5) + 1) & 0x1f) as usize); + self.insert_map(&new_shape, &exp_map, count + 1) } - } else { - let mut new_shape = *shape; - new_shape.y += 1; - let mut exp_map = *seed; - for i in 0..count { - exp_map.cubes[i] += 1 << 5; + if (coord >> 5) & 0x1f != 0 { + if !seed.cubes[0..i].contains(&(coord - (1 << 5))) { + let mut exp_map = *seed; + //faster move of top half hopefully + array_shift(&mut exp_map.cubes[i..=count]); + array_insert(coord - (1 << 5), &mut exp_map.cubes[0..=i]); + self.insert_map(shape, &exp_map, count + 1) + } + } else { + let mut new_shape = *shape; + new_shape.y += 1; + let mut exp_map = *seed; + for i in 0..count { + exp_map.cubes[i] += 1 << 5; + } + array_shift(&mut exp_map.cubes[i..=count]); + array_insert(*coord, &mut exp_map.cubes[0..=i]); + self.insert_map(&new_shape, &exp_map, count + 1) } - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(*coord, &mut exp_map.cubes[0..=i]); - insert_map(dst, &new_shape, &exp_map, count + 1) } } -} -/// try expaning each cube into both z+1 and z-1, calculating new dimension -/// and ensuring z is never negative -#[inline] -fn expand_zs(dst: &mut HashSet>, seed: &CubeMapPos<32>, shape: &Dim, count: usize) { - for (i, coord) in seed.cubes[0..count].iter().enumerate() { - if !seed.cubes[(i + 1)..count].contains(&(coord + (1 << 10))) { - let mut new_shape = *shape; - let mut exp_map = *seed; - array_insert(coord + (1 << 10), &mut exp_map.cubes[i..=count]); - new_shape.z = max(new_shape.z, (((coord >> 10) + 1) & 0x1f) as usize); - insert_map(dst, &new_shape, &exp_map, count + 1) - } - if (coord >> 10) & 0x1f != 0 { - if !seed.cubes[0..i].contains(&(coord - (1 << 10))) { + /// try expaning each cube into both z+1 and z-1, calculating new dimension + /// and ensuring z is never negative + #[inline] + fn expand_zs(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { + for (i, coord) in seed.cubes[0..count].iter().enumerate() { + if !seed.cubes[(i + 1)..count].contains(&(coord + (1 << 10))) { + let mut new_shape = *shape; let mut exp_map = *seed; - //faster move of top half hopefully - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(coord - (1 << 10), &mut exp_map.cubes[0..=i]); - insert_map(dst, shape, &exp_map, count + 1) + array_insert(coord + (1 << 10), &mut exp_map.cubes[i..=count]); + new_shape.z = max(new_shape.z, (((coord >> 10) + 1) & 0x1f) as usize); + self.insert_map(&new_shape, &exp_map, count + 1) } - } else { - let mut new_shape = *shape; - new_shape.z += 1; - let mut exp_map = *seed; - for i in 0..count { - exp_map.cubes[i] += 1 << 10; + + if (coord >> 10) & 0x1f != 0 { + if !seed.cubes[0..i].contains(&(coord - (1 << 10))) { + let mut exp_map = *seed; + //faster move of top half hopefully + array_shift(&mut exp_map.cubes[i..=count]); + array_insert(coord - (1 << 10), &mut exp_map.cubes[0..=i]); + self.insert_map(shape, &exp_map, count + 1) + } + } else { + let mut new_shape = *shape; + new_shape.z += 1; + let mut exp_map = *seed; + for i in 0..count { + exp_map.cubes[i] += 1 << 10; + } + array_shift(&mut exp_map.cubes[i..=count]); + array_insert(*coord, &mut exp_map.cubes[0..=i]); + self.insert_map(&new_shape, &exp_map, count + 1) } - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(*coord, &mut exp_map.cubes[0..=i]); - insert_map(dst, &new_shape, &exp_map, count + 1) } } -} -/// reduce number of expansions needing to be performed based on -/// X >= Y >= Z constraint on Dim -#[inline] -fn do_cube_expansion( - dst: &mut HashSet>, - seed: &CubeMapPos<32>, - shape: &Dim, - count: usize, -) { - if shape.y < shape.x { - expand_ys(dst, seed, shape, count); - } - if shape.z < shape.y { - expand_zs(dst, seed, shape, count); + /// reduce number of expansions needing to be performed based on + /// X >= Y >= Z constraint on Dim + #[inline] + fn do_cube_expansion(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { + if shape.y < shape.x { + self.expand_ys(seed, shape, count); + } + if shape.z < shape.y { + self.expand_zs(seed, shape, count); + } + self.expand_xs(seed, shape, count); } - expand_xs(dst, seed, shape, count); -} -/// perform the cube expansion for a given polycube -/// if perform extra expansions for cases where the dimensions are equal as -/// square sides may miss poly cubes otherwise -#[inline] -fn expand_cube_map( - dst: &mut HashSet>, - seed: &CubeMapPos<32>, - shape: &Dim, - count: usize, -) { - if shape.x == shape.y && shape.x > 0 { - let rotz = rot_matrix_points( - seed, - shape, - count, - MatrixCol::YN, - MatrixCol::XN, - MatrixCol::ZN, - 1025, - ); - do_cube_expansion(dst, &rotz, shape, count); - } - if shape.y == shape.z && shape.y > 0 { - let rotx = rot_matrix_points( - seed, - shape, - count, - MatrixCol::XN, - MatrixCol::ZP, - MatrixCol::YP, - 1025, - ); - do_cube_expansion(dst, &rotx, shape, count); - } - if shape.x == shape.z && shape.x > 0 { - let roty = rot_matrix_points( - seed, - shape, - count, - MatrixCol::ZP, - MatrixCol::YP, - MatrixCol::XN, - 1025, - ); - do_cube_expansion(dst, &roty, shape, count); + /// perform the cube expansion for a given polycube + /// if perform extra expansions for cases where the dimensions are equal as + /// square sides may miss poly cubes otherwise + #[inline] + fn expand_cube_map(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { + if shape.x == shape.y && shape.x > 0 { + let rotz = rot_matrix_points( + seed, + shape, + count, + MatrixCol::YN, + MatrixCol::XN, + MatrixCol::ZN, + 1025, + ); + self.do_cube_expansion(&rotz, shape, count); + } + + if shape.y == shape.z && shape.y > 0 { + let rotx = rot_matrix_points( + seed, + shape, + count, + MatrixCol::XN, + MatrixCol::ZP, + MatrixCol::YP, + 1025, + ); + self.do_cube_expansion(&rotx, shape, count); + } + if shape.x == shape.z && shape.x > 0 { + let roty = rot_matrix_points( + seed, + shape, + count, + MatrixCol::ZP, + MatrixCol::YP, + MatrixCol::XN, + 1025, + ); + self.do_cube_expansion(&roty, shape, count); + } + + self.do_cube_expansion(seed, shape, count); } - do_cube_expansion(dst, seed, shape, count); -} -fn enumerate_canonical_children(seed: &CubeMapPos<32>, count: usize, target: usize) -> usize { - let mut children = HashSet::new(); - children.clear(); - let shape = seed.extrapolate_dim(); - expand_cube_map(&mut children, seed, &shape, count); - children.retain(|child| child.is_canonical_root(count, seed)); - if count + 1 == target { - children.len() - } else { - children - .iter() - .map(|child| enumerate_canonical_children(child, count + 1, target)) - .sum() + fn enumerate_canonical_children(seed: &CubeMapPos, count: usize, target: usize) -> usize { + let mut map = Self::new(); + let shape = seed.extrapolate_dim(); + map.expand_cube_map(seed, &shape, count); + + map.inner + .retain(|child| child.is_canonical_root(count, seed)); + + if count + 1 == target { + map.inner.len() + } else { + map.inner + .iter() + .map(|child| Self::enumerate_canonical_children(child, count + 1, target)) + .sum() + } } } @@ -223,8 +231,8 @@ pub fn gen_polycubes( calculate_from - 1 )); - let process = |seed| { - let children = enumerate_canonical_children(&seed, calculate_from - 1, n); + let process = |seed: CubeMapPos<32>| { + let children = HashlessCubeMap::enumerate_canonical_children(&seed, calculate_from - 1, n); bar.set_message(format!( "seed subsets expanded for N = {}...", calculate_from - 1, From c428b40413295eecde7fcaeaea0150fe7712f691 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 19:38:58 +0200 Subject: [PATCH 26/39] Rustify expand_ys a bit --- rust/src/hashless.rs | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index 723e795..ad7dee3 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -67,37 +67,44 @@ impl HashlessCubeMap { } } - /// try expaning each cube into both y+1 and y-1, calculating new dimension + /// Try expanding each cube into both y+1 and y-1, calculating new dimension /// and ensuring y is never negative #[inline] fn expand_ys(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { - for (i, coord) in seed.cubes[0..count].iter().enumerate() { - if !seed.cubes[(i + 1)..count].contains(&(coord + (1 << 5))) { + for (i, coord) in seed.cubes[..count].iter().enumerate() { + let y_plus = coord + (1 << 5); + let y_minus = coord - (1 << 5); + + let mut new_map = *seed; + let mut new_shape = *shape; + + if !seed.cubes[(i + 1)..count].contains(&y_plus) { let mut new_shape = *shape; let mut exp_map = *seed; - array_insert(coord + (1 << 5), &mut exp_map.cubes[i..=count]); + array_insert(y_plus, &mut exp_map.cubes[i..=count]); new_shape.y = max(new_shape.y, (((coord >> 5) + 1) & 0x1f) as usize); self.insert_map(&new_shape, &exp_map, count + 1) } - if (coord >> 5) & 0x1f != 0 { - if !seed.cubes[0..i].contains(&(coord - (1 << 5))) { - let mut exp_map = *seed; - //faster move of top half hopefully - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(coord - (1 << 5), &mut exp_map.cubes[0..=i]); - self.insert_map(shape, &exp_map, count + 1) + + // Determine the new shape and the coordinate at which the next cube + // will be inserted. + let insert_coord = if (coord >> 5) & 0x1f != 0 { + if !seed.cubes[0..i].contains(&y_minus) { + y_minus + } else { + continue; } } else { - let mut new_shape = *shape; new_shape.y += 1; - let mut exp_map = *seed; for i in 0..count { - exp_map.cubes[i] += 1 << 5; + new_map.cubes[i] += 1 << 5; } - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(*coord, &mut exp_map.cubes[0..=i]); - self.insert_map(&new_shape, &exp_map, count + 1) - } + *coord + }; + + array_shift(&mut new_map.cubes[i..=count]); + array_insert(insert_coord, &mut new_map.cubes[0..=i]); + self.insert_map(&new_shape, &new_map, count + 1) } } From aa050677a513f77ce6fefb3205ed2e3588ae8ea9 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 19:51:21 +0200 Subject: [PATCH 27/39] Macros, anyone? --- rust/src/hashless.rs | 171 +++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 112 deletions(-) diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index ad7dee3..2720b93 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -15,6 +15,61 @@ pub struct HashlessCubeMap { inner: HashSet>, } +macro_rules! define_expand_fn { + ($name:ident, $shift:literal, $dim:ident, $dim_str:literal) => { + /// Try expanding each cube into + #[doc = $dim_str] + /// plus one and + #[doc = $dim_str] + /// minus one , calculating new dimension and ensuring + #[doc = $dim_str] + /// is never negative + #[inline(always)] + fn $name(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { + for (i, coord) in seed.cubes[0..count].iter().enumerate() { + let plus = coord + (1 << $shift); + let minus = coord - (1 << $shift); + + // Check if we can insert a new cube at $dim + 1 + if !seed.cubes[(i + 1)..count].contains(&plus) { + let mut new_shape = *shape; + let mut exp_map = *seed; + + array_insert(plus, &mut exp_map.cubes[i..=count]); + new_shape.$dim = max(new_shape.$dim, (((coord >> $shift) + 1) & 0x1f) as usize); + self.insert_map(&new_shape, &exp_map, count + 1) + } + + let mut new_map = *seed; + let mut new_shape = *shape; + + // If the coord is out of bounds for $dim, shift everything + // over and insert a new cube at the out-of-bounds position. + // If it is in bounds, check if the $dim - 1 value is already + // set. + // NOTE(datdenkikniet): ^^ I deduced this. Is it correct? + let insert_coord = if (coord >> $shift) & 0x1f != 0 { + if !seed.cubes[0..i].contains(&minus) { + minus + } else { + continue; + } + } else { + new_shape.$dim += 1; + for i in 0..count { + new_map.cubes[i] += 1 << $shift; + } + *coord + }; + + array_shift(&mut new_map.cubes[i..=count]); + array_insert(insert_coord, &mut new_map.cubes[0..=i]); + self.insert_map(&new_shape, &new_map, count + 1) + } + } + }; +} + impl HashlessCubeMap { pub fn new() -> Self { Self { @@ -22,6 +77,10 @@ impl HashlessCubeMap { } } + define_expand_fn!(expand_xs, 0, x, "x"); + define_expand_fn!(expand_ys, 5, y, "y"); + define_expand_fn!(expand_zs, 10, z, "z"); + /// helper function to not duplicate code for canonicalising polycubes /// and storing them in the hashset fn insert_map(&mut self, dim: &Dim, map: &CubeMapPos, count: usize) { @@ -31,118 +90,6 @@ impl HashlessCubeMap { } } - /// try expaning each cube into both x+1 and x-1, calculating new dimension - /// and ensuring x is never negative - #[inline] - fn expand_xs(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { - for (i, coord) in seed.cubes[0..count].iter().enumerate() { - if !seed.cubes[(i + 1)..count].contains(&(coord + 1)) { - let mut new_shape = *shape; - let mut exp_map = *seed; - - array_insert(coord + 1, &mut exp_map.cubes[i..=count]); - new_shape.x = max(new_shape.x, ((coord + 1) & 0x1f) as usize); - self.insert_map(&new_shape, &exp_map, count + 1) - } - - if coord & 0x1f != 0 { - if !seed.cubes[0..i].contains(&(coord - 1)) { - let mut exp_map = *seed; - //faster move of top half hopefully - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(coord - 1, &mut exp_map.cubes[0..=i]); - self.insert_map(shape, &exp_map, count + 1) - } - } else { - let mut new_shape = *shape; - new_shape.x += 1; - let mut exp_map = *seed; - for i in 0..count { - exp_map.cubes[i] += 1; - } - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(*coord, &mut exp_map.cubes[0..=i]); - self.insert_map(&new_shape, &exp_map, count + 1) - } - } - } - - /// Try expanding each cube into both y+1 and y-1, calculating new dimension - /// and ensuring y is never negative - #[inline] - fn expand_ys(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { - for (i, coord) in seed.cubes[..count].iter().enumerate() { - let y_plus = coord + (1 << 5); - let y_minus = coord - (1 << 5); - - let mut new_map = *seed; - let mut new_shape = *shape; - - if !seed.cubes[(i + 1)..count].contains(&y_plus) { - let mut new_shape = *shape; - let mut exp_map = *seed; - array_insert(y_plus, &mut exp_map.cubes[i..=count]); - new_shape.y = max(new_shape.y, (((coord >> 5) + 1) & 0x1f) as usize); - self.insert_map(&new_shape, &exp_map, count + 1) - } - - // Determine the new shape and the coordinate at which the next cube - // will be inserted. - let insert_coord = if (coord >> 5) & 0x1f != 0 { - if !seed.cubes[0..i].contains(&y_minus) { - y_minus - } else { - continue; - } - } else { - new_shape.y += 1; - for i in 0..count { - new_map.cubes[i] += 1 << 5; - } - *coord - }; - - array_shift(&mut new_map.cubes[i..=count]); - array_insert(insert_coord, &mut new_map.cubes[0..=i]); - self.insert_map(&new_shape, &new_map, count + 1) - } - } - - /// try expaning each cube into both z+1 and z-1, calculating new dimension - /// and ensuring z is never negative - #[inline] - fn expand_zs(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { - for (i, coord) in seed.cubes[0..count].iter().enumerate() { - if !seed.cubes[(i + 1)..count].contains(&(coord + (1 << 10))) { - let mut new_shape = *shape; - let mut exp_map = *seed; - array_insert(coord + (1 << 10), &mut exp_map.cubes[i..=count]); - new_shape.z = max(new_shape.z, (((coord >> 10) + 1) & 0x1f) as usize); - self.insert_map(&new_shape, &exp_map, count + 1) - } - - if (coord >> 10) & 0x1f != 0 { - if !seed.cubes[0..i].contains(&(coord - (1 << 10))) { - let mut exp_map = *seed; - //faster move of top half hopefully - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(coord - (1 << 10), &mut exp_map.cubes[0..=i]); - self.insert_map(shape, &exp_map, count + 1) - } - } else { - let mut new_shape = *shape; - new_shape.z += 1; - let mut exp_map = *seed; - for i in 0..count { - exp_map.cubes[i] += 1 << 10; - } - array_shift(&mut exp_map.cubes[i..=count]); - array_insert(*coord, &mut exp_map.cubes[0..=i]); - self.insert_map(&new_shape, &exp_map, count + 1) - } - } - } - /// reduce number of expansions needing to be performed based on /// X >= Y >= Z constraint on Dim #[inline] From 94471eb878a2f7764e4953436b0c40ac91af6c41 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 20:01:57 +0200 Subject: [PATCH 28/39] Using MatrixCol::* --- rust/src/hashless.rs | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index 2720b93..b6f4cf0 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -108,41 +108,19 @@ impl HashlessCubeMap { /// square sides may miss poly cubes otherwise #[inline] fn expand_cube_map(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { + use MatrixCol::*; + if shape.x == shape.y && shape.x > 0 { - let rotz = rot_matrix_points( - seed, - shape, - count, - MatrixCol::YN, - MatrixCol::XN, - MatrixCol::ZN, - 1025, - ); + let rotz = rot_matrix_points(seed, shape, count, YN, XN, ZN, 1025); self.do_cube_expansion(&rotz, shape, count); } if shape.y == shape.z && shape.y > 0 { - let rotx = rot_matrix_points( - seed, - shape, - count, - MatrixCol::XN, - MatrixCol::ZP, - MatrixCol::YP, - 1025, - ); + let rotx = rot_matrix_points(seed, shape, count, XN, ZP, YP, 1025); self.do_cube_expansion(&rotx, shape, count); } if shape.x == shape.z && shape.x > 0 { - let roty = rot_matrix_points( - seed, - shape, - count, - MatrixCol::ZP, - MatrixCol::YP, - MatrixCol::XN, - 1025, - ); + let roty = rot_matrix_points(seed, shape, count, ZP, YP, XN, 1025); self.do_cube_expansion(&roty, shape, count); } From 869be62bd489963214535af0ad2736707c151111 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 20:10:21 +0200 Subject: [PATCH 29/39] Move enumerate_hashless into `enumerate` --- rust/src/cli/enumerate.rs | 60 +++++++++++++++++++++++++++++++------- rust/src/hashless.rs | 61 ++++----------------------------------- 2 files changed, 56 insertions(+), 65 deletions(-) diff --git a/rust/src/cli/enumerate.rs b/rust/src/cli/enumerate.rs index bcaf492..e12e286 100644 --- a/rust/src/cli/enumerate.rs +++ b/rust/src/cli/enumerate.rs @@ -1,15 +1,19 @@ use std::{io::ErrorKind, sync::Arc, time::Instant}; use opencubes::{ - hashless, + hashless::HashlessCubeMap, iterator::{indicatif::PolycubeProgressBarIter, *}, naive_polycube::NaivePolyCube, pcube::{PCubeFile, RawPCube}, - pointlist, rotation_reduced, + pointlist, + polycube_reps::CubeMapPos, + rotation_reduced, }; use crate::{make_bar, unknown_bar, Compression, EnumerateOpts, EnumerationMode}; +use rayon::{iter::ParallelBridge, prelude::ParallelIterator}; + #[derive(Clone)] struct AllUniques { current: Arc>, @@ -213,6 +217,49 @@ fn unique_expansions( } } +/// run pointlist based generation algorithm +pub fn enumerate_hashless( + n: usize, + parallel: bool, + current: impl AllUniquePolycubeIterator + Send, +) -> usize { + let t1_start = Instant::now(); + + let start_n = current.n(); + let bar = if let (_, Some(max)) = current.size_hint() { + make_bar(max as u64) + } else { + unknown_bar() + }; + + let process = |seed| { + let seed: CubeMapPos<32> = RawPCube::into(seed); + let children = HashlessCubeMap::enumerate_canonical_children(&seed, start_n, n); + bar.set_message(format!("seed subsets expanded for N = {}...", start_n - 1,)); + bar.inc(1); + children + }; + + //convert input vector of NaivePolyCubes and convert them to + let count: usize = if parallel { + current.par_bridge().map(process).sum() + } else { + current.map(process).sum() + }; + + let time = t1_start.elapsed().as_micros(); + bar.set_message(format!( + "Found {} unique expansions (N = {n}) in {}.{:06}s", + count, + time / 1000000, + time % 1000000 + )); + + bar.finish(); + count + //count_polycubes(&seeds); +} + pub fn enumerate(opts: &EnumerateOpts) { let n = opts.n; let cache = !opts.no_cache; @@ -271,14 +318,7 @@ pub fn enumerate(opts: &EnumerateOpts) { cubes.len() } (EnumerationMode::Hashless, not_parallel) => { - let startn = seed_list.n() + 1; - let bar = if let (_, Some(max)) = seed_list.size_hint() { - make_bar(max as u64) - } else { - unknown_bar() - }; - - hashless::gen_polycubes(n, !not_parallel, seed_list.collect(), startn, &bar) + enumerate_hashless(n, !not_parallel, seed_list) } }; diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index b6f4cf0..ff7ad46 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -1,9 +1,6 @@ -use std::{cmp::max, time::Instant}; +use std::cmp::max; -use crate::pcube::RawPCube; use hashbrown::HashSet; -use indicatif::ProgressBar; -use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use crate::{ pointlist::{array_insert, array_shift}, @@ -127,7 +124,11 @@ impl HashlessCubeMap { self.do_cube_expansion(seed, shape, count); } - fn enumerate_canonical_children(seed: &CubeMapPos, count: usize, target: usize) -> usize { + pub fn enumerate_canonical_children( + seed: &CubeMapPos, + count: usize, + target: usize, + ) -> usize { let mut map = Self::new(); let shape = seed.extrapolate_dim(); map.expand_cube_map(seed, &shape, count); @@ -145,53 +146,3 @@ impl HashlessCubeMap { } } } - -/// run pointlist based generation algorithm -pub fn gen_polycubes( - n: usize, - parallel: bool, - current: Vec, - calculate_from: usize, - bar: &ProgressBar, -) -> usize { - let t1_start = Instant::now(); - - let seed_count = current.len(); - bar.set_length(seed_count as u64); - bar.set_message(format!( - "seed subsets expanded for N = {}...", - calculate_from - 1 - )); - - let process = |seed: CubeMapPos<32>| { - let children = HashlessCubeMap::enumerate_canonical_children(&seed, calculate_from - 1, n); - bar.set_message(format!( - "seed subsets expanded for N = {}...", - calculate_from - 1, - )); - bar.inc(1); - children - }; - - //convert input vector of NaivePolyCubes and convert them to - let count: usize = if parallel { - current - .par_iter() - .map(|seed| seed.into()) - .map(process) - .sum() - } else { - current.iter().map(|seed| seed.into()).map(process).sum() - }; - let time = t1_start.elapsed().as_micros(); - bar.set_message(format!( - "Found {} unique expansions (N = {n}) in {}.{:06}s", - count, - time / 1000000, - time % 1000000 - )); - - bar.finish(); - count - //count_polycubes(&seeds); -} From 1f3f9669ae6fd6374351f7fb8de89ddd67a93810 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 20:24:57 +0200 Subject: [PATCH 30/39] Make sure hashless operates on canonical items --- rust/src/hashless.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index ff7ad46..c130fd5 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -131,10 +131,14 @@ impl HashlessCubeMap { ) -> usize { let mut map = Self::new(); let shape = seed.extrapolate_dim(); - map.expand_cube_map(seed, &shape, count); + + let seed = to_min_rot_points(seed, &shape, count); + let shape = seed.extrapolate_dim(); + + map.expand_cube_map(&seed, &shape, count); map.inner - .retain(|child| child.is_canonical_root(count, seed)); + .retain(|child| child.is_canonical_root(count, &seed)); if count + 1 == target { map.inner.len() From 118c3cef11f58e79418dc74a680d46dc1b3d0ae5 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 20:26:17 +0200 Subject: [PATCH 31/39] Cleanup --- rust/src/cli/enumerate.rs | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/rust/src/cli/enumerate.rs b/rust/src/cli/enumerate.rs index e12e286..4d2c2ce 100644 --- a/rust/src/cli/enumerate.rs +++ b/rust/src/cli/enumerate.rs @@ -179,7 +179,7 @@ fn unique_expansions( loop { let bar = make_bar(current.len() as u64); - bar.set_message(format!("base polycubes expanded for N = {i}...")); + bar.set_message(format!("Expanding base polycubes of N = {i}...")); let start = Instant::now(); @@ -232,32 +232,24 @@ pub fn enumerate_hashless( unknown_bar() }; - let process = |seed| { - let seed: CubeMapPos<32> = RawPCube::into(seed); + bar.set_message(format!("seed subsets expanded for N = {}...", start_n - 1,)); + + let process = |seed: RawPCube| { + let seed: CubeMapPos<32> = seed.into(); let children = HashlessCubeMap::enumerate_canonical_children(&seed, start_n, n); - bar.set_message(format!("seed subsets expanded for N = {}...", start_n - 1,)); bar.inc(1); children }; - //convert input vector of NaivePolyCubes and convert them to let count: usize = if parallel { current.par_bridge().map(process).sum() } else { current.map(process).sum() }; - let time = t1_start.elapsed().as_micros(); - bar.set_message(format!( - "Found {} unique expansions (N = {n}) in {}.{:06}s", - count, - time / 1000000, - time % 1000000 - )); + finish_bar(&bar, t1_start.elapsed(), count, n); - bar.finish(); count - //count_polycubes(&seeds); } pub fn enumerate(opts: &EnumerateOpts) { From be8af4b60bf3069fd82895234e1528deca0d5d90 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 20:32:29 +0200 Subject: [PATCH 32/39] Cleanup some more --- rust/src/cli/enumerate.rs | 2 +- rust/src/pcube/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/src/cli/enumerate.rs b/rust/src/cli/enumerate.rs index 4d2c2ce..fdf662b 100644 --- a/rust/src/cli/enumerate.rs +++ b/rust/src/cli/enumerate.rs @@ -232,7 +232,7 @@ pub fn enumerate_hashless( unknown_bar() }; - bar.set_message(format!("seed subsets expanded for N = {}...", start_n - 1,)); + bar.set_message(format!("Expanding seeds of N = {}...", start_n)); let process = |seed: RawPCube| { let seed: CubeMapPos<32> = seed.into(); diff --git a/rust/src/pcube/mod.rs b/rust/src/pcube/mod.rs index 95a6add..b556a76 100644 --- a/rust/src/pcube/mod.rs +++ b/rust/src/pcube/mod.rs @@ -41,7 +41,7 @@ where fn size_hint(&self) -> (usize, Option) { if let Some(len) = self.len { - (0, Some(len)) + (len, Some(len)) } else { (0, None) } From ef6b460c9f4cda7afb06d9aa495b94b0c793978e Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 20:25:18 +0200 Subject: [PATCH 33/39] Add common finish_bar --- rust/src/cli/cli.rs | 10 ++++++++++ rust/src/cli/enumerate.rs | 11 ++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/rust/src/cli/cli.rs b/rust/src/cli/cli.rs index 792b94e..4d6d56d 100644 --- a/rust/src/cli/cli.rs +++ b/rust/src/cli/cli.rs @@ -12,6 +12,16 @@ use rayon::prelude::{IntoParallelIterator, ParallelIterator}; mod enumerate; use enumerate::enumerate; +fn finish_bar(bar: &ProgressBar, duration: Duration, expansions: usize, n: usize) { + let time = duration.as_micros(); + let secs = time / 1_000_000; + let micros = time % 1_000_000; + + bar.finish_with_message(format!( + "Done! Found {expansions} expansions (N = {n}) in {secs}.{micros} s" + )); +} + fn unknown_bar() -> ProgressBar { let style = ProgressStyle::with_template("[{elapsed_precise}] [{spinner:10.cyan/blue}] {msg}") .unwrap() diff --git a/rust/src/cli/enumerate.rs b/rust/src/cli/enumerate.rs index fdf662b..cc6fa1b 100644 --- a/rust/src/cli/enumerate.rs +++ b/rust/src/cli/enumerate.rs @@ -10,7 +10,7 @@ use opencubes::{ rotation_reduced, }; -use crate::{make_bar, unknown_bar, Compression, EnumerateOpts, EnumerationMode}; +use crate::{finish_bar, make_bar, unknown_bar, Compression, EnumerateOpts, EnumerationMode}; use rayon::{iter::ParallelBridge, prelude::ParallelIterator}; @@ -190,14 +190,7 @@ fn unique_expansions( NaivePolyCube::unique_expansions(with_bar).collect() }; - bar.set_message(format!( - "Found {} unique expansions (N = {}) in {} ms.", - next.len(), - i + 1, - start.elapsed().as_millis(), - )); - - bar.finish(); + finish_bar(&bar, start.elapsed(), next.len(), i + 1); if save_cache { save_to_cache(compression, i + 1, next.iter().map(Clone::clone)); From f5c744d9785f1d0c0373aa8c41f589927aabbe8a Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 20:26:12 +0200 Subject: [PATCH 34/39] Forward size hint --- rust/src/pcube/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rust/src/pcube/mod.rs b/rust/src/pcube/mod.rs index b556a76..8dc6175 100644 --- a/rust/src/pcube/mod.rs +++ b/rust/src/pcube/mod.rs @@ -305,6 +305,10 @@ where fn next(&mut self) -> Option { self.inner.next().map(|v| v.ok()).flatten() } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } } impl PolycubeIterator for IgnoreErrorIter @@ -372,6 +376,10 @@ where fn next(&mut self) -> Option { self.inner.next() } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } } impl PolycubeIterator for AllUnique From d677c6b956ce4166bf7686ff3ae4cbf567c2e2c8 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Tue, 25 Jul 2023 21:08:12 +0200 Subject: [PATCH 35/39] Remove remaining when finished --- rust/src/cli/cli.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/rust/src/cli/cli.rs b/rust/src/cli/cli.rs index 4d6d56d..b298e5d 100644 --- a/rust/src/cli/cli.rs +++ b/rust/src/cli/cli.rs @@ -17,6 +17,20 @@ fn finish_bar(bar: &ProgressBar, duration: Duration, expansions: usize, n: usize let secs = time / 1_000_000; let micros = time % 1_000_000; + if let Some(len) = bar.length() { + let pos_width = format!("{}", len).len(); + + let template = format!( + "[{{elapsed_precise}}] {{bar:40.cyan/blue}} {{pos:>{pos_width}}}/{{len}} {{msg}}" + ); + + bar.set_style( + ProgressStyle::with_template(&template) + .unwrap() + .progress_chars("#>-"), + ); + } + bar.finish_with_message(format!( "Done! Found {expansions} expansions (N = {n}) in {secs}.{micros} s" )); @@ -62,7 +76,7 @@ pub fn make_bar(len: u64) -> indicatif::ProgressBar { let pos_width = format!("{len}").len(); let template = - format!("[{{elapsed_precise}}] {{bar:40.cyan/blue}} {{pos:>{pos_width}}}/{{len}} remaining: [{{eta_precise}}] {{msg}}"); + format!("[{{elapsed_precise}}] {{bar:40.cyan/blue}} {{pos:>{pos_width}}}/{{len}} {{msg}} remaining: [{{eta_precise}}]"); bar.set_style( ProgressStyle::with_template(&template) From 94fa4359aa4fd54019c12fc67ce7926cc8ecadfe Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Wed, 26 Jul 2023 19:04:13 +0200 Subject: [PATCH 36/39] Update docs --- rust/src/hashless.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index c130fd5..905b321 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -27,7 +27,8 @@ macro_rules! define_expand_fn { let plus = coord + (1 << $shift); let minus = coord - (1 << $shift); - // Check if we can insert a new cube at $dim + 1 + // Only check if the cube at $dim + 1 already exists at the + // coordinates past `coord` (since the list sorted) if !seed.cubes[(i + 1)..count].contains(&plus) { let mut new_shape = *shape; let mut exp_map = *seed; @@ -41,10 +42,9 @@ macro_rules! define_expand_fn { let mut new_shape = *shape; // If the coord is out of bounds for $dim, shift everything - // over and insert a new cube at the out-of-bounds position. - // If it is in bounds, check if the $dim - 1 value is already - // set. - // NOTE(datdenkikniet): ^^ I deduced this. Is it correct? + // over and create the cube at the out-of-bounds position. + // If it is in bounds, check if the $dim - 1 value already + // exists. let insert_coord = if (coord >> $shift) & 0x1f != 0 { if !seed.cubes[0..i].contains(&minus) { minus From d6c8fbcc7162cd56fd6ef49a1fa67ad2b9f0d982 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Wed, 26 Jul 2023 19:13:39 +0200 Subject: [PATCH 37/39] Just go with MapStore for now --- rust/src/cli/enumerate.rs | 4 ++-- rust/src/hashless.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/src/cli/enumerate.rs b/rust/src/cli/enumerate.rs index cc6fa1b..4ec319d 100644 --- a/rust/src/cli/enumerate.rs +++ b/rust/src/cli/enumerate.rs @@ -1,7 +1,7 @@ use std::{io::ErrorKind, sync::Arc, time::Instant}; use opencubes::{ - hashless::HashlessCubeMap, + hashless::MapStore, iterator::{indicatif::PolycubeProgressBarIter, *}, naive_polycube::NaivePolyCube, pcube::{PCubeFile, RawPCube}, @@ -229,7 +229,7 @@ pub fn enumerate_hashless( let process = |seed: RawPCube| { let seed: CubeMapPos<32> = seed.into(); - let children = HashlessCubeMap::enumerate_canonical_children(&seed, start_n, n); + let children = MapStore::enumerate_canonical_children(&seed, start_n, n); bar.inc(1); children }; diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index 905b321..453a5e8 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -8,7 +8,7 @@ use crate::{ rotations::{rot_matrix_points, to_min_rot_points, MatrixCol}, }; -pub struct HashlessCubeMap { +pub struct MapStore { inner: HashSet>, } @@ -67,7 +67,7 @@ macro_rules! define_expand_fn { }; } -impl HashlessCubeMap { +impl MapStore { pub fn new() -> Self { Self { inner: HashSet::new(), From 9ea52767ea0646d6b60b70c7d6dddf9d11c75f55 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Wed, 26 Jul 2023 19:15:39 +0200 Subject: [PATCH 38/39] Clarify min_mem --- rust/src/cli/enumerate.rs | 2 +- rust/src/hashless.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/rust/src/cli/enumerate.rs b/rust/src/cli/enumerate.rs index 4ec319d..0901896 100644 --- a/rust/src/cli/enumerate.rs +++ b/rust/src/cli/enumerate.rs @@ -229,7 +229,7 @@ pub fn enumerate_hashless( let process = |seed: RawPCube| { let seed: CubeMapPos<32> = seed.into(); - let children = MapStore::enumerate_canonical_children(&seed, start_n, n); + let children = MapStore::enumerate_canonical_children_min_mem(&seed, start_n, n); bar.inc(1); children }; diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index 453a5e8..8120764 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -124,7 +124,14 @@ impl MapStore { self.do_cube_expansion(seed, shape, count); } - pub fn enumerate_canonical_children( + /// Calculate the amount of canonical children of size `target` + /// that polycube `seed` of size `count` has. + /// + /// This function does not store variants of the polycubes that + /// it enumerates, it just keeps the count. This way, memory + /// overhead is minimal. + // TODO: improve this name once we unify this and pointslist + pub fn enumerate_canonical_children_min_mem( seed: &CubeMapPos, count: usize, target: usize, @@ -145,7 +152,7 @@ impl MapStore { } else { map.inner .iter() - .map(|child| Self::enumerate_canonical_children(child, count + 1, target)) + .map(|child| Self::enumerate_canonical_children_min_mem(child, count + 1, target)) .sum() } } From b9be0ec057a49e289b77ef0ecb69b665901839ba Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Wed, 26 Jul 2023 21:03:11 +0200 Subject: [PATCH 39/39] Perform expansions as iterator --- rust/src/hashless.rs | 157 +++++++++++++++++++++++++++---------------- 1 file changed, 100 insertions(+), 57 deletions(-) diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs index 8120764..28d98f9 100644 --- a/rust/src/hashless.rs +++ b/rust/src/hashless.rs @@ -12,61 +12,99 @@ pub struct MapStore { inner: HashSet>, } -macro_rules! define_expand_fn { - ($name:ident, $shift:literal, $dim:ident, $dim_str:literal) => { - /// Try expanding each cube into - #[doc = $dim_str] - /// plus one and - #[doc = $dim_str] - /// minus one , calculating new dimension and ensuring - #[doc = $dim_str] - /// is never negative +macro_rules! cube_map_pos_expand { + ($name:ident, $dim:ident, $shift:literal) => { #[inline(always)] - fn $name(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { - for (i, coord) in seed.cubes[0..count].iter().enumerate() { - let plus = coord + (1 << $shift); - let minus = coord - (1 << $shift); - - // Only check if the cube at $dim + 1 already exists at the - // coordinates past `coord` (since the list sorted) - if !seed.cubes[(i + 1)..count].contains(&plus) { - let mut new_shape = *shape; - let mut exp_map = *seed; - - array_insert(plus, &mut exp_map.cubes[i..=count]); - new_shape.$dim = max(new_shape.$dim, (((coord >> $shift) + 1) & 0x1f) as usize); - self.insert_map(&new_shape, &exp_map, count + 1) - } + pub fn $name<'a>( + &'a self, + shape: &'a Dim, + count: usize, + ) -> impl Iterator + 'a { + struct Iter<'a, const C: usize> { + inner: &'a CubeMapPos, + shape: &'a Dim, + count: usize, + i: usize, + stored: Option<(Dim, usize, CubeMapPos)>, + } - let mut new_map = *seed; - let mut new_shape = *shape; - - // If the coord is out of bounds for $dim, shift everything - // over and create the cube at the out-of-bounds position. - // If it is in bounds, check if the $dim - 1 value already - // exists. - let insert_coord = if (coord >> $shift) & 0x1f != 0 { - if !seed.cubes[0..i].contains(&minus) { - minus - } else { - continue; + impl<'a, const C: usize> Iterator for Iter<'a, C> { + type Item = (Dim, usize, CubeMapPos); + + fn next(&mut self) -> Option { + loop { + if let Some(stored) = self.stored.take() { + return Some(stored); + } + + let i = self.i; + + if i == self.count { + return None; + } + + self.i += 1; + let coord = *self.inner.cubes.get(i)?; + + let plus = coord + (1 << $shift); + let minus = coord - (1 << $shift); + + if !self.inner.cubes[(i + 1)..self.count].contains(&plus) { + let mut new_shape = *self.shape; + let mut new_map = *self.inner; + + array_insert(plus, &mut new_map.cubes[i..=self.count]); + new_shape.$dim = + max(new_shape.$dim, (((coord >> $shift) + 1) & 0x1f) as usize); + + self.stored = Some((new_shape, self.count + 1, new_map)); + } + + let mut new_map = *self.inner; + let mut new_shape = *self.shape; + + // If the coord is out of bounds for $dim, shift everything + // over and create the cube at the out-of-bounds position. + // If it is in bounds, check if the $dim - 1 value already + // exists. + let insert_coord = if (coord >> $shift) & 0x1f != 0 { + if !self.inner.cubes[0..i].contains(&minus) { + minus + } else { + continue; + } + } else { + new_shape.$dim += 1; + for i in 0..self.count { + new_map.cubes[i] += 1 << $shift; + } + coord + }; + + array_shift(&mut new_map.cubes[i..=self.count]); + array_insert(insert_coord, &mut new_map.cubes[0..=i]); + return Some((new_shape, self.count + 1, new_map)); } - } else { - new_shape.$dim += 1; - for i in 0..count { - new_map.cubes[i] += 1 << $shift; - } - *coord - }; + } + } - array_shift(&mut new_map.cubes[i..=count]); - array_insert(insert_coord, &mut new_map.cubes[0..=i]); - self.insert_map(&new_shape, &new_map, count + 1) + Iter { + inner: self, + shape, + count, + i: 0, + stored: None, } } }; } +impl CubeMapPos { + cube_map_pos_expand!(expand_x, x, 0); + cube_map_pos_expand!(expand_y, y, 5); + cube_map_pos_expand!(expand_z, z, 10); +} + impl MapStore { pub fn new() -> Self { Self { @@ -74,10 +112,6 @@ impl MapStore { } } - define_expand_fn!(expand_xs, 0, x, "x"); - define_expand_fn!(expand_ys, 5, y, "y"); - define_expand_fn!(expand_zs, 10, z, "z"); - /// helper function to not duplicate code for canonicalising polycubes /// and storing them in the hashset fn insert_map(&mut self, dim: &Dim, map: &CubeMapPos, count: usize) { @@ -91,13 +125,22 @@ impl MapStore { /// X >= Y >= Z constraint on Dim #[inline] fn do_cube_expansion(&mut self, seed: &CubeMapPos, shape: &Dim, count: usize) { - if shape.y < shape.x { - self.expand_ys(seed, shape, count); - } - if shape.z < shape.y { - self.expand_zs(seed, shape, count); - } - self.expand_xs(seed, shape, count); + let expand_ys = if shape.y < shape.x { + Some(seed.expand_y(shape, count)) + } else { + None + }; + + let expand_zs = if shape.z < shape.y { + Some(seed.expand_z(shape, count)) + } else { + None + }; + + seed.expand_x(shape, count) + .chain(expand_ys.into_iter().flatten()) + .chain(expand_zs.into_iter().flatten()) + .for_each(|(dim, new_count, map)| self.insert_map(&dim, &map, new_count)); } /// perform the cube expansion for a given polycube