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)]
pub struct BinaryString<D>
where
D: Dim,
DefaultAllocator: Allocator<D>
{
pub vec: OVector<i8, D>
}
#[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<D> BinaryString<D>
where
D: DimName,
DefaultAllocator: Allocator<D>
{
pub fn new(vec: Vec<i8>) -> Self {
Self {
vec: OVector::from_vec(vec)
}
}
}
impl BinaryString<Dyn>
{
pub fn new_dyn(vec: Vec<i8>) -> Self {
BinaryString {
vec: OVector::from_vec_generic(Dyn(vec.len()), U1, vec)
}
}
}
impl<D> BinaryString<D>
where
D: Dim,
DefaultAllocator: Allocator<D>
{
pub fn vec(&self) -> &OVector<i8, D> {
&self.vec
}
pub fn perturb(mut self, rng: &mut dyn RngCore, p: f64) -> Self
{
self.vec.apply(|c| *c = if rng.random::<f64>() <= p { 1 - *c } else { *c });
self
}
fn to_real_internal<'a, T: DoubleEndedIterator<Item = &'a i8>>(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::<f64>();
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<TDim>(self: &Self, min: &OVector<f64, TDim>, max: &OVector<f64, TDim>) -> Result<OVector<f64, TDim>, BinaryStringConversionError>
where
TDim: Dim,
DefaultAllocator: Allocator<TDim>,
{
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::<D>::to_real_internal(chunk.iter(), chunk_size, min, max));
Ok(OVector::<f64, TDim>::from_iterator_generic(min.shape_generic().0, min.shape_generic().1, iter))
}
}
impl FromStr for BinaryString<Dyn>
{
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let binary_vec: Vec<i8> = 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::<Result<Vec<i8>, Self::Err>>()?;
Ok(BinaryString::new_dyn(binary_vec))
}
}
impl<'a, D> IntoIterator for &'a BinaryString<D>
where
D: Dim,
DefaultAllocator: nalgebra::allocator::Allocator<D>
{
type Item = &'a i8;
type IntoIter = nalgebra::iter::MatrixIter<'a, i8, D, U1, <DefaultAllocator as nalgebra::allocator::Allocator<D>>::Buffer<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, U1, U2, U5};
#[test]
fn test_binary_string_to_real_single() {
assert_eq!(
BinaryString::<U1>::new(vec![1])
.to_real_single(0.0, 32.0),
32.0
);
assert_eq!(
BinaryString::<U2>::new(vec![1, 1])
.to_real_single(0.0, 32.0),
32.0
);
assert_eq!(
BinaryString::<U2>::new(vec![0, 1])
.to_real_single(0.0, 32.0),
32.0 / 3.0
);
assert_eq!(
BinaryString::<U2>::new(vec![0, 0])
.to_real_single(-16.0, 16.0),
-16.0
);
assert_eq!(
BinaryString::<U5>::new(vec![0, 0, 0, 0, 1])
.to_real_single(0.0, 31.0),
1.0
);
assert_eq!(
BinaryString::<U5>::new(vec![1, 1, 1, 1, 1])
.to_real_single(0.0, 31.0),
31.0
);
assert_eq!(
BinaryString::<U5>::new(vec![0, 0, 0, 1, 0])
.to_real_single(0.0, 31.0),
2.0
);
assert_eq!(
BinaryString::<U2>::new(vec![1; 512])
.to_real_single(0.0, 31.0),
31.0
);
}
fn test_binary_string_to_real<const LEN: usize>(file_name: &str, min: SVector<f64, LEN>, max: SVector<f64, LEN>) {
let data = load_test_file::<i8, DataArrOfReals>(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::<Vec<_>>(),
test.out.vec
);
}
}
}
#[test]
fn test_binary_string_to_real_1d_1() {
test_binary_string_to_real(
"tests/Bin2Real_1D_1.txt",
SVector::<f64, 1>::new(0.0),
SVector::<f64, 1>::new(1.0),
);
}
#[test]
fn test_binary_string_to_real_1d_2() {
test_binary_string_to_real(
"tests/Bin2Real_1D_2.txt",
SVector::<f64, 1>::new(0.0),
SVector::<f64, 1>::new(4095.0),
);
}
#[test]
fn test_binary_string_to_real_1d_3() {
test_binary_string_to_real(
"tests/Bin2Real_1D_3.txt",
SVector::<f64, 1>::new(-5.0),
SVector::<f64, 1>::new(5.0),
);
}
#[test]
fn test_binary_string_to_real_2d_1() {
test_binary_string_to_real(
"tests/Bin2Real_2D_1.txt",
SVector::<f64, 2>::new(0.0, 0.0),
SVector::<f64, 2>::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::<f64, 2>::new(0.0, -32.0),
SVector::<f64, 2>::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::<f64, 2>::new(-5.0, 0.0),
SVector::<f64, 2>::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::<f64, 3>::new(0.0, 0.0, 0.0),
SVector::<f64, 3>::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::<f64, 3>::new(0.0, -8.0, -8.0),
SVector::<f64, 3>::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::<f64, 4>::new(0.0, 0.0, 0.0, 0.0),
SVector::<f64, 4>::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::<f64, 4>::new(0.0, -4.0, -4.0, -8.0),
SVector::<f64, 4>::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::<f64, 6>::zeros(),
SVector::<f64, 6>::from_element(1.0),
);
}
#[test]
fn test_binary_string_to_real_6d_2() {
test_binary_string_to_real(
"tests/Bin2Real_6D_2.txt",
SVector::<f64, 6>::from_iterator((0..=5).map(|x| x as f64)),
SVector::<f64, 6>::from_iterator((0..=5).map(|x| (2 * (x + 1)) as f64)),
);
}
}