Thank you for your interest in contributing to benchmORk! This document provides guidelines and information for contributors.
- Development Setup
- Project Structure
- Working with Marimo
- Adding a New Solver
- Adding a New Problem
- Code Style
- Testing
- Pull Request Process
- Python 3.13 or higher
- Git
- uv
# Clone the repository
git clone https://github.com/yourusername/benchmORk.git
cd benchmORk
# Install in development mode with all dependencies
uv sync --all-extras
# Install pre-commit hooks (optional)
pre-commit installuv run marimo run app/main.pybenchmORk/
├── README.md # Project overview and usage
├── CONTRIBUTING.md # This file
├── LICENSE # Apache-2.0 License
├── pyproject.toml # Project configuration and dependencies
│
├── benchmork/ # Core benchmark engine
│ ├── __init__.py
│ ├── problems/ # Problem definitions (solver-agnostic)
│ │ ├── __init__.py
│ │ ├── base.py # Abstract problem interface
│ │ ├── linear.py # LP: transportation, diet, blending
│ │ ├── integer.py # MIP: knapsack, assignment, TSP
│ │ └── nonlinear.py # NLP problems
│ │
│ ├── runner.py # Benchmark execution engine
│ ├── metrics.py # Timing, memory, solution quality
│ ├── scaling.py # Problem size generators
│ │
│ └── reporting/ # Results & visualization
│ ├── __init__.py
│ ├── export.py # CSV/JSON export
│ └── plots.py # Comparison charts (for Marimo)
│
├── solvers/ # Solver adapters
│ ├── __init__.py
│ ├── base.py # Abstract solver interface
│ ├── ortools_solver.py # Google OR-Tools adapter
│ ├── pyomo_solver.py # Pyomo adapter
│ ├── scipy_solver.py # SciPy optimize adapter
│ ├── solvor_solver.py # solvOR adapter
│ └── pulp_solver.py # PuLP adapter
│
├── app/ # Marimo interactive UI
│ ├── __init__.py
│ ├── main.py # Main Marimo app entry point
│ ├── components/ # Reusable UI components
│ │ ├── __init__.py
│ │ ├── solver_selector.py # Multi-select solver picker
│ │ ├── problem_config.py # Problem type & parameters
│ │ ├── scaling_controls.py # Vars, constraints, data size sliders
│ │ └── results_view.py # Tables & charts display
│ │
│ └── pages/ # Marimo notebook pages
│ ├── benchmark.py # Main benchmark runner page
│ ├── compare.py # Side-by-side comparison
│ ├── history.py # Past runs & trends
│ └── explorer.py # Deep-dive single solver
│
├── tests/ # Test suite
│ ├── __init__.py
│ ├── test_problems.py # Problem definition tests
│ ├── test_solvers.py # Solver adapter tests
│ └── test_benchmarks.py # Benchmark runner tests
│
├── configs/ # Preset benchmark configurations
│ ├── quick.yaml # Fast smoke test
│ ├── standard.yaml # Default benchmark suite
│ └── full.yaml # Comprehensive (slow)
│
└── results/ # Output directory (gitignored)
└── .gitkeep
The interactive dashboard is built with Marimo, a reactive Python notebook. See the official Marimo documentation for comprehensive guides.
Use edit mode for development with hot-reloading:
uv run marimo edit app/main.pyThis opens the notebook in edit mode where you can:
- Modify cells and see changes instantly
- Add new UI components
- Debug with the built-in variable explorer
Use run mode for the production dashboard:
uv run marimo run app/main.pyWhen working with Marimo cells, keep these patterns in mind:
-
Unique variable names: Each variable can only be defined in ONE cell. Use unique names to avoid conflicts.
-
Early exit with
mo.stop(): Instead ofreturnfor early exit, use:mo.stop(condition, mo.md("Message to show"))
-
Reactive updates: When a UI element's value changes, all cells that depend on it automatically re-run.
-
Cell dependencies: Marimo automatically tracks which cells depend on which variables. Structure your cells so data flows clearly.
-
UI elements must be returned: For a UI element to be displayed, it must be returned from a cell (either directly or wrapped in
mo.vstack/mo.hstack).
- Create UI elements in a cell and return them for display
- Use those elements in downstream cells via their
.valueproperty - Keep visualization logic separate from data processing
- Use
mo.stop()to conditionally show content
Example:
@app.cell
def my_control(mo):
slider = mo.ui.slider(start=1, stop=100, value=50, label="Size")
return slider,
@app.cell
def use_control(mo, slider):
# slider.value contains the current value
return mo.md(f"Selected size: {slider.value}")To add support for a new optimization solver:
-
Create a new file in
solvers/(e.g.,solvers/newsolver_solver.py) -
Implement the
BaseSolverinterface:
from solvers.base import BaseSolver, SolverResult
from benchmork.problems.base import BaseProblem
class NewSolver(BaseSolver):
"""Adapter for NewSolver optimization library."""
name = "newsolver"
supported_problem_types = ["linear", "integer"]
def __init__(self, **options):
super().__init__(**options)
# Initialize solver-specific settings
def solve(self, problem: BaseProblem) -> SolverResult:
# Translate problem to solver's format
# Execute solver
# Return standardized result
pass
def is_available(self) -> bool:
# Check if solver library is installed
try:
import newsolver
return True
except ImportError:
return False- Add the solver dependency to
pyproject.toml:
[project.optional-dependencies]
newsolver = ["newsolver>=1.0"]- Register the solver in
solvers/__init__.py - Add tests in
tests/test_solvers.py - Update the README solver table
To add a new benchmark problem:
- Identify the problem category (
linear.py,integer.py, ornonlinear.py) - Implement the
BaseProbleminterface:
from benchmork.problems.base import BaseProblem
from dataclasses import dataclass
import numpy as np
@dataclass
class NewProblem(BaseProblem):
"""Description of the optimization problem."""
name = "new_problem"
problem_type = "linear" # or "integer", "nonlinear"
# Problem parameters
size: int = 100
seed: int = 42
def generate(self) -> dict:
"""Generate problem data."""
rng = np.random.default_rng(self.seed)
# Generate coefficients, constraints, etc.
return {
"c": ..., # Objective coefficients
"A": ..., # Constraint matrix
"b": ..., # Constraint bounds
}
def get_scaling_params(self) -> dict:
"""Return parameters that affect problem size."""
return {"size": self.size}- Add tests in
tests/test_problems.py - Update the README problem list
We use the following tools to maintain code quality:
- Ruff for linting and formatting
- mypy for type checking
- pytest for testing
# Format code
ruff format .
# Check linting
ruff check .
# Type checking
mypy benchmork solvers- Use type hints for all function signatures
- Write docstrings for public functions and classes
- Keep functions focused and under 50 lines when possible
- Use descriptive variable names
# Run all tests
pytest
# Run with coverage
pytest --cov=benchmork --cov=solvers
# Run specific test file
pytest tests/test_solvers.py
# Run specific test
pytest tests/test_solvers.py::test_ortools_knapsack- All new solvers must have integration tests
- All new problems must have validation tests
- Aim for >80% code coverage on new code
- Fork the repository and create a feature branch
- Write tests for your changes
- Ensure all tests pass locally
- Update documentation if needed (README, docstrings)
- Submit a PR with a clear description of changes
- Tests pass (
pytest) - Code is formatted (
ruff format .) - Linting passes (
ruff check .) - Type checking passes (
mypy benchmork solvers) - Documentation updated if needed
- CHANGELOG updated for notable changes
Feel free to open an issue for questions or discussions about the project.