use crate::agent::Agent;
use crate::store::AgentStore;
use crate::types::AgentId;
pub trait SoaExtractable: Agent {
fn num_columns() -> usize;
fn column_names() -> Vec<&'static str>;
fn extract_row(&self, columns: &mut [Vec<f32>]);
fn write_back_row(&mut self, columns: &[&[f32]], row: usize);
}
pub fn extract_soa<A, S>(store: &S) -> (Vec<AgentId>, Vec<Vec<f32>>)
where
A: SoaExtractable,
S: AgentStore<A>,
{
let ids = store.iter_ids();
let n = ids.len();
let nc = A::num_columns();
let mut columns: Vec<Vec<f32>> = (0..nc).map(|_| Vec::with_capacity(n)).collect();
for &id in &ids {
if let Some(agent) = store.get(id) {
agent.extract_row(&mut columns);
}
}
(ids, columns)
}
pub fn write_back_soa<A, S>(store: &S, ids: &[AgentId], columns: &[Vec<f32>])
where
A: SoaExtractable,
S: AgentStore<A>,
{
let col_refs: Vec<&[f32]> = columns.iter().map(|c| c.as_slice()).collect();
for (row, &id) in ids.iter().enumerate() {
if let Some(mut agent) = store.get_mut(id) {
agent.write_back_row(&col_refs, row);
}
}
}
pub trait SoaExtractableF64: Agent {
fn num_columns() -> usize;
fn column_names() -> Vec<&'static str>;
fn extract_row(&self, columns: &mut [Vec<f64>]);
fn write_back_row(&mut self, columns: &[&[f64]], row: usize);
}
pub fn extract_soa_f64<A, S>(store: &S) -> (Vec<AgentId>, Vec<Vec<f64>>)
where
A: SoaExtractableF64,
S: AgentStore<A>,
{
let ids = store.iter_ids();
let n = ids.len();
let nc = <A as SoaExtractableF64>::num_columns();
let mut columns: Vec<Vec<f64>> = (0..nc).map(|_| Vec::with_capacity(n)).collect();
for &id in &ids {
if let Some(agent) = store.get(id) {
<A as SoaExtractableF64>::extract_row(&agent, &mut columns);
}
}
(ids, columns)
}
pub fn write_back_soa_f64<A, S>(store: &S, ids: &[AgentId], columns: &[Vec<f64>])
where
A: SoaExtractableF64,
S: AgentStore<A>,
{
let col_refs: Vec<&[f64]> = columns.iter().map(|c| c.as_slice()).collect();
for (row, &id) in ids.iter().enumerate() {
if let Some(mut agent) = store.get_mut(id) {
<A as SoaExtractableF64>::write_back_row(&mut agent, &col_refs, row);
}
}
}
pub fn cast_columns_f64_to_f32(columns: &[Vec<f64>]) -> Vec<Vec<f32>> {
columns
.iter()
.map(|c| c.iter().map(|v| *v as f32).collect())
.collect()
}
pub fn cast_columns_f32_to_f64(columns: &[Vec<f32>]) -> Vec<Vec<f64>> {
columns
.iter()
.map(|c| c.iter().map(|v| *v as f64).collect())
.collect()
}
#[cfg(test)]
mod tests_f64 {
use super::*;
use crate::store::{AgentStore, HashMapStore};
#[derive(Clone, Debug)]
struct P {
id: AgentId,
x: f64,
vx: f64,
}
impl Agent for P {
fn id(&self) -> AgentId {
self.id
}
}
impl SoaExtractableF64 for P {
fn num_columns() -> usize {
2
}
fn column_names() -> Vec<&'static str> {
vec!["x", "vx"]
}
fn extract_row(&self, columns: &mut [Vec<f64>]) {
columns[0].push(self.x);
columns[1].push(self.vx);
}
fn write_back_row(&mut self, columns: &[&[f64]], row: usize) {
self.x = columns[0][row];
self.vx = columns[1][row];
}
}
#[test]
fn extract_and_write_back_preserve_f64_precision() {
let mut store: HashMapStore<P> = HashMapStore::new();
let precise = 1.0_f64 + 1.0e-10_f64;
store.insert(P {
id: 1,
x: precise,
vx: 2.0,
});
let (ids, mut cols) = extract_soa_f64::<P, _>(&store);
assert_eq!(ids.len(), 1);
assert_eq!(cols[0][0], precise);
cols[0][0] = precise * 2.0;
write_back_soa_f64::<P, _>(&store, &ids, &cols);
let a = store.get(1).unwrap();
assert_eq!(a.x, precise * 2.0);
}
#[test]
fn f32_f64_cast_round_trip_loses_low_bits() {
let f64_cols = vec![vec![1.0_f64 + 1.0e-10_f64]];
let f32_cols = cast_columns_f64_to_f32(&f64_cols);
let back = cast_columns_f32_to_f64(&f32_cols);
assert_eq!(back[0][0], 1.0);
}
}