Rework reciprocal allocation to be closer to final algorithm, perform calculations off heap
This commit is contained in:
120
src/lib.rs
120
src/lib.rs
@@ -1,7 +1,8 @@
|
||||
extern crate nalgebra as na;
|
||||
|
||||
use na::DMatrix;
|
||||
use std::{collections::HashMap, ops::Mul};
|
||||
use core::slice;
|
||||
use na::{DMatrix, Dynamic, LU};
|
||||
use std::{collections::HashMap, ops::Mul, error::Error};
|
||||
|
||||
// TODO: Look into serde for serialisation, can also use it to serialise/deserialise
|
||||
// records from a csv file using the csv crate
|
||||
@@ -132,22 +133,39 @@ pub struct TotalDepartmentCost {
|
||||
value: f64,
|
||||
}
|
||||
|
||||
pub struct AccountCost {
|
||||
account: String,
|
||||
summed_department_costs: Vec<TotalDepartmentCost>,
|
||||
}
|
||||
|
||||
// TODO: Also need a way to dictate the order of the departments?
|
||||
pub trait ReciprocalAllocationSolver {
|
||||
fn solve(&self, costs: &DMatrix<f64>) -> DMatrix<f64>;
|
||||
}
|
||||
|
||||
impl ReciprocalAllocationSolver for LU<f64, Dynamic, Dynamic> {
|
||||
fn solve(&self, costs: &DMatrix<f64>) -> DMatrix<f64> {
|
||||
self.solve(costs).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ReciprocalAllocationSolver for DMatrix<f64> {
|
||||
fn solve(&self, costs: &DMatrix<f64>) -> DMatrix<f64> {
|
||||
self.mul(costs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Perform the reciprocal allocation (matrix) method to allocate servicing departments (indirect) costs
|
||||
// to functional departments. Basically just a matrix solve, uses regression (moore-penrose pseudoinverse) when
|
||||
// matrix is singular
|
||||
// TODO: Could also reduce memory by just calculating overhead costs in a first step (service departments), then
|
||||
// calculating operating department costs in a second step using the output from the service departments (multiply
|
||||
// by service department output rather than original). The second step can be a vector multiply or a loop, basically
|
||||
// same as move money step, might bven be able to just repeat it
|
||||
// Note: PPM currently does the invert for the cost centres only (so can be up to 6000 ccs), as the cost centres are the actual departments,
|
||||
// and a previous step calculates the percentages for overhead areas using their allocation statistics. Then for each account,
|
||||
// it will use the overhead allocation matrix to calculate the moved/overhead allocations from the line items calculated from the previous
|
||||
// cost definiteions/reclass rules steps. Really we'd want to batch this out so we multiple a couple hundred or so accounts at a time (maybe
|
||||
// with a batch size property)
|
||||
pub fn get_reciprocal_allocation_matrix(
|
||||
pub fn reciprocal_allocation(
|
||||
allocations: Vec<OverheadAllocationRule>,
|
||||
total_costs: Vec<TotalDepartmentCost>,
|
||||
) -> DMatrix<f64> {
|
||||
account_costs: Vec<AccountCost>,
|
||||
// TODO: Throw an appropriate error
|
||||
) -> Result<Vec<AccountCost>, Box<dyn Error>> {
|
||||
// TODO: Need to split up the rules so that we only pass overhead departments into the getreciprocal matrix method,
|
||||
// and
|
||||
let mut department_mappings: HashMap<String, usize> = HashMap::new();
|
||||
for allocation in allocations.iter() {
|
||||
let map_size = department_mappings.len();
|
||||
@@ -160,39 +178,63 @@ pub fn get_reciprocal_allocation_matrix(
|
||||
.or_insert(map_size);
|
||||
}
|
||||
|
||||
let mut slice_allocations = vec![0.; 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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// TODO: Only calculate lu/pseudoinverse once. We then do the solve for the overhead department totals for each account, and use this to
|
||||
// calculate the final totals.
|
||||
if mat.determinant() == 0. {
|
||||
let pseudo_inverse = mat.svd(true, true).pseudo_inverse(0.000001);
|
||||
pseudo_inverse.unwrap().mul(&costs_vec)
|
||||
do_solve_reciprocal(
|
||||
pseudo_inverse.unwrap(),
|
||||
account_costs,
|
||||
department_mappings,
|
||||
allocations,
|
||||
)
|
||||
} else {
|
||||
let lup = mat.lu();
|
||||
lup.solve(&costs_vec).unwrap()
|
||||
do_solve_reciprocal(
|
||||
mat.lu(),
|
||||
account_costs,
|
||||
department_mappings,
|
||||
allocations,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn do_solve_reciprocal<T: ReciprocalAllocationSolver>(
|
||||
solver: T,
|
||||
account_costs: Vec<AccountCost>,
|
||||
department_mappings: HashMap<String, usize>,
|
||||
allocations: Vec<OverheadAllocationRule>,
|
||||
) -> Result<Vec<AccountCost>, Box<dyn Error>> {
|
||||
// TODO: Could batch the accounts, although probably won't see to big a speed increase, compiler should help us out
|
||||
for total_costs in account_costs {
|
||||
let mut slice_allocations = vec![0.; department_mappings.len()];
|
||||
let mut slice_costs = vec![0.; department_mappings.len()];
|
||||
for allocation in allocations {
|
||||
let elem = &mut slice_allocations[*department_mappings
|
||||
.get(&allocation.from_department)
|
||||
.unwrap()];
|
||||
*elem = allocation.percent;
|
||||
}
|
||||
|
||||
for cost in total_costs.summed_department_costs {
|
||||
let elem = &mut slice_costs[*department_mappings.get(&cost.department).unwrap()];
|
||||
*elem = cost.value;
|
||||
}
|
||||
|
||||
let costs_vec: DMatrix<f64> =
|
||||
DMatrix::from_row_slice(department_mappings.len(), 1, &slice_costs);
|
||||
|
||||
let calculated_overheads = solver.solve(&costs_vec);
|
||||
|
||||
// Calculation: operating_overhead_usage . calculated_overheads + initial_totals
|
||||
// Where operating_overhead_usage is the direct mapping from overhead -> operating department, calculated overheads is the
|
||||
// solved overheads usages after taking into account usage between departments, and initial_totals is the initial values
|
||||
// for the operating departments.
|
||||
|
||||
}
|
||||
// TODO: return something appropriate
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user