Skip to content

Commit 95a050f

Browse files
EvanOmanclaude
andcommitted
Rewrite in Scala 3: fix optimality bugs, add tests, CLI, modern tooling
The 2016 implementation had the same epsilon-scaling termination bug as the C++ sibling (final phase ran too coarse, so tie-heavy instances came back suboptimal — its own harness printed 'Percentage correct') and its fixed 0.3 starting epsilon meant no assignment was ever produced for n <= 2. Old source preserved in legacy/. Now Scala 3.3 with no runtime deps beyond scala-parallel-collections: Auction.solve (min/max, exact optimality, deterministic parallel bidding mode), the same .apf problem format and CLI as AuctionAlgorithmCPP (cross-checked: identical output on shared instances), 18 munit tests with brute-force verification, scalafmt + fatal warnings, justfile, CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6179d73 commit 95a050f

16 files changed

Lines changed: 764 additions & 74 deletions

File tree

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* text=auto eol=lf

.github/workflows/ci.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master, main]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Set up JDK
15+
uses: actions/setup-java@v4
16+
with:
17+
distribution: temurin
18+
java-version: 17
19+
cache: sbt
20+
21+
- name: Install sbt
22+
uses: sbt/setup-sbt@v1
23+
24+
- name: Check formatting
25+
run: sbt -batch scalafmtCheckAll scalafmtSbtCheck
26+
27+
- name: Run tests
28+
run: sbt -batch test

.gitignore

