Skip to content

Fix #300: Add QuadraticAssignment model#622

Merged
isPANN merged 17 commits intomainfrom
issue-300-quadratic-assignment
Mar 18, 2026
Merged

Fix #300: Add QuadraticAssignment model#622
isPANN merged 17 commits intomainfrom
issue-300-quadratic-assignment

Conversation

@zazabap
Copy link
Copy Markdown
Collaborator

@zazabap zazabap commented Mar 13, 2026

Summary

  • Add QuadraticAssignment (QAP) problem model to src/models/algebraic/
  • Classical NP-hard facility-location optimization (Koopmans-Beckmann 1957, Sahni-Gonzalez 1976)
  • Minimizes total interaction cost: Σ c_{ij} · d_{f(i),f(j)} over injective assignments

Fixes #300

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.28%. Comparing base (70707a7) to head (f3d1708).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff            @@
##             main     #622    +/-   ##
========================================
  Coverage   97.28%   97.28%            
========================================
  Files         318      320     +2     
  Lines       41232    41399   +167     
========================================
+ Hits        40111    40277   +166     
- Misses       1121     1122     +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

zazabap and others added 3 commits March 13, 2026 07:02
- Add QuadraticAssignment model in src/models/algebraic/
- Register in CLI (dispatch, aliases, create command)
- Add unit tests (creation, evaluation, solver, serialization)
- Add paper entry with CeTZ example diagram
- Regenerate problem schemas

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add out-of-range location index test
- Add rectangular case test (n < m)
- Add #[should_panic] tests for constructor validation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zazabap
Copy link
Copy Markdown
Collaborator Author

zazabap commented Mar 13, 2026

Implementation Summary

Changes

  • src/models/algebraic/quadratic_assignment.rs — New QAP model (172 lines): struct, Problem/OptimizationProblem traits, declare_variants
  • src/unit_tests/models/algebraic/quadratic_assignment.rs — 10 unit tests: creation, evaluation (identity, swap, invalid, out-of-range), direction, serialization, solver, rectangular case, constructor panics
  • src/models/algebraic/mod.rs — Register module + export
  • src/models/mod.rs — Add to re-exports
  • src/lib.rs — Add to prelude
  • problemreductions-cli/src/dispatch.rs — load_problem + serialize_any_problem arms
  • problemreductions-cli/src/problem_name.rs — QAP alias + lowercase mapping
  • problemreductions-cli/src/commands/create.rs — Create handler with --matrix and --distance-matrix flags
  • problemreductions-cli/src/cli.rs — --distance-matrix flag + help table entry
  • docs/paper/reductions.typ — problem-def with formal definition, background, 4x4 example with CeTZ figure
  • docs/src/reductions/problem_schemas.json — Regenerated (28 schemas)

Deviations from Plan

  • The issue's example instance optimal cost is 36 (not 38 as stated in the issue for the identity assignment — the identity gives 38 but is not optimal)
  • Paper uses a different 4x4 example (optimal cost 22) for clearer exposition

Open Questions

  • Complexity string uses num_facilities ^ num_facilities (n^n) as Expr AST doesn't support factorial. This is an upper bound on the brute-force search space.

Copy link
Copy Markdown

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

Adds a new algebraic optimization model for the Quadratic Assignment Problem (QAP) and wires it into the library, CLI, and docs so it can be created/serialized/solved via existing infrastructure.

Changes:

  • Introduces QuadraticAssignment model with schema registration, variant declaration, and unit tests.
  • Exposes QAP through crate re-exports/prelude and CLI (alias resolution, dispatch load/serialize, pred create support).
  • Updates documentation outputs (schemas JSON + paper) to include the new problem.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/models/algebraic/quadratic_assignment.rs New QAP model implementation, schema registration, and variant declaration.
