extern crate nalgebra as na; use std::{collections::HashMap, ops::Mul}; use na::DMatrix; // TODO: Could probably put this up a level by indicating how much of another department // each department used, then calculate the amounts from that. // Note: No need to include the operating departments, only service departments are needed, // then once we calculate all of the pub struct OverheadDepartmentAllocation { from_department: String, to_department: String, percent: f64, } pub struct TotalDepartmentCost { department: String, value: f64, } // Gets the matrix that can be used to reciprocally allocate line items in an account // TODO: What is actually supposed to be in the solve values? Not needed here but whatever calls this function will need to know this // Also need to handle errors (return appropriate result type) // TODO: Also need to return some order so we know what order ccs in the accounts should be in.. could just do this by returning a struct with // the matrix and a method to get the value for a particular key using the hashmap we created. fn get_reciprocal_allocation_matrix(allocations: Vec, total_costs: Vec) -> DMatrix { // Convert vector to matrix form - matrix of from/to percent (usage) and vector of original costs // Matrix of all unique departments let mut department_mappings: HashMap = HashMap::new(); for allocation in allocations.iter() { let map_size = department_mappings.len(); department_mappings.entry(allocation.from_department.clone()).or_insert(map_size); let map_size = department_mappings.len(); department_mappings.entry(allocation.to_department.clone()).or_insert(map_size); } let mut slice_allocations = vec![0.; department_mappings.len() * department_mappings.len()]; // TODO: This needs to be passed in another time. let mut slice_costs = vec![0.; department_mappings.len()]; for allocation in allocations { // TODO: Is there a more idiomatic way to do this? let elem = &mut slice_allocations[*department_mappings.get(&allocation.from_department).unwrap()]; *elem = allocation.percent; } for cost in total_costs { let elem = &mut slice_costs[*department_mappings.get(&cost.department).unwrap()]; *elem = cost.value; } // TODO: Would be nice to make this batched... matrix doesn't support that though. let mat: DMatrix = DMatrix::from_row_slice(department_mappings.len(), department_mappings.len(), &slice_allocations); let costs_vec: DMatrix = DMatrix::from_row_slice(department_mappings.len(), 1, &slice_costs); // Perform reciprocal allocation (LU solve or pseudoinverse regression if the matrix is singular - pseudo inverse is done using nalgebra svd) // TODO: Is it wasteful to perform the determinant rather than just immediately attempting lu? The implementation of determinant appears calls lu anyway? if mat.determinant() == 0. { // Pseudo inverse to find mininmum allocation // TODO: Error handling let pseudo_inverse = mat.svd(true, true).pseudo_inverse(0.000001); pseudo_inverse.unwrap().mul(&costs_vec) } else { // Standard solve using lu with partial pivoting. let lup = mat.lu(); // TODO: Error handling lup.solve(&costs_vec).unwrap() } } // This is kind of a pointless function, it's just a matrix multiply... better to have a method that takes a function that can retrieve the accounts, // then an application would just need to pass in the batch retriever function and the initial overhead things. // Only issue that could come up with this is I get a case where I can't pass a function in from another language. Better the application itself just // uses the struct returned from the function above to fn allocate_overheads(allocation_matrix: DMatrix, ) { } // IDEA: // Consider a state-machine approach. Struct of allocations + total costs, then have a method to transform to // reciprocal matrix + hashmap of indexes, then another method that takes cc costs per account to transform into final outputs. // I think the state machine can be a higher-level api, and can make use of the above functions to transition between states. // This way you won't need to remember each step of the process, and it would be simpler to swap out implementations // as each struct in the state can swap out which functions it can use in the transition.