├── .github ├── dependabot.yml └── workflows │ ├── clippy.yml │ ├── fmt.yml │ ├── release-plz.yml │ ├── rust.yml │ └── windows.yaml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── bench_main.rs └── benchmarks │ ├── fenwick.rs │ ├── great_circle.rs │ ├── k_way_merge_iterator.rs │ ├── loser_tree.rs │ ├── medium_size_hash_table.rs │ ├── mercator.rs │ ├── mod.rs │ ├── polyline.rs │ └── radix_sort.rs ├── examples ├── instantiate.rs └── radix_example.rs └── src ├── addressable_binary_heap.rs ├── as_bytes.rs ├── bfs.rs ├── bin_pack.rs ├── bit_weight_iterator.rs ├── bitset_subset_iterator.rs ├── bloom_filter.rs ├── bounding_box.rs ├── cell.rs ├── chipper └── bin │ ├── command_line.rs │ ├── main.rs │ └── serialize.rs ├── complete_graph.rs ├── convex_hull.rs ├── count_min_sketch.rs ├── cycle_check.rs ├── ddsg.rs ├── dfs.rs ├── dimacs.rs ├── dinic.rs ├── dynamic_graph.rs ├── edge.rs ├── edmonds_karp.rs ├── enumerative_source_coding.rs ├── fast_hash_trait.rs ├── fenwick.rs ├── fibonacci_hash.rs ├── ford_fulkerson.rs ├── geometry.rs ├── graph.rs ├── graph_plier └── bin │ ├── command_line.rs │ └── main.rs ├── great_circle.rs ├── huffman_code.rs ├── inertial_flow.rs ├── io.rs ├── k_way_merge_iterator.rs ├── kruskal.rs ├── level_directory.rs ├── lib.rs ├── linked_list.rs ├── loser_tree.rs ├── lru.rs ├── math.rs ├── max_flow.rs ├── medium_size_hash_set.rs ├── medium_size_hash_table.rs ├── mercator.rs ├── merge_entry.rs ├── merge_tree.rs ├── metis.rs ├── one_iterator.rs ├── one_to_many_dijkstra.rs ├── partition_id.rs ├── path_based_scc.rs ├── polyline.rs ├── prim_complete_graph.rs ├── r_tree.rs ├── rdx_sort.rs ├── renumbering_table.rs ├── run_iterator.rs ├── scaffold └── bin │ ├── command_line.rs │ ├── main.rs │ └── serialize.rs ├── single_linked_list.rs ├── solver └── bin │ └── main.rs ├── space_filling_curve.rs ├── static_graph.rs ├── tabulation_hash.rs ├── tarjan.rs ├── tiny_table.rs ├── top_k.rs ├── tsplib.rs ├── unidirectional_dijkstra.rs ├── union_find.rs ├── unsafe_slice.rs ├── vector_tile.rs └── wgs84.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "01:00" 8 | # Enable version updates for Actions 9 | - package-ecosystem: "github-actions" 10 | # Look for `.github/workflows` in the `root` directory 11 | directory: "/" 12 | # Check for updates once a week 13 | schedule: 14 | interval: "weekly" 15 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: "cargo clippy" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | clippy_check: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v5 17 | - run: rustup component add clippy 18 | - uses: actions-rs/clippy-check@v1 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | args: --all-features 22 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: "cargo fmt" 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | formatting: 13 | name: cargo fmt 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v5 17 | - uses: actions-rust-lang/setup-rust-toolchain@v1 18 | with: 19 | components: rustfmt 20 | - name: Rustfmt Check 21 | uses: actions-rust-lang/rustfmt@v1 -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | 14 | # Release unpublished packages. 15 | release-plz-release: 16 | name: Release-plz release 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v5 23 | with: 24 | fetch-depth: 0 25 | - name: Install Rust toolchain 26 | uses: dtolnay/rust-toolchain@stable 27 | - name: Run release-plz 28 | uses: release-plz/action@v0.5 29 | with: 30 | command: release 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 34 | 35 | # Create a PR with the new versions and changelog, preparing the next release. 36 | release-plz-pr: 37 | name: Release-plz PR 38 | runs-on: ubuntu-latest 39 | permissions: 40 | contents: write 41 | pull-requests: write 42 | concurrency: 43 | group: release-plz-${{ github.ref }} 44 | cancel-in-progress: false 45 | steps: 46 | - name: Checkout repository 47 | uses: actions/checkout@v5 48 | with: 49 | fetch-depth: 0 50 | - name: Install Rust toolchain 51 | uses: dtolnay/rust-toolchain@stable 52 | - name: Run release-plz 53 | uses: release-plz/action@v0.5 54 | with: 55 | command: release-pr 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build_and_test: 14 | name: toolbox - latest 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, macos-latest] 19 | toolchain: 20 | - stable 21 | - nightly 22 | steps: 23 | - uses: actions/checkout@v5 24 | - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} 25 | - run: cargo build --verbose 26 | - run: cargo test --verbose 27 | -------------------------------------------------------------------------------- /.github/workflows/windows.yaml: -------------------------------------------------------------------------------- 1 | name: windows 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | jobs: 9 | test: 10 | runs-on: windows-latest 11 | name: (${{ matrix.target }}, ${{ matrix.cfg_release_channel }}) 12 | env: 13 | CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }} 14 | strategy: 15 | max-parallel: 2 16 | fail-fast: false 17 | matrix: 18 | target: [ 19 | x86_64-pc-windows-gnu, 20 | x86_64-pc-windows-msvc, 21 | ] 22 | cfg_release_channel: [nightly, stable] 23 | 24 | steps: 25 | # The Windows runners have autocrlf enabled by default 26 | # which causes failures for some of rustfmt's line-ending sensitive tests 27 | - name: disable git eol translation 28 | run: git config --global core.autocrlf false 29 | - name: checkout 30 | uses: actions/checkout@v5 31 | 32 | # Run build 33 | - name: Install Rustup using win.rustup.rs 34 | run: | 35 | # Disable the download progress bar which can cause perf issues 36 | $ProgressPreference = "SilentlyContinue" 37 | Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe 38 | .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --default-toolchain=none 39 | del rustup-init.exe 40 | rustup target add ${{ matrix.target }} 41 | shell: powershell 42 | 43 | - name: Build and Test 44 | shell: cmd 45 | run: cargo test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "A toolbox of basic data structures and algorithms" 3 | edition = "2024" 4 | homepage = "https://github.com/DennisOSRM/toolbox-rs" 5 | license = "MIT" 6 | name = "toolbox-rs" 7 | readme = "README.md" 8 | repository = "https://github.com/DennisOSRM/toolbox-rs" 9 | version = "0.6.0" 10 | 11 | [[example]] 12 | name = "instantiate" 13 | 14 | [[bin]] 15 | name = "chipper" 16 | path = "src/chipper/bin/main.rs" 17 | 18 | [[bin]] 19 | name = "scaffold" 20 | path = "src/scaffold/bin/main.rs" 21 | 22 | [[bin]] 23 | name = "graph_plier" 24 | path = "src/graph_plier/bin/main.rs" 25 | 26 | [[bin]] 27 | name = "solver" 28 | path = "src/solver/bin/main.rs" 29 | 30 | [dependencies] 31 | bincode = "2.0.0" 32 | bitvec = "1.0.1" 33 | clap = { version = "4.5.38", features = ["derive"] } 34 | env_logger = "0.11.8" 35 | fxhash = "0.2.1" 36 | geojson = "0.24.1" 37 | indicatif = "0.18.0" 38 | itertools = "0.14.0" 39 | log = "0.4.27" 40 | num = "0.4.3" 41 | rand = "0.9.1" 42 | rayon = "1.10.0" 43 | tempfile = "3.20.0" 44 | thiserror = "2.0.12" 45 | xxhash-rust = {version = "0.8.15", features = ["xxh3"] } 46 | 47 | [target.'cfg(unix)'.dependencies] 48 | jemallocator = "0.5.4" 49 | 50 | [dev-dependencies] 51 | criterion = "0.7.0" 52 | 53 | [profile.release] 54 | debug = true 55 | lto = true 56 | 57 | [[bench]] 58 | name = "bench_main" 59 | harness = false 60 | 61 | [workspace.metadata.clippy] 62 | # Enable additional clippy lints 63 | extra_lints = ["clippy::pedantic", "clippy::nursery"] 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dennis Luxen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub last commit](https://img.shields.io/github/last-commit/DennisOSRM/toolbox-rs.svg) 2 | ![Crates.io](https://img.shields.io/crates/v/toolbox-rs.svg) 3 | 4 | ![Cells](https://user-images.githubusercontent.com/1067895/169662031-a2a516df-296e-42de-8095-d2a5ff5da3c6.JPG) 5 | 6 | # Toolbox-rs 7 | A toolbox of basic data structures and algorithms. If you heard of OSRM, please draw your own conclusions. 😁 8 | 9 | ## Graph Plier 10 | A tool to normalize graphs from various input formats into a normalized intermediate representation that is easily understood by the tool set. 11 | 12 | ## Chipper 13 | A tool to bisect graphs in the DIMACS, (unweighted) METIS or DDSG format using an implementation of the Inertial Flow method. Example graphs can be downloaded on the website of the [9th DIMACS implemenation challenge](http://www.diag.uniroma1.it//challenge9/download.shtml). Chipper reproduces the runtime and quality numbers reported by [Schild and Sommer (2015)](http://sommer.jp/roadseparator.pdf). Currently, a balance factor of 0.25 is the default, and can be overridden via the command line. 14 | 15 | ## Scaffold 16 | A tool to generate run-time data structures from pre-process graph. At this point it supports visualizing cells by their convex hulls. The result of this is stored in GeoJSON format which can be easily visualized, e.g. on [Kepler.gl](https://kepler.gl/demo). 17 | 18 | ``` 19 | $ cargo r --release --bin scaffold -- -p /path/to/USA-r20-m100.assignment.bin -c /path/to/USA-road-d.USA.co --convex-cells-geojson /path/to/bbox.geojson 20 | ``` 21 | 22 | ## Complete usage work flow via cargo: 23 | The complete work flow is as follows. First, the input data is converted into a normalized format, then the tools are run for processing. 24 | 25 | Convert file to intermediate format: 26 | ``` 27 | $ cargo r --release --bin graph_plier -- -i dimacs -g /path/to/USA-road-d.USA.gr -c /path/to/USA-road-d.USA.co 28 | ``` 29 | 30 | Partition the graph recursively up to 30 times or until a cell has less than a hundred nodes: 31 | ``` 32 | $ cargo r --release --bin chipper -- -g /path/to/USA-road-d.USA.gr.toolbox -c /path/to/USA-road-d.USA.co.toolbox -o /path/to/result.txt -r30 -m100 -p /path/to/USA-r30-m100.assignment.bin 33 | ``` 34 | 35 | Generate GeoJSON file visualizing the cells: 36 | ``` 37 | $ cargo r --release --bin scaffold -- -c /path/to/USA-road-d.USA.co.toolbox -g /path/to/USA-road-d.USA.gr.toolbox -p /path/to/USA-r30-m100.assignment.bin --convex-cells-geojson /path/to/bbox.geojson 38 | ``` 39 | 40 | 41 | ## Convex Hull Visualization 42 | ![Convex Hulls USA](https://user-images.githubusercontent.com/1067895/175577261-55e38f44-07ae-4ab2-b344-23d15f5d5c89.png) 43 | ![Convex Hulls EUR](https://user-images.githubusercontent.com/1067895/184511222-3992c158-ba12-4f83-b8f3-64845e95a8bf.png) 44 | -------------------------------------------------------------------------------- /benches/bench_main.rs: -------------------------------------------------------------------------------- 1 | use criterion::criterion_main; 2 | 3 | mod benchmarks; 4 | 5 | criterion_main!( 6 | benchmarks::fenwick::all_fenwick, 7 | benchmarks::radix_sort::all_sorts, 8 | benchmarks::great_circle::distances, 9 | benchmarks::medium_size_hash_table::tables, 10 | benchmarks::k_way_merge_iterator::k_way_merge, 11 | benchmarks::loser_tree::loser_tree, 12 | benchmarks::polyline::polyline, 13 | benchmarks::mercator::mercator_benches, 14 | ); 15 | -------------------------------------------------------------------------------- /benches/benchmarks/fenwick.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BatchSize, BenchmarkId, Criterion, SamplingMode, Throughput, criterion_group}; 2 | use rand::{Rng, distr::StandardUniform}; 3 | use toolbox_rs::fenwick::Fenwick; 4 | 5 | fn create_scrambled_data(length: usize) -> Vec { 6 | let rng = rand::rng(); 7 | rng.sample_iter(StandardUniform).take(length).collect() 8 | } 9 | 10 | fn bench_range(c: &mut Criterion) { 11 | let mut group = c.benchmark_group("Fenwick range queries, brute force"); 12 | group.sampling_mode(SamplingMode::Flat); 13 | for input_length in [1000, 5_000, 10_000] { 14 | group.throughput(Throughput::Elements(input_length as u64)); 15 | let fenwick = Fenwick::from_values(&create_scrambled_data(input_length)); 16 | group.bench_function(BenchmarkId::new("Fenwick::range()", input_length), |b| { 17 | b.iter_batched( 18 | || fenwick.clone(), 19 | |fenwick| { 20 | for i in 0..input_length { 21 | for j in i..input_length { 22 | fenwick.range(i, j); 23 | } 24 | } 25 | }, 26 | BatchSize::LargeInput, 27 | ) 28 | }); 29 | group.bench_function(BenchmarkId::new("Fenwick::slow_range", input_length), |b| { 30 | b.iter_batched( 31 | || fenwick.clone(), 32 | |fenwick| { 33 | for i in 0..input_length { 34 | for j in i..input_length { 35 | fenwick.slow_range(i, j); 36 | } 37 | } 38 | }, 39 | BatchSize::LargeInput, 40 | ) 41 | }); 42 | } 43 | } 44 | 45 | criterion_group!(all_fenwick, bench_range); 46 | -------------------------------------------------------------------------------- /benches/benchmarks/great_circle.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Criterion, black_box, criterion_group}; 2 | use toolbox_rs::great_circle::*; 3 | 4 | pub fn haversine_benchmark(c: &mut Criterion) { 5 | c.bench_function("haversine", |b| { 6 | b.iter(|| { 7 | haversine( 8 | black_box(50.066389), 9 | black_box(-5.714722), 10 | black_box(58.643889), 11 | black_box(-3.070000), 12 | ) 13 | }) 14 | }); 15 | } 16 | 17 | pub fn vincenty_benchmark(c: &mut Criterion) { 18 | c.bench_function("vincenty", |b| { 19 | b.iter(|| { 20 | vincenty( 21 | black_box(50.066389), 22 | black_box(-5.714722), 23 | black_box(58.643889), 24 | black_box(-3.070000), 25 | ) 26 | }) 27 | }); 28 | } 29 | 30 | criterion_group!(distances, haversine_benchmark, vincenty_benchmark); 31 | -------------------------------------------------------------------------------- /benches/benchmarks/k_way_merge_iterator.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use rand::{Rng, seq::SliceRandom}; 3 | use toolbox_rs::{k_way_merge_iterator::KWayMergeIterator, loser_tree::LoserTree}; 4 | 5 | /// Create a list of random runs of numbers. 6 | /// 7 | /// # Panics 8 | /// Panics if k == 0 or s == 0 9 | fn create_random_runs(s: usize, k: usize) -> Vec> { 10 | assert!(k > 0, "k must be greater than 0"); 11 | assert!(s > 0, "s must be greater than 0"); 12 | 13 | let mut rng = rand::rng(); 14 | let mut numbers: Vec = (0..s as i32).collect(); 15 | numbers.shuffle(&mut rng); 16 | 17 | let mut runs = Vec::with_capacity(k); 18 | let mut start = 0; 19 | (0..k).for_each(|_| { 20 | let end = start + rng.random_range(1..=(s - start) / (k - runs.len())); 21 | let mut run: Vec = numbers[start..end].to_vec(); 22 | run.sort(); 23 | runs.push(run.into_iter()); 24 | start = end; 25 | }); 26 | 27 | runs 28 | } 29 | 30 | fn k_way_merge_heap(c: &mut Criterion) { 31 | let mut group = c.benchmark_group("k_way_merge w/ BinaryHeap"); 32 | 33 | for k in [10, 100, 1000] { 34 | group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { 35 | b.iter_with_setup( 36 | || create_random_runs(1_000_000, k), 37 | |mut list| { 38 | let heap = std::collections::BinaryHeap::new(); 39 | let k_way_merge = KWayMergeIterator::new(black_box(&mut list), heap); 40 | black_box(k_way_merge.collect::>()) 41 | }, 42 | ) 43 | }); 44 | } 45 | 46 | group.finish(); 47 | } 48 | 49 | fn k_way_merge_loser_tree(c: &mut Criterion) { 50 | let mut group = c.benchmark_group("k_way_merge w/ LoserTree"); 51 | 52 | for k in [10, 100, 1000] { 53 | group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { 54 | b.iter_with_setup( 55 | || create_random_runs(1_000_000, k), 56 | |mut list| { 57 | let heap = LoserTree::with_capacity(k); 58 | let k_way_merge = KWayMergeIterator::new(black_box(&mut list), heap); 59 | black_box(k_way_merge.collect::>()) 60 | }, 61 | ) 62 | }); 63 | } 64 | 65 | group.finish(); 66 | } 67 | criterion_group!(k_way_merge, k_way_merge_heap, k_way_merge_loser_tree,); 68 | -------------------------------------------------------------------------------- /benches/benchmarks/loser_tree.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use rand::{Rng, rng}; 3 | use toolbox_rs::{loser_tree::LoserTree, merge_entry::MergeEntry, merge_tree::MergeTree}; 4 | 5 | /// Creates k sorted sequences of random numbers for benchmarking 6 | fn create_benchmark_data(k: usize, sequence_length: usize) -> Vec> { 7 | let mut rng = rng(); 8 | let mut sequences = Vec::with_capacity(k); 9 | 10 | for _ in 0..k { 11 | let mut sequence: Vec = (0..sequence_length) 12 | .map(|_| rng.random_range(-1000..1000)) 13 | .collect(); 14 | sequence.sort_unstable(); 15 | sequences.push(sequence); 16 | } 17 | 18 | sequences 19 | } 20 | 21 | fn loser_tree_benchmark(c: &mut Criterion) { 22 | let mut group = c.benchmark_group("loser_tree"); 23 | 24 | // Test different numbers of sequences 25 | for k in [4, 8, 16, 32, 64, 128, 512, 1024] { 26 | group.bench_with_input(BenchmarkId::new("merge", k), &k, |b, &k| { 27 | b.iter_with_setup( 28 | || { 29 | // Create k sorted sequences of 1000 elements each 30 | let sequences = create_benchmark_data(k, 1000); 31 | let mut tree = LoserTree::with_capacity(k); 32 | 33 | // Initialize with first element from each sequence 34 | for (idx, seq) in sequences.iter().enumerate() { 35 | if let Some(&first) = seq.first() { 36 | tree.push(MergeEntry { 37 | item: first, 38 | index: idx, 39 | }); 40 | } 41 | } 42 | 43 | (tree, sequences) 44 | }, 45 | |(mut tree, sequences)| { 46 | let mut sequence_positions = vec![1; k]; 47 | let mut result = Vec::with_capacity(k * 1000); 48 | 49 | while let Some(entry) = black_box(tree.pop()) { 50 | result.push(entry.item); 51 | let seq_idx = entry.index; 52 | 53 | // Push next element from the same sequence 54 | if sequence_positions[seq_idx] < sequences[seq_idx].len() { 55 | tree.push(MergeEntry { 56 | item: sequences[seq_idx][sequence_positions[seq_idx]], 57 | index: seq_idx, 58 | }); 59 | sequence_positions[seq_idx] += 1; 60 | } 61 | } 62 | black_box(result) 63 | }, 64 | ) 65 | }); 66 | } 67 | 68 | group.finish(); 69 | } 70 | 71 | criterion_group!(loser_tree, loser_tree_benchmark); 72 | -------------------------------------------------------------------------------- /benches/benchmarks/medium_size_hash_table.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Criterion, black_box, criterion_group}; 2 | use std::collections::{BTreeMap, HashMap}; 3 | use toolbox_rs::{ 4 | fibonacci_hash::FibonacciHash, medium_size_hash_table::MediumSizeHashTable, 5 | tabulation_hash::TabulationHash, tiny_table::TinyTable, 6 | }; 7 | 8 | fn insert_sequential(c: &mut Criterion) { 9 | let mut group = c.benchmark_group("Insert Sequential"); 10 | 11 | // Prepare data structures 12 | let mut table = MediumSizeHashTable::::new(); 13 | let mut fibonacci = MediumSizeHashTable::::new(); 14 | let mut bmap = BTreeMap::new(); 15 | let mut tiny = TinyTable::new(); 16 | let mut hashmap = HashMap::new(); 17 | 18 | group.bench_function("TabulationHashTable", |b| { 19 | b.iter(|| { 20 | for i in 0..1000 { 21 | table.insert(black_box(i), i); 22 | } 23 | }) 24 | }); 25 | 26 | group.bench_function("FibonacciHashTable", |b| { 27 | b.iter(|| { 28 | for i in 0..1000 { 29 | fibonacci.insert(black_box(i), i); 30 | } 31 | }) 32 | }); 33 | 34 | group.bench_function("BTreeMap", |b| { 35 | b.iter(|| { 36 | for i in 0..1000 { 37 | bmap.insert(black_box(i), i); 38 | } 39 | }) 40 | }); 41 | 42 | group.bench_function("TinyTable", |b| { 43 | b.iter(|| { 44 | for i in 0..1000 { 45 | tiny.insert(black_box(i), i); 46 | } 47 | }) 48 | }); 49 | 50 | group.bench_function("Hashmap", |b| { 51 | b.iter(|| { 52 | for i in 0..1000 { 53 | hashmap.insert(black_box(i), i); 54 | } 55 | }) 56 | }); 57 | 58 | group.finish(); 59 | } 60 | 61 | fn lookup_random(c: &mut Criterion) { 62 | let mut group = c.benchmark_group("Lookup Random"); 63 | 64 | // Prepare data structures 65 | let mut table = MediumSizeHashTable::::new(); 66 | let mut fibonacci = MediumSizeHashTable::::new(); 67 | let mut bmap = BTreeMap::new(); 68 | let mut tiny = TinyTable::new(); 69 | let mut hashmap = HashMap::new(); 70 | 71 | for i in 0..1000 { 72 | fibonacci.insert(i, i); 73 | table.insert(i, i); 74 | bmap.insert(i, i); 75 | tiny.insert(i, i); 76 | hashmap.insert(i, i); 77 | } 78 | 79 | group.bench_function("TabulationHashTable", |b| { 80 | b.iter(|| { 81 | for i in (0..1000).rev() { 82 | black_box(table.peek_value(black_box(i))); 83 | } 84 | }) 85 | }); 86 | 87 | group.bench_function("FibonacciHashTable", |b| { 88 | b.iter(|| { 89 | for i in (0..1000).rev() { 90 | black_box(fibonacci.peek_value(black_box(i))); 91 | } 92 | }) 93 | }); 94 | 95 | group.bench_function("BTreeMap", |b| { 96 | b.iter(|| { 97 | for i in (0..1000).rev() { 98 | black_box(bmap.get(&black_box(i))); 99 | } 100 | }) 101 | }); 102 | 103 | group.bench_function("TinyTable", |b| { 104 | b.iter(|| { 105 | for i in (0..1000).rev() { 106 | black_box(tiny.find(black_box(&i))); 107 | } 108 | }) 109 | }); 110 | 111 | group.bench_function("Hashmap", |b| { 112 | b.iter(|| { 113 | for i in (0..1000).rev() { 114 | black_box(hashmap.get(black_box(&i))); 115 | } 116 | }) 117 | }); 118 | 119 | group.finish(); 120 | } 121 | 122 | criterion_group!(tables, insert_sequential, lookup_random); 123 | -------------------------------------------------------------------------------- /benches/benchmarks/mercator.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, black_box, criterion_group}; 2 | use rand::Rng; 3 | use toolbox_rs::{ 4 | mercator::{lat_to_y, lat_to_y_approx, lon_to_x}, 5 | wgs84::{FloatLatitude, FloatLongitude}, 6 | }; 7 | 8 | pub fn lat_to_y_benchmark(c: &mut Criterion) { 9 | let mut group = c.benchmark_group("lat_to_y_sizes"); 10 | let sizes = [10, 100, 1000, 10000]; 11 | let mut rng = rand::rng(); 12 | 13 | for size in sizes { 14 | let latitudes: Vec = (0..size) 15 | .map(|_| FloatLatitude(rng.random_range(-70.0..70.0))) 16 | .collect(); 17 | 18 | group.bench_with_input(BenchmarkId::new("exact", size), &latitudes, |b, lats| { 19 | b.iter(|| { 20 | for &lat in lats.iter() { 21 | black_box(lat_to_y(lat)); 22 | } 23 | }) 24 | }); 25 | 26 | group.bench_with_input(BenchmarkId::new("approx", size), &latitudes, |b, lats| { 27 | b.iter(|| { 28 | for &lat in lats.iter() { 29 | black_box(lat_to_y_approx(lat)); 30 | } 31 | }) 32 | }); 33 | } 34 | group.finish(); 35 | } 36 | 37 | pub fn lon_to_x_benchmark(c: &mut Criterion) { 38 | let mut group = c.benchmark_group("lon_to_x_sizes"); 39 | let sizes = [10, 100, 1000, 10000]; 40 | let mut rng = rand::rng(); 41 | 42 | for size in sizes { 43 | let longitudes: Vec = (0..size) 44 | .map(|_| FloatLongitude(rng.random_range(-70.0..70.0))) 45 | .collect(); 46 | 47 | group.bench_with_input(BenchmarkId::new("exact", size), &longitudes, |b, lons| { 48 | b.iter(|| { 49 | for &lon in lons.iter() { 50 | black_box(lon_to_x(lon)); 51 | } 52 | }) 53 | }); 54 | } 55 | group.finish(); 56 | } 57 | criterion_group!(mercator_benches, lat_to_y_benchmark, lon_to_x_benchmark); 58 | -------------------------------------------------------------------------------- /benches/benchmarks/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod fenwick; 2 | pub mod great_circle; 3 | pub mod k_way_merge_iterator; 4 | pub mod loser_tree; 5 | pub mod medium_size_hash_table; 6 | pub mod mercator; 7 | pub mod polyline; 8 | pub mod radix_sort; 9 | -------------------------------------------------------------------------------- /benches/benchmarks/polyline.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BenchmarkId, Criterion, criterion_group}; 2 | use toolbox_rs::polyline::{decode, encode}; 3 | 4 | fn bench_polyline(c: &mut Criterion) { 5 | let mut group = c.benchmark_group("polyline"); 6 | 7 | // Testdaten aus den Unit Tests 8 | let small_path = vec![[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]]; 9 | let small_encoded = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"; 10 | 11 | // Größere Testdaten für realistischere Szenarien 12 | let large_path: Vec<[f64; 2]> = (0..1000) 13 | .map(|i| { 14 | let f = i as f64; 15 | [f / 100.0, -f / 50.0] 16 | }) 17 | .collect(); 18 | let large_encoded = encode(&large_path, 5); 19 | 20 | // Encode Benchmarks 21 | group.bench_function("encode/small", |b| b.iter(|| encode(&small_path, 5))); 22 | 23 | group.bench_function("encode/large", |b| b.iter(|| encode(&large_path, 5))); 24 | 25 | // Decode Benchmarks 26 | group.bench_function("decode/small", |b| b.iter(|| decode(small_encoded, 5))); 27 | 28 | group.bench_function("decode/large", |b| { 29 | b.iter(|| decode(std::str::from_utf8(&large_encoded).unwrap(), 5)) 30 | }); 31 | 32 | // Precision Benchmarks 33 | for precision in [0, 5, 10] { 34 | group.bench_with_input( 35 | BenchmarkId::new("encode/precision", precision), 36 | &precision, 37 | |b, &p| b.iter(|| encode(&small_path, p)), 38 | ); 39 | } 40 | 41 | group.finish(); 42 | } 43 | 44 | criterion_group!(polyline, bench_polyline); 45 | -------------------------------------------------------------------------------- /benches/benchmarks/radix_sort.rs: -------------------------------------------------------------------------------- 1 | use criterion::{BatchSize, BenchmarkId, Criterion, SamplingMode, Throughput, criterion_group}; 2 | use rand::{Rng, distr::StandardUniform}; 3 | use toolbox_rs::rdx_sort::Sort; 4 | 5 | fn create_scrambled_data(length: usize) -> Vec { 6 | let rng = rand::rng(); 7 | rng.sample_iter(StandardUniform).take(length).collect() 8 | } 9 | 10 | fn bench_sorts(c: &mut Criterion) { 11 | let mut group = c.benchmark_group("Sort Algorithms"); 12 | group.sampling_mode(SamplingMode::Flat); 13 | for i in [ 14 | 1, 10, 100, 1000, 10_000, 100_000, 1_000_000, 2_000_000, 5_000_000, 10_000_000, 15 | ] { 16 | group.throughput(Throughput::Elements(i as u64)); 17 | let data = create_scrambled_data(i); 18 | group.bench_function(BenchmarkId::new("std::sort", i), |b| { 19 | b.iter_batched( 20 | || data.clone(), 21 | |mut data| data.sort(), 22 | BatchSize::LargeInput, 23 | ) 24 | }); 25 | group.bench_function(BenchmarkId::new("std::sort_unstable", i), |b| { 26 | b.iter_batched( 27 | || data.clone(), 28 | |mut data| data.sort_unstable(), 29 | BatchSize::LargeInput, 30 | ) 31 | }); 32 | group.bench_function(BenchmarkId::new("rdx_sort", i), |b| { 33 | b.iter_batched( 34 | || data.clone(), 35 | |mut data| data.rdx_sort(), 36 | BatchSize::LargeInput, 37 | ) 38 | }); 39 | } 40 | group.finish(); 41 | } 42 | 43 | criterion_group!(all_sorts, bench_sorts,); 44 | -------------------------------------------------------------------------------- /examples/instantiate.rs: -------------------------------------------------------------------------------- 1 | use toolbox_rs::{edge::InputEdge, graph::Graph, static_graph::StaticGraph}; 2 | fn main() { 3 | type Graph = StaticGraph; 4 | let edges = vec![ 5 | InputEdge::new(0, 1, 3), 6 | InputEdge::new(1, 2, 3), 7 | InputEdge::new(4, 2, 1), 8 | InputEdge::new(2, 3, 6), 9 | InputEdge::new(0, 4, 2), 10 | InputEdge::new(4, 5, 2), 11 | InputEdge::new(5, 3, 7), 12 | InputEdge::new(1, 5, 2), 13 | ]; 14 | 15 | let graph = Graph::new(edges); 16 | 17 | println!("number of nodes: {}", graph.number_of_nodes()); 18 | println!("number of edges: {}", graph.number_of_edges()); 19 | 20 | for i in graph.node_range() { 21 | println!("out_degree({})={}", i, graph.out_degree(i)); 22 | for j in graph.begin_edges(i)..graph.end_edges(i) { 23 | println!(" ({},{}): {}", i, graph.target(j), graph.data(j)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/radix_example.rs: -------------------------------------------------------------------------------- 1 | use rand::{Rng, distr::StandardUniform}; 2 | use toolbox_rs::rdx_sort::Sort; 3 | 4 | fn main() { 5 | let rng = rand::rng(); 6 | let mut input: Vec = rng.sample_iter(StandardUniform).take(100_000).collect(); 7 | 8 | let is_sorted = input.windows(2).all(|i| i[0] < i[1]); 9 | println!("before, is_sorted={is_sorted}"); 10 | 11 | input.rdx_sort(); 12 | 13 | let is_sorted = input.windows(2).all(|i| i[0] < i[1]); 14 | println!("after, is_sorted={is_sorted}"); 15 | } 16 | -------------------------------------------------------------------------------- /src/as_bytes.rs: -------------------------------------------------------------------------------- 1 | //TODO: move to lib? 2 | pub trait AsBytes { 3 | fn as_bytes(&self) -> &[u8]; 4 | } 5 | 6 | impl AsBytes for &str { 7 | fn as_bytes(&self) -> &[u8] { 8 | str::as_bytes(self) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/bin_pack.rs: -------------------------------------------------------------------------------- 1 | /// Next-Fit bin packing algorithm 2 | /// 3 | /// # Arguments 4 | /// * `items` - Slice of item sizes 5 | /// * `capacity` - Capacity of each bin 6 | /// 7 | /// # Returns 8 | /// Tuple containing the number of bins required and a vector of bin assignments 9 | /// 10 | /// # Examples 11 | /// ``` 12 | /// use toolbox_rs::bin_pack::bin_pack_next_fit; 13 | /// let items = vec![2, 5, 4, 7, 1, 3, 8]; 14 | /// assert_eq!((3, vec![0, 0, 0, 1, 1, 1, 2]), bin_pack_next_fit(&items, 11).unwrap()); 15 | /// ``` 16 | // next-first heuristic for bin packing 17 | // runs in O(N) and yields a 2-approximation 18 | pub fn bin_pack_next_fit(items: &[u32], capacity: u32) -> Result<(u32, Vec), &'static str> { 19 | if capacity == 0 { 20 | return Err("Capacity must be greater than 0"); 21 | } 22 | 23 | if items.is_empty() { 24 | return Ok((0, Vec::new())); 25 | } 26 | 27 | if items.iter().any(|&x| x > capacity) { 28 | return Err("Item exceeds bin capacity"); 29 | } 30 | 31 | let mut current_bin = 0; 32 | let mut remaining_capacity = capacity; 33 | let mut assignments = vec![0; items.len()]; 34 | 35 | for (i, &item) in items.iter().enumerate() { 36 | // If item doesn't fit in current bin, create a new one 37 | if item > remaining_capacity { 38 | current_bin += 1; 39 | remaining_capacity = capacity; 40 | } 41 | assignments[i] = current_bin; 42 | remaining_capacity -= item; 43 | } 44 | 45 | Ok((current_bin + 1, assignments)) 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use crate::bin_pack::bin_pack_next_fit; 51 | 52 | #[test] 53 | fn instance_stack_exchange1() { 54 | let weight = vec![2, 5, 4, 7, 1, 3, 8]; 55 | assert_eq!( 56 | Ok((3, vec![0, 0, 0, 1, 1, 1, 2])), 57 | bin_pack_next_fit(&weight, 11) 58 | ); 59 | } 60 | 61 | #[test] 62 | fn instance_stack_exchange2() { 63 | let weight = vec![2, 5, 4, 7, 1, 3, 8]; 64 | assert_eq!( 65 | Ok((5, vec![0, 0, 1, 2, 2, 3, 4])), 66 | bin_pack_next_fit(&weight, 10) 67 | ); 68 | } 69 | 70 | #[test] 71 | fn empty_input() { 72 | assert_eq!(Ok((0, Vec::new())), bin_pack_next_fit(&[], 10)); 73 | } 74 | 75 | #[test] 76 | fn single_item() { 77 | assert_eq!(Ok((1, vec![0])), bin_pack_next_fit(&[5], 10)); 78 | } 79 | 80 | #[test] 81 | fn exact_fit() { 82 | assert_eq!(Ok((2, vec![0, 1])), bin_pack_next_fit(&[10, 10], 10)); 83 | } 84 | 85 | #[test] 86 | fn multiple_bins_with_remainder() { 87 | let items = vec![3, 3, 3, 3, 3]; // Should pack into 2 bins: [3,3,3] and [3,3] 88 | assert_eq!(Ok((2, vec![0, 0, 0, 1, 1])), bin_pack_next_fit(&items, 10)); 89 | } 90 | 91 | #[test] 92 | fn invalid_capacity() { 93 | assert_eq!( 94 | Err("Capacity must be greater than 0"), 95 | bin_pack_next_fit(&[1, 2, 3], 0) 96 | ); 97 | } 98 | 99 | #[test] 100 | fn sequential_packing() { 101 | let items = vec![4, 4, 4, 4, 4]; // Should pack into 3 bins: [4,4], [4,4], [4] 102 | assert_eq!(Ok((3, vec![0, 0, 1, 1, 2])), bin_pack_next_fit(&items, 8)); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/bit_weight_iterator.rs: -------------------------------------------------------------------------------- 1 | use crate::{enumerative_source_coding::decode_u64, math::choose}; 2 | 3 | /// Implement an iterator of all 64 bit integers with fixed weight 4 | pub struct U64BitWeightIterator { 5 | weight: u64, 6 | ordinal: u64, 7 | max: u64, 8 | } 9 | 10 | impl U64BitWeightIterator { 11 | pub fn with_weight(weight: u64) -> Self { 12 | U64BitWeightIterator { 13 | weight, 14 | ordinal: 0, 15 | max: choose(64u64, weight), 16 | } 17 | } 18 | } 19 | 20 | impl Iterator for U64BitWeightIterator { 21 | type Item = u64; 22 | 23 | fn next(&mut self) -> Option { 24 | if self.ordinal < self.max { 25 | self.ordinal += 1; 26 | return Some(decode_u64(self.weight, self.ordinal - 1)); 27 | } 28 | None 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::U64BitWeightIterator; 35 | 36 | #[test] 37 | fn trivial_iterator_of_weight_one() { 38 | let result: Vec = U64BitWeightIterator::with_weight(1).collect(); 39 | assert_eq!(result.len(), 64); 40 | let expected: [u64; 64] = core::array::from_fn(|i| 1 << i); 41 | assert_eq!(result, expected); 42 | } 43 | 44 | #[test] 45 | fn trivial_iterator_of_weight_63() { 46 | let result: Vec = U64BitWeightIterator::with_weight(63).collect(); 47 | assert_eq!(result.len(), 64); 48 | let expected: [u64; 64] = 49 | core::array::from_fn(|i| 0xFFFF_FFFF_FFFF_FFFF ^ (1u64 << (63 - i))); 50 | assert_eq!(result, expected); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/bitset_subset_iterator.rs: -------------------------------------------------------------------------------- 1 | use num::{Integer, traits::WrappingSub}; 2 | 3 | /// Iterate all bitset subsets of a given bitset 4 | /// Implements what is known as Carry-Rippler trick 5 | /// 6 | pub struct BitsetSubsetIterator { 7 | subset: T, 8 | set: T, 9 | done: bool, 10 | } 11 | 12 | impl BitsetSubsetIterator { 13 | pub fn from_bitset(set: T) -> Self { 14 | Self { 15 | subset: T::zero(), 16 | set, 17 | done: false, 18 | } 19 | } 20 | } 21 | 22 | impl> Iterator 23 | for BitsetSubsetIterator 24 | { 25 | type Item = T; 26 | 27 | fn next(&mut self) -> Option { 28 | if self.done { 29 | return None; 30 | } 31 | 32 | let temp = self.subset; 33 | self.subset = self.subset.wrapping_sub(&self.set) & self.set; 34 | self.done = self.subset == T::zero(); 35 | Some(temp) 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::BitsetSubsetIterator; 42 | 43 | #[test] 44 | fn x55_bitmask() { 45 | let result: Vec = BitsetSubsetIterator::from_bitset(0x55).collect(); 46 | let expected = [0, 1, 4, 5, 16, 17, 20, 21, 64, 65, 68, 69, 80, 81, 84, 85]; 47 | assert_eq!(result.len(), 16); 48 | assert_eq!(result, expected); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/bloom_filter.rs: -------------------------------------------------------------------------------- 1 | use bitvec::prelude::*; 2 | use xxhash_rust::xxh3::xxh3_64_with_seed; 3 | 4 | use crate::as_bytes::AsBytes; 5 | 6 | /// Straight-forward implementation of a bloom filter on u8 slices. It applies 7 | /// the result of Kirsch and Mitzenmacher [1] of using a simple linear 8 | /// combination of two hash functions without any loss in the asymptotic false 9 | /// positive rate. 10 | /// [1] Kirsch, Mitzenmacher. "Less Hashing, Same Performance: Building a Better Bloom Filter". 11 | pub struct BloomFilter { 12 | bit_vector: BitVec, 13 | number_of_functions: usize, 14 | } 15 | 16 | /// Result type for the contains(.) operation 17 | #[derive(Debug, Eq, PartialEq)] 18 | pub enum BloomResult { 19 | No, 20 | YesWhp, 21 | } 22 | 23 | impl BloomFilter { 24 | // first hash function 25 | fn fn1(&self, t: &[u8]) -> usize { 26 | // hash operations are based on xxhash3 which is fast 27 | xxh3_64_with_seed(t, 0xdeadbeef) as usize 28 | } 29 | 30 | // second hash function 31 | fn fn2(&self, t: &[u8]) -> usize { 32 | // hash operations are based on xxhash3 which is fast 33 | xxh3_64_with_seed(t, 123) as usize 34 | } 35 | 36 | // constructs an empty filter with optimal length of bit vector and number 37 | // of simulated hash functions. 38 | pub fn new_from_size_and_probabilty(expected_set_size: usize, probability: f64) -> Self { 39 | assert!(probability > 0.); 40 | assert!(probability < 1.); 41 | 42 | // calculate optimal values on size and function count 43 | let ln_pfp = probability.log(std::f64::consts::E); 44 | let ln_2 = 1. / std::f64::consts::LOG2_E; 45 | 46 | let optimal_vector_legth = 47 | (-(expected_set_size as f64 * ln_pfp / ln_2.powi(2)).ceil()) as usize; 48 | assert!(optimal_vector_legth > 0); 49 | 50 | let bit_vector: BitVec = (0..optimal_vector_legth).map(|_a| false).collect(); 51 | let number_of_functions = (-(ln_pfp / ln_2).ceil()) as usize; 52 | assert!(number_of_functions > 0); 53 | 54 | BloomFilter { 55 | bit_vector, 56 | number_of_functions, 57 | } 58 | } 59 | 60 | // constructs a filter from a list of inputs 61 | pub fn new_from_list(list: &[T], probability: f64) -> Self { 62 | let mut filter = BloomFilter::new_from_size_and_probabilty(list.len(), probability); 63 | for i in list { 64 | // add all the items to the filter 65 | filter.add(i); 66 | } 67 | filter 68 | } 69 | 70 | // checks wether the given value is contained with high probability. 71 | pub fn contains(&self, value: &[u8]) -> BloomResult { 72 | let len = self.bit_vector.len(); 73 | let fn1_value = self.fn1(value) % len; 74 | 75 | let all_hash_fns_matched = (0..self.number_of_functions).all(|i| { 76 | let fn2_value = self.fn2(value) % len; 77 | let index = (fn1_value + (i * fn2_value)) % len; 78 | self.bit_vector[index] 79 | }); 80 | if all_hash_fns_matched { 81 | return BloomResult::YesWhp; 82 | } 83 | BloomResult::No 84 | } 85 | 86 | // adds a value via its byte representation to the filter. 87 | pub fn add_bytes(&mut self, value: &[u8]) { 88 | let len = self.bit_vector.len(); 89 | let fn1_value = self.fn1(value) % len; 90 | 91 | (0..self.number_of_functions).for_each(|i| { 92 | let fn2_value = self.fn2(value) % len; 93 | let index = (fn1_value + (i * fn2_value)) % len; 94 | self.bit_vector.set(index, true); 95 | }); 96 | } 97 | 98 | // add a value to the filter 99 | pub fn add(&mut self, value: &T) { 100 | self.add_bytes(value.as_bytes()) 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use crate::bloom_filter::BloomResult; 107 | 108 | use super::BloomFilter; 109 | 110 | #[test] 111 | fn one_sentence() { 112 | let sentence1 = "this is just a string of words with little meaning."; 113 | let sentence2 = "and this is another one with equally little meaning."; 114 | 115 | let mut filter = BloomFilter::new_from_size_and_probabilty(1, 0.001); 116 | // assert!(filter.size > 10); 117 | filter.add(&sentence1); 118 | assert_eq!(filter.contains(sentence1.as_bytes()), BloomResult::YesWhp); 119 | assert_eq!(filter.contains(sentence2.as_bytes()), BloomResult::No); 120 | } 121 | 122 | #[test] 123 | fn from_list() { 124 | let sentence1 = "this is just a string of words with little meaning."; 125 | let sentence2 = "and this is another one with equally little meaning."; 126 | let list = vec![sentence1, sentence2]; 127 | 128 | let sentence3 = "I am running out of meaningless examples to write."; 129 | 130 | let mut filter = BloomFilter::new_from_list(&list, 0.001); 131 | // assert!(filter.size > 10); 132 | filter.add(&sentence1); 133 | assert_eq!(filter.contains(sentence1.as_bytes()), BloomResult::YesWhp); 134 | assert_eq!(filter.contains(sentence2.as_bytes()), BloomResult::YesWhp); 135 | assert_eq!(filter.contains(sentence3.as_bytes()), BloomResult::No); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/cell.rs: -------------------------------------------------------------------------------- 1 | use crate::graph::NodeID; 2 | 3 | /// A Cell represents a partition of a graph with border nodes and their distance matrix. 4 | /// 5 | /// The Cell struct maintains a collection of border nodes (nodes that connect this cell to other cells) 6 | /// and a distance matrix that stores the shortest distances between these border nodes within the cell. 7 | /// 8 | /// # Examples 9 | /// 10 | /// ``` 11 | /// use toolbox_rs::cell::Cell; 12 | /// 13 | /// let border_nodes = vec![0, 1, 2]; // Three border nodes 14 | /// let distances = vec![ 15 | /// 0, 5, 7, // distances from node 0 to others 16 | /// 5, 0, 3, // distances from node 1 to others 17 | /// 7, 3, 0 // distances from node 2 to others 18 | /// ]; 19 | /// let cell = Cell::new(border_nodes, distances, 42); 20 | /// 21 | /// assert_eq!(cell.get_distance(0, 1), 5); // Distance from node 0 to 1 22 | /// assert_eq!(cell.id(), 42); 23 | /// assert_eq!(cell.border_nodes(), &[0, 1, 2]); 24 | /// ``` 25 | #[derive(Clone, Debug, Default)] 26 | pub struct Cell { 27 | border_nodes: Vec, 28 | distance_matrix: Vec, 29 | id: usize, 30 | } 31 | 32 | impl Cell { 33 | /// Creates a new Cell with the specified border nodes, distance matrix, and ID. 34 | /// 35 | /// # Arguments 36 | /// 37 | /// * `border_nodes` - Vector of node IDs that represent the border nodes of this cell 38 | /// * `distance_matrix` - A flattened matrix of distances between border nodes 39 | /// * `id` - Unique identifier for this cell 40 | /// 41 | /// # Examples 42 | /// 43 | /// ``` 44 | /// use toolbox_rs::cell::Cell; 45 | /// 46 | /// let cell = Cell::new(vec![0, 1], vec![0, 4, 4, 0], 1); 47 | /// assert_eq!(cell.get_distance(0, 1), 4); 48 | /// ``` 49 | pub fn new(border_nodes: Vec, distance_matrix: Vec, id: usize) -> Self { 50 | Self { 51 | border_nodes, 52 | distance_matrix, 53 | id, 54 | } 55 | } 56 | 57 | /// Returns the distance between two border nodes within the cell. 58 | /// 59 | /// # Arguments 60 | /// 61 | /// * `source` - Index of the source node in the border_nodes list 62 | /// * `target` - Index of the target node in the border_nodes list 63 | /// 64 | /// # Examples 65 | /// 66 | /// ``` 67 | /// use toolbox_rs::cell::Cell; 68 | /// 69 | /// let cell = Cell::new( 70 | /// vec![10, 20, 30], // border nodes 71 | /// vec![0, 5, 8, 5, 0, 3, 8, 3, 0], // 3x3 distance matrix 72 | /// 1 73 | /// ); 74 | /// assert_eq!(cell.get_distance(0, 1), 5); // Distance from first to second border node 75 | /// assert_eq!(cell.get_distance(1, 2), 3); // Distance from second to third border node 76 | /// ``` 77 | pub fn get_distance(&self, source: usize, target: usize) -> usize { 78 | self.distance_matrix[source * self.border_nodes.len() + target] 79 | } 80 | 81 | /// Returns the unique identifier of this cell. 82 | /// 83 | /// # Examples 84 | /// 85 | /// ``` 86 | /// use toolbox_rs::cell::Cell; 87 | /// 88 | /// let cell = Cell::new(vec![0], vec![0], 42); 89 | /// assert_eq!(cell.id(), 42); 90 | /// ``` 91 | pub fn id(&self) -> usize { 92 | self.id 93 | } 94 | 95 | /// Returns a slice containing all border nodes of this cell. 96 | /// 97 | /// # Examples 98 | /// 99 | /// ``` 100 | /// use toolbox_rs::cell::Cell; 101 | /// 102 | /// let cell = Cell::new(vec![1, 2, 3], vec![0, 1, 1, 1, 0, 1, 1, 1, 0], 1); 103 | /// assert_eq!(cell.border_nodes(), &[1, 2, 3]); 104 | /// ``` 105 | pub fn border_nodes(&self) -> &[NodeID] { 106 | &self.border_nodes 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | mod tests { 112 | use super::*; 113 | 114 | #[test] 115 | fn test_new_cell() { 116 | let border_nodes = vec![1, 2, 3]; 117 | let distance_matrix = vec![0, 1, 2, 1, 0, 3, 2, 3, 0]; 118 | let id = 1; 119 | let cell = Cell::new(border_nodes.clone(), distance_matrix.clone(), id); 120 | 121 | assert_eq!(cell.border_nodes, border_nodes); 122 | assert_eq!(cell.distance_matrix, distance_matrix); 123 | assert_eq!(cell.id, id); 124 | } 125 | 126 | #[test] 127 | fn test_get_distance() { 128 | let cell = Cell::new(vec![1, 2, 3], vec![0, 4, 7, 4, 0, 2, 7, 2, 0], 1); 129 | 130 | assert_eq!(cell.get_distance(0, 1), 4); 131 | assert_eq!(cell.get_distance(1, 2), 2); 132 | assert_eq!(cell.get_distance(0, 2), 7); 133 | assert_eq!(cell.get_distance(2, 0), 7); 134 | } 135 | 136 | #[test] 137 | fn test_border_nodes() { 138 | let nodes = vec![1, 2, 3, 4]; 139 | let cell = Cell::new( 140 | nodes.clone(), 141 | vec![0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0], 142 | 1, 143 | ); 144 | 145 | assert_eq!(cell.border_nodes(), &nodes); 146 | } 147 | 148 | #[test] 149 | fn test_cell_id() { 150 | let cell = Cell::new(vec![1], vec![0], 42); 151 | assert_eq!(cell.id(), 42); 152 | } 153 | 154 | #[test] 155 | fn test_cell_clone() { 156 | let original = Cell::new(vec![1, 2], vec![0, 1, 1, 0], 1); 157 | let cloned = original.clone(); 158 | 159 | assert_eq!(original.border_nodes(), cloned.border_nodes()); 160 | assert_eq!(original.id(), cloned.id()); 161 | assert_eq!(original.get_distance(0, 1), cloned.get_distance(0, 1)); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/chipper/bin/command_line.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, ops::RangeInclusive}; 2 | 3 | use clap::Parser; 4 | 5 | static RECURSION_RANGE: RangeInclusive = 1..=31; 6 | static BALANCE_RANGE: RangeInclusive = 0. ..=0.5; 7 | 8 | /// Checks whether the recursion range is within the expected range of (1, 31]. 9 | pub fn recursion_depth_in_range(s: &str) -> Result { 10 | let recursion_depth: u8 = s.parse().map_err(|_| format!("`{s}` isn't a number"))?; 11 | if RECURSION_RANGE.contains(&recursion_depth) { 12 | Ok(recursion_depth) 13 | } else { 14 | Err(format!( 15 | "recursion range not in range {}-{}", 16 | RECURSION_RANGE.start(), 17 | RECURSION_RANGE.end() 18 | )) 19 | } 20 | } 21 | 22 | /// Checks whether the balance factor is within the expected range of (0.,0.5] 23 | pub fn balance_factor_in_range(s: &str) -> Result { 24 | let factor: f64 = s.parse().map_err(|_| format!("`{s}` isn't a number"))?; 25 | if BALANCE_RANGE.contains(&factor) { 26 | Ok(factor) 27 | } else { 28 | Err(format!( 29 | "balance factor not in range {}-{}", 30 | BALANCE_RANGE.start(), 31 | BALANCE_RANGE.end() 32 | )) 33 | } 34 | } 35 | 36 | #[derive(Parser, Debug)] 37 | #[clap(author, version, about, long_about = None)] 38 | pub struct Arguments { 39 | /// Number of threads to use 40 | #[clap(short, long, action)] 41 | pub number_of_threads: Option, 42 | 43 | /// path to the input graph 44 | #[clap(short, long, action)] 45 | pub graph: String, 46 | 47 | /// path to the input coordinates 48 | #[clap(short, long, action)] 49 | pub coordinates: String, 50 | 51 | /// path to the cut-csv file 52 | #[clap(short = 'o', long, default_value_t = String::new(), action)] 53 | pub cut_csv: String, 54 | 55 | /// path to the assignment-csv file 56 | #[clap(short, long, default_value_t = String::new(), action)] 57 | pub assignment_csv: String, 58 | 59 | /// balance factor to use 60 | #[clap(short, long, value_parser = balance_factor_in_range, default_value_t = 0.25)] 61 | pub b_factor: f64, 62 | 63 | /// depth of recursive partitioning; off by one from the level of a node 64 | /// since the root node has level 1, e.g. depths of 1 gives cells on level 2 65 | #[clap(short, long, value_parser=recursion_depth_in_range, default_value_t = 1)] 66 | pub recursion_depth: u8, 67 | 68 | /// path to the output file with partition ids 69 | #[clap(short, long, default_value_t = String::new(), action)] 70 | pub partition_file: String, 71 | 72 | /// Minimum size of a cell 73 | #[clap(short, long, default_value_t = 50, action)] 74 | pub minimum_cell_size: usize, 75 | } 76 | 77 | impl Display for Arguments { 78 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 79 | writeln!(f, "command line arguments:")?; 80 | if let Some(number_of_threads) = self.number_of_threads { 81 | writeln!(f, "number_of_threads: {number_of_threads}")?; 82 | } 83 | if !self.partition_file.is_empty() { 84 | writeln!(f, "output partition file: {}", self.partition_file)?; 85 | } 86 | if !self.assignment_csv.is_empty() { 87 | writeln!(f, "assignment csv: {}", self.assignment_csv)?; 88 | } 89 | if !self.cut_csv.is_empty() { 90 | writeln!(f, "cut csv: {}", self.cut_csv)?; 91 | } 92 | writeln!(f, "graph: {}", self.graph)?; 93 | writeln!(f, "coordinates: {}", self.coordinates)?; 94 | writeln!(f, "recursion depth: {}", self.recursion_depth)?; 95 | writeln!(f, "balance factor: {}", self.b_factor)?; 96 | writeln!(f, "minimum_cell_size: {}", self.minimum_cell_size) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/chipper/bin/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(target_os = "windows"))] 2 | #[global_allocator] 3 | static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; 4 | 5 | mod command_line; 6 | mod serialize; 7 | 8 | use env_logger::Env; 9 | use itertools::Itertools; 10 | 11 | use indicatif::{ProgressBar, ProgressStyle}; 12 | use log::{debug, info}; 13 | use rayon::prelude::*; 14 | use std::sync::{Arc, atomic::AtomicI32}; 15 | use toolbox_rs::geometry::FPCoordinate; 16 | use toolbox_rs::io; 17 | use toolbox_rs::unsafe_slice::UnsafeSlice; 18 | use toolbox_rs::{ 19 | inertial_flow::{self, Flow, flow_cmp}, 20 | partition_id::PartitionID, 21 | }; 22 | use {command_line::Arguments, serialize::write_results}; 23 | 24 | fn main() { 25 | env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); 26 | 27 | println!(r#" chipping road networks into pieces. "#); 28 | println!(r#" ___ _ _ _ __ _ __ "#); 29 | println!(r#" / __| | |_ (_) | '_ \ | '_ \ ___ _ _ "#); 30 | println!(r#" | (__ | ' \ | | | .__/ | .__/ / -_) | '_| "#); 31 | println!(r#" \___| |_||_| _|_|_ |_|__ |_|__ \___| _|_|_ "#); 32 | println!(r#" _|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""| "#); 33 | println!(r#" "`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-' "#); 34 | println!("build: {}", env!("GIT_HASH")); 35 | 36 | // parse and print command line parameters 37 | let args = ::parse(); 38 | info!("{args}"); 39 | 40 | // set the number of threads if supplied on the command line 41 | if let Some(number_of_threads) = args.number_of_threads { 42 | info!("setting number of threads to {number_of_threads}"); 43 | rayon::ThreadPoolBuilder::new() 44 | .num_threads(number_of_threads) 45 | .build_global() 46 | .unwrap(); 47 | } 48 | 49 | let edges = io::read_graph_into_trivial_edges(&args.graph); 50 | let coordinates = io::read_vec_from_file::(&args.coordinates); 51 | info!( 52 | "loaded {} edges and {} coordinates", 53 | edges.len(), 54 | coordinates.len() 55 | ); 56 | 57 | // enqueue initial job for partitioning of the root node into job queue 58 | let id_vector = (0..coordinates.len()).collect_vec(); 59 | let job = (edges.clone(), id_vector); 60 | let mut current_job_queue = vec![job]; 61 | 62 | let sty = ProgressStyle::default_spinner() 63 | .template("{spinner:.green} [{elapsed_precise}] {wide_bar:.green/yellow} {msg}") 64 | .unwrap() 65 | .progress_chars("#>-"); 66 | 67 | let mut current_level = 0; 68 | let mut partition_ids_vec = vec![PartitionID::root(); coordinates.len()]; 69 | let partition_ids = UnsafeSlice::new(&mut partition_ids_vec); 70 | 71 | while !current_job_queue.is_empty() && current_level < args.recursion_depth { 72 | let pb = ProgressBar::new(current_job_queue.len() as u64); 73 | pb.set_style(sty.clone()); 74 | 75 | let next_job_queue = current_job_queue 76 | .par_iter_mut() 77 | .enumerate() 78 | .flat_map(|(id, job)| { 79 | pb.set_message(format!("cell #{id}")); 80 | pb.inc(1); 81 | 82 | // we use the count of coordinates as an upper bound to the cut size 83 | let upper_bound = Arc::new(AtomicI32::new(job.1.len().try_into().unwrap())); 84 | // run inertial flow on all four axes 85 | let best_max_flow = (0..4) 86 | .into_par_iter() 87 | .map(|axis| -> Result { 88 | inertial_flow::sub_step( 89 | &job.0, 90 | &job.1, 91 | &coordinates, 92 | axis, 93 | args.b_factor, 94 | upper_bound.clone(), 95 | ) 96 | }) 97 | .filter(|result| result.is_ok()) 98 | .map(|result| result.unwrap()) 99 | .min_by(flow_cmp); 100 | 101 | if best_max_flow.is_none() { 102 | return Vec::new(); 103 | } 104 | 105 | let result = best_max_flow.unwrap(); 106 | debug!( 107 | "best max-flow: {}, balance: {:.3}", 108 | result.flow, result.balance 109 | ); 110 | 111 | debug!("partitioning and assigning ids for all nodes"); 112 | 113 | (result.left_ids).iter().for_each(|id| unsafe { 114 | partition_ids.get_mut(*id).make_left_child(); 115 | }); 116 | (result.right_ids).iter().for_each(|id| unsafe { 117 | partition_ids.get_mut(*id).make_right_child(); 118 | }); 119 | 120 | // partition edge and node id sets for the next iteration 121 | debug!("generating next level edges"); 122 | // TODO: don't copy, but partition in place 123 | let (left_edges, right_edges): (Vec<_>, Vec<_>) = job 124 | .0 125 | .iter() 126 | .partition(|edge| partition_ids.get(edge.source).is_left_child()); 127 | debug!("generating next level ids"); 128 | 129 | // iterate on left half if larger than the minimum cell size 130 | let mut next_jobs = Vec::new(); 131 | if result.left_ids.len() > args.minimum_cell_size { 132 | next_jobs.push((left_edges, result.left_ids)); 133 | } else { 134 | let level_difference = (args.recursion_depth - current_level - 1) as usize; 135 | for i in &result.left_ids { 136 | unsafe { 137 | partition_ids 138 | .get_mut(*i) 139 | .make_leftmost_descendant(level_difference); 140 | } 141 | } 142 | } 143 | // iterate on right half if larger than the minimum cell size 144 | if result.right_ids.len() > args.minimum_cell_size { 145 | next_jobs.push((right_edges, result.right_ids)); 146 | } else { 147 | let level_difference = (args.recursion_depth - current_level - 1) as usize; 148 | for i in &result.right_ids { 149 | unsafe { 150 | partition_ids 151 | .get_mut(*i) 152 | .make_rightmost_descendant(level_difference); 153 | } 154 | } 155 | } 156 | next_jobs 157 | }) 158 | .collect(); 159 | current_level += 1; 160 | pb.finish_with_message(format!("level {current_level} done")); 161 | current_job_queue = next_job_queue; 162 | } 163 | 164 | write_results(&args, &partition_ids_vec, &coordinates, &edges); 165 | 166 | for id in &partition_ids_vec { 167 | debug_assert_eq!(id.level(), args.recursion_depth); 168 | } 169 | info!("done."); 170 | } 171 | -------------------------------------------------------------------------------- /src/chipper/bin/serialize.rs: -------------------------------------------------------------------------------- 1 | use bincode::encode_into_std_write; 2 | use log::info; 3 | use std::{ 4 | fs::File, 5 | io::{BufWriter, Write}, 6 | }; 7 | use toolbox_rs::{edge::TrivialEdge, geometry::FPCoordinate, partition_id::PartitionID}; 8 | 9 | use crate::command_line::Arguments; 10 | 11 | pub fn cut_csv( 12 | file_path: &str, 13 | edges: &[TrivialEdge], 14 | partition_ids: &[PartitionID], 15 | coordinates: &[FPCoordinate], 16 | ) { 17 | let mut file = BufWriter::new(File::create(file_path).expect("output file cannot be opened")); 18 | file.write_all("latitude, longitude\n".as_bytes()) 19 | .expect("error writing file"); 20 | // fetch the cut and output its geometry 21 | for edge in edges { 22 | if partition_ids[edge.source] != partition_ids[edge.target] { 23 | file.write_all( 24 | (coordinates[edge.source].lat as f64 / 1000000.) 25 | .to_string() 26 | .as_bytes(), 27 | ) 28 | .expect("error writing file"); 29 | file.write_all(b", ").expect("error writing file"); 30 | file.write_all( 31 | (coordinates[edge.source].lon as f64 / 1000000.) 32 | .to_string() 33 | .as_bytes(), 34 | ) 35 | .expect("error writing file"); 36 | file.write_all(b"\n").expect("error writing file"); 37 | 38 | file.write_all( 39 | (coordinates[edge.target].lat as f64 / 1000000.) 40 | .to_string() 41 | .as_bytes(), 42 | ) 43 | .expect("error writing file"); 44 | file.write_all(b", ").expect("error writing file"); 45 | file.write_all( 46 | (coordinates[edge.target].lon as f64 / 1000000.) 47 | .to_string() 48 | .as_bytes(), 49 | ) 50 | .expect("error writing file"); 51 | file.write_all(b"\n").expect("error writing file"); 52 | } 53 | } 54 | file.flush().expect("error writing file"); 55 | } 56 | 57 | pub fn assignment_csv(filename: &str, partition_ids: &[PartitionID], coordinates: &[FPCoordinate]) { 58 | let mut file = BufWriter::new(File::create(filename).expect("output file cannot be opened")); 59 | file.write_all("partition_id, latitude, longitude\n".as_bytes()) 60 | .expect("error writing file"); 61 | for i in 0..partition_ids.len() { 62 | file.write_all(partition_ids[i].to_string().as_bytes()) 63 | .expect("error writing file"); 64 | file.write_all(b", ").expect("error writing file"); 65 | 66 | file.write_all( 67 | (coordinates[i].lat as f64 / 1000000.) 68 | .to_string() 69 | .as_bytes(), 70 | ) 71 | .expect("error writing file"); 72 | file.write_all(b", ").expect("error writing file"); 73 | file.write_all( 74 | (coordinates[i].lon as f64 / 1000000.) 75 | .to_string() 76 | .as_bytes(), 77 | ) 78 | .expect("error writing file"); 79 | file.write_all(b"\n").expect("error writing file"); 80 | } 81 | } 82 | 83 | pub fn binary_partition_file(partition_file: &str, partition_ids: &[PartitionID]) { 84 | let mut f = BufWriter::new(File::create(partition_file).unwrap()); 85 | let config = bincode::config::standard(); 86 | encode_into_std_write(partition_ids, &mut f, config).unwrap(); 87 | } 88 | 89 | pub fn write_results( 90 | args: &Arguments, 91 | partition_ids: &[PartitionID], 92 | coordinates: &[FPCoordinate], 93 | edges: &[TrivialEdge], 94 | ) { 95 | if !args.assignment_csv.is_empty() { 96 | info!("writing partition csv into: {}", args.assignment_csv); 97 | assignment_csv(&args.assignment_csv, partition_ids, coordinates); 98 | } 99 | if !args.cut_csv.is_empty() { 100 | info!("writing cut csv to {}", &args.cut_csv); 101 | cut_csv(&args.cut_csv, edges, partition_ids, coordinates); 102 | } 103 | if !args.partition_file.is_empty() { 104 | info!("writing partition ids to {}", &args.partition_file); 105 | binary_partition_file(&args.partition_file, partition_ids); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/convex_hull.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of Andrew's monotone chain convex hull algorithm 2 | //! The runtime is $O(n\log n)$ by sorting the points lexicographically by 3 | //! their lon/lat coordinates, and by subsequently constructing upper and 4 | //! lower hulls. 5 | //! 6 | //! Note that the sorting order is lon/lat to make sure the x coordinate has 7 | //! higher precedence than the y coordinate -- an invariant of the algorithm. 8 | 9 | use crate::geometry::{FPCoordinate, is_clock_wise_turn}; 10 | 11 | pub fn monotone_chain(input_coordinates: &[FPCoordinate]) -> Vec { 12 | let n = input_coordinates.len(); 13 | if n <= 3 { 14 | return input_coordinates.into(); 15 | } 16 | 17 | // TODO: Implement heuristic by Akl-Toussaint to quickly exclude points 18 | 19 | let mut coordinates: Vec<_> = input_coordinates.into(); 20 | coordinates.sort_unstable_by_key(|a| (a.lon, a.lat)); 21 | 22 | // assemble the hull 23 | let mut stack = Vec::new(); 24 | coordinates.iter().for_each(|p| { 25 | while stack.len() >= 2 26 | && !is_clock_wise_turn(&stack[stack.len() - 2], &stack[stack.len() - 1], p) 27 | { 28 | stack.pop(); 29 | } 30 | stack.push(*p); 31 | }); 32 | // remove the last element since they are repeated in the beginning of the other half 33 | stack.pop(); 34 | 35 | // upper hull 36 | let lower_stack_len = stack.len(); 37 | coordinates.iter().rev().for_each(|p| { 38 | while stack.len() >= (2 + lower_stack_len) 39 | && !is_clock_wise_turn(&stack[stack.len() - 2], &stack[stack.len() - 1], p) 40 | { 41 | stack.pop(); 42 | } 43 | stack.push(*p); 44 | }); 45 | 46 | // remove the last element since they are repeated in the beginning of the other half 47 | stack.pop(); 48 | stack 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use crate::{convex_hull::monotone_chain, geometry::FPCoordinate}; 54 | 55 | #[test] 56 | fn grid() { 57 | let mut coordinates: Vec = Vec::new(); 58 | for i in 0..100 { 59 | coordinates.push(FPCoordinate::new(i / 10, i % 10)); 60 | } 61 | 62 | let expected = vec![ 63 | FPCoordinate::new(0, 0), 64 | FPCoordinate::new(0, 9), 65 | FPCoordinate::new(9, 9), 66 | FPCoordinate::new(9, 0), 67 | ]; 68 | let result = monotone_chain(&coordinates); 69 | assert_eq!(expected, result); 70 | } 71 | 72 | #[test] 73 | fn handle_overflow() { 74 | let coordinates = vec![ 75 | FPCoordinate::new_from_lat_lon(33.424732, -114.905286), 76 | FPCoordinate::new_from_lat_lon(33.412828, -114.981799), 77 | FPCoordinate::new_from_lat_lon(33.402066, -114.978244), 78 | FPCoordinate::new_from_lat_lon(33.406161, -114.974526), 79 | FPCoordinate::new_from_lat_lon(33.393332, -115.000801), 80 | FPCoordinate::new_from_lat_lon(33.393065, -114.981161), 81 | FPCoordinate::new_from_lat_lon(33.383992, -114.994943), 82 | FPCoordinate::new_from_lat_lon(33.415325, -114.933815), 83 | FPCoordinate::new_from_lat_lon(33.413086, -114.941854), 84 | FPCoordinate::new_from_lat_lon(33.376757, -114.990162), 85 | FPCoordinate::new_from_lat_lon(33.373506, -114.970202), 86 | FPCoordinate::new_from_lat_lon(33.439025, -114.898966), 87 | FPCoordinate::new_from_lat_lon(33.432417, -114.932620), 88 | FPCoordinate::new_from_lat_lon(33.438574, -114.913486), 89 | FPCoordinate::new_from_lat_lon(33.415171, -114.945400), 90 | FPCoordinate::new_from_lat_lon(33.429861, -114.935991), 91 | FPCoordinate::new_from_lat_lon(33.413931, -114.968911), 92 | FPCoordinate::new_from_lat_lon(33.413785, -115.000715), 93 | FPCoordinate::new_from_lat_lon(33.395238, -114.987989), 94 | FPCoordinate::new_from_lat_lon(33.390153, -114.990825), 95 | FPCoordinate::new_from_lat_lon(33.388738, -114.979194), 96 | FPCoordinate::new_from_lat_lon(33.387090, -114.975945), 97 | FPCoordinate::new_from_lat_lon(33.382099, -114.974277), 98 | FPCoordinate::new_from_lat_lon(33.375377, -114.984210), 99 | FPCoordinate::new_from_lat_lon(33.430011, -114.903102), 100 | FPCoordinate::new_from_lat_lon(33.424118, -114.909812), 101 | FPCoordinate::new_from_lat_lon(33.412820, -114.943641), 102 | FPCoordinate::new_from_lat_lon(33.430089, -114.903063), 103 | FPCoordinate::new_from_lat_lon(33.359699, -114.945064), 104 | FPCoordinate::new_from_lat_lon(33.413760, -115.000801), 105 | FPCoordinate::new_from_lat_lon(33.434750, -114.929788), 106 | FPCoordinate::new_from_lat_lon(33.412851, -114.948184), 107 | FPCoordinate::new_from_lat_lon(33.395008, -114.991292), 108 | FPCoordinate::new_from_lat_lon(33.385784, -114.979111), 109 | FPCoordinate::new_from_lat_lon(33.406637, -115.000801), 110 | FPCoordinate::new_from_lat_lon(33.440700, -114.920131), 111 | ]; 112 | 113 | let expected = vec![ 114 | FPCoordinate::new(33393332, -115000801), 115 | FPCoordinate::new(33383992, -114994943), 116 | FPCoordinate::new(33376756, -114990162), 117 | FPCoordinate::new(33359699, -114945064), 118 | FPCoordinate::new(33424732, -114905286), 119 | FPCoordinate::new(33439025, -114898966), 120 | FPCoordinate::new(33440700, -114920131), 121 | FPCoordinate::new(33413760, -115000801), 122 | ]; 123 | let convex_hull = monotone_chain(&coordinates); 124 | assert_eq!(expected, convex_hull); 125 | } 126 | 127 | #[test] 128 | fn tiny_instance() { 129 | let coordinates = vec![ 130 | FPCoordinate::new_from_lat_lon(33.424732, -114.905286), 131 | FPCoordinate::new_from_lat_lon(33.412828, -114.981799), 132 | FPCoordinate::new_from_lat_lon(33.440700, -114.920131), 133 | ]; 134 | 135 | let expected = vec![ 136 | FPCoordinate::new(33424732, -114905286), 137 | FPCoordinate::new(33412827, -114981799), 138 | FPCoordinate::new(33440700, -114920131), 139 | ]; 140 | let convex_hull = monotone_chain(&coordinates); 141 | assert_eq!(expected, convex_hull); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/count_min_sketch.rs: -------------------------------------------------------------------------------- 1 | use xxhash_rust::xxh3::xxh3_128_with_seed; 2 | 3 | use crate::as_bytes::AsBytes; 4 | 5 | fn optimal_k(delta: f64) -> usize { 6 | (1. / delta).ln().ceil() as usize 7 | } 8 | 9 | fn optimal_m(epsilon: f64) -> usize { 10 | (std::f64::consts::E / epsilon).ceil() as usize 11 | } 12 | 13 | fn get_seed() -> u64 { 14 | rand::Rng::random::(&mut rand::rng()) 15 | } 16 | 17 | pub struct CountMinSketch { 18 | seed: u64, 19 | counter: Vec>, 20 | m: usize, 21 | k: usize, 22 | len: usize, 23 | } 24 | 25 | impl CountMinSketch { 26 | pub fn new(delta: f64, epsilon: f64) -> Self { 27 | let seed = get_seed(); 28 | 29 | let m = optimal_m(delta); 30 | let k = optimal_k(epsilon); 31 | 32 | let counter = vec![vec![0u32; m]; k]; 33 | 34 | Self { 35 | seed, 36 | counter, 37 | m, 38 | k, 39 | len: 0, 40 | } 41 | } 42 | } 43 | 44 | impl CountMinSketch { 45 | fn hash_pair(&self, key: &K) -> (u64, u64) { 46 | let hash = xxh3_128_with_seed(key.as_bytes(), self.seed); 47 | (hash as u64, (hash >> 64) as u64) 48 | } 49 | 50 | fn get_buckets(&self, key: &K) -> Vec { 51 | let (hash1, hash2) = self.hash_pair(key); 52 | let mut bucket_indices = Vec::with_capacity(self.k); 53 | if self.k == 1 { 54 | let index = hash1 % self.m as u64; 55 | bucket_indices.push(index as usize); 56 | } else { 57 | (0..self.k as u64).for_each(|i| { 58 | let hash = hash1.wrapping_add(i.wrapping_mul(hash2)); 59 | let index = hash % self.m as u64; 60 | bucket_indices.push(index as usize); 61 | }); 62 | } 63 | bucket_indices 64 | } 65 | 66 | pub fn insert(&mut self, key: &K) { 67 | let indices = self.get_buckets(key); 68 | indices.iter().enumerate().for_each(|(k, &b)| { 69 | self.counter[k][b] = self.counter[k][b].saturating_add(1); 70 | }); 71 | self.len += 1; 72 | } 73 | 74 | pub fn estimate(&self, key: &K) -> u32 { 75 | let indices = self.get_buckets(key); 76 | indices 77 | .iter() 78 | .enumerate() 79 | .map(|(k, b)| self.counter[k][*b]) 80 | .fold(u32::MAX, |a, b| a.min(b)) 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use super::CountMinSketch; 87 | 88 | #[test] 89 | fn insert_check_1m() { 90 | let mut sketch = CountMinSketch::new(0.01, 0.2); 91 | 92 | for _ in 0..1_000_000 { 93 | sketch.insert(&"key"); 94 | } 95 | 96 | assert_eq!(sketch.estimate(&"key"), 1_000_000); 97 | assert_eq!(sketch.estimate(&"blah"), 0); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/cycle_check.rs: -------------------------------------------------------------------------------- 1 | use crate::graph::Graph; 2 | 3 | /// Returns whether the graph contains a cycle by running a node 4 | /// coloring Depth-First-Search (DFS) 5 | pub fn cycle_check(graph: &(impl Graph + 'static)) -> bool { 6 | #[derive(Clone, Eq, PartialEq)] 7 | enum Colors { 8 | White, 9 | Grey, 10 | Black, 11 | } 12 | 13 | let mut node_colors = vec![Colors::White; graph.number_of_nodes()]; 14 | let mut stack = Vec::new(); 15 | 16 | for root in graph.node_range() { 17 | if node_colors[root] != Colors::White { 18 | continue; 19 | } 20 | 21 | stack.push(root); 22 | while let Some(&node) = stack.last() { 23 | // pre-order traversal 24 | if node_colors[node] != Colors::Grey { 25 | node_colors[node] = Colors::Grey; 26 | 27 | for edge in graph.edge_range(node) { 28 | // push unvisited children to stack 29 | let target = graph.target(edge); 30 | match node_colors[target] { 31 | Colors::White => { 32 | stack.push(target); 33 | } 34 | Colors::Grey => { 35 | return true; 36 | } 37 | _ => {} 38 | }; 39 | } 40 | } else if node_colors[node] == Colors::Grey { 41 | // post-order traversal 42 | stack.pop(); 43 | node_colors[node] = Colors::Black; 44 | } 45 | } 46 | } 47 | false 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use crate::edge::InputEdge; 53 | use crate::{cycle_check::cycle_check, static_graph::StaticGraph}; 54 | 55 | #[test] 56 | fn no_cycle() { 57 | type Graph = StaticGraph; 58 | let edges = vec![ 59 | InputEdge::new(0, 1, 3), 60 | InputEdge::new(1, 2, 3), 61 | InputEdge::new(4, 2, 1), 62 | InputEdge::new(2, 3, 6), 63 | InputEdge::new(0, 4, 2), 64 | InputEdge::new(4, 5, 2), 65 | InputEdge::new(5, 3, 7), 66 | InputEdge::new(1, 5, 2), 67 | ]; 68 | let graph = Graph::new(edges); 69 | assert!(!cycle_check(&graph)); 70 | } 71 | 72 | #[test] 73 | fn cycle() { 74 | type Graph = StaticGraph; 75 | let edges = vec![ 76 | InputEdge::new(0, 1, 3), 77 | InputEdge::new(2, 3, 3), 78 | InputEdge::new(3, 4, 1), 79 | InputEdge::new(4, 5, 6), 80 | InputEdge::new(5, 2, 2), 81 | ]; 82 | let graph = Graph::new(edges); 83 | assert!(cycle_check(&graph)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/edge.rs: -------------------------------------------------------------------------------- 1 | use bincode::{Decode, Encode}; 2 | 3 | use crate::graph::NodeID; 4 | use core::mem::swap; 5 | 6 | pub trait Edge { 7 | type ID; 8 | fn source(&self) -> Self::ID; 9 | fn target(&self) -> Self::ID; 10 | } 11 | 12 | pub trait EdgeData { 13 | type DATA; 14 | fn data(&self) -> &Self::DATA; 15 | } 16 | 17 | pub trait EdgeWithData: Edge + EdgeData {} 18 | impl EdgeWithData for InputEdge {} 19 | 20 | #[derive(Clone, Copy, Debug, bincode::Decode, bincode::Encode)] 21 | pub struct TrivialEdge { 22 | pub source: usize, 23 | pub target: usize, 24 | } 25 | 26 | impl Edge for TrivialEdge { 27 | type ID = NodeID; 28 | fn source(&self) -> Self::ID { 29 | self.source 30 | } 31 | fn target(&self) -> Self::ID { 32 | self.target 33 | } 34 | } 35 | 36 | #[derive(Clone, Copy, Debug, Default, Eq, PartialOrd, Ord, PartialEq, Decode, Encode)] 37 | pub struct InputEdge { 38 | pub source: NodeID, 39 | pub target: NodeID, 40 | pub data: EdgeDataT, 41 | } 42 | 43 | impl Edge for InputEdge { 44 | type ID = NodeID; 45 | fn source(&self) -> Self::ID { 46 | self.source 47 | } 48 | fn target(&self) -> Self::ID { 49 | self.target 50 | } 51 | } 52 | 53 | impl EdgeData for InputEdge { 54 | type DATA = EdgeDataT; 55 | fn data(&self) -> &Self::DATA { 56 | &self.data 57 | } 58 | } 59 | 60 | impl InputEdge { 61 | pub fn new(source: NodeID, target: NodeID, data: EdgeDataT) -> Self { 62 | Self { 63 | source, 64 | target, 65 | data, 66 | } 67 | } 68 | 69 | pub fn is_parallel_to(&self, other: &Self) -> bool { 70 | self.source == other.source && self.target == other.target 71 | } 72 | 73 | pub fn reverse(&mut self) { 74 | swap(&mut self.source, &mut self.target); 75 | } 76 | } 77 | pub type SimpleEdge = InputEdge; 78 | 79 | #[test] 80 | fn simple_edge_parallel() { 81 | let edge1 = SimpleEdge::new(1, 2, 3); 82 | let edge2 = SimpleEdge::new(1, 2, 6); 83 | 84 | assert!(edge1.is_parallel_to(&edge1)); 85 | assert!(edge1.is_parallel_to(&edge2)); 86 | assert!(edge2.is_parallel_to(&edge1)); 87 | assert!(edge2.is_parallel_to(&edge2)); 88 | } 89 | 90 | #[test] 91 | fn trivial_edge_accessor() { 92 | let edge = TrivialEdge { 93 | source: 1, 94 | target: 2, 95 | }; 96 | 97 | assert_eq!(edge.source(), 1); 98 | assert_eq!(edge.target(), 2); 99 | } 100 | -------------------------------------------------------------------------------- /src/enumerative_source_coding.rs: -------------------------------------------------------------------------------- 1 | use crate::math::choose; 2 | 3 | /// Implementation of Cover's algorithm to enumerate of sequences of weight w, cf. 4 | /// "Enumerative Source Encoding". Thomas M. Cover. 5 | /// Appears in IEEE Transactions on Information Theory. Vol 19, Issue: 1, Jan 1973 6 | /// 7 | /// The implementation corresponds to Section "Example 2 - Enumeration of Sequences of Weight w". 8 | /// 9 | /// # Examples 10 | /// 11 | /// ``` 12 | /// use toolbox_rs::enumerative_source_coding::decode_u64; 13 | /// 14 | /// // 0th number with 3 bits set in a 64 bit number 15 | /// assert_eq!(decode_u64(3, 0), 0b000_0111); 16 | /// ``` 17 | pub fn decode_u64(mut ones: u64, mut ordinal: u64) -> u64 { 18 | debug_assert!(ordinal < choose(64, ones)); 19 | let mut result = 0; 20 | for bit in (0..64).rev() { 21 | let n_ck = choose(bit, ones); 22 | if ordinal >= n_ck { 23 | ordinal -= n_ck; 24 | result |= 1 << bit; 25 | ones -= 1; 26 | } 27 | } 28 | result 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use crate::enumerative_source_coding::decode_u64; 34 | 35 | #[test] 36 | fn paper_examples() { 37 | // 0th number with 3 bits set 38 | assert_eq!(decode_u64(3, 0), 0b0000_0111); 39 | // 1st number with 3 bits set 40 | assert_eq!(decode_u64(3, 1), 0b0000_1011); 41 | // 21st number with 3 bits set 42 | assert_eq!(decode_u64(3, 21), 0b0100_0101); 43 | // 34th number with 3 bits set 44 | assert_eq!(decode_u64(3, 34), 0b0111_0000); 45 | // 41663th (last) number with 3 bits set 46 | assert_eq!( 47 | decode_u64(3, 41663), 48 | 0b1110_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/fast_hash_trait.rs: -------------------------------------------------------------------------------- 1 | /// Maximum number of distinct elements that can be hashed. 2 | /// This is limited to 2^16 (65536) since the hash function returns a u16. 3 | pub const MAX_ELEMENTS: usize = 65536; 4 | 5 | /// A trait for fast hashing implementations that map 32-bit keys to 16-bit hash values. 6 | /// 7 | /// This trait is designed for scenarios where: 8 | /// - You need very fast hashing operations 9 | /// - A perfect hash function is not required 10 | /// - The hash space can be limited to 16 bits (0-65535) 11 | /// - Collisions are acceptable but should be minimized 12 | /// 13 | /// # Examples 14 | /// 15 | /// ``` 16 | /// use toolbox_rs::fast_hash_trait::FastHash; 17 | /// 18 | /// struct SimpleHash; 19 | /// 20 | /// impl FastHash for SimpleHash { 21 | /// fn hash(&self, key: u32) -> u16 { 22 | /// // Simple example hash: take the lower 16 bits 23 | /// (key & 0xFFFF) as u16 24 | /// } 25 | /// } 26 | /// 27 | /// let hasher = SimpleHash; 28 | /// let hash1 = hasher.hash(123456); 29 | /// let hash2 = hasher.hash(123456); 30 | /// assert_eq!(hash1, hash2); // Same input produces same hash 31 | /// ``` 32 | /// 33 | /// # Notes 34 | /// 35 | /// - The hash function must be deterministic: the same input must always produce the same output 36 | /// - The hash function should aim to distribute values uniformly across the u16 range 37 | /// - The implementation should be as fast as possible, avoiding complex operations 38 | /// 39 | /// # Safety 40 | /// 41 | /// The hash function must never panic for any input value. 42 | pub trait FastHash { 43 | /// Computes a 16-bit hash value for the given 32-bit key. 44 | /// 45 | /// # Arguments 46 | /// 47 | /// * `key` - The 32-bit unsigned integer to hash 48 | /// 49 | /// # Returns 50 | /// 51 | /// A 16-bit hash value in the range [0, 65535] 52 | fn hash(&self, key: u32) -> u16; 53 | } 54 | -------------------------------------------------------------------------------- /src/fibonacci_hash.rs: -------------------------------------------------------------------------------- 1 | use crate::fast_hash_trait::FastHash; 2 | 3 | /// A hash function implementation using the Fibonacci hashing technique. 4 | /// 5 | /// Fibonacci hashing is a multiplication-based hashing method that uses the golden ratio 6 | /// to achieve a good distribution of hash values. It works particularly well for integer keys 7 | /// and provides good performance characteristics due to its simplicity. 8 | /// 9 | /// # Examples 10 | /// 11 | /// Basic usage: 12 | /// ``` 13 | /// use toolbox_rs::fibonacci_hash::FibonacciHash; 14 | /// use toolbox_rs::fast_hash_trait::FastHash; 15 | /// 16 | /// let hasher = FibonacciHash::new(); 17 | /// let hash = hasher.hash(42); 18 | /// assert!(hash <= u16::MAX); 19 | /// ``` 20 | /// 21 | /// Different inputs produce different hashes: 22 | /// ``` 23 | /// use toolbox_rs::fibonacci_hash::FibonacciHash; 24 | /// use toolbox_rs::fast_hash_trait::FastHash; 25 | /// 26 | /// let hasher = FibonacciHash::new(); 27 | /// let hash1 = hasher.hash(1); 28 | /// let hash2 = hasher.hash(2); 29 | /// assert_ne!(hash1, hash2); 30 | /// ``` 31 | #[derive(Default)] 32 | pub struct FibonacciHash; 33 | 34 | impl FibonacciHash { 35 | /// Creates a new FibonacciHash instance with the specified shift amount. 36 | /// 37 | /// The shift amount determines how many bits to shift during the hashing process. 38 | /// A typical value is 16, which produces good distribution for most use cases. 39 | /// 40 | /// # Examples 41 | /// 42 | /// ``` 43 | /// use toolbox_rs::fibonacci_hash::FibonacciHash; 44 | /// 45 | /// let hasher = FibonacciHash::new(); 46 | /// ``` 47 | pub fn new() -> Self { 48 | Self {} 49 | } 50 | } 51 | 52 | impl FastHash for FibonacciHash { 53 | /// Computes a 16-bit hash value for a 32-bit input using Fibonacci hashing. 54 | /// 55 | /// # Algorithm 56 | /// 57 | /// 1. XORs the input with a shifted version of itself 58 | /// 2. Multiplies by the golden ratio constant (≈ (√5-1)/2 * 2⁶⁴) 59 | /// 3. Shifts the result right to get the final hash 60 | /// 61 | /// # Arguments 62 | /// 63 | /// * `key` - 32-bit unsigned integer to hash 64 | /// 65 | /// # Returns 66 | /// 67 | /// A 16-bit hash value 68 | /// 69 | /// # Examples 70 | /// 71 | /// ``` 72 | /// use toolbox_rs::fibonacci_hash::FibonacciHash; 73 | /// use toolbox_rs::fast_hash_trait::FastHash; 74 | /// 75 | /// let hasher = FibonacciHash::new(); 76 | /// 77 | /// // Same input produces same hash 78 | /// assert_eq!(hasher.hash(42), hasher.hash(42)); 79 | /// 80 | /// // Values are within u16 range 81 | /// for i in 0..10 { 82 | /// assert!(hasher.hash(i) <= u16::MAX); 83 | /// } 84 | /// ``` 85 | fn hash(&self, key: u32) -> u16 { 86 | // First XOR the high bits with the low bits 87 | let mut hash = key as u64; 88 | hash ^= hash >> 16; 89 | 90 | // Multiply by the golden ratio constant (approximately (√5-1)/2 * 2⁶⁴) 91 | const GOLDEN_RATIO: u64 = 11400714819323198485; 92 | let result = (GOLDEN_RATIO.wrapping_mul(hash)) >> 16; 93 | 94 | result as u16 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | 102 | #[test] 103 | fn test_fibonacci_hash_deterministic() { 104 | let hasher = FibonacciHash::new(); 105 | let hash1 = hasher.hash(42); 106 | let hash2 = hasher.hash(42); 107 | assert_eq!(hash1, hash2, "Same input should produce same hash"); 108 | } 109 | 110 | #[test] 111 | fn test_different_inputs() { 112 | let hasher = FibonacciHash::new(); 113 | let hash1 = hasher.hash(1); 114 | let hash2 = hasher.hash(2); 115 | assert_ne!( 116 | hash1, hash2, 117 | "Different inputs should produce different hashes" 118 | ); 119 | } 120 | 121 | #[test] 122 | fn test_hash_distribution() { 123 | let hasher = FibonacciHash::new(); 124 | let mut seen = std::collections::HashSet::new(); 125 | 126 | // Test distribution over a reasonable range 127 | for i in 0..1000 { 128 | seen.insert(hasher.hash(i)); 129 | } 130 | 131 | // We expect a good hash function to have reasonable distribution 132 | // For 1000 inputs hashed to u16, we expect a decent number of unique values 133 | assert!( 134 | seen.len() > 900, 135 | "Hash function should provide good distribution" 136 | ); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/graph.rs: -------------------------------------------------------------------------------- 1 | use core::ops::Range; 2 | 3 | pub type NodeID = usize; 4 | pub type EdgeID = usize; 5 | pub const INVALID_NODE_ID: NodeID = NodeID::MAX; 6 | pub const INVALID_EDGE_ID: EdgeID = EdgeID::MAX; 7 | pub const UNREACHABLE: usize = usize::MAX; 8 | 9 | pub trait Graph { 10 | fn node_range(&self) -> Range; 11 | fn edge_range(&self, n: NodeID) -> Range; 12 | fn number_of_nodes(&self) -> usize; 13 | fn number_of_edges(&self) -> usize; 14 | fn begin_edges(&self, n: NodeID) -> EdgeID; 15 | fn end_edges(&self, n: NodeID) -> EdgeID; 16 | fn out_degree(&self, n: NodeID) -> usize; 17 | fn target(&self, e: EdgeID) -> NodeID; 18 | fn data(&self, e: EdgeID) -> &T; 19 | fn data_mut(&mut self, e: EdgeID) -> &mut T; 20 | fn find_edge(&self, s: NodeID, t: NodeID) -> Option; 21 | fn find_edge_unchecked(&self, s: NodeID, t: NodeID) -> EdgeID; 22 | } 23 | #[derive(Clone, Copy)] 24 | pub struct EdgeArrayEntry { 25 | pub target: NodeID, 26 | pub data: EdgeDataT, 27 | } 28 | -------------------------------------------------------------------------------- /src/graph_plier/bin/command_line.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use clap::{Parser, ValueEnum}; 4 | 5 | #[derive(ValueEnum, Clone, Debug)] 6 | pub enum InputFormat { 7 | Dimacs, 8 | Ddsg, 9 | Metis, 10 | } 11 | 12 | #[derive(Parser, Debug)] 13 | #[clap(author, version, about, long_about = None)] 14 | pub struct Arguments { 15 | /// Number of threads to use 16 | #[clap(short, long, action)] 17 | pub input_format: InputFormat, 18 | 19 | /// path to the input graph 20 | #[clap(short, long, action)] 21 | pub graph: String, 22 | 23 | /// path to the input coordinates 24 | #[clap(short, long, action)] 25 | pub coordinates: String, 26 | } 27 | 28 | impl Display for Arguments { 29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 | writeln!(f, "command line arguments:")?; 31 | writeln!(f, "graph: {}", self.graph)?; 32 | writeln!(f, "coordinates: {}", self.coordinates) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/graph_plier/bin/main.rs: -------------------------------------------------------------------------------- 1 | mod command_line; 2 | use std::{fs::File, io::BufWriter}; 3 | 4 | use bincode::encode_into_std_write; 5 | use env_logger::Env; 6 | use log::info; 7 | 8 | use crate::command_line::{Arguments, InputFormat}; 9 | use toolbox_rs::{ddsg, dimacs, edge::InputEdge, metis}; 10 | 11 | fn main() { 12 | env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); 13 | 14 | println!(r#" ___ _ _ "#); 15 | println!(r#" | _ \ | | (_) ___ _ _ "#); 16 | println!(r#" | _/ | | | | / -_) | '_| "#); 17 | println!(r#" _|_|_ _|_|_ _|_|_ \___| _|_|_ "#); 18 | println!(r#" _| """ |_|"""""|_|"""""|_|"""""|_|"""""| "#); 19 | println!(r#" "`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-' "#); 20 | println!("build: {}", env!("GIT_HASH")); 21 | 22 | // parse and print command line parameters 23 | let args = ::parse(); 24 | info!("{args}"); 25 | 26 | let edges: Vec> = match args.input_format { 27 | InputFormat::Ddsg => ddsg::read_graph(&args.graph, ddsg::WeightType::Original), 28 | InputFormat::Dimacs => dimacs::read_graph(&args.graph, dimacs::WeightType::Original), 29 | InputFormat::Metis => metis::read_graph(&args.graph, metis::WeightType::Original), 30 | }; 31 | 32 | let coordinates = match args.input_format { 33 | InputFormat::Ddsg => ddsg::read_coordinates(&args.coordinates), 34 | InputFormat::Dimacs => dimacs::read_coordinates(&args.coordinates), 35 | InputFormat::Metis => metis::read_coordinates(&args.coordinates), 36 | }; 37 | 38 | let config = bincode::config::standard(); 39 | 40 | info!("writing edges into intermediate format"); 41 | let mut f = BufWriter::new(File::create(args.graph + ".toolbox").unwrap()); 42 | encode_into_std_write(&edges, &mut f, config).unwrap(); 43 | 44 | info!("writing coordinates into intermediate format"); 45 | let mut f = BufWriter::new(File::create(args.coordinates + ".toolbox").unwrap()); 46 | encode_into_std_write(&coordinates, &mut f, config).unwrap(); 47 | 48 | info!("done."); 49 | } 50 | -------------------------------------------------------------------------------- /src/great_circle.rs: -------------------------------------------------------------------------------- 1 | use crate::wgs84::EARTH_RADIUS_KM; 2 | 3 | /// Calculates the great-circle distance between two points using the Haversine formula. 4 | /// 5 | /// This function computes the shortest distance over the earth's surface between two points 6 | /// given their latitude and longitude coordinates. It uses the Haversine formula which 7 | /// provides good accuracy for most distances while being relatively simple to compute. 8 | /// 9 | /// # Arguments 10 | /// * `latitude1` - Latitude of first point in decimal degrees 11 | /// * `longitude1` - Longitude of first point in decimal degrees 12 | /// * `latitude2` - Latitude of second point in decimal degrees 13 | /// * `longitude2` - Longitude of second point in decimal degrees 14 | /// 15 | /// # Returns 16 | /// Distance between the points in kilometers 17 | /// 18 | /// # Examples 19 | /// 20 | /// ``` 21 | /// use toolbox_rs::great_circle::haversine; 22 | /// 23 | /// // Distance between New York and San Francisco 24 | /// let ny = (40.730610, -73.935242); 25 | /// let sf = (37.773972, -122.431297); 26 | /// let distance = haversine(ny.0, ny.1, sf.0, sf.1); 27 | /// 28 | /// assert!((distance - 4140.175).abs() < 0.001); 29 | /// 30 | /// // Distance to self is zero 31 | /// let distance = haversine(ny.0, ny.1, ny.0, ny.1); 32 | /// assert!(distance < 0.000001); 33 | /// ``` 34 | pub fn haversine(latitude1: f64, longitude1: f64, latitude2: f64, longitude2: f64) -> f64 { 35 | let d1 = latitude1.to_radians(); 36 | let d2 = latitude2.to_radians(); 37 | 38 | let d_lat = (latitude2 - latitude1).to_radians(); 39 | let d_lon = (longitude1 - longitude2).to_radians(); 40 | 41 | let a = (d_lat / 2.).sin().powi(2) + d1.cos() * d2.cos() * (d_lon / 2.).sin().powi(2); 42 | 43 | let c = 2. * a.sqrt().atan2((1. - a).sqrt()); 44 | 45 | EARTH_RADIUS_KM * c 46 | } 47 | 48 | /// Calculates the great-circle distance between two points using Vincenty's formula. 49 | /// 50 | /// This function implements Vincenty's formula for calculating geodesic distances. 51 | /// It's generally more accurate than the Haversine formula, especially for antipodal points 52 | /// (points on opposite sides of the Earth). 53 | /// 54 | /// # Arguments 55 | /// * `latitude1` - Latitude of first point in decimal degrees 56 | /// * `longitude1` - Longitude of first point in decimal degrees 57 | /// * `latitude2` - Latitude of second point in decimal degrees 58 | /// * `longitude2` - Longitude of second point in decimal degrees 59 | /// 60 | /// # Returns 61 | /// Distance between the points in kilometers 62 | /// 63 | /// # Examples 64 | /// 65 | /// ``` 66 | /// use toolbox_rs::great_circle::vincenty; 67 | /// 68 | /// // Distance between New York and San Francisco 69 | /// let ny = (40.730610, -73.935242); 70 | /// let sf = (37.773972, -122.431297); 71 | /// let distance = vincenty(ny.0, ny.1, sf.0, sf.1); 72 | /// 73 | /// assert!((distance - 4140.175).abs() < 0.001); 74 | /// 75 | /// // Antipodal points (approximately) 76 | /// let dist = vincenty(0.0, 0.0, 0.0, 180.0); 77 | /// assert!((dist - 20015.0).abs() < 25.0); // Half Earth's circumference 78 | /// ``` 79 | pub fn vincenty(latitude1: f64, longitude1: f64, latitude2: f64, longitude2: f64) -> f64 { 80 | let lat1 = latitude1.to_radians(); 81 | let lon1 = longitude1.to_radians(); 82 | let lat2 = latitude2.to_radians(); 83 | let lon2 = longitude2.to_radians(); 84 | 85 | let d_lon = (lon1 - lon2).abs(); 86 | 87 | // Numerator 88 | let a = (lat2.cos() * d_lon.sin()).powi(2); 89 | let b = lat1.cos() * lat2.sin(); 90 | let c = lat1.sin() * lat2.cos() * d_lon.cos(); 91 | let d = (b - c).powi(2); 92 | let e = (a + d).sqrt(); 93 | 94 | // Denominator 95 | let f = lat1.sin() * lat2.sin(); 96 | let g = lat1.cos() * lat2.cos() * d_lon.cos(); 97 | 98 | let h = f + g; 99 | 100 | let d_sigma = e.atan2(h); 101 | 102 | EARTH_RADIUS_KM * d_sigma 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use super::{haversine, vincenty}; 108 | 109 | #[test] 110 | fn haversine_sf_nyc() { 111 | let ny = (40.730610, -73.935242); 112 | let sf = (37.773972, -122.431297); 113 | assert_eq!((haversine(ny.0, ny.1, sf.0, sf.1) * 1000.) as u32, 4140175) 114 | } 115 | 116 | #[test] 117 | fn vincenty_sf_nyc() { 118 | let ny = (40.730610, -73.935242); 119 | let sf = (37.773972, -122.431297); 120 | assert_eq!((vincenty(ny.0, ny.1, sf.0, sf.1) * 1000.) as u32, 4140175) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/huffman_code.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Reverse; 2 | use std::collections::{BinaryHeap, VecDeque}; 3 | use std::fmt::Debug; 4 | use std::{cell::RefCell, rc::Rc}; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct HuffmanNode { 8 | character: Option, 9 | frequency: i32, 10 | left: Option>, 11 | right: Option>, 12 | } 13 | 14 | impl PartialEq for HuffmanNode { 15 | fn eq(&self, other: &Self) -> bool { 16 | self.frequency == other.frequency 17 | } 18 | } 19 | 20 | impl Eq for HuffmanNode {} 21 | 22 | impl PartialOrd for HuffmanNode { 23 | fn partial_cmp(&self, other: &Self) -> Option { 24 | Some(self.frequency.cmp(&other.frequency)) 25 | } 26 | } 27 | 28 | impl Ord for HuffmanNode { 29 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 30 | self.frequency.cmp(&other.frequency) 31 | } 32 | } 33 | 34 | type HuffmanNodeRef = Rc>>; 35 | 36 | fn min_node( 37 | q1: &mut VecDeque>, 38 | q2: &mut VecDeque>, 39 | ) -> HuffmanNode { 40 | if q1.is_empty() { 41 | return q2.pop_front().unwrap(); 42 | } 43 | 44 | if q2.is_empty() { 45 | return q1.pop_front().unwrap(); 46 | } 47 | 48 | if q1.front().unwrap() < q2.front().unwrap() { 49 | return q1.pop_front().unwrap(); 50 | } 51 | q2.pop_front().unwrap() 52 | } 53 | 54 | pub fn generate_huffman_code_from_sorted( 55 | v: &[(T, i32)], 56 | ) -> Vec<(T, std::string::String)> { 57 | if v.is_empty() { 58 | return Vec::new(); 59 | } 60 | 61 | let mut q1 = VecDeque::new(); 62 | let mut q2 = VecDeque::new(); 63 | 64 | for (t, f) in v { 65 | q1.push_back(HuffmanNode:: { 66 | character: Some(*t), 67 | frequency: *f, 68 | left: None, 69 | right: None, 70 | }) 71 | } 72 | 73 | while !q1.is_empty() || q2.len() > 1 { 74 | let left = min_node(&mut q1, &mut q2); 75 | let right = min_node(&mut q1, &mut q2); 76 | let node = HuffmanNode:: { 77 | character: None, // None signifies that this is an interior node 78 | frequency: left.frequency + right.frequency, 79 | left: Some(Rc::new(RefCell::new(left))), 80 | right: Some(Rc::new(RefCell::new(right))), 81 | }; 82 | q2.push_back(node); 83 | } 84 | 85 | let root = Rc::new(RefCell::new(q2.pop_front().unwrap())); 86 | 87 | retrieve_codebook(root) 88 | } 89 | 90 | fn retrieve_codebook(root: Rc>>) -> Vec<(T, String)> { 91 | // generate code book 92 | let mut code_book = Vec::new(); 93 | let mut stack = Vec::new(); 94 | stack.push((root.clone(), String::new())); 95 | 96 | while let Some((current, prefix)) = stack.pop() { 97 | if let Some(character) = current.borrow().character { 98 | code_book.push((character, prefix)); 99 | } else { 100 | if let Some(left) = current.borrow().left.clone() { 101 | stack.push((left, prefix.clone() + "0")); 102 | } 103 | if let Some(right) = current.borrow().right.clone() { 104 | stack.push((right, prefix.clone() + "1")); 105 | } 106 | }; 107 | } 108 | code_book 109 | } 110 | 111 | pub fn generate_huffman_code_from_unsorted( 112 | v: &[(T, i32)], 113 | ) -> Vec<(T, std::string::String)> { 114 | // TODO: add yet another implementation of using sort() + sorted construction. Include criterion numbers 115 | if v.is_empty() { 116 | return Vec::new(); 117 | } 118 | 119 | let mut q1 = BinaryHeap::new(); 120 | 121 | for (t, f) in v { 122 | q1.push(Reverse(Rc::new(RefCell::new(HuffmanNode:: { 123 | character: Some(*t), 124 | frequency: *f, 125 | left: None, 126 | right: None, 127 | })))); 128 | } 129 | 130 | while q1.len() > 1 { 131 | let Reverse(x) = q1.pop().unwrap(); 132 | let Reverse(y) = q1.pop().unwrap(); 133 | 134 | let f1 = x.borrow().frequency; 135 | let f2 = y.borrow().frequency; 136 | 137 | let node = Rc::new(RefCell::new(HuffmanNode:: { 138 | character: None, 139 | frequency: f1 + f2, 140 | left: Some(x), 141 | right: Some(y), 142 | })); 143 | q1.push(Reverse(node)); 144 | } 145 | 146 | let Reverse(root) = q1.pop().unwrap(); 147 | retrieve_codebook(root) 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use crate::huffman_code::HuffmanNode; 153 | 154 | use super::generate_huffman_code_from_sorted; 155 | use super::generate_huffman_code_from_unsorted; 156 | 157 | #[test] 158 | fn run_with_empty_input_unsorted() { 159 | let input = Vec::<(char, i32)>::new(); 160 | let code_book = generate_huffman_code_from_unsorted(&input); 161 | assert_eq!(0, code_book.len()); 162 | } 163 | 164 | #[test] 165 | fn run_with_empty_input_sorted() { 166 | let input = Vec::<(char, i32)>::new(); 167 | let code_book = generate_huffman_code_from_sorted(&input); 168 | assert_eq!(0, code_book.len()); 169 | } 170 | 171 | #[test] 172 | fn construction_unsorted() { 173 | let v = [ 174 | ('a', 5), 175 | ('b', 9), 176 | ('c', 12), 177 | ('d', 13), 178 | ('e', 16), 179 | ('f', 45), 180 | ]; 181 | let code_book = generate_huffman_code_from_unsorted(&v); 182 | let expected = [ 183 | ('f', "0"), 184 | ('c', "100"), 185 | ('d', "101"), 186 | ('a', "1100"), 187 | ('b', "1101"), 188 | ('e', "111"), 189 | ]; 190 | 191 | let matching = code_book 192 | .iter() 193 | .rev() 194 | .zip(&expected) 195 | .filter(|&(a, b)| a.0 == b.0 && a.1 == b.1) 196 | .count(); 197 | 198 | assert!(matching == code_book.len()); 199 | assert!(matching == expected.len()); 200 | } 201 | 202 | #[test] 203 | fn construction_sorted() { 204 | let v = [ 205 | ('a', 5), 206 | ('b', 9), 207 | ('c', 12), 208 | ('d', 13), 209 | ('e', 16), 210 | ('f', 45), 211 | ]; 212 | let code_book = generate_huffman_code_from_sorted(&v); 213 | let expected = [ 214 | ('f', "0"), 215 | ('c', "100"), 216 | ('d', "101"), 217 | ('a', "1100"), 218 | ('b', "1101"), 219 | ('e', "111"), 220 | ]; 221 | 222 | let matching = code_book 223 | .iter() 224 | .rev() 225 | .zip(&expected) 226 | .filter(|&(a, b)| a.0 == b.0 && a.1 == b.1) 227 | .count(); 228 | 229 | assert!(matching == code_book.len()); 230 | assert!(matching == expected.len()); 231 | } 232 | 233 | #[test] 234 | fn ord() { 235 | let node1 = HuffmanNode { 236 | character: Some('a'), 237 | frequency: 5, 238 | left: None, 239 | right: None, 240 | }; 241 | let node2 = HuffmanNode { 242 | character: Some('b'), 243 | frequency: 9, 244 | left: None, 245 | right: None, 246 | }; 247 | let node3 = HuffmanNode { 248 | character: Some('c'), 249 | frequency: 5, 250 | left: None, 251 | right: None, 252 | }; 253 | assert!(node1 < node2); 254 | assert!(node2 > node1); 255 | assert_eq!(node1, node3); 256 | } 257 | 258 | #[test] 259 | fn partial_ord() { 260 | let node1 = HuffmanNode { 261 | character: Some('a'), 262 | frequency: 5, 263 | left: None, 264 | right: None, 265 | }; 266 | let node2 = HuffmanNode { 267 | character: Some('b'), 268 | frequency: 9, 269 | left: None, 270 | right: None, 271 | }; 272 | assert!(node1 < node2); 273 | assert!(node2 > node1); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{self, BufRead, BufReader}, 4 | path::Path, 5 | }; 6 | 7 | use bincode::{config, decode_from_std_read}; 8 | use itertools::Itertools; 9 | 10 | use crate::edge::{InputEdge, TrivialEdge}; 11 | 12 | // The output is wrapped in a Result to allow matching on errors 13 | // Returns an Iterator to the Reader of the lines of the file. 14 | pub fn read_lines

