Add construction for reciprocal allocation matrix

This commit is contained in:
piv
2022-05-24 16:57:22 +09:30
commit 6db4a50125
5 changed files with 295 additions and 0 deletions

91
src/lib.rs Normal file
View File

@@ -0,0 +1,91 @@
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<OverheadDepartmentAllocation>, total_costs: Vec<TotalDepartmentCost>) -> DMatrix<f64> {
// 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<String, usize> = 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<f64> = DMatrix::from_row_slice(department_mappings.len(), department_mappings.len(), &slice_allocations);
let costs_vec: DMatrix<f64> = 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<f64>, ) {
}
// 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.