Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support writing problem to MPS file #71

Merged
merged 5 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Release Notes
Upcoming Release
----------------

* Support exporting problems to MPS file via fast highspy MPS-writer.
* The internal data structure of linopy classes were updated to a safer design. Instead of being defined as inherited xarray classes, the class `Variable`, `LinearExpression` and `Constraint` are now dataclasses with containing the xarray objects in the data field. This allows the package to have more flexible function design and a reduced set of wrapped functions that are sensible to use in the optimization context.
* The class `Variable` and `LinearExpression` have new functions `groupby` and `rolling` imitating the corresponding xarray functions but with safe type inheritance and application of appended operations.

Expand Down
51 changes: 38 additions & 13 deletions linopy/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from numpy import asarray, concatenate, ones_like, zeros_like
from tqdm import tqdm

from linopy import solvers

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -165,26 +167,45 @@ def binaries_to_file(m, f, log=False):

def to_file(m, fn):
"""
Write out a model to a lp file.
Write out a model to a lp or mps file.
"""
fn = m.get_problem_file(fn)
fn = Path(m.get_problem_file(fn))

if fn.exists():
fn.unlink()

if fn.suffix == ".lp":

if os.path.exists(fn):
os.remove(fn) # ensure a clear file
log = m._xCounter > 10000

log = m._xCounter > 10000
with open(fn, mode="w") as f:

with open(fn, mode="w") as f:
start = time.time()

start = time.time()
objective_to_file(m, f, log)
constraints_to_file(m, f, log)
bounds_to_file(m, f, log)
binaries_to_file(m, f, log)
f.write("end\n")

objective_to_file(m, f, log)
constraints_to_file(m, f, log)
bounds_to_file(m, f, log)
binaries_to_file(m, f, log)
f.write("end\n")
logger.info(f" Writing time: {round(time.time()-start, 2)}s")

elif fn.suffix == ".mps":
if "highs" in solvers.available_solvers:
# Use very fast highspy implementation
# Might be replaced by custom writer, however needs C bindings for performance
h = m.to_highspy()
h.writeModel(str(fn))
else:
raise RuntimeError(
"Package highspy not installed. This is required to exporting to MPS file."
)

logger.info(f" Writing time: {round(time.time()-start, 2)}s")
else:

raise ValueError(
f"Cannot write problem to {fn}, file format `{fn.suffix}` not supported."
)

return fn

Expand Down Expand Up @@ -262,6 +283,10 @@ def to_highspy(m):
lower = np.where(M.sense != "<", M.b, -np.inf)
upper = np.where(M.sense != ">", M.b, np.inf)
h.addRows(num_cons, lower, upper, A.nnz, A.indptr, A.indices, A.data)
lp = h.getLp()
lp.row_names_ = "c" + M.clabels.astype(str).astype(object)
lp.col_names_ = "x" + M.vlabels.astype(str).astype(object)
h.passModel(lp)
return h


Expand Down
41 changes: 34 additions & 7 deletions test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@
from linopy.io import float_to_str, int_to_str


@pytest.fixture
def m():
import gurobipy

m = Model()

x = m.add_variables(4, pd.Series([8, 10]))
y = m.add_variables(0, pd.DataFrame([[1, 2], [3, 4], [5, 6]]))

m.add_constraints(x + y, "<=", 10)

m.add_objective(2 * x + 3 * y)

return m


def test_str_arrays():
m = Model()

Expand Down Expand Up @@ -67,24 +83,35 @@ def test_to_netcdf(tmp_path):


@pytest.mark.skipif("gurobi" not in available_solvers, reason="Gurobipy not installed")
def test_to_file(tmp_path):
def test_to_file_lp(m, tmp_path):
import gurobipy

m = Model()
fn = tmp_path / "test.lp"
m.to_file(fn)

x = m.add_variables(4, pd.Series([8, 10]))
y = m.add_variables(0, pd.DataFrame([[1, 2], [3, 4], [5, 6]]))
gurobipy.read(str(fn))

m.add_constraints(x + y, "<=", 10)

m.add_objective(2 * x + 3 * y)
@pytest.mark.skipif(
not {"gurobi", "highs"}.issubset(available_solvers),
reason="Gurobipy of highspy not installed",
)
def test_to_file_mps(m, tmp_path):
import gurobipy

fn = tmp_path / "test.lp"
fn = tmp_path / "test.mps"
m.to_file(fn)

gurobipy.read(str(fn))


@pytest.mark.skipif("gurobi" not in available_solvers, reason="Gurobipy not installed")
def test_to_file_invalid(m, tmp_path):
with pytest.raises(ValueError):
fn = tmp_path / "test.failedtype"
m.to_file(fn)


@pytest.mark.skipif("gurobi" not in available_solvers, reason="Gurobipy not installed")
def test_to_gurobipy(tmp_path):
m = Model()
Expand Down