Skip to content

Commit

Permalink
Fix rotations
Browse files Browse the repository at this point in the history
  • Loading branch information
Dzejkop committed Feb 26, 2024
1 parent 68a6cbc commit 1000d57
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 34 deletions.
67 changes: 34 additions & 33 deletions src/bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use rand::Rng;
use serde::de::Error as _;
use serde::{Deserialize, Serialize};

use crate::distance::ROTATION_DISTANCE;
use crate::distance::ROTATIONS;

mod all_bit_patterns_test;

pub const COLS: usize = 200;
pub const STEP_MULTI: usize = 4;
Expand All @@ -25,37 +27,31 @@ const LIMBS: usize = BITS / 64;
pub struct Bits(pub [u64; LIMBS]);

impl Bits {
/// Returns an unordered iterator over the 31 possible rotations.
/// Rotations are done consecutively because the underlying `rotate_left\right`
/// methods are less efficient for larger rotations.
/// Returns an unordered iterator over the 31 possible rotations
pub fn rotations(&self) -> impl Iterator<Item = Self> + '_ {
let mut left = *self;
let iter_left = (0..ROTATION_DISTANCE).map(move |_| {
left.rotate_left(1);
left
});
let mut right = *self;
let iter_right = (0..ROTATION_DISTANCE).map(move |_| {
right.rotate_right(1);
right
});
std::iter::once(*self).chain(iter_left).chain(iter_right)
ROTATIONS.map(|rot| {
let mut x = *self;

if rot < 0 {
x.rotate_left(rot.abs() as usize * 4)
} else {
x.rotate_right(rot as usize * 4)
}

x
})
}

pub fn rotate_right(&mut self, by: usize) {
BitSlice::<_, Lsb0>::from_slice_mut(&mut self.0)
.chunks_exact_mut(COLS)
BitSlice::<_, Msb0>::from_slice_mut(&mut self.0)
.chunks_exact_mut(COLS * 4)
.for_each(|chunk| chunk.rotate_right(by));
}

/// For some insane reason, chunks_exact_mut benchmarks faster than manually indexing
/// for rotate_right but not for rotate_left. Compilers are weird.
pub fn rotate_left(&mut self, by: usize) {
let bit_slice = BitSlice::<_, Lsb0>::from_slice_mut(&mut self.0);
for row in 0..ROWS {
let row_slice = &mut bit_slice[row * COLS..(row + 1) * COLS];
row_slice.rotate_left(by);
}
BitSlice::<_, Msb0>::from_slice_mut(&mut self.0)
.chunks_exact_mut(COLS * 4)
.for_each(|chunk| chunk.rotate_left(by));
}

pub fn count_ones(&self) -> u16 {
Expand Down Expand Up @@ -133,12 +129,7 @@ impl<'de> Deserialize<'de> for Bits {
{
let s: Cow<'static, str> = Deserialize::deserialize(deserializer)?;

let bytes = BASE64_STANDARD
.decode(s.as_bytes())
.map_err(D::Error::custom)?;

Self::try_from(bytes)
.map_err(|()| D::Error::custom("Invalid bits size"))
s.parse().map_err(D::Error::custom)
}
}

Expand All @@ -164,11 +155,11 @@ impl From<Bits> for [u8; LIMBS * 8] {
}

impl TryFrom<&[u8]> for Bits {
type Error = ();
type Error = base64::DecodeError;

fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != LIMBS * 8 {
return Err(());
return Err(base64::DecodeError::InvalidLength);
}

let mut limbs = [0_u64; LIMBS];
Expand All @@ -181,13 +172,23 @@ impl TryFrom<&[u8]> for Bits {
}

impl TryFrom<Vec<u8>> for Bits {
type Error = ();
type Error = base64::DecodeError;

fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
Self::try_from(value.as_slice())
}
}

impl std::str::FromStr for Bits {
type Err = base64::DecodeError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = BASE64_STANDARD.decode(s.as_bytes())?;

Self::try_from(bytes)
}
}

impl Distribution<Bits> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Bits {
let mut values = [0_u64; LIMBS];
Expand Down
86 changes: 86 additions & 0 deletions src/bits/all_bit_patterns_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#![cfg(test)]

use std::collections::HashMap;

use bitvec::order::{BitOrder, Msb0};
use bitvec::vec::BitVec;
use eyre::{Context, ContextCompat};

use crate::bits::Bits;

const TEST_DATA: &str = include_str!("./all_rotations.txt");

struct TestData {
code: Bits,

rotations: HashMap<i32, String>,
}

fn parse_test_data(s: &str) -> eyre::Result<TestData> {
let lines = s.lines();
let mut lines = lines.map(|s| s.trim()).filter(|s| !s.is_empty());

let code: Bits = lines.next().context("Missing code")?.parse()?;

let mut rotations = HashMap::new();

for line in lines {
let mut parts = line.splitn(2, ':');
let rotation = parts
.next()
.context("Missing rotation number")?
.trim()
.parse()
.context("Invalid rotation")?;

let bit_str = parts.next().context("Missing bit string")?;

rotations.insert(rotation, bit_str.trim().to_string());
}

Ok(TestData { code, rotations })
}

fn bit_vec_to_str<B: BitOrder>(bv: BitVec<u64, B>) -> String {
let mut s = String::new();

for (i, bit) in bv.iter().enumerate() {
if i != 0 && i % 64 == 0 {
s.push(' ');
}

s.push(if *bit { '1' } else { '0' });
}

s
}

#[test]
fn all_bit_patterns() -> eyre::Result<()> {
let test_data = parse_test_data(TEST_DATA).unwrap();

let rotations: HashMap<i32, String> = test_data
.code
.rotations()
.map(|bits| BitVec::<_, Msb0>::from_slice(bits.0.as_slice()))
.map(|bv| bit_vec_to_str(bv))
.enumerate()
.map(|(i, x)| (i as i32 - 15, x))
.collect();

assert_eq!(rotations.len(), test_data.rotations.len());

let mut keys = rotations.keys().collect::<Vec<_>>();
keys.sort();

for k in keys {
similar_asserts::assert_eq!(
rotations[k],
test_data.rotations[k],
"Testing rotation {}",
k
);
}

Ok(())
}
Loading

0 comments on commit 1000d57

Please sign in to comment.