Lines changed: 6 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,8 @@
1-
#### joe made this: http://goel.io/joe
2-
3-
#####=== Scala ===#####
4-
5-
*.class
6-
*.log
7-
8-
# sbt specific
9-
.cache
10-
.history
11-
.lib/
12-
dist/*
131
target/
14-
lib_managed/
15-
src_managed/
16-
project/boot/
17-
project/plugins/project/
18-
19-
# Scala-IDE specific
20-
.scala_dependencies
21-
.worksheet
22-
23-
#####=== JetBrains ===#####
24-
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
25-
26-
*.iml
27-
28-
## Directory-based project format:
2+
project/target/
3+
project/project/
4+
.bsp/
5+
.metals/
6+
.bloop/
297
.idea/
30-
# if you remove the above rule, at least ignore the following:
31-
32-
# User-specific stuff:
33-
# .idea/workspace.xml
34-
# .idea/tasks.xml
35-
# .idea/dictionaries
36-
37-
# Sensitive or high-churn files:
38-
# .idea/dataSources.ids
39-
# .idea/dataSources.xml
40-
# .idea/sqlDataSources.xml
41-
# .idea/dynamic.xml
42-
# .idea/uiDesigner.xml
43-
44-
# Gradle:
45-
# .idea/gradle.xml
46-
# .idea/libraries
47-
48-
# Mongo Explorer plugin:
49-
# .idea/mongoSettings.xml
50-
51-
## File-based project format:
52-
*.ipr
53-
*.iws
54-
55-
## Plugin-specific files:
56-
57-
# IntelliJ
58-
/out/
59-
60-
# mpeltonen/sbt-idea plugin
61-
.idea_modules/
62-
63-
# JIRA plugin
64-
atlassian-ide-plugin.xml
65-
66-
# Crashlytics plugin (for Android Studio and IntelliJ)
67-
com_crashlytics_export_strings.xml
68-
crashlytics.properties
69-
crashlytics-build.properties
70-
8+
*.class

.scalafmt.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version = 3.8.3
2+
runner.dialect = scala3
3+
maxColumn = 100
4+
align.preset = some
5+
rewrite.scala3.convertToNewSyntax = true
6+
rewrite.scala3.removeOptionalBraces = yes

README.md

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,64 @@
11
# Auction Algorithm Scala
22

3-
This is a repo contains a `Scala` implementation of [Bertsekas's Auction Algorithm](http://dspace.mit.edu/bitstream/handle/1721.1/3233/P-2064-24690022.pdf?sequence=1). The algorithm solves the problem of optimally assigning M objects to N people given the preferences specified in a given cost matrix.
3+
A Scala 3 implementation of [Bertsekas' auction algorithm](http://dspace.mit.edu/bitstream/handle/1721.1/3233/P-2064-24690022.pdf?sequence=1) for the n-by-n assignment problem: optimally assigning n objects to n people given a cost matrix. Sibling of [AuctionAlgorithmCPP](https://github.com/EvanOman/AuctionAlgorithmCPP) — same algorithm, same problem-file format, interchangeable CLIs.
44

5-
After a bit of optimization, this implementation can solve a size 500 assignment problem in ~.125 seconds. I have also implemented a parallel version, which seems to outperform the sequential version once the problem size is over 5000. This implementation is still being optimized.
5+
[![CI](https://github.com/EvanOman/AuctionAlgorithmScala/actions/workflows/ci.yml/badge.svg)](https://github.com/EvanOman/AuctionAlgorithmScala/actions/workflows/ci.yml)
6+
7+
Originally written in 2016 (Scala 2.11 + Breeze); rewritten in 2026. The old implementation had the same epsilon-scaling termination bug as the original C++ version (the final scaling phase ran too coarse, so tie-heavy instances came back suboptimal — its own test harness literally printed "Percentage correct"), and its fixed starting epsilon of 0.3 meant it produced **no assignment at all** for n ≤ 2. Full bug taxonomy in the [C++ repo's docs/BUGS.md](https://github.com/EvanOman/AuctionAlgorithmCPP/blob/master/docs/BUGS.md); the old source is preserved at `legacy/`.
8+
9+
## What's here
10+
11+
- **`Auction.solve(costs, n, options)`** — the solver. Row-major `IndexedSeq[Long]` costs, `Minimize` (default) or `Maximize`, exact optimality for integer costs (the final scaling phase runs with n·ε < 1). An optional **parallel bidding mode** (`Options(parallel = true)`) computes each round's bids concurrently with deterministic results — the feature that distinguished the 2016 version, kept and made correct.
12+
- **`Problem`** — parser/formatter for the text problem format shared with the C++ repo.
13+
- **CLI**`solve` / `generate` subcommands mirroring the C++ `auction` binary.
14+
- **Tests** — munit suite: brute-force cross-checks over hundreds of random instances, Machol–Wien closed-form cases, the tie-heavy regression instance, n ≤ 2 regressions, parallel-equals-sequential.
15+
16+
## Usage
17+
18+
Requires JDK 11+ and [sbt](https://www.scala-sbt.org/) ([just](https://github.com/casey/just) optional but recommended).
19+
20+
```bash
21+
just test # run the test suite
22+
just solve problem.apf # solve a problem file
23+
just generate 10 --seed 42 # emit a random 10x10 instance
24+
```
25+
26+
Problem file format (`#` comments and blank lines ignored; `objective` defaults to `min`):
27+
28+
```
29+
objective min
30+
n 3
31+
4 1 3
32+
2 0 5
33+
3 2 2
34+
```
35+
36+
Output:
37+
38+
```
39+
objective: min
40+
n: 3
41+
total_cost: 5
42+
phases: 3
43+
rounds: 7
44+
assignment:
45+
0 -> 1
46+
1 -> 0
47+
2 -> 2
48+
```
49+
50+
Add `--json` for machine-readable output, `--parallel` for parallel bidding.
51+
52+
As a library:
53+
54+
```scala
55+
import com.evan.auctionalgorithm.*
56+
57+
val costs = Vector[Long](4, 1, 3, 2, 0, 5, 3, 2, 2)
58+
val result = Auction.solve(costs, 3) // minimize by default
59+
// result.assignment == Vector(1, 0, 2), result.totalCost == 5
60+
```
61+
62+
## Development
63+
64+
`just fc` before committing (format + tests). CI runs `scalafmtCheckAll` and the test suite on every push. Compiler warnings are fatal (`-Werror`).

build.sbt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1-
name := "AuctionAlgorithmScala"
1+
name := "auction-algorithm"
2+
organization := "com.evan"
3+
version := "1.0.0"
24

3-
scalaVersion := "2.11.7"
5+
scalaVersion := "3.3.4"
46

5-
libraryDependencies += "org.scalanlp" % "breeze_2.11" % "0.12"
7+
scalacOptions ++= Seq(
8+
"-deprecation",
9+
"-feature",
10+
"-unchecked",
11+
"-Wunused:all",
12+
"-Werror"
13+
)
614

7-
connectInput in run := true
15+
libraryDependencies ++= Seq(
16+
"org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4",
17+
"org.scalameta" %% "munit" % "1.0.2" % Test
18+
)
19+
20+
Compile / run / connectInput := true
21+
Compile / mainClass := Some("com.evan.auctionalgorithm.Main")

justfile

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
set shell := ["bash", "-cu"]
2+
3+
# List available recipes.
4+
default:
5+
@just --list
6+
7+
# Compile the project.
8+
build:
9+
sbt -batch compile
10+
11+
# Run the test suite.
12+
test:
13+
sbt -batch test
14+
15+
# Format all Scala sources and build files in place.
16+
fmt:
17+
sbt -batch scalafmtAll scalafmtSbt
18+
19+
# Check formatting without modifying files.
20+
format-check:
21+
sbt -batch scalafmtCheckAll scalafmtSbtCheck
22+
23+
# Lint is the format check plus fatal compiler warnings (enabled in build.sbt).
24+
lint: format-check build
25+
26+
# Format, then run the full test suite. Run before committing.
27+
fc: fmt test
28+
29+
# Run everything CI runs.
30+
ci: format-check test
31+
32+
# Solve a problem file: just solve path/to/problem.apf [--json] [--parallel]
33+
solve *ARGS:
34+
sbt -batch --error "run solve {{ ARGS }}"
35+
36+
# Generate a random problem: just generate N [--lo A --hi B --seed S --objective min|max]
37+
generate *ARGS:
38+
sbt -batch --error "run generate {{ ARGS }}"
39+
40+
# Remove build artifacts.
41+
clean:
42+
sbt -batch clean

src/main/scala-2.11/com/evan/auctionalgorithm/AssignmentProblem.scala renamed to legacy/AssignmentProblem.scala

File renamed without changes.

src/main/scala-2.11/com/evan/auctionalgorithm/Test.scala renamed to legacy/Test.scala

File renamed without changes.

project/build.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=1.10.7

0 commit comments

Comments
 (0)