92 lines
4.5 KiB
Rust
92 lines
4.5 KiB
Rust
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.
|