#[cfg(feature = "serialize")]
pub mod dmatrix {
use nalgebra as na;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(m: &na::DMatrix<f64>, s: S) -> Result<S::Ok, S::Error> {
let bits: Vec<u64> = m.as_slice().iter().map(|&f| f.to_bits()).collect();
(bits, m.nrows(), m.ncols()).serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<na::DMatrix<f64>, D::Error> {
let (bits, nrows, ncols): (Vec<u64>, usize, usize) = Deserialize::deserialize(d)?;
let data: Vec<f64> = bits.into_iter().map(f64::from_bits).collect();
Ok(na::DMatrix::from_vec(nrows, ncols, data))
}
}
#[cfg(feature = "serialize_json")]
pub mod json;
use core::error::Error;
use std::{
fs::{self, read_dir},
iter::empty,
path::Path,
};
use crate::{
population::{speciate, SpecieGroup},
Connection, Genome, Specie,
};
pub trait SerializeFile: Sized {
const SERIALIZER_ID: &'static str;
fn to_str(&self) -> Result<String, Box<dyn Error>>;
#[allow(clippy::should_implement_trait)]
fn from_str(s: &str) -> Result<Self, Box<dyn Error>>;
fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn Error>> {
let content = format!(
"eevee-serializer: {}\n{}",
Self::SERIALIZER_ID,
self.to_str()?
);
fs::write(path, content)?;
Ok(())
}
fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn Error>> {
let raw = fs::read_to_string(path)?;
let body = strip_header(&raw, Self::SERIALIZER_ID)?;
Self::from_str(body)
}
}
fn strip_header<'a>(s: &'a str, expected_id: &str) -> Result<&'a str, Box<dyn Error>> {
let expected = format!("eevee-serializer: {expected_id}");
match s.split_once('\n') {
Some((header, body)) if header == expected => Ok(body),
Some((header, _)) => Err(format!("unexpected serializer header: {header:?}").into()),
None => Err("missing serializer header".into()),
}
}
pub fn population_to_files<P: AsRef<Path>, C: Connection, G: Genome<C> + SerializeFile>(
path: P,
pop: &[Specie<C, G>],
) -> Result<(), Box<dyn Error>> {
for (idx, (member, _)) in pop
.iter()
.flat_map(|specie| specie.members.iter())
.enumerate()
{
member.to_file(path.as_ref().join(format!("{idx}.json")))?;
}
Ok(())
}
pub fn population_from_files<P: AsRef<Path>, C: Connection, G: Genome<C> + SerializeFile>(
path: P,
) -> Result<SpecieGroup<C, G>, Box<dyn Error>> {
let pop_flat = read_dir(path)?
.map(|fp| Ok::<_, Box<dyn Error>>((G::from_file(fp?.path())?, f64::MIN)))
.collect::<Result<Vec<_>, _>>()?;
if pop_flat.is_empty() {
return Err("no genomes".into());
}
let inno_head = pop_flat
.iter()
.flat_map(|(g, _)| g.connections().iter().map(|c| c.inno()))
.max()
.unwrap_or(0);
Ok((speciate(pop_flat.into_iter(), empty()), inno_head))
}
pub fn population_from_genome<
P: AsRef<Path>,
C: Connection,
G: Genome<C> + SerializeFile + Clone,
>(
path: P,
population: usize,
) -> Result<SpecieGroup<C, G>, Box<dyn Error>> {
let muse = G::from_file(path)?;
let inno_head = muse
.connections()
.iter()
.map(|c| c.inno())
.max()
.unwrap_or(0);
Ok((
speciate(vec![(muse, f64::MIN); population].into_iter(), empty()),
inno_head,
))
}