Skip to content

Add bertsekas algo for assignment problem #600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

smu160
Copy link

@smu160 smu160 commented Sep 13, 2024

Hi,

Apologies for the delay. I added a basic implementation with random cost matrices that compare to the hungarian algorithm. I'll add benchmarks, but I think I'll create a non-criterion based benchmark in order to account for possibly sub-optimal solutions. That is, we want to not only compare the speedup, but how close the total score is to what the hungarian algorithm provides.

I didn't have the time just yet to fix the lack of parallelism. However, the structure for concurrency is already there. That is why you'll see the use of mspc::channel. I also need to use the Matrix struct that is used by the current hungarian algorithm implementation. Luckily, that should only improve performance given the 1D representation. I'll keep working on this. Please feel free to let me know what you think.

Thank you!!

Fix #586

Summary by CodeRabbit

  • New Features
    • Added a new profiling configuration for detailed performance builds.
    • Expanded benchmarking capabilities to compare assignment algorithm performance over varying test sizes.
    • Introduced an example tool that demonstrates algorithm benchmarking with printed performance metrics.
    • Integrated an auction-based assignment algorithm into the public library.
    • Enhanced matrix functionality with improved row access for easier data handling.

Copy link

codspeed-hq bot commented Sep 13, 2024

CodSpeed Performance Report

Merging #600 will not alter performance

Comparing smu160:bertsekas (322f8a1) with main (e8e81df)

Summary

✅ 36 untouched benchmarks

🆕 14 new benchmarks

Benchmarks breakdown

Benchmark main smu160:bertsekas Change
🆕 Bertekas Auction[1000] N/A 18.7 ms N/A
🆕 Bertekas Auction[100] N/A 343.1 µs N/A
🆕 Bertekas Auction[10] N/A 24.1 µs N/A
🆕 Bertekas Auction[200] N/A 1.1 ms N/A
🆕 Bertekas Auction[20] N/A 30.9 µs N/A
🆕 Bertekas Auction[500] N/A 5.8 ms N/A
🆕 Bertekas Auction[50] N/A 124.8 µs N/A
🆕 Hungarian Algorithm[1000] N/A 5.8 s N/A
🆕 Hungarian Algorithm[100] N/A 1.3 ms N/A
🆕 Hungarian Algorithm[10] N/A 16.1 µs N/A
🆕 Hungarian Algorithm[200] N/A 7.2 ms N/A
🆕 Hungarian Algorithm[20] N/A 42.9 µs N/A
🆕 Hungarian Algorithm[500] N/A 434.7 ms N/A
🆕 Hungarian Algorithm[50] N/A 355 µs N/A

@smu160

This comment was marked as outdated.

@smu160 smu160 marked this pull request as ready for review September 20, 2024 00:20
@smu160
Copy link
Author

smu160 commented Sep 20, 2024

@samueltardieu

I added a benchmark, using criterion, to compare the kuhn_munkres implementation with the bertsekas implementation.

Since the Bertsekas auction can give sub-optimal results (this is dependent on $\epsilon$ and the scaling factor), I included plots I rendered via matplotlib to compare the run-time as well as the overall score of the assignments. The underlying "benchmark" for these plots can be found in examples/assignment.rs.

cargo criterion --bench kuhn_munkres_vs_bertsekas --output-format bencher
    Finished `bench` profile [optimized] target(s) in 0.02s
test Assignment Problem/Bertekas Auction/10 ... bench:        3346 ns/iter (+/- 902)
test Assignment Problem/Hungarian Algorithm/10 ... bench:         777 ns/iter (+/- 126)
test Assignment Problem/Bertekas Auction/20 ... bench:        7051 ns/iter (+/- 1342)
test Assignment Problem/Hungarian Algorithm/20 ... bench:        2634 ns/iter (+/- 362)
test Assignment Problem/Bertekas Auction/50 ... bench:       20769 ns/iter (+/- 2251)
test Assignment Problem/Hungarian Algorithm/50 ... bench:       17671 ns/iter (+/- 2471)
test Assignment Problem/Bertekas Auction/100 ... bench:       54119 ns/iter (+/- 4672)
test Assignment Problem/Hungarian Algorithm/100 ... bench:      174590 ns/iter (+/- 38993)
test Assignment Problem/Bertekas Auction/200 ... bench:      169806 ns/iter (+/- 15348)

Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 5.4s, enable flat sampling, or reduce sample count to 60.
test Assignment Problem/Hungarian Algorithm/200 ... bench:      989247 ns/iter (+/- 128546)
test Assignment Problem/Bertekas Auction/500 ... bench:      888877 ns/iter (+/- 52426)
test Assignment Problem/Hungarian Algorithm/500 ... bench:    45738048 ns/iter (+/- 2187718)
test Assignment Problem/Bertekas Auction/1000 ... bench:     2360069 ns/iter (+/- 317069)

Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 47.1s, or reduce sample count to 10.
test Assignment Problem/Hungarian Algorithm/1000 ... bench:   492148479 ns/iter (+/- 10239431)

