Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
9 changes: 3 additions & 6 deletions rust/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 :)
30 changes: 14 additions & 16 deletions rust/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -98,6 +95,7 @@ pub enum EnumerationMode {
Standard,
RotationReduced,
PointList,
Hashless,
}

#[derive(Clone, Subcommand)]
Expand Down Expand Up @@ -364,6 +362,7 @@ fn unique_expansions<F>(
compression: Compression,
current: Vec<RawPCube>,
calculate_from: usize,
bar: &ProgressBar,
) -> Vec<NaivePolyCube>
where
F: FnMut(&ProgressBar, std::slice::Iter<'_, NaivePolyCube>) -> Vec<NaivePolyCube>,
Expand All @@ -379,7 +378,7 @@ where
.collect::<Vec<_>>();

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();
Expand Down Expand Up @@ -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) {
Expand All @@ -445,6 +445,7 @@ pub fn enumerate(opts: &EnumerateOpts) {
opts.cache_compression,
seed_list,
startn,
&bar,
);
cubes.len()
}
Expand All @@ -458,6 +459,7 @@ pub fn enumerate(opts: &EnumerateOpts) {
opts.cache_compression,
seed_list,
startn,
&bar,
);
cubes.len()
}
Expand All @@ -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();
Expand Down
257 changes: 257 additions & 0 deletions rust/src/hashless.rs
Original file line number Diff line number Diff line change
@@ -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<CubeMapPos<32>>, 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<CubeMapPos<32>>, 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<CubeMapPos<32>>, 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<CubeMapPos<32>>, 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<CubeMapPos<32>>,
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<CubeMapPos<32>>,
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<RawPCube>,
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
Comment on lines +250 to +251
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this formatting! Given that it's being done relatively often around the crate, we should have some fmt_duration(&Duration) somewhere

));

bar.finish();
count
//count_polycubes(&seeds);
}
Loading