Refactor codebase into submodules
This commit is contained in:
153
src/overhead_allocation.rs
Normal file
153
src/overhead_allocation.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use itertools::Itertools;
|
||||
use nalgebra::{DMatrix, Dynamic, LU};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum DepartmentType {
|
||||
Operating,
|
||||
Overhead,
|
||||
}
|
||||
|
||||
// Note: remember these are overhead departments only when calculating the lu decomposition or pseudoinverse, and for each department,
|
||||
// you either need -1 or rest negative for a row to subtract the initial amounts so we end up effectively 0 (simultaneous equations end
|
||||
// up with negative there so yes this is expected)
|
||||
pub struct OverheadAllocationRule {
|
||||
from_overhead_department: String,
|
||||
to_department: String,
|
||||
percent: f64,
|
||||
to_department_type: DepartmentType,
|
||||
}
|
||||
|
||||
pub struct TotalDepartmentCost {
|
||||
department: String,
|
||||
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 * costs
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rules_indexes(
|
||||
allocations: &Vec<OverheadAllocationRule>,
|
||||
department_type: DepartmentType,
|
||||
) -> HashMap<String, usize> {
|
||||
allocations
|
||||
.iter()
|
||||
.filter(|allocation| allocation.to_department_type == department_type)
|
||||
.flat_map(|department| {
|
||||
[
|
||||
department.from_overhead_department.clone(),
|
||||
department.to_department.clone(),
|
||||
]
|
||||
})
|
||||
.unique()
|
||||
.enumerate()
|
||||
.map(|(index, department)| (department, index))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// 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
|
||||
pub fn reciprocal_allocation(
|
||||
allocations: Vec<OverheadAllocationRule>,
|
||||
account_costs: Vec<AccountCost>,
|
||||
// TODO: Throw an appropriate error
|
||||
) -> anyhow::Result<Vec<AccountCost>> {
|
||||
let overhead_department_mappings: HashMap<String, usize> =
|
||||
get_rules_indexes(&allocations, DepartmentType::Overhead);
|
||||
let operating_department_mappings: HashMap<String, usize> =
|
||||
get_rules_indexes(&allocations, DepartmentType::Operating);
|
||||
|
||||
let mut slice_allocations =
|
||||
vec![0.; overhead_department_mappings.len() * overhead_department_mappings.len()];
|
||||
|
||||
for allocation in allocations
|
||||
.iter()
|
||||
.filter(|allocation| allocation.to_department_type == DepartmentType::Overhead)
|
||||
{
|
||||
// TODO: Check if we need to flp this around
|
||||
let from_index = overhead_department_mappings
|
||||
.get(&allocation.from_overhead_department)
|
||||
.unwrap();
|
||||
let to_index = operating_department_mappings
|
||||
.get(&allocation.to_department)
|
||||
.unwrap();
|
||||
let elem = &mut slice_allocations
|
||||
[(*from_index) + (overhead_department_mappings.len() * (*to_index))];
|
||||
*elem = allocation.percent;
|
||||
}
|
||||
|
||||
// TODO: Also need ones along the diagonal, and negatives in some places...
|
||||
|
||||
let mat: DMatrix<f64> = DMatrix::from_row_slice(
|
||||
overhead_department_mappings.len(),
|
||||
overhead_department_mappings.len(),
|
||||
&slice_allocations,
|
||||
);
|
||||
|
||||
if mat.determinant() == 0. {
|
||||
let pseudo_inverse = mat.svd(true, true).pseudo_inverse(0.000001);
|
||||
do_solve_reciprocal(
|
||||
pseudo_inverse.unwrap(),
|
||||
account_costs,
|
||||
overhead_department_mappings,
|
||||
allocations,
|
||||
)
|
||||
} else {
|
||||
do_solve_reciprocal(
|
||||
mat.lu(),
|
||||
account_costs,
|
||||
overhead_department_mappings,
|
||||
allocations,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn do_solve_reciprocal<T: ReciprocalAllocationSolver>(
|
||||
solver: T,
|
||||
account_costs: Vec<AccountCost>,
|
||||
department_mappings: HashMap<String, usize>,
|
||||
allocations: Vec<OverheadAllocationRule>,
|
||||
) -> anyhow::Result<Vec<AccountCost>> {
|
||||
// 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_costs = vec![0.; department_mappings.len()];
|
||||
|
||||
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