use std::str::FromStr; use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector}; use rand::{Rng, RngCore}; use thiserror::Error; #[derive(Debug, Clone, PartialEq)] pub struct Bounds { min: f64, max: f64, } impl Bounds { pub fn new(min: f64, max: f64) -> Self { Bounds { min, max } } pub fn min(&self) -> f64 { self.min } pub fn max(&self) -> f64 { self.max } } #[derive(Debug, Clone, PartialEq)] pub struct BinaryString { pub vec: Vec } #[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 { pub fn new(vec: Vec) -> BinaryString { BinaryString { vec } } pub fn vec(&self) -> &Vec { &self.vec } pub fn perturb(self: &Self, rng: &mut dyn RngCore, p: f64) -> Self { BinaryString::new( self.into_iter() .map(|c| if rng.random::() <= p { 1 - *c } else { *c }) .collect::>() ) } 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.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(binary_vec)) } } impl<'a> IntoIterator for &'a BinaryString { type Item = &'a i8; type IntoIter = std::slice::Iter<'a, i8>; 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; #[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(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)), ); } }