einsum-ndarray 0.1.0

Einstein summation for dynamically shaped ndarray arrays
Documentation
#![allow(dead_code)]

use std::fs;
use std::path::PathBuf;

use serde_json::Value;

#[cfg(has_reference_evaluator)]
pub mod naive;

#[derive(Clone, Debug)]
pub struct OperandCase {
    pub shape: Vec<usize>,
    pub data: Vec<f64>,
    pub data_int: Vec<i64>,
}

#[derive(Clone, Debug)]
pub struct ExpectedCase {
    pub shape: Vec<usize>,
    pub data: Vec<f64>,
    pub data_int_scaled: Vec<i64>,
}

#[derive(Clone, Debug)]
pub struct PathCase {
    pub naive_flops: u128,
    pub naive_left_to_right_flops: u128,
    pub optimal_flops: u128,
}

#[derive(Clone, Debug)]
pub struct ConformanceCase {
    pub id: String,
    pub category: String,
    pub subscripts: String,
    pub operands: Vec<OperandCase>,
    pub expected: Option<ExpectedCase>,
    pub error_kind: Option<String>,
    pub resolved_output: Option<String>,
    pub path: Option<PathCase>,
}

pub fn load_cases() -> Vec<ConformanceCase> {
    let path =
        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/data/conformance-vectors.json");
    let text = fs::read_to_string(path).unwrap();
    let document: Value = serde_json::from_str(&text).unwrap();
    assert_eq!(document["count"].as_u64(), Some(189));
    document["cases"]
        .as_array()
        .unwrap()
        .iter()
        .map(parse_case)
        .collect()
}

fn parse_case(value: &Value) -> ConformanceCase {
    let operands = value["operands"]
        .as_array()
        .unwrap()
        .iter()
        .map(|operand| OperandCase {
            shape: usize_array(&operand["shape"]),
            data: f64_array(&operand["data"]),
            data_int: i64_array(&operand["data_int"]),
        })
        .collect();
    let expected = value.get("expected").map(|expected| ExpectedCase {
        shape: usize_array(&expected["shape"]),
        data: f64_array(&expected["data"]),
        data_int_scaled: i64_array(&expected["data_int_scaled"]),
    });
    let path = value.get("path").map(|path| PathCase {
        naive_flops: u128::from(path["naive_flops"].as_u64().unwrap()),
        naive_left_to_right_flops: u128::from(path["naive_left_to_right_flops"].as_u64().unwrap()),
        optimal_flops: u128::from(path["optimal"]["flops"].as_u64().unwrap()),
    });
    ConformanceCase {
        id: value["id"].as_str().unwrap().to_owned(),
        category: value["category"].as_str().unwrap().to_owned(),
        subscripts: value["subscripts"].as_str().unwrap().to_owned(),
        operands,
        expected,
        error_kind: value
            .get("error")
            .map(|error| error["kind"].as_str().unwrap().to_owned()),
        resolved_output: value
            .get("resolved_output")
            .map(|output| output.as_str().unwrap().to_owned()),
        path,
    }
}

fn usize_array(value: &Value) -> Vec<usize> {
    value
        .as_array()
        .unwrap()
        .iter()
        .map(|number| usize::try_from(number.as_u64().unwrap()).unwrap())
        .collect()
}

fn i64_array(value: &Value) -> Vec<i64> {
    value
        .as_array()
        .unwrap()
        .iter()
        .map(|number| number.as_i64().unwrap())
        .collect()
}

fn f64_array(value: &Value) -> Vec<f64> {
    value
        .as_array()
        .unwrap()
        .iter()
        .map(|number| number.as_f64().unwrap())
        .collect()
}

pub fn left_to_right_path(operand_count: usize) -> Vec<Vec<usize>> {
    if operand_count < 2 {
        return Vec::new();
    }
    let mut active: Vec<Option<usize>> = (0..operand_count).map(Some).collect();
    let mut path = Vec::new();
    let mut accumulated = 0;
    for next in 1..operand_count {
        let left = active
            .iter()
            .position(|entry| *entry == Some(accumulated))
            .unwrap();
        let right = active
            .iter()
            .position(|entry| *entry == Some(next))
            .unwrap();
        path.push(vec![left, right]);
        let mut removal = vec![left, right];
        removal.sort_unstable();
        for index in removal.into_iter().rev() {
            active.remove(index);
        }
        active.push(Some(accumulated));
        accumulated = 0;
    }
    path
}