src/unit_tests/models/algebraic/quadratic_assignment.rs Unit tests for construction, evaluation, serialization, and brute-force solving.
src/models/algebraic/mod.rs Adds the new module + re-export from algebraic.
src/models/mod.rs Re-exports QuadraticAssignment from top-level models.
src/lib.rs Adds QuadraticAssignment to the crate prelude.
problemreductions-cli/src/problem_name.rs Adds QAP alias and resolves qap/quadraticassignment.
problemreductions-cli/src/dispatch.rs Enables CLI load/serialize for QuadraticAssignment.
problemreductions-cli/src/commands/create.rs Adds pred create QAP handling and --distance-matrix parsing.
problemreductions-cli/src/cli.rs Documents QAP flags and adds the --distance-matrix CLI argument.
docs/src/reductions/problem_schemas.json Adds schema entry for QuadraticAssignment.
docs/paper/reductions.typ Adds QAP display name and a new problem definition section.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/paper/reductions.typ Outdated
Given $n$ facilities and $m$ locations ($n <= m$), a flow matrix $C in ZZ^(n times n)$ representing flows between facilities, and a distance matrix $D in ZZ^(m times m)$ representing distances between locations, find an injective assignment $f: {1, dots, n} -> {1, dots, m}$ that minimizes
$ sum_(i != j) C_(i j) dot D_(f(i), f(j)). $
][
The Quadratic Assignment Problem was introduced by Koopmans and Beckmann (1957) to model the optimal placement of economic activities (facilities) across geographic locations, minimizing total transportation cost weighted by inter-facility flows. It is NP-hard, as shown by Sahni and Gonzalez (1976) via reduction from the Hamiltonian Circuit problem. QAP is widely regarded as one of the hardest combinatorial optimization problems: even moderate instances ($n > 20$) challenge state-of-the-art exact solvers. Best exact approaches use branch-and-bound with Gilmore--Lawler bounds or Dreyfus--Wagner-like dynamic programming; the best known general algorithm runs in $O^*(n!)$ by exhaustive enumeration of all permutations#footnote[No algorithm significantly improving on brute-force permutation enumeration is known for general QAP.].
Comment on lines +390 to +405
{
"name": "QuadraticAssignment",
"description": "Minimize total cost of assigning facilities to locations",
"fields": [
{
"name": "cost_matrix",
"type_name": "Vec<Vec<i64>>",
"description": "Flow/cost matrix between facilities"
},
{
"name": "distance_matrix",
"type_name": "Vec<Vec<i64>>",
"description": "Distance matrix between locations"
}
]
},
module_path: module_path!(),
description: "Minimize total cost of assigning facilities to locations",
fields: &[
FieldInfo { name: "cost_matrix", type_name: "Vec<Vec<i64>>", description: "Flow/cost matrix between facilities" },
Comment on lines +121 to +146
fn evaluate(&self, config: &[usize]) -> SolutionSize<i64> {
let n = self.num_facilities();
let m = self.num_locations();

// Check that all assignments are valid locations
for &loc in config.iter().take(n) {
if loc >= m {
return SolutionSize::Invalid;
}
}

// Check injectivity: no two facilities assigned to the same location
let mut used = std::collections::HashSet::new();
for &loc in config.iter().take(n) {
if !used.insert(loc) {
return SolutionSize::Invalid;
}
}

// Compute objective: sum_{i != j} cost_matrix[i][j] * distance_matrix[config[i]][config[j]]
let mut total: i64 = 0;
for i in 0..n {
for j in 0..n {
if i != j {
total += self.cost_matrix[i][j] * self.distance_matrix[config[i]][config[j]];
}
Comment on lines +133 to +137
let mut used = std::collections::HashSet::new();
for &loc in config.iter().take(n) {
if !used.insert(loc) {
return SolutionSize::Invalid;
}
///
/// f(p) = sum_{i != j} C[i][j] * D[p(i)][p(j)]
///
/// where p is a permutation mapping facilities to locations.
Comment on lines +269 to +294
// QuadraticAssignment
"QuadraticAssignment" => {
let cost_str = args.matrix.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"QuadraticAssignment requires --matrix (cost) and --distance-matrix\n\n\
Usage: pred create QAP --matrix \"0,5;5,0\" --distance-matrix \"0,1;1,0\""
)
})?;
let dist_str = args.distance_matrix.as_deref().ok_or_else(|| {
anyhow::anyhow!(
"QuadraticAssignment requires --distance-matrix\n\n\
Usage: pred create QAP --matrix \"0,5;5,0\" --distance-matrix \"0,1;1,0\""
)
})?;
let cost_matrix = parse_i64_matrix(cost_str).context("Invalid cost matrix")?;
let distance_matrix = parse_i64_matrix(dist_str).context("Invalid distance matrix")?;
(
ser(
problemreductions::models::algebraic::QuadraticAssignment::new(
cost_matrix,
distance_matrix,
),
)?,
resolved_variant.clone(),
)
}
Comment on lines +925 to +936
s.split(';')
.map(|row| {
row.trim()
.split(',')
.map(|v| {
v.trim()
.parse::<i64>()
.map_err(|e| anyhow::anyhow!("Invalid matrix value: {}", e))
})
.collect()
})
.collect()
zazabap and others added 3 commits March 13, 2026 15:58
- Fix Dreyfus-Wagner reference in paper (not applicable to QAP, replaced with cutting-plane methods)
- Add config length check in evaluate() to return Invalid instead of panicking
- Replace HashSet with Vec<bool> for faster injectivity checking
- Fix doc wording: "permutation" -> "injective mapping" for rectangular case
- Add input validation in CLI create to surface errors instead of panicking
- Improve parse_i64_matrix with row/col context in errors and ragged matrix detection
- Regenerate problem_schemas.json after merge
- Add tests for config length mismatch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zazabap
Copy link
Copy Markdown
Collaborator Author

zazabap commented Mar 13, 2026

Review Pipeline Report

Check Result
Merge with main resolved 3 conflicts (additive entries in reductions.typ, problem_schemas.json, dispatch.rs)
Copilot comments 7 fixed (paper ref, length check, Vec optimization, doc wording, CLI validation, matrix parser, schemas regenerated)
Issue/human comments 2 checked, 0 action needed (quality check passed, implementation summary informational)
CI green (local make check passes; remote CI pending trigger)
Agentic test passed — CLI create/evaluate/solve and Rust API all work correctly
Board review-agentic → In Review

Copilot Comments Addressed

  1. reductions.typ:735 — Replaced Dreyfus-Wagner (Steiner Tree DP) with cutting-plane methods
  2. problem_schemas.json — Regenerated after merge
  3. quadratic_assignment.rs:17 — CLI flag naming (--matrix) is consistent with other problems (QUBO etc.)
  4. quadratic_assignment.rs:146 — Added config.len() != n early check returning Invalid
  5. quadratic_assignment.rs:137 — Replaced HashSet with Vec<bool> for O(1) injectivity checking
  6. quadratic_assignment.rs:32 — Updated doc: "permutation" → "injective mapping (permutation when n == m)"
  7. create.rs:338 — Added matrix squareness and n ≤ m validation before new() call
  8. create.rs:1081 — Added ragged matrix detection and row/col context in parse errors

🤖 Generated by review-pipeline

GiggleLiu and others added 4 commits March 14, 2026 02:33
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add factorial() function support to the expression parser (both proc macro
and runtime) and all Expr match sites (eval, display, canonical, big_o,
analysis). Change QuadraticAssignment's declare_variants! complexity from
"num_facilities ^ num_facilities" to "factorial(num_facilities)".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ssignment

# Conflicts:
#	docs/paper/reductions.typ
#	problemreductions-cli/src/cli.rs
#	problemreductions-cli/src/commands/create.rs
#	problemreductions-cli/src/dispatch.rs
#	problemreductions-cli/src/problem_name.rs
#	problemreductions-macros/src/parser.rs
#	src/expr.rs
#	src/lib.rs
#	src/unit_tests/trait_consistency.rs
@isPANN isPANN closed this Mar 18, 2026
@isPANN isPANN reopened this Mar 18, 2026
isPANN and others added 6 commits March 18, 2026 22:04
Resolve merge conflicts: adapt QuadraticAssignment to main's registry-based
dispatch system (declare_variants!, ProblemSchemaEntry updates), keep QAP
model/CLI/paper additions alongside all new models and infrastructure from main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iven paper

- Align unit test distance matrix with issue #300's canonical instance
  (identity cost=100, optimal f*=(3,0,1,2) cost=56)
- Add canonical_model_example_specs() for QuadraticAssignment in example_db
- Rewrite paper section to load example from example_db via
  load-model-example("QuadraticAssignment") instead of hand-written data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use math.mat(..rows) with actual array data instead of fmt-mat string
interpolation, which was rendering as inline text rather than a matrix grid.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@isPANN isPANN mentioned this pull request Mar 18, 2026
3 tasks
@isPANN isPANN merged commit 472419f into main Mar 18, 2026
3 checks passed
@GiggleLiu GiggleLiu deleted the issue-300-quadratic-assignment branch April 12, 2026 00:53
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.

[Model] QuadraticAssignment

4 participants