More progress towards reciprocal allocation algorithm
This commit is contained in:
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -97,6 +97,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"csv",
|
"csv",
|
||||||
|
"itertools",
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -122,6 +123,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -153,6 +160,15 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ csv = "1.1"
|
|||||||
|
|
||||||
# num = "0.4"
|
# num = "0.4"
|
||||||
clap = { version = "3.1.18", features = ["derive"] }
|
clap = { version = "3.1.18", features = ["derive"] }
|
||||||
|
|
||||||
|
itertools = "0.10.3"
|
||||||
|
|||||||
83
src/lib.rs
83
src/lib.rs
@@ -1,8 +1,8 @@
|
|||||||
extern crate nalgebra as na;
|
extern crate nalgebra as na;
|
||||||
|
|
||||||
use core::slice;
|
use itertools::Itertools;
|
||||||
use na::{DMatrix, Dynamic, LU};
|
use na::{DMatrix, Dynamic, LU};
|
||||||
use std::{collections::HashMap, ops::Mul, error::Error};
|
use std::{collections::HashMap, error::Error, ops::Mul};
|
||||||
|
|
||||||
// TODO: Look into serde for serialisation, can also use it to serialise/deserialise
|
// TODO: Look into serde for serialisation, can also use it to serialise/deserialise
|
||||||
// records from a csv file using the csv crate
|
// records from a csv file using the csv crate
|
||||||
@@ -117,15 +117,22 @@ pub fn move_money_2(
|
|||||||
running_total
|
running_total
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum DepartmentType {
|
||||||
|
Operating,
|
||||||
|
Overhead,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Could also look at BigDecimal rather than f64 for higher precision (even i64 might be fine if we don't need to divide...)
|
// TODO: Could also look at BigDecimal rather than f64 for higher precision (even i64 might be fine if we don't need to divide...)
|
||||||
// Note: remember these are overhead departments only when calculating the lu decomposition or pseudoinverse, and for each department,
|
// 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
|
// 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)
|
// up with negative there so yes this is expected)
|
||||||
// Also, we could potentially use this same struct for non-overhead departments when mapping from overhead to
|
// Also, we could potentially use this same struct for non-overhead departments when mapping from overhead to
|
||||||
pub struct OverheadAllocationRule {
|
pub struct OverheadAllocationRule {
|
||||||
from_department: String,
|
from_overhead_department: String,
|
||||||
to_department: String,
|
to_department: String,
|
||||||
percent: f64,
|
percent: f64,
|
||||||
|
to_department_type: DepartmentType,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TotalDepartmentCost {
|
pub struct TotalDepartmentCost {
|
||||||
@@ -155,6 +162,24 @@ impl ReciprocalAllocationSolver for DMatrix<f64> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// 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
|
// to functional departments. Basically just a matrix solve, uses regression (moore-penrose pseudoinverse) when
|
||||||
@@ -164,23 +189,35 @@ pub fn reciprocal_allocation(
|
|||||||
account_costs: Vec<AccountCost>,
|
account_costs: Vec<AccountCost>,
|
||||||
// TODO: Throw an appropriate error
|
// TODO: Throw an appropriate error
|
||||||
) -> Result<Vec<AccountCost>, Box<dyn 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,
|
let overhead_department_mappings: HashMap<String, usize> =
|
||||||
// and
|
get_rules_indexes(&allocations, DepartmentType::Overhead);
|
||||||
let mut department_mappings: HashMap<String, usize> = HashMap::new();
|
let operating_department_mappings: HashMap<String, usize> =
|
||||||
for allocation in allocations.iter() {
|
get_rules_indexes(&allocations, DepartmentType::Operating);
|
||||||
let map_size = department_mappings.len();
|
|
||||||
department_mappings
|
let mut slice_allocations =
|
||||||
.entry(allocation.from_department.clone())
|
vec![0.; overhead_department_mappings.len() * overhead_department_mappings.len()];
|
||||||
.or_insert(map_size);
|
|
||||||
let map_size = department_mappings.len();
|
for allocation in allocations
|
||||||
department_mappings
|
.iter()
|
||||||
.entry(allocation.to_department.clone())
|
.filter(|allocation| allocation.to_department_type == DepartmentType::Overhead)
|
||||||
.or_insert(map_size);
|
{
|
||||||
|
// 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(
|
let mat: DMatrix<f64> = DMatrix::from_row_slice(
|
||||||
department_mappings.len(),
|
overhead_department_mappings.len(),
|
||||||
department_mappings.len(),
|
overhead_department_mappings.len(),
|
||||||
&slice_allocations,
|
&slice_allocations,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -189,14 +226,14 @@ pub fn reciprocal_allocation(
|
|||||||
do_solve_reciprocal(
|
do_solve_reciprocal(
|
||||||
pseudo_inverse.unwrap(),
|
pseudo_inverse.unwrap(),
|
||||||
account_costs,
|
account_costs,
|
||||||
department_mappings,
|
overhead_department_mappings,
|
||||||
allocations,
|
allocations,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
do_solve_reciprocal(
|
do_solve_reciprocal(
|
||||||
mat.lu(),
|
mat.lu(),
|
||||||
account_costs,
|
account_costs,
|
||||||
department_mappings,
|
overhead_department_mappings,
|
||||||
allocations,
|
allocations,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -210,14 +247,7 @@ fn do_solve_reciprocal<T: ReciprocalAllocationSolver>(
|
|||||||
) -> Result<Vec<AccountCost>, Box<dyn Error>> {
|
) -> 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
|
// 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 {
|
for total_costs in account_costs {
|
||||||
let mut slice_allocations = vec![0.; department_mappings.len()];
|
|
||||||
let mut slice_costs = 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 {
|
for cost in total_costs.summed_department_costs {
|
||||||
let elem = &mut slice_costs[*department_mappings.get(&cost.department).unwrap()];
|
let elem = &mut slice_costs[*department_mappings.get(&cost.department).unwrap()];
|
||||||
@@ -233,7 +263,6 @@ fn do_solve_reciprocal<T: ReciprocalAllocationSolver>(
|
|||||||
// Where operating_overhead_usage is the direct mapping from overhead -> operating department, calculated overheads is the
|
// 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
|
// solved overheads usages after taking into account usage between departments, and initial_totals is the initial values
|
||||||
// for the operating departments.
|
// for the operating departments.
|
||||||
|
|
||||||
}
|
}
|
||||||
// TODO: return something appropriate
|
// TODO: return something appropriate
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
|
|||||||
Reference in New Issue
Block a user