From 3e7559420d3b611c8a9a7a862e793bfcd1e84712 Mon Sep 17 00:00:00 2001 From: Chris Pryer Date: Fri, 17 Jan 2025 23:39:13 -0500 Subject: [PATCH] Initial commit --- .github/workflows/ci.yaml | 72 ---- .../{spell-check.yaml => spelling.yaml} | 11 +- .gitignore | 6 +- Cargo.toml | 7 + README.md | 5 +- crates/solver-graph/Cargo.toml | 18 - crates/solver-graph/README.md | 34 -- crates/solver-graph/img/small-graph.png | Bin 72757 -> 0 bytes crates/solver-graph/src/helpers.rs | 121 ------ crates/solver-graph/src/lib.rs | 24 -- crates/solver-graph/src/ops.rs | 140 ------- crates/solver-graph/src/queue.rs | 119 ------ crates/solver-graph/src/small_array.rs | 225 ----------- crates/solver-graph/src/small_graph.rs | 348 ------------------ crates/solver-template/Cargo.toml | 7 - crates/solver-template/src/graph.rs | 21 -- crates/solver-template/src/main.rs | 5 - crates/solver-vrp/Cargo.toml | 6 + crates/solver-vrp/src/lib.rs | 2 + crates/solver-vrp/src/model.rs | 123 +++++++ crates/solver-vrp/src/solver.rs | 13 + 21 files changed, 155 insertions(+), 1152 deletions(-) delete mode 100644 .github/workflows/ci.yaml rename .github/workflows/{spell-check.yaml => spelling.yaml} (50%) delete mode 100644 crates/solver-graph/Cargo.toml delete mode 100644 crates/solver-graph/README.md delete mode 100644 crates/solver-graph/img/small-graph.png delete mode 100644 crates/solver-graph/src/helpers.rs delete mode 100644 crates/solver-graph/src/lib.rs delete mode 100644 crates/solver-graph/src/ops.rs delete mode 100644 crates/solver-graph/src/queue.rs delete mode 100644 crates/solver-graph/src/small_array.rs delete mode 100644 crates/solver-graph/src/small_graph.rs delete mode 100644 crates/solver-template/Cargo.toml delete mode 100644 crates/solver-template/src/graph.rs delete mode 100644 crates/solver-template/src/main.rs create mode 100644 crates/solver-vrp/Cargo.toml create mode 100644 crates/solver-vrp/src/lib.rs create mode 100644 crates/solver-vrp/src/model.rs create mode 100644 crates/solver-vrp/src/solver.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index 4e2407f..0000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,72 +0,0 @@ -name: ci - -on: - push: - branches: - - "master" - paths: - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' - - '.github/workflows/ci.yaml' - pull_request: - paths: - - 'crates/**' - - 'Cargo.toml' - - 'Cargo.lock' - - '.github/workflows/ci.yaml' - -jobs: - test-rust: - name: test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - components: rustfmt, clippy - - name: Cache Cargo - uses: actions/cache@v3 - with: - path: /github/home/.cargo - key: cargo-cache-test-rs - - name: Cache Rust dependencies - uses: actions/cache@v3 - with: - path: /github/home/target - key: ubuntu-x86-64-target-cache-stable - - name: Run tests - run: | - cargo test --all-features - lint: - name: lints - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - components: rustfmt, clippy - - name: Cache Cargo - uses: actions/cache@v3 - with: - path: /github/home/.cargo - key: cargo-cache-test-rs - - name: Cache Rust dependencies - uses: actions/cache@v3 - with: - path: /github/home/target - key: ubuntu-x86-64-target-cache-stable - - name: Run fmt - run: | - cargo fmt --all -- --check - - name: Run clippy - run: | - export RUSTFLAGS="-C debuginfo=0" - cargo clippy --all-features - cargo clippy -- -D warnings \ No newline at end of file diff --git a/.github/workflows/spell-check.yaml b/.github/workflows/spelling.yaml similarity index 50% rename from .github/workflows/spell-check.yaml rename to .github/workflows/spelling.yaml index bf864f0..263ff56 100644 --- a/.github/workflows/spell-check.yaml +++ b/.github/workflows/spelling.yaml @@ -1,19 +1,12 @@ name: spelling -on: - push: - branches: - - "master" - pull_request: - branches: - - "master" +on: [push] jobs: typos: - name: spelling runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: crate-ci/typos@master with: files: . diff --git a/.gitignore b/.gitignore index 3c54e25..3b33df2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,6 @@ -.vscode/ -/target - - # Added by cargo # # already existing elements were commented out -#/target +/target /Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index c66a4d7..33ed47c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,9 @@ [workspace] members = ["crates/*"] +resolver = "2" + +[workspace.package] +rust-version = "1.75" + +[workspace.lints.clippy] +pedantic = "warn" diff --git a/README.md b/README.md index c1c90f0..8935747 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,9 @@ -[![tests](https://github.com/cnpryer/solver/actions/workflows/ci.yaml/badge.svg)](https://github.com/cnpryer/solver/actions/workflows/ci.yaml) - # solver -A fast solver library for Rust. +Note that `solver` is currently only intended to be used for VRP. `solver-vrp` is inspired by [`nextroute`](https://github.com/nextmv-io/nextroute). # Goals - Fast - Compact - Expressive -- Domain-Driven diff --git a/crates/solver-graph/Cargo.toml b/crates/solver-graph/Cargo.toml deleted file mode 100644 index 9642f05..0000000 --- a/crates/solver-graph/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "solver-graph" -version = "0.1.0" -edition = "2021" -authors = ["cnpryer "] -license = "MIT" -description = "Fast graph data structures." -repository = "https://github.com/cnpryer/solver.git" -homepage = "https://github.com/cnpryer/solver.git" -readme = "README.md" -keywords = [ - "solver" -] - -[lib] -doctest = false - -[dependencies] diff --git a/crates/solver-graph/README.md b/crates/solver-graph/README.md deleted file mode 100644 index 2b93174..0000000 --- a/crates/solver-graph/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# solver-graph - -Compact data structures for fast graph-based solutions. - -- [x] `SmallGraph` -- [ ] [Tinygraph port](https://github.com/cnpryer/solver/issues/57) (or equivalent; see [#50](https://github.com/cnpryer/solver/issues/50), [#43](https://github.com/cnpryer/solver/issues/43)) - -## `SmallGraph` - -Use `graph!` to build a `SmallGraph` from `Nodes` and `Edges`. - -```rust -use solver_graph::small_graph::{graph, nodes, edges, shortest_path}; - -let nodes = nodes(vec![0, 1, 2]); -let edges = edges( - vec![ - vec![weighted_edge(0, 1, vec![1]), weighted_edge(0, 2, vec![10])], - vec![weighted_edge(1, 2, vec![1])], - vec![], - ] -) -let graph = graph![nodes, edges]; -``` - -Run `shortest_path` on your graph. - -```rust -assert_eq!(shortest_path(&graph, 0, 2).unwrap(), vec![&0, &1, &2]); -``` - -`SmallGraph` is designed for compact graph-based problems where `V` (N vertices) and `E` (N edges) are or can be limited. - -image of a small graph diff --git a/crates/solver-graph/img/small-graph.png b/crates/solver-graph/img/small-graph.png deleted file mode 100644 index 7422a1454c474999004aaa9f1091bc098a0cb355..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72757 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 diff --git a/crates/solver-graph/src/helpers.rs b/crates/solver-graph/src/helpers.rs deleted file mode 100644 index 4a559c7..0000000 --- a/crates/solver-graph/src/helpers.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::{ - small_array::SmallArray, - small_graph::{Edge, Edges, Nodes, SmallGraph}, - Position, Value, -}; - -/// Query the neighbors of a `Node` from a `SmallGraph`. -/// -/// ```rust -/// use solve_graph::small_graph::{graph, nodes, edges, neighbors}; -/// -/// let nodes = nodes(vec![0, 1, 2]); -/// let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); -/// let graph = graph![nodes, edges]; -/// let neighbors = neighbors(&graph, 0).unwrap(); -/// ``` -pub(crate) fn neighbors( - graph: &SmallGraph, - index: P, -) -> Option> { - graph - .edges() - .get(index) - .map(|edges| edges.iter().map(|e| &e.to).collect()) -} - -/// The `Nodes` struct composes `Node` data. -/// -/// ```rust -/// use solver_graph::small_graph::nodes; -/// -/// let nodes = nodes(vec![]); -/// ``` -pub fn nodes(nodes: Vec) -> Nodes { - Nodes(nodes) -} - -/// The `Edges` struct composes `Edge` data. -/// -/// ```rust -/// use solver_graph::small_graph::edges; -/// -/// let edges = edges(vec![]); -/// ``` -pub fn edges(edges: Vec>>) -> Edges { - // TODO - Edges( - edges - .into_iter() - .map(|e| { - if e.is_empty() { - SmallArray::Empty - } else { - SmallArray::Dynamic(e) - } - }) - .collect(), - ) -} - -/// The `Edge` struct composes the indexes of a 'from' and 'to' `Node`. -/// -/// ```rust -/// use solver_graph::small_graph::edge; -/// -/// let edge = edge(0, 1); -/// ``` -pub fn edge(from: P, to: P) -> Edge { - Edge { - from, - to, - weights: None, - } -} - -/// The `Edge` struct composes the indexes of a 'from` and 'to' `Node`. Some `Edge`s can have -/// 'weight' data assigned to them. -/// -/// ```rust -/// use solver_graph::small_graph::weighted_edge; -/// -/// let edge = weighted_edge(0, 1, vec![100]); -/// ``` -pub fn weighted_edge(from: P, to: P, weights: Vec) -> Edge { - // TODO(cnpryer): https://github.com/cnpryer/solver/issues/50 - assert!(weights.is_empty() || weights.len() == 1); - Edge { - from, - to, - weights: Some(if weights.is_empty() { - SmallArray::Empty - } else { - SmallArray::One([weights[0]]) - }), - } -} - -#[cfg(test)] -#[macro_use] -mod tests { - use crate::{ - graph, - small_graph::test_fixtures::{sample_edges, sample_nodes}, - }; - - use super::*; - - #[test] - fn test_neighbors() { - let (nodes, edges) = (sample_nodes(), sample_edges()); - let graph: _ = graph![nodes, edges.clone()]; - let neighbors = neighbors(&graph, 0).unwrap(); - let ans = edges - .get(0) - .unwrap() - .iter() - .map(|e| &e.to) - .collect::>(); - assert_eq!(ans, neighbors); - } -} diff --git a/crates/solver-graph/src/lib.rs b/crates/solver-graph/src/lib.rs deleted file mode 100644 index bc2c062..0000000 --- a/crates/solver-graph/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -///! # solver-graph -///! -///! `SmallGraph` can be used for operations on `Nodes` and `Edges`. Constructing a `SmallGraph` -///! requires using the `graph!` macro to index `nodes` and `edges`. -///! -///! ```rust -///! use solve_graph::small_graph::{graph, nodes, edges}; -///! -///! let nodes = nodes(vec![0, 1, 2]); -///! let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); -///! let graph = graph![nodes, edges]; -///! ``` -use std::fmt::Debug; -mod helpers; -mod ops; -mod queue; -mod small_array; -pub mod small_graph; - -pub trait Value: Default + Copy + Clone {} -impl Value for V {} - -pub trait Position: Default + Copy + Clone + Into + Debug {} -impl + Debug> Position for P {} diff --git a/crates/solver-graph/src/ops.rs b/crates/solver-graph/src/ops.rs deleted file mode 100644 index c8b49cc..0000000 --- a/crates/solver-graph/src/ops.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::{collections::HashMap, hash::Hash, ops::Add}; - -use crate::{ - queue::PriorityQueue, - small_array::{Reduce, Reducer, SmallArray}, - small_graph::SmallGraph, - Position, Value, -}; - -/// Sort the `Nodes` of the `SmallGraph`. -/// -/// ```rust -/// use solve_graph::small_graph::{SmallGraph, sort}; -/// -/// let mut graph = SmallGraph::new(); -/// let mut graph = sort(&mut graph); -/// ``` -pub(crate) fn sort(_graph: &mut SmallGraph) -> &mut SmallGraph { - unimplemented!() -} - -/// Query the shortest path from a `SmallGraph`. -/// -/// ```rust -/// use solve_graph::small_graph::{graph, nodes, edges, shortest_path}; -/// -/// let nodes = nodes(vec![0, 1, 2]); -/// let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); -/// let graph = graph![nodes, edges]; -/// let path = shortest_path(&graph, 0, 1).unwrap(); -/// ``` -pub fn shortest_path>( - graph: &SmallGraph, - from: P, - to: P, -) -> Option> { - let mut weights = HashMap::new(); - let mut prev_nodes: HashMap> = HashMap::new(); - // TODO(cnpryer): What capacity do I want for this? Shouldn't need V * E right? What about V + E - let mut queue = PriorityQueue::with_capacity(graph.nodes().len()); - - let start = from; - weights.insert(start, SmallArray::Empty); - queue.push((start, SmallArray::Empty)); - - while let Some((node, weight)) = queue.pop() { - if node == to { - // Found the target node, reconstruct the path - let mut path = Vec::new(); - let mut current = node; - while let Some(Some(prev)) = prev_nodes.get(¤t) { - // Node is on path - path.push( - graph - .nodes() - .get(current.into()) - .unwrap_or_else(|| panic!("invalid node ({:?})", current)), - ); - if current == start { - break; - } - current = *prev; - } - path.push( - graph - .nodes() - .get(start.into()) - .unwrap_or_else(|| panic!("invalid from node ({:?})", start)), - ); - path.reverse(); - return Some(path); - } - - // TODO(cnpryer): Can I implement a cheaper `Copy` for `SmallArray`? Don't want to clone - if let Some(edges) = graph.edges().get(node) { - for edge in edges.iter() { - let to = &edge.to; - let w = weight.reduce(Reducer::SumArray( - edge.weights().unwrap_or(&SmallArray::Empty), - )); - if let Some(d) = weights.get(to) { - if &w < d { - weights.insert(*to, w.clone()); - prev_nodes.insert(*to, Some(node)); - queue.push((*to, w.clone())); - } - } else { - weights.insert(*to, w.clone()); - prev_nodes.insert(*to, Some(node)); - queue.push((*to, w.clone())); - } - } - } - } - - None // No path found -} - -/// Query the longest path from a `SmallGraph`. -/// -/// ```rust -/// use solve_graph::small_graph::{graph, nodes, edges, longest_path}}; -/// -/// let nodes = nodes(vec![0, 1, 2]); -/// let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); -/// let graph = graph![nodes, edges]; -/// let path = longest_path(&graph, 0, 1).unwrap(); -/// ``` -pub(crate) fn longest_path( - _graph: &SmallGraph, - _from: usize, - _to: usize, -) -> Option> { - unimplemented!() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - graph, - small_graph::test_fixtures::{sample_nodes, sample_weighted_edges}, - }; - - #[test] - fn test_shortest_path() { - let (nodes, edges) = (sample_nodes(), sample_weighted_edges()); - let graph = graph![nodes, edges]; - let path = shortest_path(&graph, 0, 2).unwrap(); - assert_eq!(path, vec![&0, &1, &2]); - } - - // #[test] - // fn test_longest_path() { - // let (nodes, edges) = (sample_nodes(), sample_edges()); - // let graph = graph![nodes.clone(), edges.clone()]; - // let path = longest_path(&graph, 0, 1).unwrap(); - // assert_eq!(path, vec![&0, &2]); - // } -} diff --git a/crates/solver-graph/src/queue.rs b/crates/solver-graph/src/queue.rs deleted file mode 100644 index d6d484e..0000000 --- a/crates/solver-graph/src/queue.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::{ - cmp::Ord, - ops::{Deref, DerefMut}, -}; - -use crate::small_array::SmallArray; - -#[derive(Debug)] -pub(crate) struct PriorityQueue { - heap: SmallArray, -} - -impl PriorityQueue { - pub(crate) fn new() -> Self { - PriorityQueue { - heap: SmallArray::Dynamic(Vec::new()), - } - } - - pub(crate) fn with_capacity(size: usize) -> Self { - PriorityQueue { - heap: SmallArray::Dynamic(Vec::with_capacity(size)), - } - } - - pub(crate) fn push(&mut self, value: V) { - self.heap.push(value); - self.heapify_up(self.len() - 1); - } - - pub(crate) fn pop(&mut self) -> Option { - if self.is_empty() { - return None; - } - - let last_index = self.len() - 1; - self.heap.swap(0, last_index); - let popped = self.heap.pop(); - self.heapify_down(0); - popped - } - - fn heapify_up(&mut self, mut index: usize) { - while index > 0 { - let parent_index = (index - 1) / 2; - if self.heap[index] >= self.heap[parent_index] { - break; - } - self.heap.swap(index, parent_index); - index = parent_index; - } - } - - fn heapify_down(&mut self, mut index: usize) { - let len = self.heap.len(); - loop { - let left_child = 2 * index + 1; - let right_child = 2 * index + 2; - let mut smallest = index; - - if left_child < len && self.heap[left_child] < self.heap[smallest] { - smallest = left_child; - } - if right_child < len && self.heap[right_child] < self.heap[smallest] { - smallest = right_child; - } - - if smallest != index { - self.heap.swap(index, smallest); - index = smallest; - } else { - break; - } - } - } - - pub(crate) fn is_empty(&self) -> bool { - self.heap.is_empty() - } - - pub(crate) fn len(&self) -> usize { - self.heap.len() - } -} - -impl Deref for PriorityQueue { - type Target = SmallArray; - - fn deref(&self) -> &Self::Target { - &self.heap - } -} - -impl DerefMut for PriorityQueue { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.heap - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_priority_queue() { - let mut q = PriorityQueue::new(); - - q.push(0); - q.push(2); - q.push(3); - q.push(15); - - while !q.is_empty() { - _ = q.pop(); - } - - assert!(q.is_empty()) - } -} diff --git a/crates/solver-graph/src/small_array.rs b/crates/solver-graph/src/small_array.rs deleted file mode 100644 index 1502486..0000000 --- a/crates/solver-graph/src/small_array.rs +++ /dev/null @@ -1,225 +0,0 @@ -use std::ops::{Add, Deref, DerefMut}; - -pub(crate) trait Reduce { - fn reduce(&self, reducer: Reducer) -> SmallArray; -} - -/// The `Sort` trait defines implementations for sortable data structures. -pub(crate) trait Sort { - fn sorted(&mut self, sorting: Sorting) -> &mut Self; -} - -#[derive(Debug, Clone)] -/// `SmallArray` is a compact array data structure for optimizing small graph problem search times. -/// The goal is to implement constraint-based sorting for `SmallArray`s. -/// -/// ```rust -/// use solver_graph::SmallArray; -/// -/// let arr = SmallArray::Five([0; 5]).sorted(Sorting::Ascend); -/// ``` -pub(crate) enum SmallArray { - Empty, - One([V; 1]), - Two([V; 2]), - Three([V; 3]), - Four([V; 4]), - Five([V; 5]), - Six([V; 6]), - Seven([V; 7]), - Eight([V; 8]), - Nine([V; 9]), - Ten([V; 10]), - Dynamic(Vec), -} - -impl SmallArray { - pub(crate) fn push(&mut self, value: V) { - match self { - SmallArray::Empty => (), - SmallArray::Dynamic(it) => it.push(value), - _ => unimplemented!(), - } - } - - pub(crate) fn pop(&mut self) -> Option { - match self { - SmallArray::Empty => None, - SmallArray::Dynamic(it) => it.pop(), - _ => unimplemented!(), - } - } - - pub(crate) fn as_slice(&self) -> &[V] { - match self { - SmallArray::Empty => &[], - SmallArray::One(it) => it, - SmallArray::Two(it) => it, - SmallArray::Three(it) => it, - SmallArray::Four(it) => it, - SmallArray::Five(it) => it, - SmallArray::Six(it) => it, - SmallArray::Seven(it) => it, - SmallArray::Eight(it) => it, - SmallArray::Nine(it) => it, - SmallArray::Ten(it) => it, - SmallArray::Dynamic(it) => it, - } - } - - pub(crate) fn is_empty(&self) -> bool { - matches!(self, Self::Empty) || self.as_slice().is_empty() - } -} - -impl Sort for SmallArray { - fn sorted(&mut self, sorting: Sorting) -> &mut Self { - if self.is_empty() { - self - } else { - sort_small_array(self, sorting) - } - } -} - -impl> Reduce for SmallArray { - fn reduce(&self, reducer: Reducer) -> SmallArray { - match (self, reducer) { - (Self::Empty, Reducer::SumArray(Self::Empty)) => Self::Empty, - (Self::Empty, Reducer::SumArray(SmallArray::One(b))) => SmallArray::One([b[0]]), - (Self::One(a), Reducer::SumArray(Self::Empty)) => SmallArray::One([a[0]]), - (Self::One(a), Reducer::SumArray(b)) if !a.is_empty() && !b.is_empty() => { - SmallArray::One([a[0] + b[0]]) - } - _ => unimplemented!(), - } - } -} - -/// Sort a `SmallArray` with some `Sorting` variant. TODO: -fn sort_small_array( - arr: &mut SmallArray, - sorting: Sorting, -) -> &mut SmallArray { - match arr { - SmallArray::One(it) => sort(it, sorting), - SmallArray::Two(it) => sort(it, sorting), - SmallArray::Three(it) => sort(it, sorting), - SmallArray::Four(it) => sort(it, sorting), - SmallArray::Five(it) => sort(it, sorting), - SmallArray::Six(it) => sort(it, sorting), - SmallArray::Seven(it) => sort(it, sorting), - SmallArray::Eight(it) => sort(it, sorting), - SmallArray::Nine(it) => sort(it, sorting), - SmallArray::Ten(it) => sort(it, sorting), - SmallArray::Dynamic(it) => sort(it, sorting), - SmallArray::Empty => (), - } - arr -} - -fn sort(it: &mut [V], sorting: Sorting) { - match sorting { - Sorting::Ascend => it.sort(), - Sorting::Descend => it.reverse(), - _ => (), - } -} - -#[derive(Default)] -/// The `Sorting` enum provides different variants useful for describing how to sort an array. -/// TODO: Constraints(vec![Constraint]) -pub(crate) enum Sorting { - #[default] - Ascend, - Descend, - Constraint(Constraint), -} - -pub(crate) struct Constraint; -pub(crate) enum Reducer<'a, V> { - Sum, - SumArray(&'a SmallArray), - SumArrays(SmallArray<&'a SmallArray>), -} - -impl Deref for SmallArray { - type Target = [V]; - - fn deref(&self) -> &Self::Target { - self.as_slice() - } -} - -impl DerefMut for SmallArray { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - SmallArray::Empty => &mut [], - SmallArray::One(inner) => inner, - SmallArray::Two(inner) => inner, - SmallArray::Three(inner) => inner, - SmallArray::Four(inner) => inner, - SmallArray::Five(inner) => inner, - SmallArray::Six(inner) => inner, - SmallArray::Seven(inner) => inner, - SmallArray::Eight(inner) => inner, - SmallArray::Nine(inner) => inner, - SmallArray::Ten(inner) => inner, - SmallArray::Dynamic(inner) => inner.as_mut_slice(), - } - } -} - -impl<'a, V> IntoIterator for &'a SmallArray { - type Item = &'a V; - type IntoIter = std::slice::Iter<'a, V>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl PartialEq for SmallArray { - fn eq(&self, other: &Self) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl Eq for SmallArray {} - -impl PartialOrd for SmallArray { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for SmallArray { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.as_slice().cmp(other.as_slice()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_small_array() { - let arr = SmallArray::Two([0, 5]); - assert_ne!(arr, SmallArray::Two([1, 5])); - } - - #[test] - fn test_sorted() { - let mut arr = SmallArray::Five([1, 2, 3, 4, 5]); - let mut desc = arr.clone(); - assert_eq!( - arr.sorted(Sorting::Ascend), - &mut SmallArray::Five([1, 2, 3, 4, 5]) - ); - assert_eq!( - desc.sorted(Sorting::Descend), - &mut SmallArray::Five([5, 4, 3, 2, 1]) - ); - } -} diff --git a/crates/solver-graph/src/small_graph.rs b/crates/solver-graph/src/small_graph.rs deleted file mode 100644 index 6849e49..0000000 --- a/crates/solver-graph/src/small_graph.rs +++ /dev/null @@ -1,348 +0,0 @@ -use crate::{helpers, ops, small_array::SmallArray, Position, Value}; -pub use helpers::{edge, edges, nodes, weighted_edge}; -pub use ops::shortest_path; -use std::ops::Deref; - -#[macro_export] -/// Use `graph![nodes, edges]` to create a `SmallGraph`. -/// -/// ```rust -/// use solve_graph::small_graph::{graph, nodes, edges}; -/// -/// let nodes = nodes(vec![0, 1, 2]); -/// let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); -/// let graph = graph![nodes, edges]; -/// ``` -macro_rules! graph { - ($nodes:expr, $edges:expr) => {{ - $crate::small_graph::small_graph($nodes, $edges) - }}; -} - -/// `SmallGraph`s are compact data structures composed of `Nodes` and `Edges`. -/// -/// ```rust -/// use solve_graph::{graph, nodes, edges}; -/// -/// let nodes = nodes(vec![0, 1, 2]); -/// let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); -/// let graph = graph![nodes, edges]; -/// ``` -#[derive(Debug)] -pub struct SmallGraph { - nodes: Nodes, - edges: Edges, -} - -/// The `SmallGraph` is composed of `Nodes` and `Edges`. -/// -/// ```rust -/// use solver_graph::small_graph::graph; -/// -/// let graph = graph![nodes(vec![]), edges(vec![]))]; -/// ``` -pub fn small_graph(nodes: Nodes, edges: Edges) -> SmallGraph { - SmallGraph { nodes, edges } -} - -impl SmallGraph { - /// The `SmallGraph` struct composes the `Nodes` and `Edges` for efficient operations. - /// - /// ```rust - /// use solve_graph::small_graph::SmallGraph; - /// - /// let graph = SmallGraph::new(); - /// ``` - pub(crate) fn new() -> Self { - Self { - nodes: Nodes(Vec::new()), - edges: Edges(Vec::new()), - } - } - - /// Get the `Nodes` of the `SmallGraph`. - /// - /// ```rust - /// use solve_graph::small_graph::SmallGraph; - /// - /// let graph = SmallGraph::new(); - /// let nodes = graph.nodes(); - /// ``` - pub(crate) fn nodes(&self) -> &Nodes { - &self.nodes - } - - /// Get the `Edges` of the `SmallGraph`. - /// - /// ```rust - /// use solve_graph::small_graph::SmallGraph; - /// - /// let graph = SmallGraph::new(); - /// let edges = graph.edges(); - /// ``` - pub(crate) fn edges(&self) -> &Edges { - &self.edges - } -} - -/// The `SmallGraph` struct composes the `Nodes` and `Edges` for efficient operations. -/// -/// ```rust -/// use solver_graph::small_graph::SmallGraph; -/// -/// let graph = SmallGraph::default(); -/// ``` -impl Default for SmallGraph { - fn default() -> Self { - Self::new() - } -} - -#[derive(Clone, Debug)] -pub struct Nodes(pub(crate) Vec); - -impl Nodes { - /// Get an indexed `Node`. - /// - /// ```rust - /// use solver_graph::small_graph::nodes; - /// - /// let nodes = nodes(vec![0, 1, 2]); - /// let first = nodes.get(0).unwrap() - /// ``` - pub(crate) fn get(&self, index: usize) -> Option<&V> { - self.0.get(index) - } - - /// Get the first `Node`. - /// - /// ```rust - /// use solver_graph::small_graph::nodes; - /// - /// let nodes = nodes(vec![0, 1, 2]); - /// let first = nodes.first().unwrap() - /// ``` - pub(crate) fn first(&self) -> Option<&V> { - self.0.first() - } - - /// Get the last `Node`. - /// - /// ```rust - /// use solver_graph::small_graph::nodes; - /// - /// let nodes = nodes(vec![0, 1, 2]); - /// let last = nodes.last().unwrap() - /// ``` - pub(crate) fn last(&self) -> Option<&V> { - self.0.last() - } - - /// Get the length of the `Nodes`. - /// - /// ```rust - /// use solver_graph::small_graph::nodes; - /// - /// let nodes = nodes(vec![0, 1, 2]); - /// let last = nodes.last().unwrap() - /// ``` - pub(crate) fn len(&self) -> usize { - self.0.len() - } -} - -#[derive(Default, Clone, Debug)] -pub struct Edges(pub(crate) Vec>>); - -#[derive(Default, Clone, Debug)] -pub struct Edge { - pub(crate) from: P, - pub(crate) to: P, - pub(crate) weights: Option>, -} - -impl Edge { - pub(crate) fn weights(&self) -> Option<&SmallArray> { - self.weights.as_ref() - } -} - -impl PartialEq for Edge { - fn eq(&self, other: &Self) -> bool { - // TODO(cnpryer): Better weight handling - let eq = self.from == other.from && self.to == other.to; - match (&self.weights, &other.weights) { - (Some(a), Some(b)) if eq => a == b, - (None, None) if eq => true, - _ => false, - } - } -} - -impl Eq for Edge {} - -impl PartialOrd for Edge { - fn partial_cmp(&self, other: &Self) -> Option { - match self.from.partial_cmp(&other.from) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - match self.to.partial_cmp(&other.to) { - Some(core::cmp::Ordering::Equal) => {} - ord => return ord, - } - self.weights.partial_cmp(&other.weights) - } -} - -impl Ord for Edge { - fn cmp(&self, _other: &Self) -> std::cmp::Ordering { - unimplemented!() - } -} - -impl Edges { - /// Get an indexed `Edge`. - /// - /// ```rust - /// use solver_graph::small_graph::{edges, edge}; - /// - /// let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); - /// let first = edges.get(0).unwrap() - /// ``` - pub(crate) fn get(&self, index: P) -> Option<&SmallArray>> { - self.0.get(index.into()) - } - - /// Get the first `Edge`. - /// - /// ```rust - /// use solver_graph::small_graph::{edges, edge}; - /// - /// let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); - /// let first = edges.first().unwrap() - /// ``` - pub(crate) fn first(&self) -> Option<&SmallArray>> { - self.0.get(0) - } - - /// Get the last `Edge`. - /// - /// ```rust - /// use solver_graph::small_graph::{edges, edge}; - /// - /// let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); - /// let last = edges.last().unwrap() - pub(crate) fn last(&self) -> Option<&SmallArray>> { - self.0.get(self.len() - 1) - } - - /// Get the number of edges. - /// - /// ```rust - /// use solver_graph::small_graph::nodes; - /// - /// let edges = edges(vec![Some(vec![edge(0, 1), edge(0, 2)]), Some(vec![edge(1, 2)]), None]); - /// let count = edges.len() - /// ``` - pub(crate) fn len(&self) -> usize { - self.0.len() - } -} - -impl PartialEq for Edges { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for Edges {} - -fn compare_to_static( - a: &Vec>, - b: &SmallArray>, -) -> bool { - a.as_slice() == b.deref() -} - -fn compare_static( - a: &SmallArray>, - b: &SmallArray>, -) -> bool { - a.deref() == b.deref() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - helpers::{edge, nodes}, - small_graph::test_fixtures::{sample_edges, sample_nodes, sample_weighted_edges}, - }; - - #[test] - fn test_graph() { - let (nodes, edges) = (sample_nodes(), sample_edges()); - let graph = small_graph(nodes.clone(), edges.clone()); - assert_eq!(nodes.first(), graph.nodes().first()); - assert_eq!(edges.first(), graph.edges().first()); - } - - #[test] - fn test_nodes() { - let nodes = nodes(vec![1]); - assert_eq!(nodes.len(), 1); - assert_eq!(nodes.first(), Some(&1)); - assert_eq!(nodes.last(), Some(&1)); - } - - #[test] - fn test_edges() { - let edges = sample_edges(); - assert_eq!(edges.len(), 4); - assert_eq!( - edges.first(), - Some(&SmallArray::Two([edge(0, 1), edge(0, 2)])) - ); - assert_eq!(edges.last(), Some(&SmallArray::Empty)); - } - - #[test] - fn test_weighted_edges() { - let (nodes, edges) = (sample_nodes(), sample_weighted_edges()); - let graph = small_graph(nodes.clone(), edges.clone()); - assert_eq!(nodes.last(), graph.nodes().last()); - assert_eq!(edges.last(), graph.edges().last()); - } -} - -#[cfg(test)] -pub(crate) mod test_fixtures { - use crate::{ - helpers::{edge, edges, nodes, weighted_edge}, - small_graph::{Edges, Nodes}, - }; - - pub(crate) fn sample_nodes() -> Nodes { - println!("test"); - nodes(vec![0, 1, 2, 3]) - } - - pub(crate) fn sample_edges() -> Edges { - edges(vec![ - vec![edge(0, 1), edge(0, 2)], - vec![edge(1, 2)], - vec![edge(1, 0)], - vec![], - ]) - } - - pub(crate) fn sample_weighted_edges() -> Edges { - edges(vec![ - vec![weighted_edge(0, 1, vec![1]), weighted_edge(0, 2, vec![100])], - vec![weighted_edge(1, 2, vec![1])], - vec![weighted_edge(2, 0, vec![2])], - vec![], - ]) - } -} diff --git a/crates/solver-template/Cargo.toml b/crates/solver-template/Cargo.toml deleted file mode 100644 index c086b88..0000000 --- a/crates/solver-template/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "solver-template" -version = "0.1.0" -edition = "2021" - -[dependencies] -solver-graph = { path = "../solver-graph" } diff --git a/crates/solver-template/src/graph.rs b/crates/solver-template/src/graph.rs deleted file mode 100644 index d069af1..0000000 --- a/crates/solver-template/src/graph.rs +++ /dev/null @@ -1,21 +0,0 @@ -// TODO(cnpryer): TSPSolver with expressive API for designing and executing instructions -use solver_graph::{ - graph, - small_graph::{edges, nodes, shortest_path, weighted_edge, Edges}, -}; - -pub fn shortest_path_example() { - println!("searching for shortest path..."); - let nodes = nodes(vec![0, 1, 2, 3]); - // TODO(cnpryer): Use Indexable or some trait to implement hashing for From - let edges: Edges = edges(vec![ - vec![weighted_edge(0, 1, vec![1]), weighted_edge(0, 2, vec![100])], - vec![weighted_edge(1, 2, vec![1])], - vec![weighted_edge(2, 0, vec![2])], - vec![], - ]); - let graph = graph![nodes, edges]; - println!("graph: {:?}", &graph); - let shortest_path = shortest_path(&graph, 0, 2); - println!("solution: {:?}", shortest_path); -} diff --git a/crates/solver-template/src/main.rs b/crates/solver-template/src/main.rs deleted file mode 100644 index eeb367c..0000000 --- a/crates/solver-template/src/main.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod graph; - -fn main() { - graph::shortest_path_example(); -} diff --git a/crates/solver-vrp/Cargo.toml b/crates/solver-vrp/Cargo.toml new file mode 100644 index 0000000..003e25d --- /dev/null +++ b/crates/solver-vrp/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "solver-vrp" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/crates/solver-vrp/src/lib.rs b/crates/solver-vrp/src/lib.rs new file mode 100644 index 0000000..6067fe5 --- /dev/null +++ b/crates/solver-vrp/src/lib.rs @@ -0,0 +1,2 @@ +mod model; +mod solver; diff --git a/crates/solver-vrp/src/model.rs b/crates/solver-vrp/src/model.rs new file mode 100644 index 0000000..6ba5b4c --- /dev/null +++ b/crates/solver-vrp/src/model.rs @@ -0,0 +1,123 @@ +pub struct Model<'model> { + stops: Stops<'model>, + vehicles: Vehicles<'model>, +} + +impl Model<'_> { + pub fn new() -> Self { + Self { + stops: Stops::new(), + vehicles: Vehicles::new(), + } + } + + fn stops(&self) -> Stops { + Stops(self.stops.0.clone()) + } + + fn vehicles(&self) -> Vehicles { + Vehicles(self.vehicles.0.clone()) + } +} + +#[derive(Clone)] +struct Stops<'model>(Vec>); + +impl Stops<'_> { + fn new() -> Self { + Self(Vec::new()) + } +} + +#[derive(Clone)] +struct Stop<'model> { + id: String, + index: usize, + location: Location, + model: &'model Model<'model>, +} + +impl Stop<'_> { + fn id(&self) -> &str { + &self.id + } + + fn index(&self) -> usize { + self.index + } + + fn location(&self) -> Location { + self.location + } + + fn model(&self) -> &Model { + self.model + } +} + +#[derive(Clone, Copy)] +struct Location { + latitude: f64, + longitude: f64, +} + +#[derive(Clone)] +struct Vehicles<'model>(Vec>); + +impl Vehicles<'_> { + fn new() -> Self { + Self(Vec::new()) + } +} + +#[derive(Clone)] +struct Vehicle<'model> { + id: String, + index: usize, + vehicle_type: VehicleType<'model>, + stops: Stops<'model>, +} + +impl Vehicle<'_> { + fn id(&self) -> &str { + &self.id + } + + fn index(&self) -> usize { + self.index + } + + fn vehicle_type(&self) -> VehicleType { + self.vehicle_type.clone() + } + + fn stops(&self) -> Stops<'_> { + Stops(self.stops.0.clone()) + } +} + +#[derive(Clone)] +struct VehicleType<'model> { + id: String, + index: usize, + model: &'model Model<'model>, + vehicles: &'model Vehicles<'model>, +} + +impl VehicleType<'_> { + fn id(&self) -> &str { + &self.id + } + + fn index(&self) -> usize { + self.index + } + + fn model(&self) -> &Model { + self.model + } + + fn vehicles(&self) -> &Vehicles { + self.vehicles + } +} diff --git a/crates/solver-vrp/src/solver.rs b/crates/solver-vrp/src/solver.rs new file mode 100644 index 0000000..e0ee281 --- /dev/null +++ b/crates/solver-vrp/src/solver.rs @@ -0,0 +1,13 @@ +use crate::model::Model; + +struct Solver<'model> { + model: Model<'model>, +} + +impl Solver<'_> { + fn new() -> Self { + Self { + model: Model::new(), + } + } +}