diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 3d96406..1dc02ba 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -10,6 +10,9 @@ diagnostics = [] size16 = [] smallset = [] +[profile.release] +debug = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/rust/README.md b/rust/README.md index e2c4115..0f98f29 100644 --- a/rust/README.md +++ b/rust/README.md @@ -47,7 +47,7 @@ Commands: - Times are measured with a large sample set of 1 run in an environment with many background processes of varying resource intensiveness throughout and rounded to 3 sig figs -- No cache files used. +- No cache files used (hashless uses N=12). - Working with Low% speedrun rules - more cubes is always better no matter the time, a faster time is better than a slower time if cube count is equal. @@ -67,12 +67,9 @@ Ryzen 9 7900X | afa90ad | 0.184ms | 1.43ms | 11.4ms | 8.41ms | 0.686s | 6.58s | 62.45s | 574s | OoM | OoM | OoM | OoM | rotation-reduced | | 68090de | 13.2ms | 20.4ms | 37.4ms | 85.3ms | 0.304s | 1.74s | 14.2s | 124s | OoM | OoM | OoM | OoM | point-list + rayon | | b83f9c6 | 3ms | 4.3ms | 8.58ms | 25.4ms | 0.137s | 0.986s | 8.02s | 66.7s | OoM | OoM | OoM | OoM | point-list + rayon | +| 88bca6b | NA | NA | NA | NA | NA | NA | NA | 1m28 | 12m55 | 1h50 | 16h10 | NA | hashless + rayon | ## To Do -- implement hashtableless solution that just enumerates - - profile and optimise further -- deduplicate mirrors as well to reduce (optimistically) ~50% memory usage in the -hashset and then test for symatry when counting unique variants - +- folding@home home style cluster :) diff --git a/rust/src/cli.rs b/rust/src/cli.rs index 31b5a45..ceccfe3 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -8,16 +8,13 @@ use std::{ use clap::{Args, Parser, Subcommand, ValueEnum}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use opencubes::{ + hashless, naive_polycube::NaivePolyCube, pcube::{PCubeFile, RawPCube}, + pointlist, rotation_reduced, }; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; -mod pointlist; -mod polycube_reps; -mod rotation_reduced; -mod rotations; - fn unknown_bar() -> ProgressBar { let style = ProgressStyle::with_template("[{elapsed_precise}] [{spinner:10.cyan/blue}] {msg}") .unwrap() @@ -53,7 +50,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}} {{msg}}"); + format!("[{{elapsed_precise}}] {{bar:40.cyan/blue}} {{pos:>{pos_width}}}/{{len}} remaining: [{{eta_precise}}] {{msg}}"); bar.set_style( ProgressStyle::with_template(&template) @@ -98,6 +95,7 @@ pub enum EnumerationMode { Standard, RotationReduced, PointList, + Hashless, } #[derive(Clone, Subcommand)] @@ -364,6 +362,7 @@ fn unique_expansions( compression: Compression, current: Vec, calculate_from: usize, + bar: &ProgressBar, ) -> Vec where F: FnMut(&ProgressBar, std::slice::Iter<'_, NaivePolyCube>) -> Vec, @@ -379,7 +378,7 @@ where .collect::>(); for i in calculate_from..=n { - let bar = make_bar(current.len() as u64); + bar.set_length(current.len() as u64); bar.set_message(format!("base polycubes expanded for N = {i}...")); let start = Instant::now(); @@ -432,6 +431,7 @@ pub fn enumerate(opts: &EnumerateOpts) { //calculate from 2 because 1 is in the vec (current, 2) }; + let bar = make_bar(seed_list.len() as u64); //Select enumeration function to run let cubes_len = match (opts.mode, opts.no_parallelism) { @@ -445,6 +445,7 @@ pub fn enumerate(opts: &EnumerateOpts) { opts.cache_compression, seed_list, startn, + &bar, ); cubes.len() } @@ -458,6 +459,7 @@ pub fn enumerate(opts: &EnumerateOpts) { opts.cache_compression, seed_list, startn, + &bar, ); cubes.len() } @@ -469,23 +471,19 @@ pub fn enumerate(opts: &EnumerateOpts) { if !para { println!("no parallel implementation for rotation-reduced, running single threaded") } - rotation_reduced::gen_polycubes(n) + rotation_reduced::gen_polycubes(n, &bar) } (EnumerationMode::PointList, para) => { if n > 16 { println!("n > 16 not supported for point-list"); return; } - let cubes = pointlist::gen_polycubes( - n, - cache, - opts.cache_compression, - !para, - seed_list, - startn, - ); + let cubes = pointlist::gen_polycubes(n, cache, !para, seed_list, startn, &bar); cubes.len() } + (EnumerationMode::Hashless, para) => { + hashless::gen_polycubes(n, !para, seed_list, startn, &bar) + } }; let duration = start.elapsed(); diff --git a/rust/src/hashless.rs b/rust/src/hashless.rs new file mode 100644 index 0000000..4ce4ed2 --- /dev/null +++ b/rust/src/hashless.rs @@ -0,0 +1,257 @@ +use std::{cmp::max, time::Instant}; + +use crate::pcube::RawPCube; +use hashbrown::HashSet; +use indicatif::ProgressBar; +use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; + +use crate::{ + pointlist::{array_insert, array_shift}, + polycube_reps::{CubeMapPos, Dim}, + 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); + } +} + +/// 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; + + 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) + } + 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]); + insert_map(dst, 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]); + 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))) { + 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) + } + } 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]); + 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))) { + 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) + } + } 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]); + 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); + } + 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); + } + 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() + } +} + +/// 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| { + let children = 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); +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 86c3040..b45964d 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -4,3 +4,9 @@ mod test; pub mod pcube; pub mod naive_polycube; + +pub mod hashless; +pub mod pointlist; +pub mod polycube_reps; +pub mod rotation_reduced; +pub mod rotations; diff --git a/rust/src/pointlist.rs b/rust/src/pointlist.rs index 0f7ae5a..5de9030 100644 --- a/rust/src/pointlist.rs +++ b/rust/src/pointlist.rs @@ -1,15 +1,13 @@ use std::{cmp::max, time::Instant}; use crate::{ - make_bar, polycube_reps::{CubeMapPos, CubeMapPosPart, Dim}, rotations::{rot_matrix_points, to_min_rot_points, MatrixCol}, - Compression, }; +use crate::pcube::RawPCube; use hashbrown::{HashMap, HashSet}; use indicatif::ProgressBar; -use opencubes::pcube::{PCubeFile, RawPCube}; use parking_lot::RwLock; use rayon::prelude::*; @@ -23,10 +21,10 @@ type MapStore = HashMap<(Dim, u16), RwLock>>; /// helper function to not duplicate code for canonicalising polycubes /// and storing them in the hashset -fn insert_map(store: &MapStore, dim: &Dim, map: &CubeMapPos, count: usize) { +fn insert_map(store: &MapStore, dim: &Dim, map: &CubeMapPos<16>, count: usize) { let map = to_min_rot_points(map, dim, count); let mut body = CubeMapPosPart { cubes: [0; 15] }; - for i in 1..16 { + for i in 1..count { body.cubes[i - 1] = map.cubes[i]; } match store.get(&(*dim, map.cubes[0])) { @@ -44,7 +42,7 @@ fn insert_map(store: &MapStore, dim: &Dim, map: &CubeMapPos, count: usize) { ///linearly scan backwards to insertion point overwrites end of slice #[inline] -fn array_insert(val: u16, arr: &mut [u16]) { +pub fn array_insert(val: u16, arr: &mut [u16]) { for i in 1..(arr.len()) { if arr[arr.len() - 1 - i] > val { arr[arr.len() - i] = arr[arr.len() - 1 - i]; @@ -58,7 +56,7 @@ fn array_insert(val: u16, arr: &mut [u16]) { /// moves contents of slice to index x+1, x==0 remains #[inline] -fn array_shift(arr: &mut [u16]) { +pub fn array_shift(arr: &mut [u16]) { for i in 1..(arr.len()) { arr[arr.len() - i] = arr[arr.len() - 1 - i]; } @@ -67,7 +65,7 @@ fn array_shift(arr: &mut [u16]) { /// 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: &MapStore, seed: &CubeMapPos, shape: &Dim, count: usize) { +fn expand_xs(dst: &MapStore, seed: &CubeMapPos<16>, 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; @@ -102,7 +100,7 @@ fn expand_xs(dst: &MapStore, seed: &CubeMapPos, shape: &Dim, count: usize) { /// 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: &MapStore, seed: &CubeMapPos, shape: &Dim, count: usize) { +fn expand_ys(dst: &MapStore, seed: &CubeMapPos<16>, 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; @@ -136,7 +134,7 @@ fn expand_ys(dst: &MapStore, seed: &CubeMapPos, shape: &Dim, count: usize) { /// 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: &MapStore, seed: &CubeMapPos, shape: &Dim, count: usize) { +fn expand_zs(dst: &MapStore, seed: &CubeMapPos<16>, 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; @@ -170,7 +168,7 @@ fn expand_zs(dst: &MapStore, seed: &CubeMapPos, shape: &Dim, count: usize) { /// reduce number of expansions needing to be performed based on /// X >= Y >= Z constraint on Dim #[inline] -fn do_cube_expansion(dst: &MapStore, seed: &CubeMapPos, shape: &Dim, count: usize) { +fn do_cube_expansion(dst: &MapStore, seed: &CubeMapPos<16>, shape: &Dim, count: usize) { if shape.y < shape.x { expand_ys(dst, seed, shape, count); } @@ -184,7 +182,7 @@ fn do_cube_expansion(dst: &MapStore, seed: &CubeMapPos, shape: &Dim, count: usiz /// 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: &MapStore, seed: &CubeMapPos, shape: &Dim, count: usize) { +fn expand_cube_map(dst: &MapStore, seed: &CubeMapPos<16>, shape: &Dim, count: usize) { if shape.x == shape.y && shape.x > 0 { let rotz = rot_matrix_points( seed, @@ -246,7 +244,7 @@ fn expand_cube_set( seeds: &MapStore, count: usize, dst: &mut MapStore, - bar: &mut ProgressBar, + bar: &ProgressBar, parallel: bool, ) { // set up the dst sets before starting parallel processing so accessing doesnt block a global mutex @@ -260,13 +258,11 @@ fn expand_cube_set( } } - let bar = RwLock::new(bar); - bar.write() - .set_message(format!("seed subsets expanded for N = {}...", count + 1)); + bar.set_message(format!("seed subsets expanded for N = {}...", count + 1)); let inner_exp = |(ss, body)| { expand_cube_sub_set(ss, body, count, dst); - bar.write().inc(1); + bar.inc(1); }; //use parallel iterator or not to run expand_cube_set @@ -300,13 +296,13 @@ fn count_polycubes(maps: &MapStore) -> usize { } /// distructively move the data from hashset to vector -fn move_polycubes_to_vec(maps: &mut MapStore) -> Vec { +fn move_polycubes_to_vec(maps: &mut MapStore) -> Vec> { let mut v = Vec::new(); while let Some(((dim, head), body)) = maps.iter().next() { //extra scope to free lock and make borrow checker allow mutation of maps { let bod = body.read(); - let mut cmp = CubeMapPos { cubes: [0; 16] }; + let mut cmp = CubeMapPos::new(); cmp.cubes[0] = *head; for b in bod.iter() { for i in 0..15 { @@ -323,14 +319,14 @@ fn move_polycubes_to_vec(maps: &mut MapStore) -> Vec { } /// distructively move the data from hashset to vector -fn clone_polycubes_to_vec(maps: &mut MapStore) -> Vec { +fn _clone_polycubes_to_vec(maps: &mut MapStore) -> Vec> { let mut v = Vec::new(); for ((_, head), body) in maps.iter() { //extra scope to free lock and make borrow checker allow mutation of maps { let bod = body.read(); - let mut cmp = CubeMapPos { cubes: [0; 16] }; + let mut cmp = CubeMapPos::new(); cmp.cubes[0] = *head; for b in bod.iter() { for i in 0..15 { @@ -346,18 +342,18 @@ fn clone_polycubes_to_vec(maps: &mut MapStore) -> Vec { /// run pointlist based generation algorithm pub fn gen_polycubes( n: usize, - use_cache: bool, - compression: Compression, + _use_cache: bool, parallel: bool, current: Vec, calculate_from: usize, -) -> Vec { + bar: &ProgressBar, +) -> Vec> { let t1_start = Instant::now(); //convert input vector of NaivePolyCubes and convert them to let mut seeds = MapStore::new(); for seed in current.iter() { - let seed: CubeMapPos = seed.into(); + let seed: CubeMapPos<16> = seed.into(); let dim = seed.extrapolate_dim(); if !seeds.contains_key(&(dim, seed.cubes[0])) { for i in 0..(dim.y * 32 + dim.x + 1) { @@ -369,28 +365,27 @@ pub fn gen_polycubes( drop(current); for i in calculate_from..=n as usize { - let mut bar = make_bar(seeds.len() as u64); bar.set_message(format!("seed subsets expanded for N = {}...", i)); let mut dst = MapStore::new(); - expand_cube_set(&mut seeds, i - 1, &mut dst, &mut bar, parallel); + expand_cube_set(&mut seeds, i - 1, &mut dst, bar, parallel); seeds = dst; - if use_cache && i < n { - let next = clone_polycubes_to_vec(&mut seeds); - 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(|v| v.into()), - name, - ) - .unwrap(); - } else { - println!("Cache file already exists for N = {i}. Not overwriting."); - } - } + // if use_cache && i < n { + // let next = clone_polycubes_to_vec(&mut seeds); + // 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(|v| v.into()), + // // name, + // // ) + // // .unwrap(); + // // } else { + // // println!("Cache file already exists for N = {i}. Not overwriting."); + // // } + // } let t1_stop = Instant::now(); let time = t1_stop.duration_since(t1_start).as_micros(); @@ -405,21 +400,21 @@ pub fn gen_polycubes( } // exported eperately for memory concerns. already quite a lot more probably but not much I can do let next = move_polycubes_to_vec(&mut seeds); - if use_cache { - let name = &format!("cubes_{n}.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(|v| v.into()), - name, - ) - .unwrap(); - } else { - println!("Cache file already exists for N = {n}. Not overwriting."); - } - } + // if use_cache { + // let name = &format!("cubes_{n}.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(|v| v.into()), + // name, + // ) + // .unwrap(); + // } else { + // println!("Cache file already exists for N = {n}. Not overwriting."); + // } + // } next //count_polycubes(&seeds); } diff --git a/rust/src/polycube_reps.rs b/rust/src/polycube_reps.rs index 57a67e4..ac82979 100644 --- a/rust/src/polycube_reps.rs +++ b/rust/src/polycube_reps.rs @@ -1,23 +1,23 @@ -use std::cmp::max; +use std::cmp::{max, min}; -use opencubes::pcube::RawPCube; +use crate::pcube::RawPCube; -use crate::rotations::{map_coord, MatrixCol}; +use crate::rotations::{map_coord, to_min_rot_points, MatrixCol::*}; /// Polycube representation /// stores up to 16 blocks (number of cubes normally implicit or seperate in program state) /// well formed polycubes are a sorted list of coordinates low to high /// cordinates are group of packed 5 bit unsigned integers '-ZZZZZYYYYYXXXXX' #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, PartialOrd, Ord)] -pub struct CubeMapPos { - pub cubes: [u16; 16], +pub struct CubeMapPos { + pub cubes: [u16; N], } /// Conversion function to assist with loading Polycubes from cache file to point-list implementation /// Returned cubes may not be fully canonicalized (X >= Y >= Z guarenteed but not exact rotation) -impl From<&RawPCube> for CubeMapPos { +impl From<&RawPCube> for CubeMapPos { fn from(src: &RawPCube) -> Self { - let mut dst = CubeMapPos { cubes: [0; 16] }; + let mut dst = CubeMapPos { cubes: [0; N] }; let (x, y, z) = src.dims(); let x = x as usize; let y = y as usize; @@ -30,12 +30,12 @@ impl From<&RawPCube> for CubeMapPos { //correction matrix to convert to canonical dimension. I dont like it but it works let (x_col, y_col, z_col, rdim) = if x >= y && y >= z { - (MatrixCol::XP, MatrixCol::YP, MatrixCol::ZP, dim) + (XP, YP, ZP, dim) } else if x >= z && z >= y { ( - MatrixCol::XP, - MatrixCol::ZP, - MatrixCol::YN, + XP, + ZP, + YN, Dim { x: x - 1, y: z - 1, @@ -44,9 +44,9 @@ impl From<&RawPCube> for CubeMapPos { ) } else if y >= x && x >= z { ( - MatrixCol::YP, - MatrixCol::XP, - MatrixCol::ZN, + YP, + XP, + ZN, Dim { x: y - 1, y: x - 1, @@ -55,9 +55,9 @@ impl From<&RawPCube> for CubeMapPos { ) } else if y >= z && z >= x { ( - MatrixCol::YP, - MatrixCol::ZP, - MatrixCol::XP, + YP, + ZP, + XP, Dim { x: y - 1, y: z - 1, @@ -66,9 +66,9 @@ impl From<&RawPCube> for CubeMapPos { ) } else if z >= x && x >= y { ( - MatrixCol::ZN, - MatrixCol::XP, - MatrixCol::YN, + ZN, + XP, + YN, Dim { x: z - 1, y: x - 1, @@ -77,9 +77,9 @@ impl From<&RawPCube> for CubeMapPos { ) } else if z >= y && y >= x { ( - MatrixCol::ZN, - MatrixCol::YN, - MatrixCol::XP, + ZN, + YN, + XP, Dim { x: z - 1, y: y - 1, @@ -112,8 +112,8 @@ impl From<&RawPCube> for CubeMapPos { } } -impl From<&'_ CubeMapPos> for RawPCube { - fn from(src: &'_ CubeMapPos) -> Self { +impl From<&'_ CubeMapPos> for RawPCube { + fn from(src: &'_ CubeMapPos) -> Self { //cube is sorted numerically and then has trailing zeros let count = src.extrapolate_count(); let dim = src.extrapolate_dim(); @@ -129,19 +129,22 @@ impl From<&'_ CubeMapPos> for RawPCube { } } -impl From for CubeMapPos { +impl From for CubeMapPos { fn from(value: RawPCube) -> Self { - value.into() + (&value).into() } } -impl From for RawPCube { - fn from(value: CubeMapPos) -> Self { - value.into() +impl From> for RawPCube { + fn from(value: CubeMapPos) -> Self { + (&value).into() } } -impl CubeMapPos { +impl CubeMapPos { + pub fn new() -> Self { + CubeMapPos { cubes: [0; N] } + } pub fn extrapolate_count(&self) -> usize { let mut count = 1; while self.cubes[count] > self.cubes[count - 1] { @@ -162,6 +165,159 @@ impl CubeMapPos { } dim } + + fn is_continuous(&self, len: usize) -> bool { + let start = self.cubes[0]; + let mut polycube2 = [start; 32]; + for i in 1..len { + polycube2[i] = self.cubes[i]; + } + let polycube = polycube2; + //sets were actually slower even when no allocating + let mut to_explore = [start; 32]; + let mut exp_head = 1; + let mut exp_tail = 0; + //to_explore[0] = start; + while exp_head > exp_tail { + let p = to_explore[exp_tail]; + exp_tail += 1; + if p & 0x1f != 0 && !to_explore.contains(&(p - 1)) && polycube.contains(&(p - 1)) { + to_explore[exp_head] = p - 1; + // unsafe {*to_explore.get_unchecked_mut(exp_head) = p - 1;} + exp_head += 1; + } + if p & 0x1f != 0x1f && !to_explore.contains(&(p + 1)) && polycube.contains(&(p + 1)) { + to_explore[exp_head] = p + 1; + // unsafe {*to_explore.get_unchecked_mut(exp_head) = p + 1;} + exp_head += 1; + } + if (p >> 5) & 0x1f != 0 + && !to_explore.contains(&(p - (1 << 5))) + && polycube.contains(&(p - (1 << 5))) + { + to_explore[exp_head] = p - (1 << 5); + // unsafe {*to_explore.get_unchecked_mut(exp_head) = p - (1 << 5);} + exp_head += 1; + } + if (p >> 5) & 0x1f != 0x1f + && !to_explore.contains(&(p + (1 << 5))) + && polycube.contains(&(p + (1 << 5))) + { + to_explore[exp_head] = p + (1 << 5); + // unsafe {*to_explore.get_unchecked_mut(exp_head) = p + (1 << 5);} + exp_head += 1; + } + if (p >> 10) & 0x1f != 0 + && !to_explore.contains(&(p - (1 << 10))) + && polycube.contains(&(p - (1 << 10))) + { + to_explore[exp_head] = p - (1 << 10); + // unsafe {*to_explore.get_unchecked_mut(exp_head) = p - (1 << 10);} + exp_head += 1; + } + if (p >> 10) & 0x1f != 0x1f + && !to_explore.contains(&(p + (1 << 10))) + && polycube.contains(&(p + (1 << 10))) + { + to_explore[exp_head] = p + (1 << 10); + // unsafe {*to_explore.get_unchecked_mut(exp_head) = p + (1 << 10);} + exp_head += 1; + } + } + exp_head == len + } + + fn renormalize(&self, dim: &Dim, count: usize) -> (Self, Dim) { + let mut dst = CubeMapPos::new(); + let x = dim.x; + let y = dim.y; + let z = dim.z; + let (x_col, y_col, z_col, rdim) = if x >= y && y >= z { + (XP, YP, ZP, Dim { x: x, y: y, z: z }) + } else if x >= z && z >= y { + (XP, ZP, YN, Dim { x: x, y: z, z: y }) + } else if y >= x && x >= z { + (YP, XP, ZN, Dim { x: y, y: x, z: z }) + } else if y >= z && z >= x { + (YP, ZP, XP, Dim { x: y, y: z, z: x }) + } else if z >= x && x >= y { + (ZN, XP, YN, Dim { x: z, y: x, z: y }) + } else if z >= y && y >= x { + (ZN, YN, XP, Dim { x: z, y: y, z: x }) + } else { + panic!("imposible dimension of shape {:?}", dim) + }; + for (i, d) in self.cubes[0..count].iter().enumerate() { + let dx = d & 0x1f; + let dy = (d >> 5) & 0x1f; + let dz = (d >> 10) & 0x1f; + let cx = map_coord(dx, dy, dz, &dim, x_col); + let cy = map_coord(dx, dy, dz, &dim, y_col); + let cz = map_coord(dx, dy, dz, &dim, z_col); + let pack = ((cz << 10) | (cy << 5) | cx) as u16; + dst.cubes[i] = pack; + // unsafe {*dst.cubes.get_unchecked_mut(i) = pack;} + } + //dst.cubes.sort(); + (dst, rdim) + } + + fn remove_cube(&self, point: usize, count: usize) -> (Self, Dim) { + let mut min_corner = Dim { + x: 0x1f, + y: 0x1f, + z: 0x1f, + }; + let mut max_corner = Dim { x: 0, y: 0, z: 0 }; + let mut root_candidate = CubeMapPos::new(); + let mut candidate_ptr = 0; + for i in 0..=count { + if i != point { + let pos = self.cubes[i]; + // let pos = unsafe {*exp.cubes.get_unchecked(i)}; + let x = pos as usize & 0x1f; + let y = (pos as usize >> 5) & 0x1f; + let z = (pos as usize >> 10) & 0x1f; + min_corner.x = min(min_corner.x, x); + min_corner.y = min(min_corner.y, y); + min_corner.z = min(min_corner.z, z); + max_corner.x = max(max_corner.x, x); + max_corner.y = max(max_corner.y, y); + max_corner.z = max(max_corner.z, z); + root_candidate.cubes[candidate_ptr] = pos; + // unsafe {*root_candidate.cubes.get_unchecked_mut(candidate_ptr) = pos;} + candidate_ptr += 1; + } + } + let offset = (min_corner.z << 10) | (min_corner.y << 5) | min_corner.x; + for i in 0..count { + root_candidate.cubes[i] -= offset as u16; + } + max_corner.x = max_corner.x - min_corner.x; + max_corner.y = max_corner.y - min_corner.y; + max_corner.z = max_corner.z - min_corner.z; + (root_candidate, max_corner) + } + + pub fn is_canonical_root(&self, count: usize, seed: &Self) -> bool { + for sub_cube_id in 0..=count { + let (mut root_candidate, mut dim) = self.remove_cube(sub_cube_id, count); + if !root_candidate.is_continuous(count) { + continue; + } + if dim.x < dim.y || dim.y < dim.z || dim.x < dim.z { + let (rroot_candidate, rdim) = root_candidate.renormalize(&dim, count); + root_candidate = rroot_candidate; + dim = rdim; + root_candidate.cubes[0..count].sort_unstable(); + } + let mrp = to_min_rot_points(&root_candidate, &dim, count); + if &mrp < seed { + return false; + } + } + true + } } /// Partial Polycube representation for storage diff --git a/rust/src/rotation_reduced.rs b/rust/src/rotation_reduced.rs index f44b767..22854cb 100644 --- a/rust/src/rotation_reduced.rs +++ b/rust/src/rotation_reduced.rs @@ -6,7 +6,6 @@ use std::{cmp::max, collections::HashSet, time::Instant}; use indicatif::ProgressBar; use crate::{ - make_bar, polycube_reps::CubeMap, rotations::{self, rot_matrix, MatrixCol}, }; @@ -445,7 +444,7 @@ fn expand_cube_set( in_set: &HashSet, #[cfg(feature = "diagnostics")] depth: usize, out_set: &mut HashSet, - bar: &mut ProgressBar, + bar: &ProgressBar, ) { let mut i = 0; for map in in_set.iter() { @@ -487,7 +486,7 @@ fn expand_cube_set( } } -pub fn gen_polycubes(n: usize) -> usize { +pub fn gen_polycubes(n: usize, bar: &ProgressBar) -> usize { let unit_cube = CubeMap { x: 1, y: 0, @@ -507,14 +506,13 @@ pub fn gen_polycubes(n: usize) -> usize { 2, ); for i in 3..=n as usize { - let mut bar = make_bar(seeds.len() as u64); bar.set_message(format!("seed subsets expanded for N = {}...", i)); expand_cube_set( &seeds, #[cfg(feature = "diagnostics")] i, &mut dst, - &mut bar, + bar, ); //if diagnostics enabled panic if the returned values are wrong #[cfg(feature = "diagnostics")] diff --git a/rust/src/rotations.rs b/rust/src/rotations.rs index 88a45ec..c9834a9 100644 --- a/rust/src/rotations.rs +++ b/rust/src/rotations.rs @@ -339,16 +339,16 @@ pub fn map_coord(x: u16, y: u16, z: u16, shape: &Dim, col: MatrixCol) -> u16 { } #[inline] -pub fn rot_matrix_points( - map: &CubeMapPos, +pub fn rot_matrix_points( + map: &CubeMapPos, shape: &Dim, count: usize, x_col: MatrixCol, y_col: MatrixCol, z_col: MatrixCol, pmin: u16, -) -> CubeMapPos { - let mut res = CubeMapPos { cubes: [0; 16] }; +) -> CubeMapPos { + let mut res = CubeMapPos::new(); let mut mmin = 1024; for (i, coord) in map.cubes[0..count].iter().enumerate() { let ix = coord & 0x1f; @@ -371,7 +371,12 @@ pub fn rot_matrix_points( } #[inline] -fn xy_rots_points(map: &CubeMapPos, shape: &Dim, count: usize, res: &mut CubeMapPos) { +fn xy_rots_points( + map: &CubeMapPos, + shape: &Dim, + count: usize, + res: &mut CubeMapPos, +) { *res = min( *res, rot_matrix_points( @@ -426,7 +431,12 @@ fn xy_rots_points(map: &CubeMapPos, shape: &Dim, count: usize, res: &mut CubeMap } #[inline] -fn yz_rots_points(map: &CubeMapPos, shape: &Dim, count: usize, res: &mut CubeMapPos) { +fn yz_rots_points( + map: &CubeMapPos, + shape: &Dim, + count: usize, + res: &mut CubeMapPos, +) { *res = min( *res, rot_matrix_points( @@ -481,7 +491,12 @@ fn yz_rots_points(map: &CubeMapPos, shape: &Dim, count: usize, res: &mut CubeMap } #[inline] -fn xyz_rots_points(map: &CubeMapPos, shape: &Dim, count: usize, res: &mut CubeMapPos) { +fn xyz_rots_points( + map: &CubeMapPos, + shape: &Dim, + count: usize, + res: &mut CubeMapPos, +) { //xz *res = min( *res, @@ -641,7 +656,11 @@ fn xyz_rots_points(map: &CubeMapPos, shape: &Dim, count: usize, res: &mut CubeMa ); } -pub fn to_min_rot_points(map: &CubeMapPos, shape: &Dim, count: usize) -> CubeMapPos { +pub fn to_min_rot_points( + map: &CubeMapPos, + shape: &Dim, + count: usize, +) -> CubeMapPos { let mut res = *map; if shape.x == shape.y && shape.x != 0 { xy_rots_points(map, shape, count, &mut res);