Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix rotations #55

Merged
merged 3 commits into from
Feb 26, 2024
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
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.unsigned_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(bit_vec_to_str)
.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
Loading