execution_time_comparison
score_comparison

This PR is definitely still a work in progress, but I thought I'd share these preliminary results with you so you can have a clearer picture.

Note that the code is setup for parallelism already via the use of channels. Thus, these benchmark results contain a lot of unnecessary overhead for the bertseka's algorithm implementation, without the advantage of using multiple threads.

In addition this is a very basic implementation of this algorithm. We can actually converge on the solution much quicker if we have a forward/reverse auction. Luckily, this current implementation can be easily extended to facilitate this.

Lastly, please note this current implementation does not support asymmetric assignment problems. Thus, we always have to have $N == M$, where $N$ is the # of agents, and $M$ is the # of tasks. The addition of the reverse auction (see the above paper) would facilitate assignment problems where $N < M$.

Thank you!!

@samueltardieu
Copy link
Collaborator

The preliminary results are encouraging, this is interesting to read.

@samueltardieu samueltardieu requested a review from Copilot March 28, 2025 22:58
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request adds a new implementation of the Bertsekas assignment algorithm alongside benchmarks that compare its performance and score to the Hungarian algorithm. Key changes include:

  • A new method in Matrix to access a row as a slice.
  • The introduction of the bertsekas module in src/lib.rs.
  • Examples and benchmarks to evaluate the Bertsekas approach versus the Kuhn-Munkres implementation.

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/matrix.rs Adds a get_row method to extract a slice for a row.
src/lib.rs Introduces a new module for the Bertsekas algorithm.
examples/assignment.rs Provides a runnable example comparing both algorithms.
benches/kuhn_munkres_vs_bertsekas.rs Adds benchmarks to compare performance and score between algorithms.
Cargo.toml Updates configuration to include the new benchmark; minor formatting adjustments.

let elements = size * size;

