Machine-learning enhanced optimization library with pluggable solvers
Project description
mlopt
Machine-learning enhanced optimization with a clean modeling API, pluggable solvers (CBC/HiGHS/GA/Bayesian), and learning-augmented features. Model variables, constraints, and objectives in Python, then solve with mathematical programming or black-box global optimizers. Add surrogate models, pass learning hints, and scale from notebooks to CI/CD and PyPI.
- Friendly modeling API inspired by LP/MIP tools
- Multiple backends: CBC via PuLP, HiGHS via PuLP, Genetic Algorithm, Bayesian Optimization
- ML-first utilities: Gaussian-process surrogates, learning-augmented solver hints
- Robust validation and logging for safe, observable runs
- Extensible plugin registry for custom solvers
Install
Prerequisites:
- Python 3.9+
- For LP/MIP: install PuLP. For Bayesian Opt: scikit-optimize. For surrogates: scikit-learn.
Examples:
- Core + CBC via PuLP:
- pip install pulp
- Bayesian optimization:
- pip install scikit-optimize
- Surrogates:
- pip install scikit-learn
Local editable install:
- pip install -e .
Optional developer tools:
- pip install pytest ruff mypy
Quick start
Linear program with CBC:
from mlopt.core.model import Model
from mlopt.core.objective import Objective
from mlopt.core.constraints import Constraint
from mlopt.plugins.registry import SolverRegistry
m = Model("diet_like")
x1 = m.add_var("x1", low=0)
x2 = m.add_var("x2", low=0)
obj = m.linexpr({x1: 3.0, x2: 2.0})
m.set_objective(Objective(obj, "min"))
m.add_constraint(Constraint(m.linexpr({x1: 2.0, x2: 1.0}), "<=", 100, "c1"))
m.add_constraint(Constraint(m.linexpr({x1: 1.0, x2: 1.0}), ">=", 50, "c2"))
solver = SolverRegistry.create("pulp_cbc")
m.set_solver(solver)
res = m.solve()
print(res.status, res.obj, res.x, res.solver_name)
Bayesian optimization for a black-box:
from typing import Dict
from mlopt.core.model import Model
from mlopt.core.objective import Objective
from mlopt.core.constraints import Constraint
from mlopt.plugins.registry import SolverRegistry
def black_box(a: Dict[str, float]) -> float:
# (x1 - 2)^2 + (x2 - 3)^2
return (a["x1"] - 2.0) ** 2 + (a["x2"] - 3.0) ** 2
m = Model("black_box")
m.add_var("x1", low=0.0, up=5.0)
m.add_var("x2", low=0.0, up=5.0)
m.set_objective(Objective(m.linexpr({}, 0.0), "min"))
m.add_constraint(Constraint(m.linexpr({}, 0.0), ">=", 0.0, "noop"))
solver = SolverRegistry.create("bayesopt", n_calls=25, random_state=42)
# If callable API available:
try:
res = solver.solve_with_callable(m, black_box) # type: ignore[attr-defined]
except Exception:
setattr(m, "_black_box", black_box)
res = solver.solve(m)
print(res.status, res.obj, res.x)
Genetic algorithm (heuristic):
from mlopt.core.model import Model
from mlopt.core.objective import Objective
from mlopt.core.constraints import Constraint
from mlopt.plugins.registry import SolverRegistry
m = Model("ga_example")
x = m.add_var("x", low=0, up=100)
y = m.add_var("y", low=0, up=100)
m.set_objective(Objective(m.linexpr({x: 1.0, y: 2.0}), "min"))
m.add_constraint(Constraint(m.linexpr({x: 1.0, y: 1.0}), ">=", 10, "c1"))
solver = SolverRegistry.create("genetic", pop_size=60, generations=120, seed=123)
m.set_solver(solver)
res = m.solve()
print(res.status, res.obj, res.x)
Concepts
- Variables: name, bounds, category (Continuous, Integer, Binary)
- Linear expressions: sum of coefficients times variables plus constant
- Constraints: LinExpr with sense in {<=, >=, ==} and RHS
- Objective: LinExpr with direction in {min, max}
- Model: container for vars, constraints, objective, and a solver
- SolveResult: status, objective value, solution map, solver name
Public API
Convenience imports:
from mlopt import (
Model, Var, LinExpr, Constraint, Objective,
SolveResult, SolverRegistry,
PuLPCBC, GeneticSolver, BayesianOpt,
SurrogateModel, LearningHints,
)
Subpackages:
- core: modeling primitives
- solvers: backends (CBC, HiGHS, GA, Bayesian)
- ml: surrogates and hints
- plugins: registry
- utils: logging and validation
Solvers
- CBC via PuLP (pulp_cbc)
- Requires pulp
- Deterministic LP/MIP solver, good defaults
- HiGHS via PuLP (highs)
- Requires PuLP with HiGHS support
- Modern LP/MIP solver; adapter selects HiGHS_CMD/Highs_CMD automatically
- GeneticSolver (meta_ga)
- Population-based heuristic for nonconvex/black-box; supports seed and early stopping
- BayesianOpt (bayesopt)
- Global optimization with Gaussian Process surrogate
- Accepts black-box via model._black_box or direct callable API (if available)
- Requires scikit-optimize; respects variable bounds and adds constraint penalties
To create solvers:
solver = SolverRegistry.create("pulp_cbc")
# or
solver = SolverRegistry.create("highs", msg=False)
# or
solver = SolverRegistry.create("genetic", pop_size=50, generations=100, seed=42)
# or
solver = SolverRegistry.create("bayesopt", n_calls=30, random_state=42)
ML features
- SurrogateModel (Gaussian Process)
- fit(X, y), predict(X, return_std=False), reproducible with random_state
- Useful for custom Bayesian/global loops outside the built-in solver
- LearningHints
- var_priorities: branching priorities by name
- var_fixings: proposed fixings (e.g., binary 0/1)
- cut_hints: numeric cut thresholds
- to_solver_kwargs() returns normalized dict usable by adapters
Example usage:
from mlopt.ml.surrogate import SurrogateModel
from mlopt.ml.learning_hints import LearningHints
sur = SurrogateModel(random_state=42)
# sur.fit(X, y), sur.predict(X, return_std=True)
hints = LearningHints(var_priorities={"x": 10}, var_fixings={"y": 0})
kwargs = hints.to_solver_kwargs()
# Pass kwargs to adapters that support hint ingestion
Validation and logging
Validation:
- validate_readiness_for_solver(model) checks:
- Objective set, variables present, senses valid
- Bounds consistency, duplicate names
- Expressions reference declared variables
Logging:
- get_logger(name) creates a namespaced logger
- Environment variable MLOPT_LOG_LEVEL controls default level (e.g., DEBUG, INFO)
from mlopt.utils.validation import validate_readiness_for_solver, summary
from mlopt.utils.logging import get_logger, enable_debug
logger = get_logger(__name__)
enable_debug()
validate_readiness_for_solver(m)
logger.info(summary(m))
res = m.solve()
logger.info("Solved: %s obj=%.6f", res.status, res.obj)
Examples
- examples/lp_example.py: basic LP with CBC
- examples/bayes_example.py: black-box optimization with BayesianOpt
Run:
- python examples/lp_example.py
- python examples/bayes_example.py
Extending
Add a new solver:
- Implement a class conforming to SolverBase with a solve(model) method returning SolveResult
- Register it in plugins/registry.py:
- SolverRegistry.register("my_solver", MySolver)
Support nonlinear or convex modeling:
- Add new expression/objective types and adapters that translate to the target solver’s API
- Keep the core API backward compatible
Learning-augmented strategies:
- Log solve traces, train models to predict branching priorities or variable fixings
- Use LearningHints to pass them into adapters that support hints
Project structure
- mlopt/
- core/: model, variables, expressions, constraints, objective
- solvers/: base, pulp_cbc, highs, meta_ga, bayesopt
- ml/: surrogate, learning_hints
- plugins/: registry
- utils/: logging, validation
- examples/: lp_example.py, bayes_example.py
- tests/: add unit tests for APIs and solvers
- README.md, pyproject.toml, LICENSE
Development
Setup:
- python -m venv .venv && source .venv/bin/activate # on Windows: .venv\Scripts\activate
- pip install -e .
- pip install pytest ruff mypy
Quality:
- ruff check .
- mypy mlopt
- pytest
Conventional commits and PR guidance:
- feat:, fix:, docs:, refactor:, test:, chore:
- Include tests and update README/docs for user-facing changes
Release
- Update version in pyproject.toml
- Build:
- python -m pip install --upgrade build twine
- python -m build
- twine check dist/*
- Publish:
- twine upload dist/* # or use your CI workflow on tag
Troubleshooting
- Type checker error “Variable not allowed in type expression”:
- Avoid annotating with runtime classes from external libs; prefer typing.Any or checker-only aliases.
- CBC/HiGHS not available:
- Ensure PuLP is installed and supports the chosen command (PULP_CBC_CMD, HiGHS_CMD/Highs_CMD).
- Bayesian optimization import error:
- Install scikit-optimize. If using surrogates, also install scikit-learn.
- Black-box objective not used:
- Use solver.solve_with_callable(model, fn) if available, or set model._black_box = fn before solve.
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
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 mlopter-0.1.4.tar.gz.
File metadata
- Download URL: mlopter-0.1.4.tar.gz
- Upload date:
- Size: 13.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1eff76c867a4d99e40cd19bdf33dfd0b30d0004f3d83d48474abfcd0d90668c
|
|
| MD5 |
c8ffa4b69f7268220b3c8e6ee2e1eba8
|
|
| BLAKE2b-256 |
eae960d18114664285b88decc52eed08e4ee6e6d878d46d3d1e3d641cf9b1788
|
File details
Details for the file mlopter-0.1.4-py3-none-any.whl.
File metadata
- Download URL: mlopter-0.1.4-py3-none-any.whl
- Upload date:
- Size: 4.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10c7ea528a95b1161b617e538b9018fbf3caf95d19471914936a182c9730e9b6
|
|
| MD5 |
34f6184fc5fdc03d31cbf8533ea5d232
|
|
| BLAKE2b-256 |
0f136830bc0a6978e7a6f6f72a1e90a9379483784b9380993e8eb49f71635735
|