Skip to content
Open
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
54 changes: 48 additions & 6 deletions linopy/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,42 @@ def print_constraint(cons: Any) -> str:
return print_variable, print_constraint


def vectorized_label_names(
labels: np.ndarray,
prefix: str,
printer: Callable | None = None,
) -> np.ndarray:
"""
Generate label name arrays using vectorized string ops when possible.

For simple prefix-based names (e.g. "x0", "x1", ..., "c0", "c1", ...),
uses np.char operations which are ~100x faster than np.vectorize for large
arrays (500K+ elements).

Falls back to np.vectorize for custom printer functions that require
per-element lookups (e.g. explicit coordinate names).

Parameters
----------
labels : np.ndarray
Integer label array.
prefix : str
Single-character prefix ("x" for variables, "c" for constraints).
Only used when printer is None.
printer : callable, optional
Custom scalar printer function. If provided, falls back to
np.vectorize (needed for explicit_coordinate_names mode).

Returns
-------
np.ndarray
Object array of string names.
"""
if printer is not None:
return np.vectorize(printer)(labels).astype(object)
return np.char.add(prefix, labels.astype(str)).astype(object)


def get_printers(
m: Model, explicit_coordinate_names: bool = False
) -> tuple[Callable, Callable]:
Expand Down Expand Up @@ -665,7 +701,9 @@ def to_mosek(
# for j, n in enumerate(("x" + M.vlabels.astype(str).astype(object))):
# task.putvarname(j, n)

labels = np.vectorize(print_variable)(M.vlabels).astype(object)
var_printer = print_variable if explicit_coordinate_names else None
con_printer = print_constraint if explicit_coordinate_names else None
labels = vectorized_label_names(M.vlabels, "x", var_printer)
task.generatevarnames(
np.arange(0, len(labels)), "%0", [len(labels)], None, [0], list(labels)
)
Expand Down Expand Up @@ -704,7 +742,7 @@ def to_mosek(
## Constraints

if len(m.constraints) > 0:
names = np.vectorize(print_constraint)(M.clabels).astype(object)
names = vectorized_label_names(M.clabels, "c", con_printer)
for i, n in enumerate(names):
task.putconname(i, n)
bkc = [
Expand Down Expand Up @@ -773,7 +811,9 @@ def to_gurobipy(

M = m.matrices

names = np.vectorize(print_variable)(M.vlabels).astype(object)
var_printer = print_variable if explicit_coordinate_names else None
con_printer = print_constraint if explicit_coordinate_names else None
names = vectorized_label_names(M.vlabels, "x", var_printer)
kwargs = {}
if (
len(m.binaries.labels)
Expand All @@ -792,7 +832,7 @@ def to_gurobipy(
model.ModelSense = -1

if len(m.constraints):
names = np.vectorize(print_constraint)(M.clabels).astype(object)
names = vectorized_label_names(M.clabels, "c", con_printer)
c = model.addMConstr(M.A, x, M.sense, M.b) # type: ignore
c.setAttr("ConstrName", list(names)) # type: ignore

Expand Down Expand Up @@ -881,9 +921,11 @@ def to_highspy(m: Model, explicit_coordinate_names: bool = False) -> Highs:
h.addRows(num_cons, lower, upper, A.nnz, A.indptr, A.indices, A.data)

lp = h.getLp()
lp.col_names_ = np.vectorize(print_variable)(M.vlabels).astype(object)
var_printer = print_variable if explicit_coordinate_names else None
con_printer = print_constraint if explicit_coordinate_names else None
lp.col_names_ = vectorized_label_names(M.vlabels, "x", var_printer)
if len(M.clabels):
lp.row_names_ = np.vectorize(print_constraint)(M.clabels).astype(object)
lp.row_names_ = vectorized_label_names(M.clabels, "c", con_printer)
h.passModel(lp)

# quadrative objective
Expand Down
Loading