group.throughput(Throughput::Elements(elements as u64));
group.bench_function(BenchmarkId::new("Bertekas Auction", size), |b| {
Copy link
Preview

Copilot AI Mar 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in the benchmark name: 'Bertekas Auction' should be corrected to 'Bertsekas Auction' to match the module name and maintain consistency.

Suggested change
group.bench_function(BenchmarkId::new("Bertekas Auction", size), |b| {
group.bench_function(BenchmarkId::new("Bertsekas Auction", size), |b| {

Copilot uses AI. Check for mistakes.

@samueltardieu
Copy link
Collaborator

@coderabbitai review

Copy link

coderabbitai bot commented Apr 5, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

coderabbitai bot commented Apr 5, 2025

Walkthrough

This pull request updates project configurations and introduces new benchmarking and algorithm functionalities. The Cargo.toml file is reformatted, a new profiling configuration is added, and a benchmark entry is introduced. Additionally, new Rust files are added to benchmark assignment algorithms and provide an example usage. The auction algorithm—implementing Dmitri Bertsekas’s approach—is introduced with supporting data structures and methods, and its public API is exposed via a new module. A utility method for matrix row access is also added.

Changes

File(s) Summary
Cargo.toml Reformatted the pre-release-replacements, added a [profile.profiling] section inheriting from release, and inserted a new benchmark entry.
benches/kuhn_munkres_vs_bertsekas.rs, examples/assignment.rs Introduced new benchmark files: one using Criterion to compare the Hungarian algorithm against the Bertsekas Auction, and an example demo for assignment problems.
src/bertsekas.rs, src/lib.rs Added a new implementation of the Bertsekas Auction Algorithm including new structs, methods (e.g., initialization, bidding, scoring), and tests; exposed via the public module.
src/matrix.rs Added a public method get_row to retrieve a specific row as a slice from a matrix.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant BM as Benchmark (Criterion)
    participant CM as Matrix Creator
    participant HA as Hungarian Algorithm
    participant AA as Auction Algorithm
    participant R as Results Logger

    U->>BM: Trigger benchmark run
    BM->>CM: Generate matrices for given sizes
    CM->>HA: Provide integer matrix for Hungarian algorithm
    CM->>AA: Provide floating-point matrix for Auction algorithm
    HA->>BM: Return performance metrics
    AA->>BM: Return performance metrics
    BM->>R: Record and plot throughput data
Loading
sequenceDiagram
    participant A as Agent
    participant AU as Auction Context
    participant T as Task Manager

    A->>AU: Submit bid for a task
    AU->>T: Evaluate bid and update task assignment
    T-->>AU: Confirm assignment update
    AU->>A: Notify bid outcome
Loading

Assessment against linked issues

Objective Addressed Explanation
Implement a faster maximum weight matching solver using the auction algorithm (#586)
Benchmark and compare the auction algorithm against the Hungarian algorithm (#586)
Validate algorithm correctness with integrated tests (#586)

Poem

I hopped through lines of code so light,
Auction bids and benchmarks in plain sight.
Matrices sang with rows unfurled,
Algorithms danced in a joyful world.
With every tweak, my code took flight—
A rabbit’s cheer in code so bright! 🐰✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/lib.rs (1)

101-101: New bertsekas module added but not included in the prelude

The new module has been correctly added to the public API. However, it's not included in the prelude (lines 117-137), meaning users will need to import it explicitly.

Consider adding the module to the prelude if it's intended to be a stable part of the library:

pub mod prelude {
    pub use crate::directed::astar::*;
    // Other imports...
    pub use crate::kuhn_munkres::*;
    pub use crate::matrix::*;
+   pub use crate::bertsekas::*;
    pub use crate::undirected::connected_components::*;
    // Rest of imports...
}
examples/assignment.rs (2)

7-20: Clarify the random value range or consider including zero or negative values.
Currently, the function generates random integers in the range 1..500. If you need to test zero or negative costs, consider broadening the range or documenting why the range excludes them.


11-11: Handle matrix creation errors gracefully.
Using .unwrap() can cause panics in production. If this example is intended for broader usage, consider more robust error handling (e.g., returning a Result).

src/bertsekas.rs (2)

10-13: Consider merging agent and bid vectors for easier maintenance.
Right now, each task has separate vectors: one for agents and one for bids. Storing (agent, bid) tuples in a single vector could simplify lookups and help avoid index misalignment.


357-473: Expand tests for edge cases.
Tests cover basic usage, but consider adding cases with negative or zero costs and more agents than tasks (if supported). This will help verify correctness and detect logical regressions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c61369 and 6c707cf.

📒 Files selected for processing (6)
  • Cargo.toml (3 hunks)
  • benches/kuhn_munkres_vs_bertsekas.rs (1 hunks)
  • examples/assignment.rs (1 hunks)
  • src/bertsekas.rs (1 hunks)
  • src/lib.rs (1 hunks)
  • src/matrix.rs (1 hunks)
🔇 Additional comments (9)
src/matrix.rs (1)

454-462: Well-implemented row access method

This is a clean implementation of a useful utility method that efficiently provides access to matrix rows as slices, with proper bounds checking.

Cargo.toml (3)

46-48: Good addition of profiling configuration

Adding a profiling profile with debug symbols enabled is useful for performance analysis while maintaining the optimizations of a release build.


20-21: Code formatting improvement

The addition of spaces improves the readability of the configuration entries.


75-77: New benchmark for algorithm comparison

The benchmark entry is well defined and consistent with the project's benchmarking structure.

benches/kuhn_munkres_vs_bertsekas.rs (4)

8-13: Well structured matrix creation for benchmarking

Clean implementation of the test matrix creation that provides both integer and floating-point variants.


23-23: Typo in benchmark name

There's a spelling error in the benchmark name.

-        group.bench_function(BenchmarkId::new("Bertekas Auction", size), |b| {
+        group.bench_function(BenchmarkId::new("Bertsekas Auction", size), |b| {

15-45: Comprehensive benchmark implementation

The benchmark function is well structured with appropriate throughput measurements and configuration. The use of logarithmic scale for visualization is appropriate given the wide range of matrix sizes being tested.


19-36:

❌ Incorrect review comment

Potential inconsistency in benchmark matrix types

The benchmark uses floating-point matrices for Bertsekas auction and integer matrices for the Hungarian algorithm, which may make the comparison less direct.

Consider using the same matrix type for both algorithms if they can operate on the same data type, to ensure a fair comparison:


🏁 Script executed:

#!/bin/bash
# Check if the Bertsekas algorithm can also work with integer matrices
rg -A 5 "fn bertsekas_aaap" --type rust

Length of output: 330


Matrix Type Usage is Intentional

The Bertsekas auction algorithm is designed to work with floating‐point matrices (as enforced by its type constraints), while the Hungarian algorithm is implemented for integer matrices. Since these type differences are intrinsic to each algorithm’s design, using different matrix types in the benchmarks is appropriate. If further clarification is needed, consider adding inline comments to document the reasoning for the difference.

Likely an incorrect or invalid review comment.

examples/assignment.rs (1)

27-42:

❓ Verification inconclusive

Mind the performance for large matrix sizes.
Looping up to 5000×5000 can be expensive and memory-intensive, especially with random matrix generation. If benchmarks show significant resource usage, consider parallel generation or smaller default sizes.


🏁 Script executed:

#!/bin/bash
# Check memory usage for large matrix sizes by searching mentions in the code.
rg -A 5 'let sizes:' 

Length of output: 393


Performance Consideration for Large Matrix Sizes

After verifying that the sizes array in examples/assignment.rs includes values up to 5000, it's confirmed that benchmarking will indeed handle matrices up to 5000×5000. Given that generating random matrices at these sizes can be both memory- and compute-intensive, please ensure that performance benchmarks are run. If the results show significant resource usage, consider either parallelizing matrix generation or adjusting the default sizes to mitigate potential performance issues.

Comment on lines +290 to +296
// Send a sentinel value, `None`, indicating we are finished sending all bids.
auction_data.tx.send(None).unwrap();

while let Some(bid) = auction_data.rx.recv().unwrap() {
auction_data.add_task_bid(bid.agent, bid.task, bid.amount);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Evaluate the overhead of bid-channel usage.
The design suggests concurrency, but the code processes bids serially. If concurrency is not implemented, you could remove the channel and loop pattern for simpler direct data handling.

// The # of columns in the matrix corresponds to the # of tasks
let n = cost_matrix.columns;

assert!(m <= n);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Contradictory assert versus code handling.
You assert m <= n, but later code (all_assigned) explicitly checks and attempts to handle m > n. Decide if you truly need to handle more agents than tasks or remove the mismatch.

- assert!(m <= n);
+ // Remove or revise accordingly if you intend to handle m > n
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert!(m <= n);
// Remove or revise accordingly if you intend to handle m > n

Comment on lines +350 to +355
while !auction_data.all_assigned() {
bid_phase(auction_data);
assignment_phase(auction_data);
auction_data.scale_epsilon();
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Check for endless loop scenarios if no feasible assignment exists.
Currently, if no complete assignment is possible, the loop might keep scaling epsilon and never terminate. Ensure the algorithm has a fallback or termination condition in impossible cases (if you plan to support them).


🏁 Script executed:

#!/bin/bash
# Attempt to find usage or definition of fallback logic or a break condition.
rg -A 5 'while !auction_data.all_assigned()'

Length of output: 308


Ensure termination for unassignable auction scenarios.

The auction loop in src/bertsekas.rs (lines 350–355) currently relies solely on auction_data.all_assigned() and lacks a fallback for cases where a complete assignment is impossible. This may result in an endless loop where auction_data.scale_epsilon() is called indefinitely. Please consider implementing a termination condition—such as a maximum iteration count, an epsilon threshold, or another fallback strategy—to guarantee that the loop exits gracefully when no feasible assignment exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consider using a faster solver for maximum weight matching
2 participants