commit 6db4a501254f807f77a867a112b30cb4806fa78d Author: piv <> Date: Tue May 24 16:57:22 2022 +0930 Add construction for reciprocal allocation matrix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d6e1c04 --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..32b083d --- /dev/null +++ b/Cargo.toml @@ -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" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f02f1bd --- /dev/null +++ b/src/lib.rs @@ -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, total_costs: Vec) -> DMatrix { + // 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 = 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 = DMatrix::from_row_slice(department_mappings.len(), department_mappings.len(), &slice_allocations); + let costs_vec: DMatrix = 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, ) { + +} + + +// 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. diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}