orx-funvec
Traits to unify access to elements of n-dimensional vectors which are particularly useful in algorithms requiring both flexibility through abstraction over inputs and performance through monomorphization.
Traits and Methods
trait FunVec<const DIM: usize, T> represents DIM dimensional vectors of T requiring only the following method to be implemented:
fn at<Idx: IntoIndex<DIM>>(&self, index: Idx) -> Option<T>
FunVec is different from, a generalization of, multi dimensional vectors due to the following:
- The elements are not necessarily contagious or dense. They can rather represent any level of sparsity/density depending on the underlying type.
- Assumed to be of infinite length.
Additionally, the trait provides with, auto implements, the iter_over method which allows to iterate over values of the vector over given indices.
The crate also provides the reference returning counterpart FunVecRef<const DIM: usize, T> requiring the method fn ref_at<Idx: IntoIndex<DIM>>(&self, index: Idx) -> Option<&T>.
Implementations and Features
This crate provides implementations to a wide range of types. The following implementations are optionally provided through features:
ndarraybyimpl_ndarrayfeature,indexmapbyimpl_indexmapfeature,smallvecbyimpl_smallvecfeature,- or all implementations by
impl_allfeature.
Motivation
The motivation of the create is demonstrated by a use case in the following example.
Assume we need to solve a network problem, namely minimum cost network flow (mcnf) problem (wikipedia). In the example, we ignore the graph input. Instead, we focus on the following inputs:
- demands: each node of the graph has an amount of demand, which represents supply when negative.
- costs: each arc has an associated unit flow cost.
- capacities: each arc has a limited flow capacity.
The mcnf problem provides a certain level of abstraction over inputs. For instance:
- If demands are non-zero only for two nodes, the problem is a single commodity problem; otherwise, it can represent a multi commodity mcnf problem.
- If demands are 1 and -1 for source and sink nodes and zero for all others, and if capacities are irrelevant, the problem becomes a shortest path problem.
- If we want to find the shortest number of arcs rather than the shortest path, we can use a costs matrix which is 1 for all arcs.
Abstraction over the inputs is powerful since it allows to implement a generic mcnf solver without the need to make assumptions on the concrete input types.
Problem Setup
Below we implement our fake solver generic over the input types.
use *;
const N: usize = 4;
type Unit = i32;
Variant 1: Single Commodity
In the below example, we use our generic solver with:
- a single commodity problem where demands vector is a cheap closure (
Closure<_, usize, Unit>), - a complete costs matrix (
Vec<Vec<Unit>>), - a uniform capacity matrix represented as a cheap closure (
Box<dyn Fn((usize, usize)) -> Unit>).
use Capture;
// mcnf problem with a single source and sink
let source = 0;
let sink = 2;
let demand = 10;
// demands vector as a no-box orx_closure::Closure
let demands =
Capture.fun;
// complete cost matrix
let costs = vec!;
// capacities matrix as a box dyn Fn
let capacities: =
Boxnew;
// simulate & assert
let solver = new;
let result = solver.fake_solve;
assert_eq!;
assert_eq!;
assert_eq!;
Variant 2: Multi Commodity
In the second example variant, we use our solver with:
- a multi commodity problem where demands vector is computed by a closure on the fly (
Closure<_, usize, Unit>), - arc costs are computed as Euclidean distances by a closure using captured node locations (
Closure<_, (usize, usize), Unit>), - a sparse capacity matrix using hash map (
Vec<HashMap<usize, Unit>>).
use Capture;
use HashMap;
// multi-commodity mcnf problem
let commodities = vec!;
// demands vector as a no-box orx_closure::Closure capturing a reference of commodities collection
let demands = Capture.fun;
// costs computed as Euclidean distances of node coordinates
let locations = vec!;
let costs = Capture.fun;
// capacities defined as a Vec of HashMap to take advantage of sparsity in the graph
let capacities = vec!;
// simulate & assert
let solver = new;
let result = solver.fake_solve;
assert_eq!;
assert_eq!;
assert_eq!;
Variant 3: Shortest Distance
Next, we solve a shortest distance problem with the generic solver:
- demands vector is all zeros except for the source and sink represented as a cheap closure (
Closure<_, usize, Unit>), - arc costs are computed as Euclidean distances by a closure using captured node locations (
Closure<_, (usize, usize), Unit>), - capacities are all-ones-matrix represented by a scalar value which has the memory size of a number and matrix accesses will completely be with the value (
ScalarAsVec<Unit>).
let source = 3;
let sink = 1;
// demands vector as a no-box orx_closure::Closure
let demands = Capture.fun;
// costs computed as Euclidean distances of node coordinates
let locations = vec!;
let costs = Capture.fun;
// uniform capacities for all edges
let capacities = ScalarAsVec;
// simulate & assert
let solver = new;
let result = solver.fake_solve;
assert_eq!;
assert_eq!;
assert_eq!;