(filename: P) -> io::Result>> 15 | where 16 | P: AsRef, 17 | { 18 | let file = File::open(filename)?; 19 | Ok(io::BufReader::new(file).lines()) 20 | } 21 | 22 | pub fn read_graph_into_trivial_edges(filename: &str) -> Vec { 23 | let mut reader = BufReader::new(File::open(filename).unwrap()); 24 | let config = config::standard(); 25 | 26 | let input_edges: Vec> = decode_from_std_read(&mut reader, config).unwrap(); 27 | 28 | input_edges 29 | .iter() 30 | .map(|edge| TrivialEdge { 31 | source: edge.source, 32 | target: edge.target, 33 | }) 34 | .collect_vec() 35 | } 36 | 37 | pub fn read_vec_from_file>(filename: &str) -> Vec { 38 | let mut reader = BufReader::new(File::open(filename).unwrap()); 39 | let config = config::standard(); 40 | decode_from_std_read(&mut reader, config).unwrap() 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | use std::io::Write; 47 | use tempfile::NamedTempFile; 48 | 49 | // Test `read_lines` function 50 | #[test] 51 | fn test_read_lines() { 52 | // Create a temporary file with some lines 53 | let mut file = NamedTempFile::new().unwrap(); 54 | writeln!(file, "line1").unwrap(); 55 | writeln!(file, "line2").unwrap(); 56 | writeln!(file, "line3").unwrap(); 57 | 58 | // Read lines from the file 59 | let lines = read_lines(file.path()).unwrap(); 60 | let lines: Vec = lines.map(|line| line.unwrap()).collect(); 61 | 62 | // Verify the lines are read correctly 63 | assert_eq!(lines, vec!["line1", "line2", "line3"]); 64 | } 65 | 66 | // Test `read_lines` with a non-existent file 67 | #[test] 68 | fn test_read_lines_nonexistent_file() { 69 | let result = read_lines("nonexistent_file.txt"); 70 | assert!(result.is_err()); 71 | } 72 | 73 | // Test `read_graph_into_trivial_edges` function 74 | #[test] 75 | fn test_read_graph_into_trivial_edges() { 76 | // Define test input edges 77 | #[derive(bincode::Encode)] 78 | struct TestEdge { 79 | source: usize, 80 | target: usize, 81 | weight: usize, 82 | } 83 | 84 | let input_edges = vec![ 85 | TestEdge { 86 | source: 1, 87 | target: 2, 88 | weight: 10, 89 | }, 90 | TestEdge { 91 | source: 2, 92 | target: 3, 93 | weight: 20, 94 | }, 95 | ]; 96 | 97 | // Serialize the input edges to a temporary file 98 | let mut file = NamedTempFile::new().unwrap(); 99 | let config = config::standard(); 100 | bincode::encode_into_std_write(&input_edges, &mut file, config).unwrap(); 101 | 102 | // Read the graph into trivial edges 103 | let trivial_edges = read_graph_into_trivial_edges(file.path().to_str().unwrap()); 104 | 105 | // Verify the output 106 | assert_eq!(trivial_edges.len(), 2); 107 | assert_eq!(trivial_edges[0].source, 1); 108 | assert_eq!(trivial_edges[0].target, 2); 109 | assert_eq!(trivial_edges[1].source, 2); 110 | assert_eq!(trivial_edges[1].target, 3); 111 | } 112 | 113 | // Test `read_graph_into_trivial_edges` with a non-existent file 114 | #[test] 115 | #[should_panic] 116 | fn test_read_graph_into_trivial_edges_nonexistent_file() { 117 | read_graph_into_trivial_edges("nonexistent_file.bin"); 118 | } 119 | 120 | // Test `read_vec_from_file` function 121 | #[test] 122 | fn test_read_vec_from_file() { 123 | // Define test data 124 | let test_data = vec![1, 2, 3, 4, 5]; 125 | 126 | // Serialize the test data to a temporary file 127 | let mut file = NamedTempFile::new().unwrap(); 128 | let config = config::standard(); 129 | bincode::encode_into_std_write(&test_data, &mut file, config).unwrap(); 130 | 131 | // Read the vector from the file 132 | let result: Vec = read_vec_from_file(file.path().to_str().unwrap()); 133 | 134 | // Verify the output 135 | assert_eq!(result, test_data); 136 | } 137 | 138 | // Test `read_vec_from_file` with a custom struct 139 | #[test] 140 | fn test_read_vec_from_file_with_custom_struct() { 141 | // Define a custom struct for testing 142 | #[derive(Debug, PartialEq, bincode::Encode, bincode::Decode)] 143 | struct TestStruct { 144 | id: u64, 145 | name: String, 146 | } 147 | 148 | let test_data = vec![ 149 | TestStruct { 150 | id: 1, 151 | name: "Alice".to_string(), 152 | }, 153 | TestStruct { 154 | id: 2, 155 | name: "Bob".to_string(), 156 | }, 157 | ]; 158 | 159 | // Serialize the test data to a temporary file 160 | let mut file = NamedTempFile::new().unwrap(); 161 | let config = config::standard(); 162 | bincode::encode_into_std_write(&test_data, &mut file, config).unwrap(); 163 | 164 | // Read the vector from the file 165 | let result: Vec = read_vec_from_file(file.path().to_str().unwrap()); 166 | 167 | // Verify the output 168 | assert_eq!(result, test_data); 169 | } 170 | 171 | // Test `read_vec_from_file` with a non-existent file 172 | #[test] 173 | #[should_panic] 174 | fn test_read_vec_from_file_nonexistent_file() { 175 | read_vec_from_file::("nonexistent_file.bin"); 176 | } 177 | 178 | // Test `read_vec_from_file` with invalid data 179 | #[test] 180 | #[should_panic] 181 | fn test_read_vec_from_file_invalid_data() { 182 | // Create a temporary file with invalid binary data 183 | let mut file = NamedTempFile::new().unwrap(); 184 | file.write_all(b"invalid binary data").unwrap(); 185 | 186 | // Attempt to read the invalid data 187 | let _: Vec = read_vec_from_file(file.path().to_str().unwrap()); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/k_way_merge_iterator.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Iterator; 2 | 3 | use crate::{merge_entry::MergeEntry, merge_tree::MergeTree}; 4 | 5 | pub struct KWayMergeIterator<'a, T, I: Iterator, M: MergeTree> { 6 | heap: M, 7 | list: &'a mut [I], 8 | } 9 | 10 | impl<'a, T: std::cmp::Ord, I: Iterator, M: MergeTree> KWayMergeIterator<'a, T, I, M> { 11 | pub fn new(list: &'a mut [I], mut heap: M) -> Self { 12 | for (i, iterator) in list.iter_mut().enumerate() { 13 | if let Some(first) = iterator.next() { 14 | heap.push(MergeEntry { 15 | item: first, 16 | index: i, 17 | }); 18 | } 19 | } 20 | Self { heap, list } 21 | } 22 | } 23 | 24 | impl, M: MergeTree> Iterator 25 | for KWayMergeIterator<'_, T, I, M> 26 | { 27 | type Item = T; 28 | 29 | fn next(&mut self) -> Option { 30 | let MergeEntry { 31 | item: value, 32 | index: list, 33 | } = self.heap.pop()?; 34 | if let Some(next) = self.list[list].next() { 35 | self.heap.push(MergeEntry { 36 | item: next, 37 | index: list, 38 | }); 39 | } 40 | Some(value) 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod test { 46 | use std::collections::BinaryHeap; 47 | 48 | use crate::k_way_merge_iterator::MergeEntry; 49 | 50 | #[test] 51 | fn four_way_merge() { 52 | let mut list = vec![ 53 | vec![1, 3, 5, 7, 9].into_iter(), 54 | vec![2, 4, 6, 8, 10].into_iter(), 55 | vec![11, 13, 15, 17, 19].into_iter(), 56 | vec![12, 14, 16, 18, 20].into_iter(), 57 | ]; 58 | let heap = BinaryHeap::new(); 59 | let k_way_merge = super::KWayMergeIterator::new(&mut list, heap); 60 | let result: Vec<_> = k_way_merge.collect(); 61 | assert_eq!( 62 | result, 63 | vec![ 64 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 65 | ] 66 | ); 67 | } 68 | 69 | #[test] 70 | fn three_way_merge_of_differently_sized_sequences() { 71 | let mut list = vec![ 72 | vec![1, 3, 5, 6, 7, 12].into_iter(), 73 | vec![2, 4, 8, 11].into_iter(), 74 | vec![9, 10].into_iter(), 75 | ]; 76 | let heap = BinaryHeap::new(); 77 | let k_way_merge = super::KWayMergeIterator::new(&mut list, heap); 78 | let result: Vec<_> = k_way_merge.collect(); 79 | assert_eq!(result, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); 80 | } 81 | 82 | #[test] 83 | fn merge_of_empty_sequences() { 84 | let mut list: Vec> = vec![vec![].into_iter(), vec![].into_iter()]; 85 | let heap = BinaryHeap::new(); 86 | let k_way_merge = super::KWayMergeIterator::new(&mut list, heap); 87 | let result: Vec<_> = k_way_merge.collect(); 88 | assert_eq!(result, Vec::::new()); 89 | } 90 | 91 | #[test] 92 | fn test_merge_entry_ordering() { 93 | let entry1 = MergeEntry { item: 2, index: 0 }; 94 | let entry2 = MergeEntry { item: 1, index: 1 }; 95 | let entry3 = MergeEntry { item: 3, index: 1 }; 96 | 97 | // check ascending order 98 | assert!(entry1 < entry2); // Note the reverse order 99 | assert!(entry1 > entry3); // Note the reverse order 100 | assert!(entry2 > entry3); // Note the reverse order 101 | 102 | let mut heap = BinaryHeap::new(); 103 | heap.push(entry1); 104 | heap.push(entry2); 105 | heap.push(entry3); 106 | 107 | // output: 1, 2, 3 108 | assert_eq!(heap.pop().unwrap().item, 1); 109 | assert_eq!(heap.pop().unwrap().item, 2); 110 | assert_eq!(heap.pop().unwrap().item, 3); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/kruskal.rs: -------------------------------------------------------------------------------- 1 | /// Implementation of Kruskal's Minimum Spanning Tree algorithm. 2 | /// 3 | /// This module provides an implementation of Kruskal's algorithm for finding 4 | /// the minimum spanning tree (MST) of an undirected weighted graph. 5 | use crate::{edge::SimpleEdge, union_find::UnionFind}; 6 | use core::cmp::{Reverse, max}; 7 | use std::collections::BinaryHeap; 8 | 9 | /// Computes the minimum spanning tree using Kruskal's algorithm. 10 | /// 11 | /// # Arguments 12 | /// 13 | /// * `input_edges` - A slice of `SimpleEdge`s representing the graph's edge 14 | /// Each edge contains source and target vertices and a weight. 15 | /// 16 | /// # Returns 17 | /// 18 | /// Returns a tuple containing: 19 | /// * The total cost of the minimum spanning tree 20 | /// * A vector of edges that form the minimum spanning tree 21 | /// 22 | /// # Example 23 | /// 24 | /// ``` 25 | /// use toolbox_rs::edge::SimpleEdge; 26 | /// use toolbox_rs::kruskal::kruskal; 27 | /// 28 | /// let edges = vec![ 29 | /// SimpleEdge::new(0, 1, 7), 30 | /// SimpleEdge::new(0, 3, 5), 31 | /// SimpleEdge::new(1, 2, 8) 32 | /// ]; 33 | /// 34 | /// let (cost, mst) = kruskal(&edges); 35 | /// ``` 36 | pub fn kruskal(input_edges: &[SimpleEdge]) -> (u32, Vec) { 37 | // find max node id 38 | let mut number_of_nodes = 0; 39 | let mut heap = BinaryHeap::new(); 40 | for edge in input_edges { 41 | number_of_nodes = max(edge.source, number_of_nodes); 42 | number_of_nodes = max(edge.target, number_of_nodes); 43 | heap.push((Reverse(edge.data), heap.len())); 44 | } 45 | 46 | let mut mst = Vec::new(); 47 | let mut uf = UnionFind::new(number_of_nodes + 1); 48 | let mut mst_cost = 0; 49 | 50 | while mst.len() < number_of_nodes && !heap.is_empty() { 51 | // pop the smallest edge 52 | // we use the index to avoid having to sort the edges 53 | // in the heap 54 | // this is a bit of a hack, but it works 55 | // and is faster than sorting the edges 56 | // in the heap 57 | let (_, idx) = heap.pop().unwrap(); 58 | let edge = input_edges[idx]; 59 | let x = uf.find(edge.source); 60 | let y = uf.find(edge.target); 61 | 62 | if x == y { 63 | continue; 64 | } 65 | 66 | mst.push(edge); 67 | uf.union(x, y); 68 | mst_cost += edge.data; 69 | } 70 | (mst_cost, mst) 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use crate::{edge::SimpleEdge, kruskal::kruskal}; 76 | 77 | #[test] 78 | fn wiki_example() { 79 | let edges = vec![ 80 | SimpleEdge::new(0, 1, 7), 81 | SimpleEdge::new(0, 3, 5), 82 | SimpleEdge::new(1, 3, 9), 83 | SimpleEdge::new(1, 2, 8), 84 | SimpleEdge::new(1, 4, 7), 85 | SimpleEdge::new(2, 4, 5), 86 | SimpleEdge::new(3, 4, 15), 87 | SimpleEdge::new(3, 5, 6), 88 | SimpleEdge::new(5, 4, 8), 89 | SimpleEdge::new(6, 4, 9), 90 | SimpleEdge::new(5, 6, 11), 91 | ]; 92 | 93 | let (cost, mst) = kruskal(&edges); 94 | assert_eq!(cost, 39); 95 | 96 | // Verify the expected edges in the MST 97 | let expected_edges: Vec<(usize, usize)> = 98 | vec![(0, 3), (2, 4), (3, 5), (0, 1), (1, 4), (4, 6)]; 99 | 100 | assert_eq!(mst.len(), expected_edges.len()); 101 | for (src, tgt) in expected_edges { 102 | assert!( 103 | mst.iter().any(|e| (e.source == src && e.target == tgt) 104 | || (e.source == tgt && e.target == src)) 105 | ); 106 | } 107 | } 108 | 109 | #[test] 110 | fn clr_example() { 111 | let edges = vec![ 112 | SimpleEdge::new(0, 1, 16), 113 | SimpleEdge::new(0, 2, 13), 114 | SimpleEdge::new(1, 2, 10), 115 | SimpleEdge::new(1, 3, 12), 116 | SimpleEdge::new(2, 1, 4), 117 | SimpleEdge::new(2, 4, 14), 118 | SimpleEdge::new(3, 2, 9), 119 | SimpleEdge::new(3, 5, 20), 120 | SimpleEdge::new(4, 3, 7), 121 | SimpleEdge::new(4, 5, 4), 122 | ]; 123 | 124 | let (cost, mst) = kruskal(&edges); 125 | assert_eq!(cost, 37); 126 | 127 | // Verify the expected edges in the MST 128 | let expected_edges: Vec<(usize, usize)> = vec![(2, 1), (4, 5), (4, 3), (0, 2), (2, 3)]; 129 | 130 | // Check if the MST contains the expected edges 131 | // Note: The order of edges in the MST may vary, so we check for presence 132 | // rather than exact order 133 | 134 | assert_eq!(mst.len(), expected_edges.len()); 135 | for (src, tgt) in expected_edges { 136 | assert!( 137 | mst.iter().any(|e| (e.source == src && e.target == tgt) 138 | || (e.source == tgt && e.target == src)) 139 | ); 140 | } 141 | } 142 | 143 | #[test] 144 | fn empty_graph() { 145 | let edges = vec![]; 146 | let (cost, mst) = kruskal(&edges); 147 | assert_eq!(cost, 0); 148 | assert!(mst.is_empty()); 149 | } 150 | 151 | #[test] 152 | fn single_edge() { 153 | let edges = vec![SimpleEdge::new(0, 1, 5)]; 154 | let (cost, mst) = kruskal(&edges); 155 | assert_eq!(cost, 5); 156 | assert_eq!(mst.len(), 1); 157 | assert_eq!(mst[0].data, 5); 158 | } 159 | 160 | #[test] 161 | fn disconnected_graph() { 162 | let edges = vec![ 163 | SimpleEdge::new(0, 1, 1), 164 | SimpleEdge::new(2, 3, 2), 165 | // No edges between components (0,1) and (2,3) 166 | ]; 167 | let (cost, mst) = kruskal(&edges); 168 | assert_eq!(cost, 3); 169 | assert_eq!(mst.len(), 2); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/level_directory.rs: -------------------------------------------------------------------------------- 1 | use crate::partition_id::PartitionID; 2 | 3 | /// Note: LevelDirectory is a poor naming choice. 4 | /// This struct encapsulates the logic to decide whether 5 | /// - two nodes are within the same cell at a given level 6 | /// - TBD. 7 | pub struct LevelDirectory<'a> { 8 | partition_ids: &'a [PartitionID], 9 | levels: &'a [u32], 10 | } 11 | 12 | impl<'a> LevelDirectory<'a> { 13 | pub fn new(partition_ids: &'a [PartitionID], levels: &'a [u32]) -> Self { 14 | Self { 15 | partition_ids, 16 | levels, 17 | } 18 | } 19 | pub fn crosses_at_level(&self, u: usize, v: usize, level: u32) -> bool { 20 | let u_id = self.partition_ids[u]; 21 | let v_id = self.partition_ids[v]; 22 | let u_id = u_id.parent_at_level(level); 23 | let v_id = v_id.parent_at_level(level); 24 | u_id != v_id 25 | } 26 | 27 | // return a slice of all the levels where two nodes are in different cells 28 | pub fn get_crossing_levels(&self, u: usize, v: usize) -> &[u32] { 29 | let mut i = 0; 30 | for level in self.levels { 31 | if self.crosses_at_level(u, v, *level) { 32 | i += 1; 33 | } else { 34 | break; 35 | } 36 | } 37 | &self.levels[..i] 38 | } 39 | } 40 | 41 | // TODO: Add tests 42 | // let id = PartitionID::new(0xffff_ffff); 43 | // for l in &levels { 44 | // // TODO: remove 45 | // println!("[{l:#02}] {:#032b}", id.parent_at_level(*l)); 46 | // } 47 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod addressable_binary_heap; 2 | pub mod as_bytes; 3 | pub mod bfs; 4 | pub mod bin_pack; 5 | pub mod bit_weight_iterator; 6 | pub mod bitset_subset_iterator; 7 | pub mod bloom_filter; 8 | pub mod bounding_box; 9 | pub mod cell; 10 | pub mod complete_graph; 11 | pub mod convex_hull; 12 | pub mod count_min_sketch; 13 | pub mod cycle_check; 14 | pub mod ddsg; 15 | pub mod dfs; 16 | pub mod dimacs; 17 | pub mod dinic; 18 | pub mod dynamic_graph; 19 | pub mod edge; 20 | pub mod edmonds_karp; 21 | pub mod enumerative_source_coding; 22 | pub mod fast_hash_trait; 23 | pub mod fenwick; 24 | pub mod fibonacci_hash; 25 | pub mod ford_fulkerson; 26 | pub mod geometry; 27 | pub mod graph; 28 | pub mod great_circle; 29 | pub mod huffman_code; 30 | pub mod inertial_flow; 31 | pub mod io; 32 | pub mod k_way_merge_iterator; 33 | pub mod kruskal; 34 | pub mod level_directory; 35 | pub mod linked_list; 36 | pub mod loser_tree; 37 | pub mod lru; 38 | pub mod math; 39 | pub mod max_flow; 40 | pub mod medium_size_hash_set; 41 | pub mod medium_size_hash_table; 42 | pub mod mercator; 43 | pub mod merge_entry; 44 | pub mod merge_tree; 45 | pub mod metis; 46 | pub mod one_iterator; 47 | pub mod one_to_many_dijkstra; 48 | pub mod partition_id; 49 | pub mod path_based_scc; 50 | pub mod polyline; 51 | pub mod prim_complete_graph; 52 | pub mod r_tree; 53 | pub mod rdx_sort; 54 | pub mod renumbering_table; 55 | pub mod run_iterator; 56 | pub mod single_linked_list; 57 | pub mod space_filling_curve; 58 | pub mod static_graph; 59 | pub mod tabulation_hash; 60 | pub mod tarjan; 61 | pub mod tiny_table; 62 | pub mod top_k; 63 | pub mod tsplib; 64 | pub mod unidirectional_dijkstra; 65 | pub mod union_find; 66 | pub mod unsafe_slice; 67 | pub mod vector_tile; 68 | pub mod wgs84; 69 | 70 | #[macro_export] 71 | macro_rules! invoke_macro_for_types { 72 | ($macro:ident, $($args:ident),*) => { 73 | $( $macro!($args); )* 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/loser_tree.rs: -------------------------------------------------------------------------------- 1 | use crate::{merge_entry::MergeEntry, merge_tree::MergeTree}; 2 | 3 | pub struct LoserTree 4 | where 5 | T: Ord, 6 | { 7 | /// array of loser indices 8 | losers: Vec, 9 | /// array of leaves 10 | leaves: Vec>>, 11 | /// index of the winner 12 | winner: usize, 13 | size: usize, 14 | } 15 | 16 | impl LoserTree { 17 | pub fn with_capacity(capacity: usize) -> Self { 18 | let size = capacity.next_power_of_two(); 19 | let mut losers = Vec::with_capacity(size - 1); 20 | let mut leaves = Vec::with_capacity(size); 21 | 22 | losers.resize(size - 1, 0); 23 | leaves.resize(size, None); 24 | Self { 25 | losers, 26 | leaves, 27 | winner: 0, 28 | size: 0, 29 | } 30 | } 31 | 32 | /// play a match between two leaves and return the index of the winner 33 | #[inline] 34 | fn play_match(&mut self, pos1: usize, pos2: usize) -> usize { 35 | use std::cmp::Ordering; 36 | 37 | match (&self.leaves[pos1], &self.leaves[pos2]) { 38 | (None, _) => pos2, 39 | (_, None) => pos1, 40 | (Some(v1), Some(v2)) => match v1.cmp(v2) { 41 | Ordering::Greater => pos1, 42 | _ => pos2, 43 | }, 44 | } 45 | } 46 | 47 | /// rebuild only the path from leaf i to the root 48 | fn rebuild_path(&mut self, mut i: usize) { 49 | let n = self.leaves.len(); 50 | let internal_nodes = n - 1; 51 | 52 | // convert leaf index to internal node index 53 | i += internal_nodes; 54 | 55 | // walk up the tree till the root 56 | while i > 0 { 57 | // find parent node 58 | let parent = (i - 1) / 2; 59 | 60 | // sibling of the current node either to the left or to the right 61 | let sibling = if i % 2 == 0 { i - 1 } else { i + 1 }; 62 | 63 | // determine the winner of the match 64 | let winner = self.play_match( 65 | if i >= internal_nodes { 66 | i - internal_nodes 67 | } else { 68 | self.losers[i] 69 | }, 70 | if sibling >= internal_nodes { 71 | sibling - internal_nodes 72 | } else { 73 | self.losers[sibling] 74 | }, 75 | ); 76 | 77 | self.losers[parent] = winner; 78 | i = parent; 79 | } 80 | 81 | self.winner = self.losers[0]; 82 | } 83 | 84 | pub fn clear(&mut self) { 85 | self.size = 0; 86 | self.winner = 0; 87 | } 88 | 89 | pub fn capacity(&self) -> usize { 90 | self.leaves.len() 91 | } 92 | } 93 | 94 | impl MergeTree for LoserTree { 95 | fn push(&mut self, item: MergeEntry) { 96 | debug_assert!(item.index < self.leaves.len(), "index out of bounds"); 97 | 98 | let index = item.index; 99 | self.leaves[index] = Some(item); 100 | 101 | self.rebuild_path(index); 102 | self.size += 1; 103 | } 104 | 105 | fn pop(&mut self) -> std::option::Option> { 106 | let winner = self.leaves[self.winner].take(); 107 | if winner.is_some() { 108 | self.size -= 1; 109 | self.rebuild_path(self.winner); // O(log n) operation 110 | } 111 | winner 112 | } 113 | 114 | fn is_empty(&self) -> bool { 115 | self.size == 0 116 | } 117 | 118 | fn len(&self) -> usize { 119 | self.size 120 | } 121 | } 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | use super::*; 126 | 127 | #[test] 128 | fn test_new_loser_tree() { 129 | let tree: LoserTree = LoserTree::with_capacity(3); 130 | assert_eq!(tree.leaves.len(), 4); // next higher power of 2 131 | assert_eq!(tree.losers.len(), 3); // internal nodes 132 | assert!(tree.is_empty()); 133 | assert_eq!(tree.len(), 0); 134 | } 135 | 136 | #[test] 137 | fn test_push_and_pop() { 138 | let mut tree = LoserTree::with_capacity(4); 139 | 140 | tree.push(MergeEntry { item: 3, index: 0 }); 141 | tree.push(MergeEntry { item: 1, index: 1 }); 142 | tree.push(MergeEntry { item: 4, index: 2 }); 143 | tree.push(MergeEntry { item: 2, index: 3 }); 144 | 145 | assert_eq!(tree.len(), 4); 146 | assert!(!tree.is_empty()); 147 | 148 | // items shall be sorted 149 | assert_eq!(tree.pop().unwrap().item, 1); 150 | assert_eq!(tree.pop().unwrap().item, 2); 151 | assert_eq!(tree.pop().unwrap().item, 3); 152 | assert_eq!(tree.pop().unwrap().item, 4); 153 | 154 | assert!(tree.is_empty()); 155 | assert_eq!(tree.pop(), None); 156 | } 157 | 158 | #[test] 159 | fn test_partial_fill() { 160 | let mut tree = LoserTree::with_capacity(4); 161 | 162 | tree.push(MergeEntry { item: 2, index: 0 }); 163 | tree.push(MergeEntry { item: 1, index: 1 }); 164 | 165 | assert_eq!(tree.len(), 2); 166 | 167 | assert_eq!(tree.pop().unwrap().item, 1); 168 | assert_eq!(tree.pop().unwrap().item, 2); 169 | assert!(tree.pop().is_none()); 170 | } 171 | 172 | #[test] 173 | fn test_rebuild_after_pop() { 174 | let mut tree = LoserTree::with_capacity(4); 175 | 176 | tree.push(MergeEntry { item: 3, index: 0 }); 177 | tree.push(MergeEntry { item: 1, index: 1 }); 178 | tree.push(MergeEntry { item: 4, index: 2 }); 179 | 180 | assert_eq!(tree.pop().unwrap().item, 1); 181 | tree.push(MergeEntry { item: 2, index: 1 }); 182 | 183 | assert_eq!(tree.pop().unwrap().item, 2); 184 | assert_eq!(tree.pop().unwrap().item, 3); 185 | assert_eq!(tree.pop().unwrap().item, 4); 186 | } 187 | 188 | #[test] 189 | fn test_merge_tree_interface() { 190 | let mut tree = LoserTree::with_capacity(4); 191 | 192 | // Test empty state 193 | assert!(tree.is_empty()); 194 | assert_eq!(tree.len(), 0); 195 | assert!(tree.pop().is_none()); 196 | 197 | // Test pushing elements 198 | tree.push(MergeEntry { item: 3, index: 0 }); 199 | assert_eq!(tree.len(), 1); 200 | assert!(!tree.is_empty()); 201 | 202 | tree.push(MergeEntry { item: 1, index: 1 }); 203 | assert_eq!(tree.len(), 2); 204 | 205 | // Test popping elements in order 206 | assert_eq!(tree.pop().unwrap().item, 1); 207 | assert_eq!(tree.len(), 1); 208 | 209 | assert_eq!(tree.pop().unwrap().item, 3); 210 | assert_eq!(tree.len(), 0); 211 | assert!(tree.is_empty()); 212 | } 213 | 214 | #[test] 215 | #[should_panic(expected = "index out of bounds")] 216 | fn test_push_invalid_index() { 217 | let mut tree = LoserTree::with_capacity(2); 218 | tree.push(MergeEntry { item: 1, index: 2 }); // index out of bounds 219 | } 220 | 221 | #[test] 222 | fn test_clear() { 223 | let mut tree = LoserTree::with_capacity(4); 224 | tree.push(MergeEntry { item: 1, index: 0 }); 225 | tree.clear(); 226 | assert!(tree.is_empty()); 227 | assert_eq!(tree.len(), 0); 228 | } 229 | 230 | #[test] 231 | fn test_capacity() { 232 | let tree = LoserTree::::with_capacity(3); 233 | assert_eq!(tree.capacity(), 4); // nächste Zweierpotenz 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use num::{Integer, PrimInt}; 2 | 3 | pub fn choose(n: u64, k: u64) -> u64 { 4 | if k > n { 5 | return 0; 6 | } 7 | 8 | if k == 0 || k == n { 9 | return 1; 10 | } 11 | 12 | let k = if k > n - k { n - k } else { k }; 13 | 14 | let mut result = 1; 15 | for i in 1..=k { 16 | result = result * (n - i + 1) / i; 17 | } 18 | 19 | result 20 | } 21 | 22 | /// calculate the largest power of 2 less or equal to n 23 | pub fn prev_power_of_two(n: T) -> T { 24 | if n == T::zero() { 25 | return T::zero(); 26 | } 27 | let leading_zeros = n.leading_zeros() as usize; 28 | let sizeof = 8 * std::mem::size_of::(); 29 | T::one() << (sizeof - leading_zeros - 1) 30 | } 31 | 32 | /// computes the least-significant bit set. 33 | pub fn lsb_index(n: T) -> Option { 34 | if n == T::zero() { 35 | return None; 36 | } 37 | 38 | Some(n.trailing_zeros()) 39 | } 40 | 41 | /// computes the least-significant bit set. Doesn't return the correct answer if the input is zero 42 | pub fn non_zero_lsb_index(n: T) -> u32 { 43 | if n == T::zero() { 44 | return 0; 45 | } 46 | 47 | n.trailing_zeros() 48 | } 49 | 50 | /// Evaluates a polynomial using Horner's method. 51 | /// 52 | /// Given a polynomial in the form a₀xⁿ + a₁xⁿ⁻¹ + ... + aₙ₋₁x + aₙ, 53 | /// the coefficients should be provided in reverse order: [a₀, a₁, ..., aₙ]. 54 | /// 55 | /// # Arguments 56 | /// 57 | /// * `x` - The value at which to evaluate the polynomial 58 | /// * `coefficients` - The coefficients of the polynomial in descending order of degree 59 | /// 60 | /// # Examples 61 | /// 62 | /// ``` 63 | /// use toolbox_rs::math::horner; 64 | /// 65 | /// // Evaluate 2x² + 3x + 1 at x = 2 66 | /// let coefficients = [2.0, 3.0, 1.0]; 67 | /// assert_eq!(horner(2.0, &coefficients), 15.0); 68 | /// 69 | /// // Evaluate constant polynomial f(x) = 5 70 | /// assert_eq!(horner(42.0, &[5.0]), 5.0); 71 | /// 72 | /// // Empty coefficient array represents the zero polynomial 73 | /// assert_eq!(horner(1.0, &[]), 0.0); 74 | /// ``` 75 | pub fn horner(x: f64, coefficients: &[f64]) -> f64 { 76 | coefficients.iter().fold(0.0, |acc, &coeff| acc * x + coeff) 77 | } 78 | 79 | /// Encodes a signed integer into an unsigned integer using zigzag encoding. 80 | /// 81 | /// ZigZag encoding maps signed integers to unsigned integers in a way that preserves 82 | /// magnitude ordering while using fewer bits for small negative values. 83 | /// 84 | /// # Examples 85 | /// 86 | /// ``` 87 | /// use toolbox_rs::math::zigzag_encode; 88 | /// 89 | /// assert_eq!(zigzag_encode(0i32), 0u32); 90 | /// assert_eq!(zigzag_encode(-1i32), 1u32); 91 | /// assert_eq!(zigzag_encode(1i32), 2u32); 92 | /// assert_eq!(zigzag_encode(-2i32), 3u32); 93 | /// ``` 94 | pub fn zigzag_encode(value: i32) -> u32 { 95 | ((value << 1) ^ (value >> 31)) as u32 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use crate::math::{ 101 | choose, horner, lsb_index, non_zero_lsb_index, prev_power_of_two, zigzag_encode, 102 | }; 103 | 104 | #[test] 105 | fn some_well_known_n_choose_k_values() { 106 | let test_cases = [ 107 | ((64u64, 1u64), 64u64), 108 | ((64, 63), 64), 109 | ((9, 4), 126), 110 | ((10, 5), 252), 111 | ((50, 2), 1_225), 112 | ((5, 2), 10), 113 | ((10, 4), 210), 114 | ((37, 17), 15905368710), 115 | ((52, 5), 2598960), 116 | ]; 117 | 118 | test_cases.into_iter().for_each(|((n, k), expected)| { 119 | assert_eq!(choose(n, k), expected); 120 | }); 121 | } 122 | 123 | #[test] 124 | fn lsb_well_known_values() { 125 | assert_eq!(lsb_index(0), None); 126 | assert_eq!(lsb_index(10), Some(1)); 127 | assert_eq!(lsb_index(16), Some(4)); 128 | assert_eq!(lsb_index(255), Some(0)); 129 | assert_eq!(lsb_index(1024), Some(10)); 130 | assert_eq!(lsb_index(72057594037927936_i64), Some(56)); 131 | } 132 | 133 | #[test] 134 | fn lsb_index_well_known_values() { 135 | assert_eq!(non_zero_lsb_index(0), 0); 136 | assert_eq!(non_zero_lsb_index(10), 1); 137 | assert_eq!(non_zero_lsb_index(16), 4); 138 | assert_eq!(non_zero_lsb_index(255), 0); 139 | assert_eq!(non_zero_lsb_index(1024), 10); 140 | assert_eq!(non_zero_lsb_index(72057594037927936_i64), 56); 141 | } 142 | 143 | #[test] 144 | fn largest_power_of_two_less_or_equal() { 145 | assert_eq!(prev_power_of_two(16_u8), 16); 146 | assert_eq!(prev_power_of_two(17_i32), 16); 147 | assert_eq!(prev_power_of_two(0x5555555555555_u64), 0x4000000000000) 148 | } 149 | 150 | #[test] 151 | fn test_horner1() { 152 | // Test of polynom: 2x² + 3x + 1 153 | let coefficients = [2.0, 3.0, 1.0]; 154 | assert_eq!(horner(0.0, &coefficients), 1.0); 155 | assert_eq!(horner(1.0, &coefficients), 6.0); 156 | assert_eq!(horner(2.0, &coefficients), 15.0); 157 | } 158 | 159 | #[test] 160 | fn test_horner2() { 161 | // Test of polynom: x³ - 2x² + 3x - 4 162 | let coefficients = [1.0, -2.0, 3.0, -4.0]; 163 | assert!((horner(0.0, &coefficients) - (-4.0)).abs() < 1e-10); 164 | assert!((horner(1.0, &coefficients) - (-2.0)).abs() < 1e-10); 165 | assert!((horner(2.0, &coefficients) - 2.0).abs() < 1e-10); 166 | } 167 | 168 | #[test] 169 | fn test_horner3() { 170 | // Test of empty polynom 171 | assert_eq!(horner(1.0, &[]), 0.0); 172 | } 173 | 174 | #[test] 175 | fn test_horner4() { 176 | // Test of constant polynom 177 | assert_eq!(horner(42.0, &[5.0]), 5.0); 178 | } 179 | 180 | #[test] 181 | fn test_zigzag_encode() { 182 | assert_eq!(zigzag_encode(0), 0); 183 | assert_eq!(zigzag_encode(-1), 1); 184 | assert_eq!(zigzag_encode(1), 2); 185 | assert_eq!(zigzag_encode(-2), 3); 186 | assert_eq!(zigzag_encode(i32::MIN), u32::MAX); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/max_flow.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, atomic::AtomicI32}; 2 | 3 | use crate::{ 4 | edge::{EdgeWithData, InputEdge}, 5 | graph::NodeID, 6 | }; 7 | use bitvec::vec::BitVec; 8 | use log::debug; 9 | 10 | #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 11 | pub struct ResidualEdgeData { 12 | pub capacity: i32, 13 | } 14 | 15 | impl ResidualEdgeData { 16 | pub fn new(capacity: i32) -> ResidualEdgeData { 17 | ResidualEdgeData { capacity } 18 | } 19 | } 20 | 21 | pub trait MaxFlow { 22 | fn run(&mut self); 23 | fn run_with_upper_bound(&mut self, bound: Arc); 24 | fn max_flow(&self) -> Result; 25 | fn assignment(&self, source: NodeID) -> Result; 26 | fn from_edge_list( 27 | edges: Vec>, 28 | source: NodeID, 29 | sink: NodeID, 30 | ) -> Self; 31 | fn from_generic_edge_list( 32 | input_edges: &[E], 33 | source: NodeID, 34 | target: NodeID, 35 | function: impl Fn(&E) -> ResidualEdgeData, 36 | ) -> Self 37 | where 38 | Self: Sized, 39 | { 40 | debug_assert!(!input_edges.is_empty()); 41 | debug!("instantiating max-flow solver"); 42 | let edge_list: Vec> = input_edges 43 | .iter() 44 | .map(move |edge| InputEdge { 45 | source: edge.source(), 46 | target: edge.target(), 47 | data: function(edge), 48 | }) 49 | .collect(); 50 | 51 | debug!("created {} ff edges", edge_list.len()); 52 | Self::from_edge_list(edge_list, source, target) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/merge_entry.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | #[derive(PartialEq, Eq, Clone, Debug)] 4 | pub struct MergeEntry { 5 | pub item: T, 6 | pub index: usize, 7 | } 8 | 9 | impl PartialOrd for MergeEntry { 10 | fn partial_cmp(&self, other: &Self) -> Option { 11 | Some(self.cmp(other)) 12 | } 13 | } 14 | 15 | impl Ord for MergeEntry { 16 | fn cmp(&self, other: &Self) -> Ordering { 17 | // reverse ordering for a min heap 18 | other.item.cmp(&self.item) 19 | } 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn test_ordering() { 28 | let e1 = MergeEntry { item: 1, index: 0 }; 29 | let e2 = MergeEntry { item: 2, index: 1 }; 30 | assert!(e1 > e2); // Umgekehrte Ordnung für Min-Heap 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/merge_tree.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BinaryHeap; 2 | 3 | use crate::merge_entry::MergeEntry; 4 | 5 | pub trait MergeTree { 6 | /// Pushes an item onto the merge tree 7 | fn push(&mut self, item: MergeEntry); 8 | 9 | /// Removes and returns the minimum item from the tree 10 | fn pop(&mut self) -> Option>; 11 | 12 | /// Returns true if the tree is empty 13 | fn is_empty(&self) -> bool; 14 | 15 | /// Returns the number of items in the tree 16 | fn len(&self) -> usize; 17 | } 18 | 19 | impl MergeTree for BinaryHeap> { 20 | fn push(&mut self, item: MergeEntry) { 21 | self.push(item); 22 | } 23 | 24 | fn pop(&mut self) -> std::option::Option> { 25 | self.pop() 26 | } 27 | 28 | fn is_empty(&self) -> bool { 29 | self.is_empty() 30 | } 31 | 32 | fn len(&self) -> usize { 33 | self.len() 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::*; 40 | 41 | #[test] 42 | fn test_binary_heap_merge_tree() { 43 | let mut heap: BinaryHeap> = BinaryHeap::new(); 44 | 45 | // test empty heap 46 | assert!(heap.is_empty()); 47 | assert_eq!(heap.len(), 0); 48 | assert!(heap.pop().is_none()); 49 | 50 | // test push operations 51 | heap.push(MergeEntry { item: 3, index: 0 }); 52 | assert_eq!(heap.len(), 1); 53 | assert!(!heap.is_empty()); 54 | 55 | heap.push(MergeEntry { item: 1, index: 1 }); 56 | heap.push(MergeEntry { item: 4, index: 2 }); 57 | heap.push(MergeEntry { item: 2, index: 3 }); 58 | assert_eq!(heap.len(), 4); 59 | 60 | // test pop operations - items shall be sorted 61 | assert_eq!(heap.pop().unwrap().item, 1); 62 | assert_eq!(heap.pop().unwrap().item, 2); 63 | assert_eq!(heap.pop().unwrap().item, 3); 64 | assert_eq!(heap.pop().unwrap().item, 4); 65 | 66 | // test empty heap - again 67 | assert!(heap.is_empty()); 68 | assert_eq!(heap.len(), 0); 69 | assert!(heap.pop().is_none()); 70 | } 71 | 72 | #[test] 73 | fn test_push_duplicate_values() { 74 | let mut heap: BinaryHeap> = BinaryHeap::new(); 75 | 76 | heap.push(MergeEntry { item: 1, index: 0 }); 77 | heap.push(MergeEntry { item: 1, index: 1 }); 78 | 79 | assert_eq!(heap.len(), 2); 80 | assert_eq!(heap.pop().unwrap().index, 0); 81 | assert_eq!(heap.pop().unwrap().index, 1); 82 | } 83 | 84 | #[test] 85 | fn test_mixed_operations() { 86 | let mut heap: BinaryHeap> = BinaryHeap::new(); 87 | 88 | heap.push(MergeEntry { item: 3, index: 0 }); 89 | assert_eq!(heap.len(), 1); 90 | 91 | heap.push(MergeEntry { item: 1, index: 1 }); 92 | assert_eq!(heap.len(), 2); 93 | 94 | assert_eq!(heap.pop().unwrap().item, 1); 95 | assert_eq!(heap.len(), 1); 96 | 97 | heap.push(MergeEntry { item: 2, index: 2 }); 98 | assert_eq!(heap.len(), 2); 99 | 100 | assert_eq!(heap.pop().unwrap().item, 2); 101 | assert_eq!(heap.pop().unwrap().item, 3); 102 | assert!(heap.is_empty()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/metis.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use log::info; 3 | 4 | use crate::{edge::InputEdge, geometry::FPCoordinate, graph::NodeID, io::read_lines}; 5 | 6 | pub enum WeightType { 7 | Unit, 8 | Original, 9 | } 10 | 11 | pub enum Direction { 12 | Both = 0, 13 | Forward = 1, 14 | Reverse = 2, 15 | Closed = 3, 16 | } 17 | 18 | impl TryFrom for Direction { 19 | type Error = (); 20 | 21 | fn try_from(v: i32) -> Result { 22 | match v { 23 | x if x == Direction::Both as i32 => Ok(Direction::Both), 24 | x if x == Direction::Forward as i32 => Ok(Direction::Forward), 25 | x if x == Direction::Reverse as i32 => Ok(Direction::Reverse), 26 | x if x == Direction::Closed as i32 => Ok(Direction::Closed), 27 | _ => Err(()), 28 | } 29 | } 30 | } 31 | 32 | pub fn read_graph>( 33 | filename: &str, 34 | _weight_type: WeightType, 35 | ) -> Vec> { 36 | let mut edges = Vec::new(); 37 | 38 | let mut lines = read_lines(filename).expect("could not load graph file"); 39 | 40 | let first_line = lines.next().unwrap().unwrap(); 41 | let sizes = first_line 42 | .get(..) 43 | .unwrap_or("") 44 | .split_ascii_whitespace() 45 | .collect_vec(); 46 | let number_of_nodes = sizes[0].parse::().unwrap(); 47 | 48 | // load unweighted metis graph and coordinates 49 | for (source, line) in lines.enumerate() { 50 | let line = line.unwrap(); 51 | let tokens = line.get(..).unwrap_or("").split_whitespace().collect_vec(); 52 | 53 | assert!(source < number_of_nodes); 54 | 55 | for token in tokens { 56 | let target = token.parse::().unwrap() - 1; 57 | assert!(target < number_of_nodes); 58 | // avoid eigenloops 59 | if source == target { 60 | continue; 61 | } 62 | 63 | edges.push(InputEdge { 64 | source, 65 | target, 66 | data: T::from(1), 67 | }); 68 | } 69 | } 70 | info!("loaded {} directed edges", edges.len()); 71 | edges 72 | } 73 | 74 | pub fn read_coordinates(filename: &str) -> Vec { 75 | let mut coordinates = Vec::new(); 76 | for line in read_lines(filename).expect("could not load coordinates file") { 77 | let line = line.unwrap(); 78 | let tokens = line.get(..).unwrap_or("").split_whitespace().collect_vec(); 79 | let lon = tokens[0].parse::().unwrap() / 100_000.; 80 | let lat = tokens[1].parse::().unwrap() / 100_000.; 81 | // let _z = tokens[2].parse::().unwrap(); 82 | coordinates.push(FPCoordinate::new_from_lat_lon(lat, lon)); 83 | } 84 | 85 | coordinates 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | use std::fs::write; 92 | use tempfile::NamedTempFile; 93 | 94 | #[test] 95 | fn read_graph_valid() { 96 | let graph_content = "4 8\n3 2\n2 3\n1 3\n1 2\n"; 97 | let tmp_file = NamedTempFile::new().unwrap(); 98 | write(tmp_file.path(), graph_content).unwrap(); 99 | 100 | let edges = read_graph::(tmp_file.path().to_str().unwrap(), WeightType::Unit); 101 | 102 | assert_eq!(edges.len(), 6); 103 | assert_eq!( 104 | edges[0], 105 | InputEdge { 106 | source: 0, 107 | target: 2, 108 | data: 1 109 | } 110 | ); 111 | assert_eq!( 112 | edges[1], 113 | InputEdge { 114 | source: 0, 115 | target: 1, 116 | data: 1 117 | } 118 | ); 119 | assert_eq!( 120 | edges[2], 121 | InputEdge { 122 | source: 1, 123 | target: 2, 124 | data: 1 125 | } 126 | ); 127 | assert_eq!( 128 | edges[3], 129 | InputEdge { 130 | source: 2, 131 | target: 0, 132 | data: 1 133 | } 134 | ); 135 | assert_eq!( 136 | edges[4], 137 | InputEdge { 138 | source: 3, 139 | target: 0, 140 | data: 1 141 | } 142 | ); 143 | assert_eq!( 144 | edges[5], 145 | InputEdge { 146 | source: 3, 147 | target: 1, 148 | data: 1 149 | } 150 | ); 151 | } 152 | 153 | #[test] 154 | fn read_coordinates_valid() { 155 | let coord_content = "1234567 4567890\n2345678 5678901\n"; 156 | let tmp_file = NamedTempFile::new().unwrap(); 157 | write(tmp_file.path(), coord_content).unwrap(); 158 | 159 | let coords = read_coordinates(tmp_file.path().to_str().unwrap()); 160 | 161 | assert_eq!(coords.len(), 2); 162 | let (lon, lat) = coords[0].to_lon_lat_pair(); 163 | assert!((lat - 45.67890).abs() < 1e-5); 164 | assert!((lon - 12.34567).abs() < 1e-5); 165 | } 166 | 167 | #[test] 168 | #[should_panic] 169 | fn read_graph_invalid_file() { 170 | read_graph::("nonexistent_file.txt", WeightType::Unit); 171 | } 172 | 173 | #[test] 174 | #[should_panic] 175 | fn read_graph_invalid_format() { 176 | let graph_content = "not a number\n1 2\n"; 177 | let tmp_file = NamedTempFile::new().unwrap(); 178 | write(tmp_file.path(), graph_content).unwrap(); 179 | 180 | read_graph::(tmp_file.path().to_str().unwrap(), WeightType::Unit); 181 | } 182 | 183 | #[test] 184 | #[should_panic] 185 | fn read_graph_node_out_of_bounds() { 186 | let graph_content = "2 1\n3\n1\n"; // Node 3 exceeds number_of_nodes 187 | let tmp_file = NamedTempFile::new().unwrap(); 188 | write(tmp_file.path(), graph_content).unwrap(); 189 | 190 | read_graph::(tmp_file.path().to_str().unwrap(), WeightType::Unit); 191 | } 192 | 193 | #[test] 194 | fn read_graph_skip_eigenloops() { 195 | let graph_content = "2 1\n1 1\n2\n"; 196 | let tmp_file = NamedTempFile::new().unwrap(); 197 | write(tmp_file.path(), graph_content).unwrap(); 198 | 199 | let edges = read_graph::(tmp_file.path().to_str().unwrap(), WeightType::Unit); 200 | 201 | assert_eq!(edges.len(), 0); // Eigenloop should be skipped 202 | } 203 | 204 | #[test] 205 | fn direction_try_from() { 206 | // test valid input values 207 | assert!(matches!(Direction::try_from(0), Ok(Direction::Both))); 208 | assert!(matches!(Direction::try_from(1), Ok(Direction::Forward))); 209 | assert!(matches!(Direction::try_from(2), Ok(Direction::Reverse))); 210 | assert!(matches!(Direction::try_from(3), Ok(Direction::Closed))); 211 | 212 | // test invalid input values 213 | assert!(Direction::try_from(-1).is_err()); 214 | assert!(Direction::try_from(4).is_err()); 215 | } 216 | 217 | #[test] 218 | fn direction_as_i32() { 219 | assert_eq!(Direction::Both as i32, 0); 220 | assert_eq!(Direction::Forward as i32, 1); 221 | assert_eq!(Direction::Reverse as i32, 2); 222 | assert_eq!(Direction::Closed as i32, 3); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/one_iterator.rs: -------------------------------------------------------------------------------- 1 | // iterate the indices of the ones in the binary representation of a u32 integer 2 | pub struct OneIterator { 3 | value: u32, 4 | } 5 | 6 | impl Iterator for OneIterator { 7 | type Item = u32; 8 | 9 | fn next(&mut self) -> Option { 10 | if self.value == 0 { 11 | return None; 12 | } 13 | let first_bit = 31 - self.value.leading_zeros(); 14 | self.value ^= 1 << first_bit; 15 | Some(first_bit) 16 | } 17 | } 18 | 19 | impl From for OneIterator { 20 | fn from(value: u32) -> Self { 21 | OneIterator { value } 22 | } 23 | } 24 | 25 | pub trait OneIter { 26 | /// Instantiate a OneIterator for the underlying object 27 | fn one_iter(&self) -> OneIterator; 28 | } 29 | 30 | impl OneIter for u32 { 31 | fn one_iter(&self) -> OneIterator { 32 | OneIterator::from(*self) 33 | } 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use crate::one_iterator::{OneIter, OneIterator}; 39 | 40 | #[test] 41 | fn iterate_ones_from() { 42 | let count = OneIterator::from(0xFFFFFFFF).count(); 43 | assert_eq!(32, count); 44 | let indices: Vec = OneIterator::from(0xFFFFFFFF).collect(); 45 | assert_eq!( 46 | indices, 47 | vec![ 48 | 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 49 | 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 50 | ] 51 | ); 52 | let indices: Vec = OneIterator::from(0x44104085).collect(); 53 | assert_eq!(indices, vec![30, 26, 20, 14, 7, 2, 0]); 54 | } 55 | 56 | #[test] 57 | fn iterate_ones_one_iter() { 58 | let count = 0xFFFFFFFF.one_iter().count(); 59 | assert_eq!(32, count); 60 | let indices: Vec = 0xFFFFFFFF.one_iter().collect(); 61 | assert_eq!( 62 | indices, 63 | vec![ 64 | 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 65 | 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 66 | ] 67 | ); 68 | let indices: Vec = 0x44104085.one_iter().collect(); 69 | assert_eq!(indices, vec![30, 26, 20, 14, 7, 2, 0]); 70 | } 71 | 72 | #[test] 73 | fn iterate_zero() { 74 | let mut iter = 0.one_iter(); 75 | assert_eq!(None, iter.next()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/path_based_scc.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Path-based SCC algorithm (Gabow's Algorithm) 3 | * 4 | * The algorithm numbers SCCs in reverse order of their discovery: 5 | * - Initially, component counter starts at n (number of vertices) 6 | * - Each time an SCC is found, counter is decremented 7 | * - Final numbering is from n-1 down to 0 8 | * 9 | * This approach serves multiple purposes: 10 | * 1. Avoids conflicts with temporary stack indices during processing 11 | * 2. Creates a reverse topological ordering of SCCs 12 | * 3. Ensures each SCC gets a unique, consecutive number 13 | * 14 | * Example: In a graph with 5 vertices and 3 SCCs: 15 | * - First discovered SCC gets number 4 16 | * - Second SCC gets number 3 17 | * - Last SCC gets number 2 18 | */ 19 | 20 | use crate::graph::Graph; 21 | 22 | #[derive(Clone, Copy)] 23 | enum DfsState { 24 | Visit(usize), // Knoten zum ersten Mal besuchen 25 | ProcessNeighbors(usize), // Nachbarn verarbeiten 26 | Finalize(usize), // SCC finalisieren 27 | } 28 | 29 | pub struct PathBasedScc { 30 | scc: Vec, 31 | stack: Vec, // Stack S 32 | bounds: Vec, // Stack B 33 | component: usize, 34 | } 35 | 36 | impl Default for PathBasedScc { 37 | fn default() -> Self { 38 | Self::new() 39 | } 40 | } 41 | 42 | impl PathBasedScc { 43 | pub fn new() -> Self { 44 | Self { 45 | bounds: Vec::new(), 46 | scc: Vec::new(), 47 | stack: Vec::new(), 48 | component: 0, 49 | } 50 | } 51 | 52 | pub fn run(&mut self, graph: &(impl Graph + 'static)) -> Vec { 53 | // initialization 54 | self.bounds = Vec::new(); 55 | self.scc.resize(graph.number_of_nodes(), usize::MAX); 56 | self.stack = Vec::new(); 57 | self.component = graph.number_of_nodes(); 58 | 59 | // main loop 60 | for v in graph.node_range() { 61 | if self.scc[v] == usize::MAX { 62 | self.dfs_iterative(v, graph); 63 | } 64 | } 65 | 66 | self.scc.clone() 67 | } 68 | 69 | fn dfs_iterative(&mut self, start: usize, graph: &(impl Graph + 'static)) { 70 | let mut dfs_stack = vec![DfsState::Visit(start)]; 71 | let mut edge_indices = vec![0usize; graph.number_of_nodes()]; 72 | 73 | while let Some(state) = dfs_stack.pop() { 74 | match state { 75 | DfsState::Visit(v) => { 76 | // step 1: push v onto S 77 | self.stack.push(v); 78 | self.scc[v] = self.stack.len() - 1; 79 | self.bounds.push(self.scc[v]); 80 | dfs_stack.push(DfsState::ProcessNeighbors(v)); 81 | } 82 | 83 | DfsState::ProcessNeighbors(v) => { 84 | let edges: Vec<_> = graph.edge_range(v).collect(); 85 | if edge_indices[v] < edges.len() { 86 | let e = edges[edge_indices[v]]; 87 | edge_indices[v] += 1; 88 | dfs_stack.push(DfsState::ProcessNeighbors(v)); 89 | 90 | let w = graph.target(e); 91 | if self.scc[w] == usize::MAX { 92 | dfs_stack.push(DfsState::Visit(w)); 93 | } else { 94 | // contract if necessary 95 | while let Some(&bound) = self.bounds.last() { 96 | if self.scc[w] < bound { 97 | self.bounds.pop(); 98 | } else { 99 | break; 100 | } 101 | } 102 | } 103 | } else { 104 | dfs_stack.push(DfsState::Finalize(v)); 105 | } 106 | } 107 | 108 | DfsState::Finalize(v) => { 109 | if Some(&self.scc[v]) == self.bounds.last() { 110 | self.bounds.pop(); 111 | self.component -= 1; 112 | while let Some(u) = self.stack.pop() { 113 | self.scc[u] = self.component; 114 | if u == v { 115 | break; 116 | } 117 | } 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use crate::edge::InputEdge; 128 | use crate::path_based_scc::PathBasedScc; 129 | use crate::static_graph::StaticGraph; 130 | 131 | #[test] 132 | fn scc_wiki1() { 133 | type Graph = StaticGraph; 134 | let edges = vec![ 135 | InputEdge::new(0, 1, 3), 136 | InputEdge::new(1, 2, 3), 137 | InputEdge::new(1, 4, 1), 138 | InputEdge::new(1, 5, 6), 139 | InputEdge::new(2, 3, 2), 140 | InputEdge::new(2, 6, 2), 141 | InputEdge::new(3, 2, 2), 142 | InputEdge::new(3, 7, 2), 143 | InputEdge::new(4, 0, 2), 144 | InputEdge::new(4, 5, 2), 145 | InputEdge::new(5, 6, 2), 146 | InputEdge::new(6, 5, 2), 147 | InputEdge::new(7, 3, 2), 148 | InputEdge::new(7, 6, 2), 149 | ]; 150 | let graph = Graph::new(edges); 151 | 152 | let mut scc = PathBasedScc::new(); 153 | assert_eq!(vec![5, 5, 6, 6, 5, 7, 7, 6], scc.run(&graph)); 154 | } 155 | 156 | #[test] 157 | fn geekforgeeks() { 158 | type Graph = StaticGraph; 159 | let edges = vec![ 160 | InputEdge::new(1, 0, 3), 161 | InputEdge::new(0, 3, 3), 162 | InputEdge::new(0, 2, 1), 163 | InputEdge::new(2, 1, 6), 164 | InputEdge::new(3, 4, 2), 165 | ]; 166 | let graph = Graph::new(edges); 167 | 168 | let mut scc = PathBasedScc::new(); 169 | assert_eq!(vec![2, 2, 2, 3, 4], scc.run(&graph)); 170 | } 171 | 172 | #[test] 173 | fn stanford2() { 174 | type Graph = StaticGraph; 175 | let edges = vec![ 176 | InputEdge::new(0, 6, 3), 177 | InputEdge::new(6, 3, 3), 178 | InputEdge::new(3, 0, 1), 179 | InputEdge::new(6, 8, 6), 180 | InputEdge::new(8, 5, 2), 181 | InputEdge::new(5, 2, 2), 182 | InputEdge::new(2, 8, 2), 183 | InputEdge::new(5, 7, 2), 184 | InputEdge::new(7, 1, 2), 185 | InputEdge::new(4, 7, 2), 186 | InputEdge::new(1, 4, 2), 187 | ]; 188 | let graph = Graph::new(edges); 189 | 190 | let mut scc = PathBasedScc::new(); 191 | assert_eq!(vec![6, 8, 7, 6, 8, 7, 6, 8, 7], scc.run(&graph)); 192 | } 193 | 194 | #[test] 195 | fn web1() { 196 | type Graph = StaticGraph; 197 | let edges = vec![ 198 | InputEdge::new(0, 1, 3), 199 | InputEdge::new(1, 3, 3), 200 | InputEdge::new(1, 4, 1), 201 | InputEdge::new(1, 2, 6), 202 | InputEdge::new(2, 5, 2), 203 | InputEdge::new(4, 1, 2), 204 | InputEdge::new(4, 5, 2), 205 | InputEdge::new(4, 6, 2), 206 | InputEdge::new(5, 7, 2), 207 | InputEdge::new(6, 7, 2), 208 | InputEdge::new(6, 8, 2), 209 | InputEdge::new(7, 9, 2), 210 | InputEdge::new(9, 10, 2), 211 | InputEdge::new(10, 8, 2), 212 | InputEdge::new(8, 11, 2), 213 | InputEdge::new(11, 6, 2), 214 | ]; 215 | let graph = Graph::new(edges); 216 | 217 | let mut scc = PathBasedScc::default(); 218 | assert_eq!( 219 | vec![6, 7, 9, 8, 7, 10, 11, 11, 11, 11, 11, 11], 220 | scc.run(&graph) 221 | ); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/renumbering_table.rs: -------------------------------------------------------------------------------- 1 | use fxhash::FxHashMap; 2 | 3 | enum Implementation { 4 | Vec(Vec), 5 | Map(FxHashMap), 6 | } 7 | 8 | pub struct RenumberingTable { 9 | table: Implementation, 10 | } 11 | 12 | impl RenumberingTable { 13 | pub fn new_with_size_hint(universe_size: usize, usage_bound: usize) -> Self { 14 | debug_assert!(universe_size >= usage_bound); 15 | 16 | let factor = universe_size / usage_bound; 17 | if factor > 8 { 18 | // the table will filled with at most 12.5% of the number of elements 19 | return Self { 20 | table: Implementation::Map(FxHashMap::default()), 21 | }; 22 | } 23 | 24 | let mut vector = Vec::new(); 25 | vector.resize(universe_size, usize::MAX); 26 | Self { 27 | table: Implementation::Vec(vector), 28 | } 29 | } 30 | 31 | pub fn set(&mut self, key: usize, value: usize) { 32 | match &mut self.table { 33 | Implementation::Vec(vector) => vector[key] = value, 34 | Implementation::Map(map) => { 35 | map.insert(key, value); 36 | } 37 | } 38 | } 39 | 40 | pub fn get(&self, key: usize) -> usize { 41 | match &self.table { 42 | Implementation::Vec(vector) => vector[key], 43 | Implementation::Map(map) => *map.get(&key).unwrap(), 44 | } 45 | } 46 | 47 | pub fn contains_key(&self, key: usize) -> bool { 48 | match &self.table { 49 | Implementation::Vec(vector) => vector[key] != usize::MAX, 50 | Implementation::Map(map) => map.contains_key(&key), 51 | } 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::RenumberingTable; 58 | 59 | #[test] 60 | fn full_universe() { 61 | let mut table = RenumberingTable::new_with_size_hint(10, 10); 62 | for i in 0..10 { 63 | table.set(i, 10 - i); 64 | } 65 | for i in 0..10 { 66 | assert_eq!(10 - i, table.get(i)); 67 | } 68 | } 69 | 70 | #[test] 71 | fn sparse_universe() { 72 | let mut table = RenumberingTable::new_with_size_hint(10000, 10); 73 | for i in 0..10 { 74 | table.set(1234 + i, i); 75 | } 76 | for i in 0..10 { 77 | assert_eq!(i, table.get(1234 + i)); 78 | } 79 | for i in 0..1234 { 80 | assert!(!table.contains_key(i)); 81 | } 82 | for i in 1234..1244 { 83 | assert!(table.contains_key(i)); 84 | } 85 | for i in 1244..10000 { 86 | assert!(!table.contains_key(i)); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/run_iterator.rs: -------------------------------------------------------------------------------- 1 | /// Iterator that yields consecutive runs of equal elements from a slice. 2 | /// 3 | /// A run is a sequence of consecutive elements that are equal according to 4 | /// [`PartialEq`]. The iterator yields references to subslices containing these runs. 5 | /// 6 | /// # Type Parameters 7 | /// 8 | /// * `'a` - Lifetime of the referenced slice 9 | /// * `T` - Type of elements in the slice, must implement [`PartialEq`] 10 | /// 11 | /// # Examples 12 | /// 13 | /// ``` 14 | /// use toolbox_rs::run_iterator::RunIterator; 15 | /// 16 | /// let array = [1, 1, 2, 2, 2, 3, 3, 1]; 17 | /// let runs: Vec<&[i32]> = RunIterator::new(&array).collect(); 18 | /// let expected: Vec<&[i32]> = vec![ 19 | /// &[1, 1], // First run of 1s 20 | /// &[2, 2, 2], // Run of 2s 21 | /// &[3, 3], // Run of 3s 22 | /// &[1], // Single 1 23 | /// ]; 24 | /// assert_eq!(runs, expected); 25 | /// ``` 26 | pub struct RunIterator<'a, T> { 27 | array: &'a [T], 28 | pos: usize, 29 | } 30 | 31 | impl<'a, T: PartialEq> RunIterator<'a, T> { 32 | /// Creates a new `RunIterator` from a slice. 33 | /// 34 | /// # Arguments 35 | /// 36 | /// * `array` - Slice to iterate over 37 | /// 38 | /// # Examples 39 | /// 40 | /// ``` 41 | /// use toolbox_rs::run_iterator::RunIterator; 42 | /// 43 | /// let data = vec!["a", "a", "b", "c", "c"]; 44 | /// let iterator = RunIterator::new(&data); 45 | /// ``` 46 | pub fn new(array: &'a [T]) -> Self { 47 | RunIterator { array, pos: 0 } 48 | } 49 | } 50 | 51 | impl<'a, T> RunIterator<'a, T> { 52 | /// Creates a new `RunIterator` with a custom predicate for determining runs. 53 | /// 54 | /// The predicate function determines if two consecutive elements belong to the same run. 55 | /// Elements are grouped into runs as long as the predicate returns `true` for adjacent pairs. 56 | /// 57 | /// # Arguments 58 | /// 59 | /// * `array` - Slice to iterate over 60 | /// * `pred` - Predicate function that takes two references and returns bool 61 | /// 62 | /// # Examples 63 | /// 64 | /// ``` 65 | /// use toolbox_rs::run_iterator::RunIterator; 66 | /// 67 | /// // Group numbers by their difference being less than 2 68 | /// let input = vec![1, 3, 4, 8, 9, 10, 15]; 69 | /// let result: Vec<&[i32]> = RunIterator::new_by(&input, |a, b| b - a <= 2).collect(); 70 | /// assert_eq!(result, vec![&input[0..3], &input[3..6], &input[6..]]); 71 | /// 72 | /// // Group strings by their length 73 | /// let strings = vec!["a", "b", "cd", "ef", "g"]; 74 | /// let runs: Vec<&[&str]> = RunIterator::new_by(&strings, |a, b| a.len() == b.len()).collect(); 75 | /// assert_eq!(runs, vec![&strings[0..2], &strings[2..4], &[strings[4]]]); 76 | /// ``` 77 | pub fn new_by(array: &'a [T], pred: F) -> RunIteratorBy<'a, T, F> 78 | where 79 | F: Fn(&T, &T) -> bool, 80 | { 81 | RunIteratorBy { 82 | array, 83 | pos: 0, 84 | pred, 85 | } 86 | } 87 | } 88 | 89 | /// Iterator that yields runs of elements based on a custom predicate 90 | pub struct RunIteratorBy<'a, T, F> { 91 | array: &'a [T], 92 | pos: usize, 93 | pred: F, 94 | } 95 | 96 | impl<'a, T, F> Iterator for RunIteratorBy<'a, T, F> 97 | where 98 | F: Fn(&T, &T) -> bool, 99 | { 100 | type Item = &'a [T]; 101 | 102 | fn next(&mut self) -> Option { 103 | if self.pos >= self.array.len() { 104 | return None; 105 | } 106 | 107 | let start = self.pos; 108 | self.pos += 1; 109 | 110 | while self.pos < self.array.len() 111 | && (self.pred)(&self.array[self.pos - 1], &self.array[self.pos]) 112 | { 113 | self.pos += 1; 114 | } 115 | 116 | Some(&self.array[start..self.pos]) 117 | } 118 | } 119 | 120 | impl<'a, T: PartialEq> Iterator for RunIterator<'a, T> { 121 | type Item = &'a [T]; 122 | 123 | /// Returns the next run of equal elements. 124 | /// 125 | /// # Returns 126 | /// 127 | /// * `Some(&[T])` - Reference to the next run of equal elements 128 | /// * `None` - When iteration is complete 129 | /// 130 | /// # Examples 131 | /// 132 | /// ``` 133 | /// use toolbox_rs::run_iterator::RunIterator; 134 | /// 135 | /// let data = vec![1, 1, 2, 2, 3]; 136 | /// let mut iter = RunIterator::new(&data); 137 | /// 138 | /// assert_eq!(iter.next(), Some(&[1, 1][..])); 139 | /// assert_eq!(iter.next(), Some(&[2, 2][..])); 140 | /// assert_eq!(iter.next(), Some(&[3][..])); 141 | /// assert_eq!(iter.next(), None); 142 | /// ``` 143 | fn next(&mut self) -> Option { 144 | if self.pos >= self.array.len() { 145 | return None; 146 | } 147 | 148 | let start = self.pos; 149 | self.pos += 1; 150 | 151 | self.pos += self.array[start + 1..] 152 | .iter() 153 | .position(|x| x != &self.array[start]) 154 | .unwrap_or(self.array.len() - start - 1); 155 | 156 | Some(&self.array[start..self.pos]) 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use super::RunIterator; 163 | #[test] 164 | fn unsorted_runs_tests() { 165 | let array = [1, 1, 2, 2, 2, 3, 3, 1]; 166 | let run_iter = RunIterator::new(&array); 167 | 168 | let result: Vec<&[i32]> = run_iter.collect(); 169 | let expected: Vec<&[i32]> = vec![&[1; 2], &[2; 3], &[3; 2], &[1; 1]]; 170 | assert_eq!(expected, result); 171 | } 172 | 173 | #[test] 174 | fn object_runs() { 175 | #[derive(Debug)] 176 | struct SimplePair { 177 | key: i32, 178 | _value: i32, 179 | } 180 | 181 | impl PartialEq for SimplePair { 182 | fn eq(&self, other: &Self) -> bool { 183 | self.key == other.key 184 | } 185 | } 186 | 187 | let input = vec![ 188 | SimplePair { key: 1, _value: 2 }, 189 | SimplePair { key: 1, _value: 1 }, 190 | SimplePair { key: 21, _value: 1 }, 191 | SimplePair { key: 1, _value: 1 }, 192 | ]; 193 | 194 | let run_iter = RunIterator::new(&input); 195 | 196 | let result: Vec<&[SimplePair]> = run_iter.collect(); 197 | assert_eq!(3, result.len()); 198 | let expected = vec![&input[0..2], &input[2..3], &input[3..]]; 199 | assert_eq!(expected, result); 200 | } 201 | 202 | #[test] 203 | fn test_custom_predicate() { 204 | let input = vec![1, 3, 4, 8, 9, 10, 15]; 205 | let result: Vec<&[i32]> = RunIterator::new_by(&input, |a, b| b - a <= 2).collect(); 206 | // The expected runs are: 207 | // [1, 3, 4], [8, 9, 10], [15] 208 | assert_eq!(result, vec![&input[0..3], &input[3..6], &input[6..]]); 209 | 210 | let inputs = vec![19, 21, 23, 20, 25, 18]; 211 | let result: Vec<&[i32]> = RunIterator::new_by(&inputs, |a, b| a % 2 == b % 2).collect(); 212 | // The expected runs are: 213 | // [19, 21, 23], [20], [25], [18] 214 | assert_eq!( 215 | result, 216 | vec![&inputs[0..3], &inputs[3..4], &inputs[4..5], &inputs[5..]] 217 | ); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/scaffold/bin/command_line.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use clap::Parser; 4 | 5 | #[derive(Parser, Debug)] 6 | #[clap(author, version, about, long_about = None)] 7 | pub struct Arguments { 8 | /// cell assignments from chipper tool 9 | #[clap(short, long, action)] 10 | pub partition_file: String, 11 | /// input coordinates files 12 | #[clap(short, long, action)] 13 | pub coordinates_file: String, 14 | /// input graph file 15 | #[clap(short, long, action)] 16 | pub graph: String, 17 | 18 | /// output convex hull cell geometry 19 | #[clap(long, action, default_value_t = String::new())] 20 | pub convex_cells_geojson: String, 21 | /// output boundary node locations 22 | #[clap(long, action, default_value_t = String::new())] 23 | pub boundary_nodes_geojson: String, 24 | } 25 | 26 | impl Display for Arguments { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | writeln!(f, "command line arguments:")?; 29 | writeln!(f, "partition_file: {}", self.partition_file)?; 30 | writeln!(f, "coordinates_file: {}", self.coordinates_file)?; 31 | writeln!(f, "graph: {}", self.graph)?; 32 | writeln!(f, "convex_cells_geojson: {}", self.convex_cells_geojson)?; 33 | writeln!(f, "boundary_nodes_geojson: {}", self.boundary_nodes_geojson) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/scaffold/bin/main.rs: -------------------------------------------------------------------------------- 1 | mod command_line; 2 | mod serialize; 3 | 4 | use command_line::Arguments; 5 | use env_logger::{Builder, Env}; 6 | use fxhash::{FxHashMap, FxHashSet}; 7 | use itertools::Itertools; 8 | use log::info; 9 | use rayon::prelude::*; 10 | use toolbox_rs::{ 11 | bounding_box::BoundingBox, convex_hull::monotone_chain, edge::InputEdge, 12 | geometry::FPCoordinate, io, partition_id::PartitionID, space_filling_curve::zorder_cmp, 13 | }; 14 | 15 | // TODO: tool to generate all the runtime data 16 | 17 | pub fn main() { 18 | Builder::from_env(Env::default().default_filter_or("info")).init(); 19 | 20 | println!(r#" ___ __ __ _ _ "#); 21 | println!(r#" / __| __ __ _ / _| / _| ___ | | __| |"#); 22 | println!(r#" \__ \ / _| / _` | | _| | _| / _ \ | | / _` |"#); 23 | println!(r#" |___/ \__|_ \__,_| _|_|_ _|_|_ \___/ _|_|_ \__,_|"#); 24 | println!(r#"_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|_|"""""|"#); 25 | println!(r#""`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"#); 26 | println!("build: {}", env!("GIT_HASH")); 27 | // parse and print command line parameters 28 | let args = ::parse(); 29 | info!("{args}"); 30 | 31 | let partition_ids = io::read_vec_from_file::(&args.partition_file); 32 | info!("loaded {} partition ids", partition_ids.len()); 33 | 34 | let coordinates = io::read_vec_from_file::(&args.coordinates_file); 35 | info!("loaded {} coordinates", coordinates.len()); 36 | 37 | let edges = io::read_vec_from_file::>(&args.graph); 38 | info!("loaded {} edges", edges.len()); 39 | 40 | info!("creating and sorting proxy vector"); 41 | let mut known_ids = FxHashSet::default(); 42 | let mut proxy_vector = Vec::new(); 43 | for (i, partition_id) in partition_ids.iter().enumerate() { 44 | if !known_ids.contains(partition_id) { 45 | proxy_vector.push(i); 46 | known_ids.insert(partition_id); 47 | } 48 | } 49 | 50 | proxy_vector.sort(); 51 | info!("number of unique cell ids is {}", proxy_vector.len()); 52 | 53 | if !args.convex_cells_geojson.is_empty() { 54 | info!("generating convex hulls"); 55 | let mut cells: FxHashMap> = FxHashMap::default(); 56 | for (i, partition_id) in partition_ids.iter().enumerate() { 57 | if !cells.contains_key(partition_id) { 58 | cells.insert(*partition_id, Vec::new()); 59 | } 60 | cells.get_mut(partition_id).unwrap().push(i); 61 | } 62 | let mut hulls: Vec<_> = cells 63 | .par_iter() 64 | .map(|(id, indexes)| { 65 | let cell_coordinates = indexes.iter().map(|i| coordinates[*i]).collect_vec(); 66 | let convex_hull = monotone_chain(&cell_coordinates); 67 | let bbox = BoundingBox::from_coordinates(&convex_hull); 68 | 69 | (convex_hull, bbox, id) 70 | }) 71 | .collect(); 72 | 73 | info!("sorting convex cell hulls by Z-order"); 74 | hulls.sort_by(|a, b| zorder_cmp(&a.1.center(), &b.1.center())); 75 | info!("writing to {}", &args.convex_cells_geojson); 76 | serialize::convex_cell_hull_geojson(&hulls, &args.convex_cells_geojson); 77 | } 78 | 79 | if !args.boundary_nodes_geojson.is_empty() { 80 | info!("computing geometry of boundary nodes"); 81 | let boundary_coordinates = edges 82 | .iter() 83 | .filter(|edge| partition_ids[edge.source] != partition_ids[edge.target]) 84 | .map(|edge| coordinates[edge.source]) 85 | .collect_vec(); 86 | info!("detection {} boundary nodes", boundary_coordinates.len()); 87 | 88 | serialize::boundary_geometry_geojson(&boundary_coordinates, &args.boundary_nodes_geojson); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/scaffold/bin/serialize.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::BufWriter}; 2 | 3 | use geojson::{Feature, FeatureWriter, Geometry, Value, feature::Id}; 4 | use itertools::Itertools; 5 | use toolbox_rs::{bounding_box::BoundingBox, geometry::FPCoordinate, partition_id::PartitionID}; 6 | 7 | pub(crate) fn convex_cell_hull_geojson( 8 | hulls: &[(Vec, BoundingBox, &PartitionID)], 9 | filename: &str, 10 | ) { 11 | let file = BufWriter::new(File::create(filename).expect("output file cannot be opened")); 12 | let mut writer = FeatureWriter::from_writer(file); 13 | for (convex_hull, bbox, id) in hulls { 14 | // map n + 1 points of the closed polygon into a format that is geojson compliant 15 | let convex_hull = convex_hull 16 | .iter() 17 | .cycle() 18 | .take(convex_hull.len() + 1) 19 | .map(|c| { 20 | // TODO: should this be implemented via the Into<> trait? 21 | c.to_lon_lat_vec() 22 | }) 23 | .collect_vec(); 24 | 25 | // serialize convex hull polygons as geojson 26 | let geometry = Geometry::new(Value::Polygon(vec![convex_hull])); 27 | 28 | writer 29 | .write_feature(&Feature { 30 | bbox: Some(bbox.into()), 31 | geometry: Some(geometry), 32 | id: Some(Id::String(id.to_string())), 33 | // Features tbd 34 | properties: None, 35 | foreign_members: None, 36 | }) 37 | .unwrap_or_else(|_| panic!("error writing feature: {id}")); 38 | } 39 | writer.finish().expect("error writing file"); 40 | } 41 | 42 | pub(crate) fn boundary_geometry_geojson(coordinates: &[FPCoordinate], filename: &str) { 43 | let file = BufWriter::new(File::create(filename).expect("output file cannot be opened")); 44 | let mut writer = FeatureWriter::from_writer(file); 45 | for coordinate in coordinates { 46 | // serialize convex hull polygons as geojson 47 | let geometry = Geometry::new(Value::Point(coordinate.to_lon_lat_vec())); 48 | 49 | writer 50 | .write_feature(&Feature { 51 | bbox: None, 52 | geometry: Some(geometry), 53 | id: None, 54 | // Features tbd 55 | properties: None, 56 | foreign_members: None, 57 | }) 58 | .unwrap_or_else(|_| panic!("error writing feature: {coordinate}")); 59 | } 60 | writer.finish().expect("error writing file"); 61 | } 62 | -------------------------------------------------------------------------------- /src/single_linked_list.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::PartialOrd, fmt::Debug}; 2 | 3 | #[derive(Debug)] 4 | struct Node { 5 | next: Option>>, 6 | elem: T, 7 | } 8 | 9 | /// A singly-linked list implementation with sorting capabilities. 10 | /// 11 | /// # Type Parameters 12 | /// 13 | /// * `T` - The element type, which must be `Copy`, `Debug`, and `PartialOrd` 14 | /// 15 | /// # Examples 16 | /// 17 | /// ``` 18 | /// use toolbox_rs::single_linked_list::SingleLinkedList; 19 | /// 20 | /// let mut list = SingleLinkedList::new(); 21 | /// list.push_front(1); 22 | /// list.push_front(2); 23 | /// assert_eq!(list.pop_front(), Some(2)); 24 | /// ``` 25 | pub struct SingleLinkedList { 26 | head: Option>>, 27 | } 28 | 29 | impl SingleLinkedList { 30 | /// Creates a new empty linked list. 31 | /// 32 | /// # Examples 33 | /// ``` 34 | /// use toolbox_rs::single_linked_list::SingleLinkedList; 35 | /// let list: SingleLinkedList = SingleLinkedList::new(); 36 | /// assert!(list.is_empty()); 37 | /// ``` 38 | pub fn new() -> Self { 39 | Self { head: None } 40 | } 41 | 42 | /// Adds an element to the front of the list. 43 | /// 44 | /// # Arguments 45 | /// * `elem` - The element to add 46 | /// 47 | /// # Examples 48 | /// ``` 49 | /// use toolbox_rs::single_linked_list::SingleLinkedList; 50 | /// let mut list = SingleLinkedList::new(); 51 | /// list.push_front(1); 52 | /// assert_eq!(list.peek_front(), Some(&1)); 53 | /// ``` 54 | pub fn push_front(&mut self, elem: T) { 55 | let new_node = Box::new(Node { 56 | next: self.head.take(), 57 | elem, 58 | }); 59 | self.head = Some(new_node); 60 | } 61 | 62 | /// Removes and returns the first element of the list. 63 | /// 64 | /// # Returns 65 | /// * `Some(T)` - The first element if the list is not empty 66 | /// * `None` - If the list is empty 67 | /// 68 | /// # Examples 69 | /// ``` 70 | /// use toolbox_rs::single_linked_list::SingleLinkedList; 71 | /// let mut list = SingleLinkedList::new(); 72 | /// list.push_front(1); 73 | /// assert_eq!(list.pop_front(), Some(1)); 74 | /// assert_eq!(list.pop_front(), None); 75 | /// ``` 76 | pub fn pop_front(&mut self) -> Option { 77 | self.head.take().map(|node| { 78 | self.head = node.next; 79 | node.elem 80 | }) 81 | } 82 | 83 | /// Returns a reference to the first element without removing it. 84 | /// 85 | /// # Returns 86 | /// * `Some(&T)` - Reference to the first element if the list is not empty 87 | /// * `None` - If the list is empty 88 | pub fn peek_front(&self) -> Option<&T> { 89 | self.head.as_ref().map(|node| &node.elem) 90 | } 91 | 92 | /// Returns a mutable reference to the first element without removing it. 93 | /// 94 | /// # Returns 95 | /// * `Some(&mut T)` - Mutable reference to the first element if the list is not empty 96 | /// * `None` - If the list is empty 97 | pub fn peek_front_mut(&mut self) -> Option<&mut T> { 98 | self.head.as_mut().map(|node| &mut node.elem) 99 | } 100 | 101 | /// Checks if the list is empty. 102 | /// 103 | /// # Returns 104 | /// * `true` - If the list contains no elements 105 | /// * `false` - If the list contains at least one element 106 | pub fn is_empty(&self) -> bool { 107 | self.head.is_none() 108 | } 109 | 110 | /// Checks if the list is sorted in ascending order. 111 | /// 112 | /// # Returns 113 | /// * `true` - If the list is sorted or has fewer than 2 elements 114 | /// * `false` - If the list is not sorted 115 | pub fn is_sorted(&self) -> bool { 116 | let mut current = &self.head; 117 | while let Some(node) = current { 118 | if let Some(next_node) = &node.next { 119 | if node.elem > next_node.elem { 120 | return false; 121 | } 122 | } 123 | current = &node.next; 124 | } 125 | true 126 | } 127 | 128 | /// Inserts an element into the list maintaining sorted order. 129 | /// 130 | /// # Arguments 131 | /// * `elem` - The element to insert 132 | /// 133 | /// # Note 134 | /// Assumes the list is already sorted. If the list is not sorted, 135 | /// the resulting order is undefined. 136 | pub fn insert_sorted(&mut self, elem: T) { 137 | let mut current = &mut self.head; 138 | while let Some(node) = current { 139 | let next_is_smaller = match &node.next { 140 | Some(next) => next.elem < elem, 141 | None => false, 142 | }; 143 | 144 | if next_is_smaller { 145 | current = &mut node.next; 146 | } else { 147 | let new_node = Box::new(Node { 148 | next: node.next.take(), 149 | elem, 150 | }); 151 | node.next = Some(new_node); 152 | return; 153 | } 154 | } 155 | } 156 | 157 | /// Removes all elements from the list. 158 | /// 159 | /// # Examples 160 | /// ``` 161 | /// use toolbox_rs::single_linked_list::SingleLinkedList; 162 | /// let mut list = SingleLinkedList::new(); 163 | /// list.push_front(1); 164 | /// list.clear(); 165 | /// assert!(list.is_empty()); 166 | /// ``` 167 | pub fn clear(&mut self) { 168 | self.head = None; 169 | } 170 | } 171 | 172 | impl Default for SingleLinkedList { 173 | fn default() -> Self { 174 | Self::new() 175 | } 176 | } 177 | 178 | #[cfg(test)] 179 | mod test { 180 | #[test] 181 | fn creation_push_peek_pop() { 182 | let mut list = super::SingleLinkedList::new(); 183 | assert!(list.is_empty()); 184 | list.push_front(1); 185 | list.push_front(2); 186 | list.push_front(3); 187 | assert!(!list.is_empty()); 188 | assert_eq!(list.peek_front(), Some(&3)); 189 | assert_eq!(list.peek_front_mut(), Some(&mut 3)); 190 | assert_eq!(list.pop_front(), Some(3)); 191 | assert_eq!(list.pop_front(), Some(2)); 192 | assert_eq!(list.pop_front(), Some(1)); 193 | assert_eq!(list.pop_front(), None); 194 | assert!(list.is_empty()); 195 | } 196 | 197 | #[test] 198 | fn find_not_less() { 199 | let mut list = super::SingleLinkedList::new(); 200 | assert!(list.is_empty()); 201 | assert!(list.is_sorted()); 202 | list.push_front(8); 203 | list.push_front(5); 204 | list.push_front(1); 205 | assert!(list.is_sorted()); 206 | 207 | list.insert_sorted(3); 208 | list.insert_sorted(2); 209 | assert!(list.is_sorted()); 210 | list.insert_sorted(6); 211 | list.insert_sorted(4); 212 | list.insert_sorted(7); 213 | list.insert_sorted(9); 214 | } 215 | 216 | #[test] 217 | fn unsorted() { 218 | let mut list = super::SingleLinkedList::default(); 219 | assert!(list.is_empty()); 220 | assert!(list.is_sorted()); 221 | list.push_front(5); 222 | list.push_front(8); 223 | list.push_front(1); 224 | assert!(!list.is_sorted()); 225 | } 226 | 227 | #[test] 228 | fn clear_list() { 229 | let mut list = super::SingleLinkedList::new(); 230 | 231 | // Clear empty list 232 | list.clear(); 233 | assert!(list.is_empty()); 234 | 235 | // Add elements and clear 236 | list.push_front(1); 237 | list.push_front(2); 238 | list.push_front(3); 239 | assert!(!list.is_empty()); 240 | assert_eq!(list.peek_front(), Some(&3)); 241 | 242 | list.clear(); 243 | assert!(list.is_empty()); 244 | assert_eq!(list.peek_front(), None); 245 | 246 | // Verify operations work after clearing 247 | list.push_front(4); 248 | assert!(!list.is_empty()); 249 | assert_eq!(list.peek_front(), Some(&4)); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/space_filling_curve.rs: -------------------------------------------------------------------------------- 1 | use crate::geometry::FPCoordinate; 2 | 3 | /// Provides a total order on fixed-point coordinates that corresponds to the 4 | /// well-known Z-order space-filling curve. 5 | /// 6 | /// This implementation ensures a proper total ordering by: 7 | /// 1. First comparing the most significant differing bits 8 | /// 2. Using consistent tie-breaking when bits are equal 9 | /// 3. Properly handling all edge cases 10 | /// 11 | /// # Arguments 12 | /// * `lhs` - First coordinate to compare 13 | /// * `rhs` - Second coordinate to compare 14 | /// 15 | /// # Returns 16 | /// A total ordering between the coordinates based on their Z-order: 17 | /// * `Ordering::Less` - if `lhs` comes before `rhs` 18 | /// * `Ordering::Equal` - if coordinates are identical 19 | /// * `Ordering::Greater` - if `lhs` comes after `rhs` 20 | /// 21 | /// # Examples 22 | /// ```rust 23 | /// use std::cmp::Ordering; 24 | /// use toolbox_rs::geometry::FPCoordinate; 25 | /// use toolbox_rs::space_filling_curve::zorder_cmp; 26 | /// 27 | /// // Create some test coordinates 28 | /// let berlin = FPCoordinate::new_from_lat_lon(52.520008, 13.404954); 29 | /// let paris = FPCoordinate::new_from_lat_lon(48.856613, 2.352222); 30 | /// let london = FPCoordinate::new_from_lat_lon(51.507351, -0.127758); 31 | /// 32 | /// // Test total ordering properties 33 | /// 34 | /// // 1. Antisymmetry: if a ≤ b and b ≤ a then a = b 35 | /// assert_eq!(zorder_cmp(&berlin, &berlin), Ordering::Equal); 36 | /// 37 | /// // 2. Transitivity: if a ≤ b and b ≤ c then a ≤ c 38 | /// if zorder_cmp(&paris, &london) == Ordering::Less 39 | /// && zorder_cmp(&london, &berlin) == Ordering::Less { 40 | /// assert_eq!(zorder_cmp(&paris, &berlin), Ordering::Less); 41 | /// } 42 | /// 43 | /// // 3. Totality: either a ≤ b or b ≤ a must be true 44 | /// let order = zorder_cmp(&paris, &london); 45 | /// assert!(order == Ordering::Less || order == Ordering::Equal || order == Ordering::Greater); 46 | /// ``` 47 | pub fn zorder_cmp(lhs: &FPCoordinate, rhs: &FPCoordinate) -> std::cmp::Ordering { 48 | let lat_xor = lhs.lat ^ rhs.lat; 49 | let lon_xor = lhs.lon ^ rhs.lon; 50 | 51 | // If both coordinates are identical 52 | if lat_xor == 0 && lon_xor == 0 { 53 | return std::cmp::Ordering::Equal; 54 | } 55 | 56 | // If one dimension has no differences 57 | if lat_xor == 0 { 58 | return lhs.lon.cmp(&rhs.lon); 59 | } 60 | if lon_xor == 0 { 61 | return lhs.lat.cmp(&rhs.lat); 62 | } 63 | 64 | // Compare most significant bits 65 | let lat_msb = 31 - lat_xor.leading_zeros(); 66 | let lon_msb = 31 - lon_xor.leading_zeros(); 67 | 68 | match lat_msb.cmp(&lon_msb) { 69 | std::cmp::Ordering::Greater => lhs.lat.cmp(&rhs.lat), 70 | std::cmp::Ordering::Less => lhs.lon.cmp(&rhs.lon), 71 | std::cmp::Ordering::Equal => { 72 | // If MSBs are at same position, use consistent ordering 73 | if (lhs.lat >> lat_msb) & 1 != (rhs.lat >> lat_msb) & 1 { 74 | lhs.lat.cmp(&rhs.lat) 75 | } else { 76 | lhs.lon.cmp(&rhs.lon) 77 | } 78 | } 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use crate::{geometry::FPCoordinate, space_filling_curve::zorder_cmp}; 85 | 86 | #[test] 87 | fn compare_greater() { 88 | let ny = FPCoordinate::new_from_lat_lon(40.730610, -73.935242); 89 | let sf = FPCoordinate::new_from_lat_lon(37.773972, -122.431297); 90 | 91 | assert_eq!(std::cmp::Ordering::Greater, zorder_cmp(&ny, &sf)); 92 | } 93 | 94 | #[test] 95 | fn compare_less() { 96 | let ny = FPCoordinate::new_from_lat_lon(40.730610, -73.935242); 97 | let sf = FPCoordinate::new_from_lat_lon(37.773972, -122.431297); 98 | 99 | assert_eq!(std::cmp::Ordering::Less, zorder_cmp(&sf, &ny)); 100 | } 101 | 102 | #[test] 103 | fn compare_equal() { 104 | let ny = FPCoordinate::new_from_lat_lon(40.730610, -73.935242); 105 | assert_eq!(std::cmp::Ordering::Equal, zorder_cmp(&ny, &ny)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/tabulation_hash.rs: -------------------------------------------------------------------------------- 1 | use rand::rngs::StdRng; 2 | use rand::{Rng, SeedableRng}; 3 | 4 | use crate::fast_hash_trait::{FastHash, MAX_ELEMENTS}; 5 | 6 | /// Implementation of Tabulation hashing with XOR operations. 7 | /// 8 | /// Properties: 9 | /// - Universal hashing properties 10 | /// - Space requirement: 2*2^16 = 256kb (fits in L2 cache) 11 | /// - Efficient evaluation (approximately 10 assembly instructions on x86 and ARM) 12 | /// - Deterministic output for same input values 13 | #[derive(Debug)] 14 | pub struct TabulationHash { 15 | table1: Vec, 16 | table2: Vec, 17 | } 18 | 19 | impl Default for TabulationHash { 20 | fn default() -> Self { 21 | Self::new() 22 | } 23 | } 24 | 25 | impl TabulationHash { 26 | /// Creates a new TabulationHash instance with shuffled tables. 27 | /// 28 | /// The tables are initialized with random values from 0..=u16::MAX by 29 | /// using a seeded random number generator. The seed is set to 0 for 30 | /// reproducibility. This means that the same input will always produce 31 | /// the same hash value, making it suitable for applications where 32 | /// consistent hashing is required. 33 | /// 34 | /// # Example 35 | /// 36 | /// ``` 37 | /// use toolbox_rs::tabulation_hash::TabulationHash; 38 | /// let hasher = TabulationHash::new(); 39 | /// ``` 40 | pub fn new() -> Self { 41 | let mut rng = StdRng::seed_from_u64(0); 42 | 43 | // Initialize tables with random values 44 | let table1: Vec = (0..MAX_ELEMENTS) 45 | .map(|_| rng.random_range(0..=u16::MAX)) 46 | .collect(); 47 | let table2: Vec = (0..MAX_ELEMENTS) 48 | .map(|_| rng.random_range(0..=u16::MAX)) 49 | .collect(); 50 | 51 | debug_assert_eq!(table1.len(), 65_536); 52 | debug_assert_eq!(table2.len(), 65_536); 53 | 54 | Self { table1, table2 } 55 | } 56 | } 57 | 58 | impl FastHash for TabulationHash { 59 | /// Computes a 16-bit hash value for a 32-bit input using tabulation hashing. 60 | /// 61 | /// # Algorithm 62 | /// 63 | /// 1. Splits input into 16-bit LSB and MSB parts 64 | /// 2. Uses parts to index into pre-computed tables 65 | /// 3. Combines results using XOR operation 66 | /// 67 | /// # Arguments 68 | /// 69 | /// * `value` - 32-bit unsigned integer to hash 70 | /// 71 | /// # Returns 72 | /// 73 | /// A 16-bit hash value in range 0..MAX_ELEMENTS 74 | /// 75 | /// # Example 76 | /// 77 | /// ``` 78 | /// use crate::toolbox_rs::fast_hash_trait::FastHash; 79 | /// use toolbox_rs::tabulation_hash::TabulationHash; 80 | /// let hasher = TabulationHash::new(); 81 | /// let hash = hasher.hash(42); 82 | /// assert!(hash <= u16::MAX); 83 | /// ``` 84 | #[inline] 85 | fn hash(&self, key: u32) -> u16 { 86 | let lsb = (key & 0xffff) as usize; 87 | let msb = (key >> 16) as usize; 88 | 89 | self.table1[lsb] ^ self.table2[msb] 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::*; 96 | 97 | #[test] 98 | fn test_hash_deterministic() { 99 | let hasher1 = TabulationHash::new(); 100 | let hasher2 = TabulationHash::new(); 101 | 102 | assert_eq!(hasher1.hash(42), hasher2.hash(42)); 103 | } 104 | 105 | #[test] 106 | fn test_hash_different_inputs() { 107 | let hasher = TabulationHash::new(); 108 | 109 | let hash1 = hasher.hash(1); 110 | let hash2 = hasher.hash(26); 111 | assert_ne!(hash1, hash2); 112 | } 113 | 114 | #[test] 115 | fn test_tabulation_hash_default() { 116 | let default_hasher = TabulationHash::default(); 117 | let new_hasher = TabulationHash::new(); 118 | 119 | // Test that default gives same results as new() 120 | assert_eq!(default_hasher.hash(42), new_hasher.hash(42)); 121 | assert_eq!(default_hasher.hash(100), new_hasher.hash(100)); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/tarjan.rs: -------------------------------------------------------------------------------- 1 | use log::info; 2 | 3 | use crate::graph::{Graph, NodeID}; 4 | use core::cmp::min; 5 | 6 | #[derive(Clone)] 7 | struct DFSNode { 8 | caller: NodeID, 9 | index: usize, 10 | lowlink: NodeID, 11 | neighbor: usize, 12 | on_stack: bool, 13 | } 14 | 15 | impl DFSNode { 16 | pub fn new() -> Self { 17 | DFSNode { 18 | caller: NodeID::MAX, 19 | index: usize::MAX, 20 | lowlink: NodeID::MAX, 21 | neighbor: usize::MAX, 22 | on_stack: false, 23 | } 24 | } 25 | } 26 | 27 | #[derive(Default)] 28 | pub struct Tarjan { 29 | dfs_state: Vec, 30 | tarjan_stack: Vec, 31 | } 32 | 33 | impl Tarjan { 34 | pub fn new() -> Self { 35 | Self { 36 | dfs_state: Vec::new(), 37 | tarjan_stack: Vec::new(), 38 | } 39 | } 40 | 41 | // TODO: consider adding handlers for small/large SCCs 42 | pub fn run(&mut self, graph: &(impl Graph + 'static)) -> Vec { 43 | let mut assignment = Vec::new(); 44 | assignment.resize(graph.number_of_nodes(), usize::MAX); 45 | let mut index = 0; 46 | let mut num_scc = 0; 47 | 48 | self.dfs_state 49 | .resize(graph.number_of_nodes(), DFSNode::new()); 50 | 51 | // assign each node to an SCC if not yet done 52 | for n in graph.node_range() { 53 | if self.dfs_state[n].index != usize::MAX { 54 | continue; 55 | } 56 | // TODO: consider moving the following to a function to save indentation 57 | 58 | self.stack_push(n, usize::MAX, index); 59 | index += 1; 60 | let mut last = n; 61 | 62 | loop { 63 | if self.dfs_state[last].neighbor < graph.out_degree(last) { 64 | let e = graph 65 | .edge_range(last) 66 | .nth(self.dfs_state[last].neighbor) 67 | .expect("edge range exhausted"); 68 | let w = graph.target(e); 69 | self.dfs_state[last].neighbor += 1; 70 | if self.dfs_state[w].index == usize::MAX { 71 | self.stack_push(w, last, index); 72 | index += 1; 73 | last = w; 74 | } else if self.dfs_state[w].on_stack { 75 | self.dfs_state[last].lowlink = 76 | min(self.dfs_state[last].lowlink, self.dfs_state[w].index); 77 | } 78 | } else { 79 | if self.dfs_state[last].lowlink == self.dfs_state[last].index { 80 | num_scc += 1; 81 | let mut size = 0; 82 | loop { 83 | let top = self.tarjan_stack.pop().expect("tarjan_stack empty"); 84 | self.dfs_state[top].on_stack = false; 85 | size += 1; 86 | assignment[top] = num_scc; 87 | if top == last { 88 | break; 89 | } 90 | } 91 | // TODO: call handler for small/large SCCs 92 | info!("detected SCC of size {size}"); 93 | } 94 | 95 | let new_last = self.dfs_state[last].caller; 96 | if new_last != usize::MAX { 97 | self.dfs_state[new_last].lowlink = min( 98 | self.dfs_state[new_last].lowlink, 99 | self.dfs_state[last].lowlink, 100 | ); 101 | last = new_last; 102 | } else { 103 | debug_assert!(n == last); 104 | break; 105 | } 106 | } 107 | } 108 | } 109 | assignment 110 | } 111 | 112 | fn stack_push(&mut self, w: usize, caller: usize, index: usize) { 113 | self.dfs_state[w].caller = caller; 114 | self.dfs_state[w].neighbor = 0; 115 | self.dfs_state[w].index = index; 116 | self.dfs_state[w].lowlink = index; 117 | self.dfs_state[w].on_stack = true; 118 | self.tarjan_stack.push(w); 119 | } 120 | } 121 | 122 | #[cfg(test)] 123 | mod tests { 124 | use crate::edge::InputEdge; 125 | use crate::static_graph::StaticGraph; 126 | use crate::tarjan::Tarjan; 127 | 128 | #[test] 129 | fn scc_wiki1() { 130 | type Graph = StaticGraph; 131 | let edges = vec![ 132 | InputEdge::new(0, 1, 3), 133 | InputEdge::new(1, 2, 3), 134 | InputEdge::new(1, 4, 1), 135 | InputEdge::new(1, 5, 6), 136 | InputEdge::new(2, 3, 2), 137 | InputEdge::new(2, 6, 2), 138 | InputEdge::new(3, 2, 2), 139 | InputEdge::new(3, 7, 2), 140 | InputEdge::new(4, 0, 2), 141 | InputEdge::new(4, 5, 2), 142 | InputEdge::new(5, 6, 2), 143 | InputEdge::new(6, 5, 2), 144 | InputEdge::new(7, 3, 2), 145 | InputEdge::new(7, 6, 2), 146 | ]; 147 | let graph = Graph::new(edges); 148 | 149 | let mut tarjan = Tarjan::new(); 150 | assert_eq!(vec![3, 3, 2, 2, 3, 1, 1, 2], tarjan.run(&graph)); 151 | } 152 | 153 | #[test] 154 | fn geekforgeeks() { 155 | type Graph = StaticGraph; 156 | let edges = vec![ 157 | InputEdge::new(1, 0, 3), 158 | InputEdge::new(0, 3, 3), 159 | InputEdge::new(0, 2, 1), 160 | InputEdge::new(2, 1, 6), 161 | InputEdge::new(3, 4, 2), 162 | ]; 163 | let graph = Graph::new(edges); 164 | 165 | let mut tarjan = Tarjan::new(); 166 | assert_eq!(vec![3, 3, 3, 2, 1], tarjan.run(&graph)); 167 | } 168 | 169 | #[test] 170 | fn stanford2() { 171 | type Graph = StaticGraph; 172 | let edges = vec![ 173 | InputEdge::new(0, 6, 3), 174 | InputEdge::new(6, 3, 3), 175 | InputEdge::new(3, 0, 1), 176 | InputEdge::new(6, 8, 6), 177 | InputEdge::new(8, 5, 2), 178 | InputEdge::new(5, 2, 2), 179 | InputEdge::new(2, 8, 2), 180 | InputEdge::new(5, 7, 2), 181 | InputEdge::new(7, 1, 2), 182 | InputEdge::new(4, 7, 2), 183 | InputEdge::new(1, 4, 2), 184 | ]; 185 | let graph = Graph::new(edges); 186 | 187 | let mut tarjan = Tarjan::new(); 188 | assert_eq!(vec![3, 1, 2, 3, 1, 2, 3, 1, 2], tarjan.run(&graph)); 189 | } 190 | 191 | #[test] 192 | fn web1() { 193 | type Graph = StaticGraph; 194 | let edges = vec![ 195 | InputEdge::new(0, 1, 3), 196 | InputEdge::new(1, 3, 3), 197 | InputEdge::new(1, 4, 1), 198 | InputEdge::new(1, 2, 6), 199 | InputEdge::new(2, 5, 2), 200 | InputEdge::new(4, 1, 2), 201 | InputEdge::new(4, 5, 2), 202 | InputEdge::new(4, 6, 2), 203 | InputEdge::new(5, 7, 2), 204 | InputEdge::new(6, 7, 2), 205 | InputEdge::new(6, 8, 2), 206 | InputEdge::new(7, 9, 2), 207 | InputEdge::new(9, 10, 2), 208 | InputEdge::new(10, 8, 2), 209 | InputEdge::new(8, 11, 2), 210 | InputEdge::new(11, 6, 2), 211 | ]; 212 | let graph = Graph::new(edges); 213 | 214 | let mut tarjan = Tarjan::new(); 215 | assert_eq!(vec![6, 5, 3, 4, 5, 2, 1, 1, 1, 1, 1, 1], tarjan.run(&graph)); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/tiny_table.rs: -------------------------------------------------------------------------------- 1 | /// hash table semantics build over an unsorted vector and linear search. 2 | /// This is fast for small data sets with small keys up to dozens of entries. 3 | pub struct TinyTable { 4 | data: Vec<(K, V)>, 5 | } 6 | 7 | impl TinyTable { 8 | pub fn contains(&self, k: &K) -> bool { 9 | self.data.iter().any(|x| -> bool { x.0 == *k }) 10 | } 11 | 12 | pub fn find(&self, k: &K) -> Option<&V> { 13 | if let Some(entry) = self.data.iter().find(|x| x.0 == *k) { 14 | return Some(&entry.1); 15 | } 16 | None 17 | } 18 | 19 | pub fn remove(&mut self, k: &K) -> bool { 20 | if let Some(index) = self.data.iter().position(|value| value.0 == *k) { 21 | self.data.swap_remove(index); 22 | return true; 23 | } 24 | false 25 | } 26 | 27 | pub fn find_mut(&mut self, k: &K) -> Option<&mut (K, V)> { 28 | self.data.iter_mut().find(|x| x.0 == *k) 29 | } 30 | 31 | pub fn insert(&mut self, k: K, v: V) -> bool { 32 | let result = self.remove(&k); 33 | self.data.push((k, v)); 34 | result 35 | } 36 | 37 | pub fn new() -> Self { 38 | TinyTable { data: Vec::new() } 39 | } 40 | 41 | pub fn len(&self) -> usize { 42 | self.data.len() 43 | } 44 | 45 | pub fn is_empty(&self) -> bool { 46 | self.data.is_empty() 47 | } 48 | 49 | pub fn clear(&mut self) { 50 | self.data.clear(); 51 | } 52 | } 53 | 54 | impl Default for TinyTable { 55 | fn default() -> Self { 56 | Self::new() 57 | } 58 | } 59 | 60 | // TODO: proper implementation, rather than using unit type that must be explicitly provided 61 | pub type TinySet = TinyTable; 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use crate::tiny_table::{TinySet, TinyTable}; 66 | 67 | #[test] 68 | fn insert_find_remove_table() { 69 | let mut table = TinyTable::new(); 70 | assert!(table.is_empty()); 71 | 72 | table.insert(1, 999); 73 | table.insert(0, 31337); 74 | 75 | assert_eq!(table.len(), 2); 76 | assert_eq!(table.find(&1), Some(&999)); 77 | assert_eq!(table.find(&0), Some(&31337)); 78 | assert!(table.contains(&0)); 79 | assert!(table.contains(&1)); 80 | assert!(!table.contains(&2)); 81 | 82 | table.remove(&1); 83 | assert_eq!(table.len(), 1); 84 | assert_eq!(table.find(&1), None); 85 | assert_eq!(table.find(&0), Some(&31337)); 86 | assert!(table.contains(&0)); 87 | assert!(!table.contains(&1)); 88 | assert!(!table.contains(&2)); 89 | 90 | table.insert(7, 0xbeef); 91 | assert_eq!(table.len(), 2); 92 | assert_eq!(table.find(&1), None); 93 | assert_eq!(table.find(&7), Some(&0xbeef)); 94 | assert_eq!(table.find(&0), Some(&31337)); 95 | assert!(table.contains(&0)); 96 | assert!(!table.contains(&1)); 97 | assert!(!table.contains(&2)); 98 | assert!(table.contains(&7)); 99 | 100 | table.clear(); 101 | assert!(table.is_empty()); 102 | } 103 | 104 | #[test] 105 | fn insert_find_remove_set() { 106 | let mut table = TinySet::new(); 107 | assert!(table.is_empty()); 108 | 109 | table.insert(1, ()); 110 | table.insert(0, ()); 111 | 112 | assert_eq!(table.len(), 2); 113 | assert!(table.find(&1).is_some()); 114 | assert!(table.find(&0).is_some()); 115 | assert!(table.contains(&0)); 116 | assert!(table.contains(&1)); 117 | assert!(!table.contains(&2)); 118 | 119 | assert!(table.remove(&1)); 120 | assert_eq!(table.len(), 1); 121 | assert!(table.find(&1).is_none()); 122 | assert!(table.find(&0).is_some()); 123 | assert!(table.contains(&0)); 124 | assert!(!table.contains(&1)); 125 | assert!(!table.contains(&2)); 126 | 127 | table.insert(7, ()); 128 | assert_eq!(table.len(), 2); 129 | assert!(table.find(&1).is_none()); 130 | assert!(table.find(&7).is_some()); 131 | assert!(table.find(&0).is_some()); 132 | assert!(table.contains(&0)); 133 | assert!(!table.contains(&1)); 134 | assert!(!table.contains(&2)); 135 | assert!(table.contains(&7)); 136 | 137 | table.clear(); 138 | assert!(table.is_empty()); 139 | } 140 | 141 | #[test] 142 | fn insert_find_mut() { 143 | let mut table = TinyTable::::default(); 144 | assert!(table.is_empty()); 145 | 146 | table.insert(1, 1); 147 | table.insert(0, 2); 148 | 149 | assert_eq!(table.find(&0), Some(&2)); 150 | if let Some(entry) = table.find_mut(&0) { 151 | entry.1 = 0; 152 | } 153 | assert_eq!(table.find(&0).unwrap(), &0); 154 | 155 | assert_eq!(table.find(&2), None); 156 | if let Some(entry) = table.find_mut(&1) { 157 | entry.0 = 2; 158 | entry.1 = 2; 159 | } 160 | assert_eq!(table.find(&2).unwrap(), &2); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/top_k.rs: -------------------------------------------------------------------------------- 1 | use crate::invoke_macro_for_types; 2 | 3 | pub trait ComparisonValue { 4 | type Integral: Default; 5 | fn value(&self) -> Self::Integral; 6 | } 7 | 8 | macro_rules! cv { 9 | // short-hand to add a default ComparisonValue implementation for the 10 | // given input type. Works with built-in types like integers. 11 | ($a:ident) => { 12 | impl ComparisonValue for $a { 13 | type Integral = $a; 14 | 15 | fn value(&self) -> Self::Integral { 16 | *self 17 | } 18 | } 19 | }; 20 | } 21 | 22 | invoke_macro_for_types!( 23 | cv, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize 24 | ); 25 | 26 | pub fn top_k( 27 | input: impl IntoIterator, 28 | k: usize, 29 | ) -> Vec 30 | where 31 | ::Integral: PartialOrd, 32 | { 33 | if k == 0 { 34 | return Vec::new(); 35 | } 36 | 37 | let mut top_k = Vec::with_capacity(2 * k); 38 | let mut threshold: Option = None; 39 | for item in input { 40 | if let Some(ref t) = threshold { 41 | if item.value() >= *t { 42 | continue; 43 | } 44 | } 45 | top_k.push(item); 46 | if top_k.len() == 2 * k { 47 | let (_, median, _) = top_k.select_nth_unstable(k - 1); 48 | threshold = Some(median.value()); 49 | top_k.truncate(k); 50 | } 51 | } 52 | 53 | // TODO: consider running select_nth + truncate before sorting (benchmark using criterion) 54 | top_k.sort_unstable(); 55 | top_k.truncate(k); 56 | 57 | top_k 58 | } 59 | 60 | #[cfg(test)] 61 | mod test { 62 | use super::top_k; 63 | use crate::top_k::ComparisonValue; 64 | 65 | #[test] 66 | fn top_3_5_hit() { 67 | #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 68 | pub struct Hit { 69 | pub score: u64, 70 | } 71 | 72 | impl ComparisonValue for Hit { 73 | type Integral = u64; 74 | 75 | fn value(&self) -> Self::Integral { 76 | self.score 77 | } 78 | } 79 | 80 | let input = [ 81 | Hit { score: 8 }, 82 | Hit { score: 12 }, 83 | Hit { score: 5 }, 84 | Hit { score: 1 }, 85 | Hit { score: 20 }, 86 | ]; 87 | let output = top_k(input, 3); 88 | let expected = [Hit { score: 1 }, Hit { score: 5 }, Hit { score: 8 }]; 89 | 90 | output.iter().zip(expected.iter()).for_each(|(a, b)| { 91 | assert_eq!(a.value(), b.value()); 92 | }); 93 | } 94 | 95 | #[test] 96 | fn top_3_5_i32() { 97 | let input = [8, 12, 5, 1, 20]; 98 | let output = top_k(input, 3); 99 | assert_eq!(output, vec![1, 5, 8,]); 100 | } 101 | 102 | #[test] 103 | fn top_3_15_i32() { 104 | let input = [8, 12, 5, 1, 20, 7, 2, 6, 3, 4, 9, 21, 26, 27, 8]; 105 | let output = top_k(input, 3); 106 | assert_eq!(output, vec![1, 2, 3,]); 107 | } 108 | 109 | #[test] 110 | fn top_0_15_i32() { 111 | let input = [8, 12, 5, 1, 20, 7, 2, 6, 3, 4, 9, 21, 26, 27, 8]; 112 | let output = top_k(input, 0); 113 | assert_eq!(output, Vec::::new()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/union_find.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | pub struct UnionFind { 4 | number_of_sets: usize, 5 | parent: Vec, 6 | rank: Vec, 7 | } 8 | 9 | impl UnionFind { 10 | pub fn new(max: usize) -> Self { 11 | Self { 12 | number_of_sets: max, 13 | parent: (0..max).collect(), 14 | rank: vec![0; max], 15 | } 16 | } 17 | 18 | pub fn len(&self) -> usize { 19 | self.parent.len() 20 | } 21 | 22 | pub fn is_empty(&self) -> bool { 23 | self.len() == 0 24 | } 25 | 26 | // union of two sets represented by x and y. 27 | pub fn union(&mut self, x: usize, y: usize) { 28 | let x_set = self.find(x); 29 | let y_set = self.find(y); 30 | 31 | if x_set == y_set { 32 | return; 33 | } 34 | 35 | // merge lower ranked set with higher ranked set 36 | match self.rank[x_set].cmp(&self.rank[y_set]) { 37 | Ordering::Less => { 38 | self.parent[x_set] = y_set; 39 | } 40 | Ordering::Greater => { 41 | self.parent[y_set] = x_set; 42 | } 43 | Ordering::Equal => { 44 | self.parent[y_set] = x_set; 45 | self.rank[x_set] += 1; 46 | } 47 | } 48 | 49 | self.number_of_sets -= 1; 50 | } 51 | 52 | // find the representative of the set that x is an element of 53 | pub fn find(&mut self, x: usize) -> usize { 54 | let mut p = x; 55 | while self.parent[p] != p { 56 | // lazy path compression, set every node to it's parent 57 | self.parent[p] = self.parent[self.parent[p]]; 58 | p = self.parent[p]; 59 | } 60 | p 61 | } 62 | 63 | pub fn number_of_sets(&self) -> usize { 64 | self.number_of_sets 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | 71 | use crate::union_find::UnionFind; 72 | 73 | #[test] 74 | fn default_all_self_parent() { 75 | let mut uf = UnionFind::new(10); 76 | assert!(!uf.is_empty()); 77 | assert_eq!(10, uf.len()); 78 | assert_eq!(10, uf.number_of_sets); 79 | for i in 0..10_usize { 80 | assert_eq!(i, uf.find(i)); 81 | } 82 | assert_eq!(10, uf.number_of_sets); 83 | } 84 | 85 | #[test] 86 | fn unions_in_a_row() { 87 | let mut uf = UnionFind::new(10); 88 | assert!(!uf.is_empty()); 89 | assert_eq!(10, uf.len()); 90 | assert_eq!(10, uf.number_of_sets); 91 | 92 | for i in 0..10_usize { 93 | uf.union(3, i); 94 | } 95 | 96 | for i in 0..10_usize { 97 | // all elements are merged into the representative 98 | assert_eq!(3, uf.find(i)); 99 | } 100 | 101 | // check that all paths are compressed 102 | assert_eq!(uf.parent, vec![3, 3, 3, 3, 3, 3, 3, 3, 3, 3]); 103 | 104 | // check that all ranks are 0 except for item '3' it's 1 105 | assert_eq!(uf.rank, vec![0, 0, 0, 1, 0, 0, 0, 0, 0, 0]); 106 | 107 | // check that all sets have been merged into a single one 108 | assert_eq!(1, uf.number_of_sets); 109 | } 110 | 111 | #[test] 112 | fn test_number_of_sets() { 113 | let mut uf = UnionFind::new(6); 114 | 115 | // Initial state: 6 separate sets 116 | assert_eq!(6, uf.number_of_sets()); 117 | 118 | // Unite elements 0 and 1 -> 5 sets 119 | uf.union(0, 1); 120 | assert_eq!(5, uf.number_of_sets()); 121 | 122 | // Unite elements 2 and 3 -> 4 sets 123 | uf.union(2, 3); 124 | assert_eq!(4, uf.number_of_sets()); 125 | 126 | // Unite elements 0 and 2 (merging two existing sets) -> 3 sets 127 | uf.union(0, 2); 128 | assert_eq!(3, uf.number_of_sets()); 129 | 130 | // Unite elements already in same set -> still 3 sets 131 | uf.union(1, 3); 132 | assert_eq!(3, uf.number_of_sets()); 133 | 134 | // Unite remaining elements 135 | uf.union(4, 5); 136 | assert_eq!(2, uf.number_of_sets()); 137 | 138 | uf.union(0, 4); 139 | assert_eq!(1, uf.number_of_sets()); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/unsafe_slice.rs: -------------------------------------------------------------------------------- 1 | use std::cell::UnsafeCell; 2 | 3 | /// This struct serves as a wrapper around unsafe accesses to a vector of 4 | /// elements. 5 | /// 6 | /// This can be used if (and only if) we know (and the compiler doesn't) that 7 | /// threads do not access the same index during the concurrent processing. 8 | /// 9 | /// DO NOT REUSE IN YOUR PROJECTS! 10 | /// 11 | #[derive(Copy, Clone)] 12 | pub struct UnsafeSlice<'a, T> { 13 | slice: &'a [UnsafeCell], 14 | } 15 | unsafe impl Send for UnsafeSlice<'_, T> {} 16 | unsafe impl Sync for UnsafeSlice<'_, T> {} 17 | 18 | impl<'a, T> UnsafeSlice<'a, T> { 19 | pub fn new(slice: &'a mut [T]) -> Self { 20 | let ptr = slice as *mut [T] as *const [UnsafeCell]; 21 | Self { 22 | slice: unsafe { &*ptr }, 23 | } 24 | } 25 | 26 | /// # Safety 27 | /// Two threads concurrently writing to the same location will cause UB!! 28 | #[allow(clippy::mut_from_ref)] 29 | pub unsafe fn get_mut(&self, index: usize) -> &mut T { 30 | unsafe { &mut *self.slice[index].get() } 31 | } 32 | 33 | /// Returns a shared reference to the element at the given index. 34 | /// 35 | /// This function is safe to call because it returns an immutable reference, 36 | /// which can be shared between multiple threads. However, it's important to note 37 | /// that this safety relies on the exclusive access guarantee provided by the 38 | /// mutable reference passed to `UnsafeSlice::new()`. 39 | /// 40 | /// # Arguments 41 | /// 42 | /// * `index` - Position of the element to access 43 | /// 44 | /// # Returns 45 | /// 46 | /// Reference to the element at `index` 47 | /// 48 | /// # Panics 49 | /// 50 | /// Panics if `index` is out of bounds 51 | /// 52 | /// # Examples 53 | /// 54 | /// ``` 55 | /// # use toolbox_rs::unsafe_slice::UnsafeSlice; 56 | /// let mut data = vec![1, 2, 3]; 57 | /// let slice = UnsafeSlice::new(&mut data); 58 | /// assert_eq!(*slice.get(0), 1); 59 | /// ``` 60 | pub fn get(&self, index: usize) -> &T { 61 | unsafe { &mut *self.slice[index].get() } 62 | } 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::UnsafeSlice; 68 | 69 | #[test] 70 | fn instantiate() { 71 | let mut data = vec![0, 1, 23, 83, 38, 3, 8947, 2762]; 72 | let slice = UnsafeSlice::new(&mut data); 73 | assert_eq!(*slice.get(0), 0); 74 | assert_eq!(*slice.get(1), 1); 75 | assert_eq!(*slice.get(2), 23); 76 | assert_eq!(*slice.get(3), 83); 77 | assert_eq!(*slice.get(4), 38); 78 | assert_eq!(*slice.get(5), 3); 79 | assert_eq!(*slice.get(6), 8947); 80 | assert_eq!(*slice.get(7), 2762); 81 | } 82 | 83 | #[test] 84 | fn test_get_mut() { 85 | let mut data = vec![1, 2, 3, 4]; 86 | let slice = UnsafeSlice::new(&mut data); 87 | 88 | // SAFETY: We're only accessing each index once 89 | // and not sharing the slice between threads 90 | unsafe { 91 | *slice.get_mut(0) = 10; 92 | *slice.get_mut(2) = 30; 93 | } 94 | 95 | assert_eq!(*slice.get(0), 10); 96 | assert_eq!(*slice.get(1), 2); 97 | assert_eq!(*slice.get(2), 30); 98 | assert_eq!(*slice.get(3), 4); 99 | 100 | // Verify we can read the modified values 101 | assert_eq!(data[0], 10); 102 | assert_eq!(data[2], 30); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/wgs84.rs: -------------------------------------------------------------------------------- 1 | //! WGS84 coordinate handling and conversions. 2 | //! 3 | //! This module provides functionality for working with WGS84 coordinates and converting between 4 | //! different coordinate systems (WGS84, Web Mercator, pixel coordinates). 5 | 6 | use crate::mercator::EPSG3857_MAX_LATITUDE; 7 | 8 | /// Earth's radius at the equator in kilometers (semi-major axis of WGS84 ellipsoid) 9 | pub const EARTH_RADIUS_KM: f64 = 6_378.137; 10 | /// Maximum longitude in degrees 11 | const MAX_LONGITUDE: f64 = 180.0; 12 | 13 | /// Represents a latitude value in degrees 14 | #[derive(Debug, Clone, Copy)] 15 | pub struct FloatLatitude(pub f64); 16 | 17 | /// Represents a longitude value in degrees 18 | #[derive(Debug, Clone, Copy)] 19 | pub struct FloatLongitude(pub f64); 20 | 21 | /// Represents a coordinate pair in degrees (longitude, latitude) 22 | #[derive(Debug, Clone, Copy)] 23 | pub struct FloatCoordinate { 24 | pub lon: FloatLongitude, 25 | pub lat: FloatLatitude, 26 | } 27 | 28 | impl FloatLatitude { 29 | /// Clamps the latitude value to valid Web Mercator range (-85.051129° to +85.051129°) 30 | pub fn clamp(self) -> Self { 31 | FloatLatitude(self.0.clamp(-EPSG3857_MAX_LATITUDE, EPSG3857_MAX_LATITUDE)) 32 | } 33 | } 34 | 35 | impl FloatLongitude { 36 | /// Clamps the longitude value to valid range (-180° to +180°) 37 | pub fn clamp(self) -> Self { 38 | FloatLongitude(self.0.clamp(-MAX_LONGITUDE, MAX_LONGITUDE)) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | 46 | #[test] 47 | fn test_latitude_bounds() { 48 | // Test latitude clamping 49 | let max_lat = FloatLatitude(90.0); 50 | let min_lat = FloatLatitude(-90.0); 51 | 52 | assert!((max_lat.clamp().0 - EPSG3857_MAX_LATITUDE).abs() < f64::EPSILON); 53 | assert!((min_lat.clamp().0 + EPSG3857_MAX_LATITUDE).abs() < f64::EPSILON); 54 | } 55 | 56 | #[test] 57 | fn test_coordinate_clamping() { 58 | // Test Latitude Clamping 59 | let test_cases_lat = [ 60 | (-90.0, -EPSG3857_MAX_LATITUDE), 61 | (-86.0, -85.051_128_779_806_59), 62 | (0.0, 0.0), 63 | (85.0, 85.0), 64 | (90.0, EPSG3857_MAX_LATITUDE), 65 | ]; 66 | 67 | for (input, expected) in test_cases_lat { 68 | let lat = FloatLatitude(input); 69 | let clamped = lat.clamp(); 70 | assert!( 71 | (clamped.0 - expected).abs() < f64::EPSILON, 72 | "Latitude clamping failed for {}: expected {}, got {}", 73 | input, 74 | expected, 75 | clamped.0 76 | ); 77 | } 78 | 79 | // Test Longitude Clamping 80 | let test_cases_lon = [ 81 | (-200.0, -180.0), 82 | (-180.0, -180.0), 83 | (0.0, 0.0), 84 | (180.0, 180.0), 85 | (200.0, 180.0), 86 | ]; 87 | 88 | for (input, expected) in test_cases_lon { 89 | let lon = FloatLongitude(input); 90 | let clamped = lon.clamp(); 91 | assert!( 92 | (clamped.0 - expected).abs() < f64::EPSILON, 93 | "Longitude clamping failed for {}: expected {}, got {}", 94 | input, 95 | expected, 96 | clamped.0 97 | ); 98 | } 99 | } 100 | } 101 | --------------------------------------------------------------------------------