Skip to content

Commit 63485af

Browse files
committed
added variable names with direct solver and mps. CBC and GLPK are incompatible with variables names. MindOpt untested since my license is not functioning
1 parent 701a890 commit 63485af

File tree

3 files changed

+70
-48
lines changed

3 files changed

+70
-48
lines changed

linopy/io.py

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -69,48 +69,45 @@ def print_coord(coord):
6969
return coord
7070

7171

72-
def get_printers(m: Model, io_api: str, anonymously: bool = True):
73-
if anonymously:
74-
if io_api == "lp":
75-
def print_variable_anonymous(var):
76-
return f"x{var}"
77-
78-
def print_constraint_anonymous(cons):
79-
return f"c{cons}"
80-
81-
elif io_api == "lp-polars":
82-
def print_variable_anonymous(series):
83-
return pl.lit(" x").alias("x"), series.cast(pl.String)
84-
85-
def print_constraint_anonymous(series):
86-
return pl.lit("c").alias("c"), series.cast(pl.String)
87-
else:
88-
return None
89-
90-
return print_variable_anonymous, print_constraint_anonymous
91-
else:
92-
def print_variable_lp(var):
72+
def get_printers(m: Model, for_polars: bool = False, with_names: bool = False):
73+
if with_names:
74+
def print_variable(var):
9375
name, coord = m.variables.get_label_position(var)
9476
name = clean_name(name)
9577
return f"{name}{print_coord(coord)}#{var}"
9678

97-
def print_constraint_lp(cons):
79+
def print_constraint(cons):
9880
name, coord = m.constraints.get_label_position(cons)
9981
name = clean_name(name)
10082
return f"{name}{print_coord(coord)}#{cons}"
10183

10284
def print_variable_polars(series):
103-
return pl.lit(" "), series.map_elements(print_variable_lp, return_dtype=pl.String)
85+
return pl.lit(" "), series.map_elements(print_variable, return_dtype=pl.String)
86+
87+
def print_constraint_polars(series):
88+
return pl.lit(None), series.map_elements(print_constraint, return_dtype=pl.String)
89+
90+
if for_polars:
91+
return print_variable_polars, print_constraint_polars
92+
else:
93+
return print_variable, print_constraint
94+
else:
95+
def print_variable(var):
96+
return f"x{var}"
97+
98+
def print_constraint(cons):
99+
return f"c{cons}"
100+
101+
def print_variable_polars(series):
102+
return pl.lit(" x").alias("x"), series.cast(pl.String)
104103

105104
def print_constraint_polars(series):
106-
return pl.lit(None), series.map_elements(print_constraint_lp, return_dtype=pl.String)
105+
return pl.lit("c").alias("c"), series.cast(pl.String)
107106

108-
if io_api == "lp":
109-
return print_variable_lp, print_constraint_lp
110-
elif io_api == "lp-polars":
107+
if for_polars:
111108
return print_variable_polars, print_constraint_polars
112109
else:
113-
return None
110+
return print_variable, print_constraint
114111

115112

