use crate::config::Config;
use crate::context::Context;
use crate::error::Result;
use crate::ops::log::create_log_op;
use crate::ops::matrix::create_fit_op;
use crate::ops::matrix::float_format::format_g;
use crate::ops::OpVec;
use crate::transforms::{AllocationTransform, BuildOps, Validate};
use crate::types::{Allocation, TransformDirection};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct AllocationData {
pub allocation: Allocation,
pub vars: Vec<f32>,
}
impl AllocationData {
pub fn from_transform(t: &AllocationTransform) -> Self {
Self {
allocation: t.allocation,
vars: t.vars.iter().map(|&v| v as f32).collect(),
}
}
pub fn cache_id(&self) -> String {
let mut s = format!("{} ", self.allocation.as_str());
for v in &self.vars {
s.push_str(&format_g(*v as f64, 7));
s.push(' ');
}
s
}
}
pub fn create_allocation_ops(
ops: &mut OpVec,
data: &AllocationData,
dir: TransformDirection,
) -> Result<()> {
match data.allocation {
Allocation::Uniform => {
let mut oldmin = [0.0; 4];
let mut oldmax = [1.0; 4];
let newmin = [0.0; 4];
let newmax = [1.0; 4];
if data.vars.len() >= 2 {
for i in 0..3 {
oldmin[i] = data.vars[0] as f64;
oldmax[i] = data.vars[1] as f64;
}
}
create_fit_op(ops, &oldmin, &oldmax, &newmin, &newmax, dir)
}
Allocation::Lg2 => {
let mut oldmin = [-10.0, -10.0, -10.0, 0.0];
let mut oldmax = [6.0, 6.0, 6.0, 1.0];
let newmin = [0.0; 4];
let newmax = [1.0; 4];
if data.vars.len() >= 2 {
for i in 0..3 {
oldmin[i] = data.vars[0] as f64;
oldmax[i] = data.vars[1] as f64;
}
}
let base = 2.0;
let log_slope = [1.0; 3];
let lin_slope = [1.0; 3];
let mut lin_offset = [0.0; 3];
let log_offset = [0.0; 3];
if data.vars.len() >= 3 {
lin_offset = [data.vars[2] as f64; 3];
}
match dir {
TransformDirection::Forward => {
create_log_op(
ops,
base,
&log_slope,
&log_offset,
&lin_slope,
&lin_offset,
dir,
)?;
create_fit_op(ops, &oldmin, &oldmax, &newmin, &newmax, dir)
}
TransformDirection::Inverse => {
create_fit_op(ops, &oldmin, &oldmax, &newmin, &newmax, dir)?;
create_log_op(
ops,
base,
&log_slope,
&log_offset,
&lin_slope,
&lin_offset,
dir,
)
}
}
}
Allocation::Unknown => crate::bail!("Unsupported Allocation Type."),
}
}
impl Validate for AllocationTransform {
fn validate(&self) -> Result<()> {
let n = self.vars.len();
match self.allocation {
Allocation::Uniform => {
if n != 2 && n != 0 {
crate::bail!(
"AllocationTransform: wrong number of values for the uniform allocation"
);
}
}
Allocation::Lg2 => {
if n != 3 && n != 2 && n != 0 {
crate::bail!("AllocationTransform: wrong number of values for the logarithmic allocation");
}
}
Allocation::Unknown => crate::bail!("AllocationTransform: invalid allocation type"),
}
Ok(())
}
}
impl BuildOps for AllocationTransform {
fn build_ops(
&self,
ops: &mut OpVec,
_config: &Config,
_context: &Context,
dir: TransformDirection,
) -> Result<()> {
self.validate()?;
let combined = dir.combine(self.direction);
create_allocation_ops(ops, &AllocationData::from_transform(self), combined)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ops::log::LogOp;
use crate::ops::matrix::test_utils::*;
use crate::ops::matrix::MatrixOp;
use crate::processor::optimize_ops;
use crate::types::OptimizationFlags;
fn is_log(op: &crate::ops::OpRc) -> bool {
op.downcast_ref::<LogOp>().is_some()
}
#[test]
fn allocation_ops_create() {
let mut ops = OpVec::new();
let mut data = AllocationData {
allocation: Allocation::Unknown,
vars: vec![],
};
for dir in [TransformDirection::Forward, TransformDirection::Inverse] {
let e = create_allocation_ops(&mut ops, &data, dir).unwrap_err();
assert!(e.message().contains("Unsupported Allocation Type"));
assert!(ops.is_empty());
}
data.allocation = Allocation::Uniform;
create_allocation_ops(&mut ops, &data, TransformDirection::Forward).unwrap();
assert_eq!(ops.len(), 1);
assert!(ops[0].is_no_op());
ops.clear();
create_allocation_ops(&mut ops, &data, TransformDirection::Inverse).unwrap();
assert_eq!(ops.len(), 1);
ops.clear();
data.vars = vec![0.0, 10.0];
create_allocation_ops(&mut ops, &data, TransformDirection::Forward).unwrap();
assert_eq!(ops.len(), 1);
assert!(ops[0].downcast_ref::<MatrixOp>().is_some());
ops.clear();
create_allocation_ops(&mut ops, &data, TransformDirection::Inverse).unwrap();
assert_eq!(ops.len(), 1);
ops.clear();
data.allocation = Allocation::Lg2;
data.vars.clear();
create_allocation_ops(&mut ops, &data, TransformDirection::Forward).unwrap();
assert_eq!(ops.len(), 2);
assert!(ops[1].downcast_ref::<MatrixOp>().is_some());
let ops = optimize_ops(&ops, OptimizationFlags::DEFAULT);
assert_eq!(ops.len(), 2);
let default_log = ops[0].clone();
let default_fit = ops[1].clone();
let src = [
0.16f32, 0.2, 0.3, 0.4, -0.16, -0.2, 32.0, 123.4, 1.0, 1.0, 1.0, 1.0,
];
let dst_log = [
-2.64385629f32,
-2.32192802,
-1.73696554,
0.4,
-126.0,
-126.0,
5.0,
123.4,
0.0,
0.0,
0.0,
1.0,
];
let dst_fit = [
0.635f32, 0.6375, 0.64375, 0.4, 0.615, 0.6125, 2.625, 123.4, 0.6875, 0.6875, 0.6875,
1.0,
];
let tmp = apply_op(default_log.as_ref(), &src);
for i in 0..12 {
assert_close_f(tmp[i], dst_log[i] as f64, 1e-6);
}
let tmp = apply_op(default_fit.as_ref(), &src);
for i in 0..12 {
assert_close_f(tmp[i], dst_fit[i] as f64, 1e-6);
}
let mut ops = OpVec::new();
create_allocation_ops(&mut ops, &data, TransformDirection::Inverse).unwrap();
assert_eq!(ops.len(), 2);
let l0 = default_log.downcast_ref::<LogOp>().unwrap().data();
let l1 = ops[1].downcast_ref::<LogOp>().unwrap().data();
assert!(l0.is_inverse(l1));
data.vars = vec![0.0, 1.0];
for dir in [TransformDirection::Forward, TransformDirection::Inverse] {
let mut ops = OpVec::new();
create_allocation_ops(&mut ops, &data, dir).unwrap();
assert_eq!(ops.len(), 2);
let ops = optimize_ops(&ops, OptimizationFlags::DEFAULT);
assert_eq!(ops.len(), 1);
assert!(is_log(&ops[0]));
}
data.vars.push(10.0);
let mut ops = OpVec::new();
create_allocation_ops(&mut ops, &data, TransformDirection::Forward).unwrap();
assert_eq!(ops.len(), 2);
let ops = optimize_ops(&ops, OptimizationFlags::DEFAULT);
assert_eq!(ops.len(), 1);
let dst_log_shift = [
3.34482837f32,
3.35049725,
3.36457253,
0.4,
3.29865813,
3.29278183,
5.39231730,
123.4,
3.45943165,
3.45943165,
3.45943165,
1.0,
];
let tmp = apply_op(ops[0].as_ref(), &src);
for i in 0..12 {
assert_close_f(tmp[i], dst_log_shift[i] as f64, 1e-6);
}
assert_eq!(data.cache_id(), "lg2 0 1 10 ");
}
#[test]
fn allocation_transform_validate() {
let mut al = AllocationTransform {
allocation: Allocation::Uniform,
..Default::default()
};
assert!(al.validate().is_ok());
al.vars = vec![0.0, 0.0];
assert!(al.validate().is_ok());
al.vars.push(0.01);
assert!(al.validate().is_err());
al.allocation = Allocation::Lg2;
assert!(al.validate().is_ok());
al.vars.push(0.1);
assert!(al.validate().is_err());
al.vars.clear();
assert!(al.validate().is_ok());
al.allocation = Allocation::Unknown;
assert_eq!(
al.validate().unwrap_err().message(),
"AllocationTransform: invalid allocation type"
);
}
#[test]
fn allocation_transform_build_ops() {
let config = Config::create_raw();
let ctx = Context::new();
let al = AllocationTransform {
allocation: Allocation::Lg2,
vars: vec![-8.0, 5.0, 0.00390625],
..Default::default()
};
let mut ops = OpVec::new();
al.build_ops(&mut ops, &config, &ctx, TransformDirection::Forward)
.unwrap();
al.build_ops(&mut ops, &config, &ctx, TransformDirection::Inverse)
.unwrap();
assert_eq!(ops.len(), 4);
assert!(is_log(&ops[0]));
assert!(is_log(&ops[3]));
let src = [0.18f32, 1.0, 4.0, 0.5];
let out = apply_ops(&ops, &src);
for i in 0..4 {
assert_close_f(out[i], src[i] as f64, 1e-5);
}
let opt = optimize_ops(&ops, OptimizationFlags::DEFAULT);
assert_eq!(opt.len(), 1);
assert_eq!(opt[0].name(), "Range");
let mut inv = al.clone();
inv.direction = TransformDirection::Inverse;
let mut ops = OpVec::new();
inv.build_ops(&mut ops, &config, &ctx, TransformDirection::Forward)
.unwrap();
assert!(ops[0].downcast_ref::<MatrixOp>().is_some());
assert!(is_log(&ops[1]));
}
}