Skip to content

Commit

Permalink
Merge pull request #188 from PyPSA/mindopt
Browse files Browse the repository at this point in the history
add MindOpt solver
  • Loading branch information
FabianHofmann authored Oct 31, 2023
2 parents 09234b8 + 6500536 commit ae5d511
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: |
sudo apt-get install glpk-utils
sudo apt-get install coinor-cbc
pip install highspy
pip install highspy mindoptpy
- name: Install macos dependencies
if: matrix.os == 'macos-latest'
Expand Down
5 changes: 3 additions & 2 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ IO functions
io.read_netcdf

Solvers
=======
========

.. autosummary::
:toctree: generated/
Expand All @@ -133,10 +133,11 @@ Solvers
solvers.run_cplex
solvers.run_gurobi
solvers.run_xpress
solvers.run_mindopt
solvers.run_copt

Solving
=======
========

.. autosummary::
:toctree: generated/
Expand Down
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ flexible data-handling features:
- `Cbc <https://projects.coin-or.org/Cbc>`__
- `GLPK <https://www.gnu.org/software/glpk/>`__
- `HiGHS <https://www.maths.ed.ac.uk/hall/HiGHS/>`__
- `MindOpt <https://solver.damo.alibaba.com/doc/en/html/index.html>`__
- `Gurobi <https://www.gurobi.com/>`__
- `Xpress <https://www.fico.com/en/products/fico-xpress-solver>`__
- `Cplex <https://www.ibm.com/de-de/analytics/cplex-optimizer>`__
Expand Down
1 change: 1 addition & 0 deletions doc/prerequisites.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Linopy won't work without a solver. Currently, the following solvers are support
- `Gurobi <https://www.gurobi.com/>`__ - closed source, commercial, very fast
- `Xpress <https://www.fico.com/en/products/fico-xpress-solver>`__ - closed source, commercial, very fast
- `Cplex <https://www.ibm.com/de-de/analytics/cplex-optimizer>`__ - closed source, commercial, very fast
- `MindOpt <https://solver.damo.alibaba.com/doc/en/html/index.html>`__ -
- `COPT <https://www.shanshu.ai/copt>`__ - closed source, commercial, very fast

For a subset of the solvers, Linopy provides a wrapper.
Expand Down
3 changes: 3 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Release Notes
Upcoming Release
----------------

**New Features**

* Support for MindOpt solver was added.
* Added solver interface for COPT by Cardinal Optimizer.

Version 0.3.0
Expand Down
98 changes: 97 additions & 1 deletion linopy/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
import xpress

available_solvers.append("xpress")
with contextlib.suppress(ImportError):
import mindoptpy

available_solvers.append("mindopt")
with contextlib.suppress(ImportError):
import coptpy

Expand All @@ -62,7 +66,7 @@


io_structure = dict(
lp_file={"gurobi", "xpress", "cbc", "glpk", "cplex"}, blocks={"pips"}
lp_file={"gurobi", "xpress", "cbc", "glpk", "cplex", "mindopt"}, blocks={"pips"}
)


Expand Down Expand Up @@ -810,6 +814,98 @@ def get_solver_solution() -> Solution:
return Result(status, solution, m)


def run_mindopt(
model,
io_api=None,
problem_fn=None,
solution_fn=None,
log_fn=None,
warmstart_fn=None,
basis_fn=None,
keep_files=False,
env=None,
**solver_options,
):
"""
Solve a linear problem using the MindOpt solver.
https://solver.damo.alibaba.com/doc/en/html/index.html
For more information on solver options, see
https://solver.damo.alibaba.com/doc/en/html/API2/param/index.html
"""
CONDITION_MAP = {
-1: "error",
0: "unknown",
1: "optimal",
2: "infeasible",
3: "unbounded",
4: "infeasible_or_unbounded",
5: "suboptimal",
}

if io_api is not None and io_api not in ["lp", "mps"]:
logger.warning(
f"IO setting '{io_api}' not available for mindopt solver. "
"Falling back to `lp`."
)

problem_fn = model.to_file(problem_fn)

problem_fn = maybe_convert_path(problem_fn)
log_fn = "" if not log_fn else maybe_convert_path(log_fn)
warmstart_fn = maybe_convert_path(warmstart_fn)
basis_fn = maybe_convert_path(basis_fn)

if env is None:
env = mindoptpy.Env(log_fn)
env.start()

m = mindoptpy.read(problem_fn, env)

for k, v in solver_options.items():
m.setParam(k, v)

if warmstart_fn:
try:
m.read(warmstart_fn)
except mindoptpy.MindoptError as err:
logger.info("Model basis could not be read. Raised error:", err)

m.optimize()

if basis_fn:
try:
m.write(basis_fn)
except mindoptpy.MindoptError as err:
logger.info("No model basis stored. Raised error:", err)

condition = m.status
termination_condition = CONDITION_MAP.get(condition, condition)
status = Status.from_termination_condition(termination_condition)
status.legacy_status = condition

def get_solver_solution() -> Solution:
objective = m.objval

sol = pd.Series({v.varname: v.X for v in m.getVars()}, dtype=float)
sol = set_int_index(sol)

try:
dual = pd.Series({c.constrname: c.DualSoln for c in m.getConstrs()})
dual = set_int_index(dual)
except mindoptpy.MindoptError:
logger.warning("Dual values of MILP couldn't be parsed")
dual = pd.Series(dtype=float)

return Solution(sol, dual, objective)

solution = safe_get_solution(status, get_solver_solution)
maybe_adjust_objective_sign(solution, model.objective.sense, io_api, "mindopt")

return Result(status, solution, m)


def run_pips(
model,
io_api=None,
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"highspy",
"cplex",
"xpress",
"mindoptpy",
"coptpy",
],
},
Expand Down
1 change: 1 addition & 0 deletions test/test_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ def test_solver_options(model, solver, io_api):
"scip": {"time_limit": 1},
"xpress": {"maxtime": 1},
"highs": {"time_limit": 1},
"mindopt": {"MaxTime": 1},
"copt": {"TimeLimit": 1},
}
status, condition = model.solve(solver, io_api=io_api, **time_limit_option[solver])
Expand Down

0 comments on commit ae5d511

Please sign in to comment.