116113
def objective_write_linear_terms(
@@ -739,9 +736,7 @@ def to_file(
739736
if progress is None:
740737
progress = m._xCounter > 10_000
741738

742-
printers = get_printers(m, io_api, anonymously=not with_names)
743-
if with_names and io_api not in ["lp", "lp-polars"]:
744-
logger.warning("Debug names are only supported for LP files.")
739+
printers = get_printers(m, for_polars=io_api=="lp-polars", with_names=with_names)
745740

746741
if io_api == "lp":
747742
to_lp_file(m, fn, integer_label, slice_size=slice_size, progress=progress, printers=printers)
@@ -758,7 +753,7 @@ def to_file(
758753

759754
# Use very fast highspy implementation
760755
# Might be replaced by custom writer, however needs C/Rust bindings for performance
761-
h = m.to_highspy()
756+
h = m.to_highspy(with_names=with_names)
762757
h.writeModel(str(fn))
763758
else:
764759
raise ValueError(
@@ -768,7 +763,7 @@ def to_file(
768763
return fn
769764

770765

771-
def to_mosek(m: Model, task: Any | None = None) -> Any:
766+
def to_mosek(m: Model, task: Any | None = None, with_names: bool = False) -> Any:
772767
"""
773768
Export model to MOSEK.
774769
@@ -786,6 +781,8 @@ def to_mosek(m: Model, task: Any | None = None) -> Any:
786781

787782
import mosek
788783

784+
print_variable, print_constraint = get_printers(m, with_names=with_names)
785+
789786
if task is None:
790787
task = mosek.Task()
791788

@@ -796,9 +793,9 @@ def to_mosek(m: Model, task: Any | None = None) -> Any:
796793
# for j, n in enumerate(("x" + M.vlabels.astype(str).astype(object))):
797794
# task.putvarname(j, n)
798795

799-
labels = M.vlabels.astype(str).astype(object)
796+
labels = np.vectorize(print_variable)(M.vlabels).astype(object)
800797
task.generatevarnames(
801-
np.arange(0, len(labels)), "x%0", [len(labels)], None, [0], list(labels)
798+
np.arange(0, len(labels)), "%0", [len(labels)], None, [0], list(labels)
802799
)
803800

804801
## Variables
@@ -835,7 +832,7 @@ def to_mosek(m: Model, task: Any | None = None) -> Any:
835832
## Constraints
836833

837834
if len(m.constraints) > 0:
838-
names = "c" + M.clabels.astype(str).astype(object)
835+
names = np.vectorize(print_constraint)(M.clabels).astype(object)
839836
for i, n in enumerate(names):
840837
task.putconname(i, n)
841838
bkc = [
@@ -874,7 +871,7 @@ def to_mosek(m: Model, task: Any | None = None) -> Any:
874871
return task
875872

876873

877-
def to_gurobipy(m: Model, env: Any | None = None) -> Any:
874+
def to_gurobipy(m: Model, env: Any | None = None, with_names: bool = False) -> Any:
878875
"""
879876
Export the model to gurobipy.
880877
@@ -893,12 +890,14 @@ def to_gurobipy(m: Model, env: Any | None = None) -> Any:
893890
"""
894891
import gurobipy
895892

893+
print_variable, print_constraint = get_printers(m, with_names=with_names)
894+
896895
m.constraints.sanitize_missings()
897896
model = gurobipy.Model(env=env)
898897

899898
M = m.matrices
900899

901-
names = "x" + M.vlabels.astype(str).astype(object)
900+
names = np.vectorize(print_variable)(M.vlabels).astype(object)
902901
kwargs = {}
903902
if len(m.binaries.labels) + len(m.integers.labels):
904903
kwargs["vtype"] = M.vtypes
@@ -913,15 +912,15 @@ def to_gurobipy(m: Model, env: Any | None = None) -> Any:
913912
model.ModelSense = -1
914913

915914
if len(m.constraints):
916-
names = "c" + M.clabels.astype(str).astype(object)
915+
names = np.vectorize(print_constraint)(M.clabels).astype(object)
917916
c = model.addMConstr(M.A, x, M.sense, M.b) # type: ignore
918917
c.setAttr("ConstrName", list(names)) # type: ignore
919918

920919
model.update()
921920
return model
922921

923922

924-
def to_highspy(m: Model) -> Highs:
923+
def to_highspy(m: Model, with_names: bool = False) -> Highs:
925924
"""
926925
Export the model to highspy.
927926
@@ -940,6 +939,8 @@ def to_highspy(m: Model) -> Highs:
940939
"""
941940
import highspy
942941

942+
print_variable, print_constraint = get_printers(m, with_names=with_names)
943+
943944
M = m.matrices
944945
h = highspy.Highs()
945946
h.addVars(len(M.vlabels), M.lb, M.ub)
@@ -966,9 +967,9 @@ def to_highspy(m: Model) -> Highs:
966967
h.addRows(num_cons, lower, upper, A.nnz, A.indptr, A.indices, A.data)
967968

968969
lp = h.getLp()
969-
lp.col_names_ = "x" + M.vlabels.astype(str).astype(object)
970+
lp.col_names_ = np.vectorize(print_variable)(M.vlabels).astype(object)
970971
if len(M.clabels):
971-
lp.row_names_ = "c" + M.clabels.astype(str).astype(object)
972+
lp.row_names_ = np.vectorize(print_constraint)(M.clabels).astype(object)
972973
h.passModel(lp)
973974

974975
# quadrative objective

linopy/model.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,8 +1125,6 @@ def solve(
11251125
**solver_options,
11261126
)
11271127
if io_api == "direct":
1128-
if with_names:
1129-
logger.warning("Passing variable and constraint names is only supported with lp files")
11301128
# no problem file written and direct model is set for solver
11311129
result = solver.solve_problem_from_model(
11321130
model=self,
@@ -1135,8 +1133,12 @@ def solve(
11351133
warmstart_fn=to_path(warmstart_fn),
11361134
basis_fn=to_path(basis_fn),
11371135
env=env,
1136+
with_names=with_names,
11381137
)
11391138
else:
1139+
if solver_name in ["glpk", "cbc"] and with_names:
1140+
logger.warning(f"{solver_name} does not support writing names to lp files, disabling it.")
1141+
with_names = False
11401142
problem_fn = self.to_file(
11411143
to_path(problem_fn),
11421144
io_api=io_api,

linopy/solvers.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ def solve_problem_from_model(
240240
warmstart_fn: Path | None = None,
241241
basis_fn: Path | None = None,
242242
env: None = None,
243+
with_names: bool = False,
243244
) -> Result:
244245
"""
245246
Abstract method to solve a linear problem from a model.
@@ -278,6 +279,7 @@ def solve_problem(
278279
warmstart_fn: Path | None = None,
279280
basis_fn: Path | None = None,
280281
env: None = None,
282+
with_names: bool = False,
281283
) -> Result:
282284
"""
283285
Solve a linear problem either from a model or a problem file.
@@ -297,6 +299,7 @@ def solve_problem(
297299
warmstart_fn=warmstart_fn,
298300
basis_fn=basis_fn,
299301
env=env,
302+
with_names=with_names,
300303
)
301304
elif problem_fn is not None:
302305
return self.solve_problem_from_file(
@@ -340,6 +343,7 @@ def solve_problem_from_model(
340343
warmstart_fn: Path | None = None,
341344
basis_fn: Path | None = None,
342345
env: None = None,
346+
with_names: bool = False,
343347
):
344348
msg = "Direct API not implemented for CBC"
345349
raise NotImplementedError(msg)
@@ -497,6 +501,7 @@ def solve_problem_from_model(
497501
warmstart_fn: Path | None = None,
498502
basis_fn: Path | None = None,
499503
env: None = None,
504+
with_names: bool = False,
500505
) -> Result:
501506
msg = "Direct API not implemented for GLPK"
502507
raise NotImplementedError(msg)
@@ -679,6 +684,7 @@ def solve_problem_from_model(
679684
warmstart_fn: Path | None = None,
680685
basis_fn: Path | None = None,
681686
env: None = None,
687+
with_names: bool = False,
682688
) -> Result:
683689
"""
684690
Solve a linear problem directly from a linopy model using the Highs solver.
@@ -700,6 +706,8 @@ def solve_problem_from_model(
700706
Path to the basis file.
701707
env : None, optional
702708
Environment for the solver
709+
with_names : bool, optional
710+
Transfer variable and constraint names to the solver (default: False)
703711
704712
Returns
705713
-------
@@ -719,7 +727,7 @@ def solve_problem_from_model(
719727
"Drop the solver option or use 'choose' to enable quadratic terms / integrality."
720728
)
721729

722-
h = model.to_highspy()
730+
h = model.to_highspy(with_names=with_names)
723731

724732
if log_fn is None and model is not None:
725733
log_fn = model.solver_dir / "highs.log"
@@ -896,6 +904,7 @@ def solve_problem_from_model(
896904
warmstart_fn: Path | None = None,
897905
basis_fn: Path | None = None,
898906
env: None = None,
907+
with_names: bool = False,
899908
) -> Result:
900909
"""
901910
Solve a linear problem directly from a linopy model using the Gurobi solver.
@@ -916,6 +925,8 @@ def solve_problem_from_model(
916925
Path to the basis file.
917926
env : None, optional
918927
Gurobi environment for the solver
928+
with_names : bool, optional
929+
Transfer variable and constraint names to the solver (default: False)
919930
920931
Returns
921932
-------
@@ -927,7 +938,7 @@ def solve_problem_from_model(
927938
else:
928939
env_ = env
929940

930-
m = model.to_gurobipy(env=env_)
941+
m = model.to_gurobipy(env=env_, with_names=with_names)
931942

932943
return self._solve(
933944
m,
@@ -1128,6 +1139,7 @@ def solve_problem_from_model(
11281139
warmstart_fn: Path | None = None,
11291140
basis_fn: Path | None = None,
11301141
env: None = None,
1142+
with_names: bool = False,
11311143
) -> Result:
11321144
msg = "Direct API not implemented for Cplex"
11331145
raise NotImplementedError(msg)
@@ -1270,6 +1282,7 @@ def solve_problem_from_model(
12701282
warmstart_fn: Path | None = None,
12711283
basis_fn: Path | None = None,
12721284
env: None = None,
1285+
with_names: bool = False,
12731286
) -> Result:
12741287
msg = "Direct API not implemented for SCIP"
12751288
raise NotImplementedError(msg)
@@ -1411,6 +1424,7 @@ def solve_problem_from_model(
14111424
warmstart_fn: Path | None = None,
14121425
basis_fn: Path | None = None,
14131426
env: None = None,
1427+
with_names: bool = False,
14141428
) -> Result:
14151429
msg = "Direct API not implemented for Xpress"
14161430
raise NotImplementedError(msg)
@@ -1556,6 +1570,7 @@ def solve_problem_from_model(
15561570
warmstart_fn: Path | None = None,
15571571
basis_fn: Path | None = None,
15581572
env: None = None,
1573+
with_names: bool = False,
15591574
) -> Result:
15601575
"""
15611576
Solve a linear problem directly from a linopy model using the MOSEK solver.
@@ -1573,7 +1588,9 @@ def solve_problem_from_model(
15731588
basis_fn : Path, optional
15741589
Path to the basis file.
15751590
env : None, optional
1576-
Gurobi environment for the solver
1591+
Mosek environment for the solver
1592+
with_names : bool, optional
1593+
Transfer variable and constraint names to the solver (default: False)
15771594
15781595
Returns
15791596
-------
@@ -1584,7 +1601,7 @@ def solve_problem_from_model(
15841601
env_ = stack.enter_context(mosek.Env())
15851602

15861603
with env_.Task() as m:
1587-
m = model.to_mosek(m)
1604+
m = model.to_mosek(m, with_names=with_names)
15881605

15891606
return self._solve(
15901607
m,
@@ -1878,6 +1895,7 @@ def solve_problem_from_model(
18781895
warmstart_fn: Path | None = None,
18791896
basis_fn: Path | None = None,
18801897
env: None = None,
1898+
with_names: bool = False,
18811899
) -> Result:
18821900
msg = "Direct API not implemented for COPT"
18831901
raise NotImplementedError(msg)
@@ -2020,6 +2038,7 @@ def solve_problem_from_model(
20202038
warmstart_fn: Path | None = None,
20212039
basis_fn: Path | None = None,
20222040
env: None = None,
2041+
with_names: bool = False,
20232042
) -> Result:
20242043
msg = "Direct API not implemented for MindOpt"
20252044
raise NotImplementedError(msg)

0 commit comments

Comments
 (0)