Unified Python framework for Linear Assignment Problem solvers
Project description
py-lap-solver
A unified Python framework for Linear Assignment Problem (LAP) solvers.
Overview
py-lap-solver provides a common interface for multiple LAP solver implementations, ranging from pure Python (scipy) to optimized C++ implementations with OpenMP and CUDA support.
The Linear Assignment Problem seeks to find an optimal assignment between two sets given a cost matrix, minimizing (or maximizing) the total cost of the assignment.
Installation
Install from pypi
pip install py-lap-solver
Or install from source
git clone git@github.com:nikitapond/py-lap-solver.git
# Install the package in editable mode
pip install -e .
# Install with development dependencies
pip install -e ".[dev]"
Features
- Unified Interface: Common API across all solver implementations
- Multiple Backends:
- ScipySolver: Pure Python implementation using scipy's Hungarian algorithm
- BatchedScipySolver: C++ implementation with OpenMP parallelization for batch processing
- Lap1015Solver: Highly optimized C++ implementation (shortest augmenting path algorithm)
- Batch Processing: Solve multiple LAP instances efficiently with OpenMP parallelization
- Flexible Input: Support for square and rectangular cost matrices
- Optional GPU Support: CUDA support in LAP1015 (not yet fully exposed in Python bindings)
Quick Start
Using the Solver Registry (Recommended)
The easiest way to use the solvers is through the pre-configured Solvers registry:
from py_lap_solver.solvers import Solvers
import numpy as np
# Create a batch of cost matrices
batch_matrices = np.random.rand(100, 500, 500)
# Use the fastest available solver with OpenMP parallelization
# This will give you ~6x speedup over sequential processing
assignments = Solvers.BatchedScipyOMP.batch_solve(batch_matrices)
# For single problems, use the standard scipy solver
cost_matrix = np.random.rand(500, 500)
single_assignment = Solvers.Scipy.solve_single(cost_matrix)
Available solvers in the registry:
Solvers.Scipy- Pure Python scipy implementation (always available)Solvers.BatchedScipyOMP- C++ scipy with OpenMP batch parallelizationSolvers.BatchedScipySequential- C++ scipy without parallelizationSolvers.Lap1015OMP- LAP1015 algorithm with OpenMP (limited benefit)Solvers.Lap1015Sequential- LAP1015 algorithm without OpenMP
Manual Configuration
You can also instantiate solvers directly with custom parameters:
from py_lap_solver.solvers import ScipySolver, BatchedScipySolver, Lap1015Solver
import numpy as np
# Use scipy solver (always available)
scipy_solver = ScipySolver()
assignments = scipy_solver.solve_single(cost_matrix)
# Use batched scipy solver with runtime OpenMP control
if BatchedScipySolver.is_available():
# Create solver with OpenMP enabled (default)
batch_solver_omp = BatchedScipySolver(use_openmp=True)
# Create solver without OpenMP for comparison
batch_solver_seq = BatchedScipySolver(use_openmp=False)
batch_matrices = np.random.rand(10, 100, 100)
fast_assignments = batch_solver_omp.batch_solve(batch_matrices) # ~6x faster
slow_assignments = batch_solver_seq.batch_solve(batch_matrices)
# Use LAP1015 solver
if Lap1015Solver.is_available():
# Note: OpenMP provides minimal benefit for LAP1015 due to algorithm structure
lap_solver = Lap1015Solver(use_openmp=False)
assignments = lap_solver.solve_single(cost_matrix)
Return Format
All solvers return assignments in a consistent format:
- Single problem: 1D array of shape
(N,)whereresult[i]is the column assigned to rowi - Batch problem: 2D array of shape
(B, N)whereresult[b, i]is the column assigned to rowiin batchb - Unassigned rows are marked with
-1(or customunassigned_value)
import numpy as np
from py_lap_solver.solvers import Solvers
cost_matrix = np.array([[1, 2], [3, 4]])
assignments = Solvers.Scipy.solve_single(cost_matrix)
# assignments = [1, 0] (row 0 -> col 1, row 1 -> col 0)
Building with C++ Extensions
To enable the optimized C++ solvers, you need CMake and build tools:
# Install build dependencies
pip install scikit-build-core pybind11
# Build and install with C++ extensions
pip install -e . --no-build-isolation
# On macOS, you may need to install libomp for OpenMP support
brew install libomp
OpenMP Runtime Control
All C++ solvers support runtime OpenMP control through the use_openmp parameter:
from py_lap_solver.solvers import BatchedScipySolver
import numpy as np
# Create solver with OpenMP enabled
solver_parallel = BatchedScipySolver(use_openmp=True)
# Create solver without OpenMP
solver_sequential = BatchedScipySolver(use_openmp=False)
batch = np.random.rand(100, 500, 500)
# Parallel: ~126ms for 100 matrices
assignments_fast = solver_parallel.batch_solve(batch)
# Sequential: ~762ms for 100 matrices
assignments_slow = solver_sequential.batch_solve(batch)
Why OpenMP Helps for Batched Scipy but Not LAP1015:
- BatchedScipySolver: Each matrix in the batch is independent → perfect parallelization with
#pragma omp parallel for - LAP1015Solver: Complex intra-matrix data dependencies → synchronization barriers dominate, killing performance
Recommended Usage Patterns
from py_lap_solver.solvers import Solvers
import numpy as np
# Pattern 1: Batch processing (FAST - use OpenMP)
batch_matrices = np.random.rand(1000, 100, 100)
assignments = Solvers.BatchedScipyOMP.batch_solve(batch_matrices)
# Pattern 2: Single large problem (no parallelization benefit)
single_matrix = np.random.rand(5000, 5000)
assignment = Solvers.Scipy.solve_single(single_matrix) # or BatchedScipySequential
# Pattern 3: Many small problems in a loop
for i in range(1000):
matrix = generate_matrix()
# BAD: Calling solve_single in a loop
result = Solvers.Scipy.solve_single(matrix)
# Better: Batch them together
all_matrices = np.array([generate_matrix() for _ in range(1000)])
results = Solvers.BatchedScipyOMP.batch_solve(all_matrices) # 6x faster!
Development
Installation
# Install with development dependencies (includes black, ruff, pytest)
pip install -e ".[dev]"
Code Formatting and Linting
The project uses black for code formatting and ruff for linting. A Makefile is provided for convenience:
# Format code with black
make format
# Lint code with ruff
make lint
# Auto-fix linting issues
make lint-fix
# Run all checks
make check
# Format, lint-fix, check, and test in one command
make all
Or use the tools directly:
# Format code
black src/ tests/
# Lint code
ruff check src/ tests/
# Auto-fix linting issues
ruff check --fix src/ tests/
Testing
# Run tests with pytest
pytest tests/
# Or use make
make test
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_lap_solver-0.1.1.tar.gz.
File metadata
- Download URL: py_lap_solver-0.1.1.tar.gz
- Upload date:
- Size: 10.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
84a8d54fea970398f6554a903de8f8fa71e53c8c32e8897bb5de7b1f089fe7bd
|
|
| MD5 |
594a6d8c9bf733ec76b749cb7f34d290
|
|
| BLAKE2b-256 |
331185483314ed55bf3c70102e6046559d2b9cedd1c2d87c4f7dade0f0d40dd6
|
File details
Details for the file py_lap_solver-0.1.1-cp311-cp311-macosx_14_0_arm64.whl.
File metadata
- Download URL: py_lap_solver-0.1.1-cp311-cp311-macosx_14_0_arm64.whl
- Upload date:
- Size: 192.3 kB
- Tags: CPython 3.11, macOS 14.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d1b79873181b621ce85d38050f1a010eaecb194a7f77ed23f49b62d91dbadcf
|
|
| MD5 |
8947ebc830342ef34e2a5018d9ed4b55
|
|
| BLAKE2b-256 |
5ec8a8fa976c1d9cf6bf978f21d882f3f7452ea6f0fa6286f0b7987cd20a12c5
|