Add construction for reciprocal allocation matrix
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
191
Cargo.lock
generated
Normal file
191
Cargo.lock
generated
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "approx"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coster-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"nalgebra",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matrixmultiply"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84"
|
||||||
|
dependencies = [
|
||||||
|
"rawpointer",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nalgebra"
|
||||||
|
version = "0.31.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18a89248335f688e4bd994e6d030fd7e185eb41769b8c435395075425e100ac6"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"matrixmultiply",
|
||||||
|
"nalgebra-macros",
|
||||||
|
"num-complex",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"simba",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nalgebra-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rawpointer"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "safe_arch"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simba"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13a2609e876d4f77f6ab7ff5254fc39b4f1927ba8e6db3d18be7c32534d3725e"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"num-complex",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
"wide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wide"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3aba2d1dac31ac7cae82847ac5b8be822aee8f99a4e100f279605016b185c5f"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"safe_arch",
|
||||||
|
]
|
||||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "coster-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nalgebra = "0.31.0"
|
||||||
91
src/lib.rs
Normal file
91
src/lib.rs
Normal 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.
|
||||||
3
src/main.rs
Normal file
3
src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user