use std::str::FromStr; use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, DimName, Dyn, OVector, U1}; use rand::{Rng, RngCore}; use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq)] pub struct BinaryString where D: Dim, DefaultAllocator: Allocator { pub vec: OVector } impl PartialOrd for BinaryString where D: Dim, DefaultAllocator: Allocator { fn partial_cmp(&self, other: &Self) -> Option { // TODO: implement more efficient ordering by iteration... self.to_real_single(0.0, 100.0).partial_cmp(&other.to_real_single(0.0, 100.0)) } } #[derive(Error, Debug, Clone, PartialEq)] pub enum BinaryStringConversionError { #[error("The dimension of the bounds does not divide the length of the binary string.")] DimensionMismatch, } impl BinaryString where D: Dim, DefaultAllocator: Allocator { pub fn from_ovector(vec: OVector) -> Self { Self { vec } } } impl BinaryString where D: DimName, DefaultAllocator: Allocator { pub fn new(vec: Vec) -> Self { Self { vec: OVector::from_vec(vec) } } } impl BinaryString { pub fn new_dyn(vec: Vec) -> Self { BinaryString { vec: OVector::from_vec_generic(Dyn(vec.len()), U1, vec) } } } impl BinaryString where D: Dim, DefaultAllocator: Allocator { pub fn vec(&self) -> &OVector { &self.vec } pub fn perturb(&mut self, rng: &mut dyn RngCore, p: f64) { self.vec.apply(|c| *c = if rng.random::() <= p { 1 - *c } else { *c }); } fn to_real_internal<'a, T: DoubleEndedIterator>(vec: T, len: usize, min: f64, max: f64) -> f64 { let diff = max - min; let len = len as i32; let max_represent_num = 2f64.powi(len) - 1.0; let represented_num = vec .rev() .enumerate() .map(|(bit, c)| diff * (*c as f64) * 2f64.powi(bit as i32)) .sum::(); min + (represented_num / max_represent_num) } pub fn to_real_single(self: &Self, min: f64, max: f64) -> f64 { BinaryString::to_real_internal(self.vec.iter(), self.vec.len(), min, max) } pub fn to_real(self: &Self, min: &OVector, max: &OVector) -> Result, BinaryStringConversionError> where TDim: Dim, DefaultAllocator: Allocator, { let chunk_size = self.vec.len() / min.len(); if self.vec.len() % min.len() != 0 { return Err(BinaryStringConversionError::DimensionMismatch); } let iter = self.vec.as_slice().chunks(chunk_size) .zip(min.iter().zip(max)) .map(|(chunk, (&min, &max))| BinaryString::::to_real_internal(chunk.iter(), chunk_size, min, max)); Ok(OVector::::from_iterator_generic(min.shape_generic().0, min.shape_generic().1, iter)) } } impl FromStr for BinaryString { type Err = String; fn from_str(s: &str) -> Result { let binary_vec: Vec = s .chars() // skip spaces .filter(|c| *c != ' ') // Map ones and zeros .map(|c| match c { '0' => Ok(0), '1' => Ok(1), _ => Err(format!("Invalid binary character: {}", c)), }) .collect::, Self::Err>>()?; Ok(BinaryString::new_dyn(binary_vec)) } } impl<'a, D> IntoIterator for &'a BinaryString where D: Dim, DefaultAllocator: nalgebra::allocator::Allocator { type Item = &'a i8; type IntoIter = nalgebra::iter::MatrixIter<'a, i8, D, U1, >::Buffer>; fn into_iter(self) -> Self::IntoIter { self.vec.iter() } } #[cfg(test)] pub mod tests { use crate::{binary_string::{BinaryString, BinaryStringConversionError}, test_infra::{load_test_file, DataArrOfReals}}; use nalgebra::{SVector, U1, U2, U5}; #[test] fn test_binary_string_to_real_single() { assert_eq!( BinaryString::::new(vec![1]) .to_real_single(0.0, 32.0), 32.0 ); assert_eq!( BinaryString::::new(vec![1, 1]) .to_real_single(0.0, 32.0), 32.0 ); assert_eq!( BinaryString::::new(vec![0, 1]) .to_real_single(0.0, 32.0), 32.0 / 3.0 ); assert_eq!( BinaryString::::new(vec![0, 0]) .to_real_single(-16.0, 16.0), -16.0 ); assert_eq!( BinaryString::::new(vec![0, 0, 0, 0, 1]) .to_real_single(0.0, 31.0), 1.0 ); assert_eq!( BinaryString::::new(vec![1, 1, 1, 1, 1]) .to_real_single(0.0, 31.0), 31.0 ); assert_eq!( BinaryString::::new(vec![0, 0, 0, 1, 0]) .to_real_single(0.0, 31.0), 2.0 ); assert_eq!( BinaryString::::new(vec![1; 512]) .to_real_single(0.0, 31.0), 31.0 ); } fn test_binary_string_to_real(file_name: &str, min: SVector, max: SVector) { let data = load_test_file::(file_name); for test in data { let res = BinaryString::new_dyn(test.inp) .to_real(&min, &max); if !test.out.valid { assert_eq!( res, Err(BinaryStringConversionError::DimensionMismatch) ); } else { assert_eq!( res.unwrap().iter().map(|&x| x).collect::>(), test.out.vec ); } } } #[test] fn test_binary_string_to_real_1d_1() { test_binary_string_to_real( "tests/Bin2Real_1D_1.txt", SVector::::new(0.0), SVector::::new(1.0), ); } #[test] fn test_binary_string_to_real_1d_2() { test_binary_string_to_real( "tests/Bin2Real_1D_2.txt", SVector::::new(0.0), SVector::::new(4095.0), ); } #[test] fn test_binary_string_to_real_1d_3() { test_binary_string_to_real( "tests/Bin2Real_1D_3.txt", SVector::::new(-5.0), SVector::::new(5.0), ); } #[test] fn test_binary_string_to_real_2d_1() { test_binary_string_to_real( "tests/Bin2Real_2D_1.txt", SVector::::new(0.0, 0.0), SVector::::new(1.0, 1.0), ); } #[test] fn test_binary_string_to_real_2d_2() { test_binary_string_to_real( "tests/Bin2Real_2D_2.txt", SVector::::new(0.0, -32.0), SVector::::new(63.0, 31.0), ); } #[test] fn test_binary_string_to_real_2d_3() { test_binary_string_to_real( "tests/Bin2Real_2D_3.txt", SVector::::new(-5.0, 0.0), SVector::::new(5.0, 10.0), ); } #[test] fn test_binary_string_to_real_3d_1() { test_binary_string_to_real( "tests/Bin2Real_3D_1.txt", SVector::::new(0.0, 0.0, 0.0), SVector::::new(1.0, 1.0, 1.0), ); } #[test] fn test_binary_string_to_real_3d_2() { test_binary_string_to_real( "tests/Bin2Real_3D_2.txt", SVector::::new(0.0, -8.0, -8.0), SVector::::new(15.0, 7.0, 8.0), ); } #[test] fn test_binary_string_to_real_4d_1() { test_binary_string_to_real( "tests/Bin2Real_4D_1.txt", SVector::::new(0.0, 0.0, 0.0, 0.0), SVector::::new(1.0, 1.0, 1.0, 1.0), ); } #[test] fn test_binary_string_to_real_4d_2() { test_binary_string_to_real( "tests/Bin2Real_4D_2.txt", SVector::::new(0.0, -4.0, -4.0, -8.0), SVector::::new(7.0, 3.0, 4.0, 0.0), ); } #[test] fn test_binary_string_to_real_6d_1() { test_binary_string_to_real( "tests/Bin2Real_6D_1.txt", SVector::::zeros(), SVector::::from_element(1.0), ); } #[test] fn test_binary_string_to_real_6d_2() { test_binary_string_to_real( "tests/Bin2Real_6D_2.txt", SVector::::from_iterator((0..=5).map(|x| x as f64)), SVector::::from_iterator((0..=5).map(|x| (2 * (x + 1)) as f64)), ); } }