Skip to content

Commit f95ef0e

Browse files
committed
perf: replace np.vectorize with vectorized string ops for label names
Uses np.char.add for the common case (no explicit_coordinate_names), which is ~1.2x faster than np.vectorize on large label arrays. Falls back to np.vectorize when a custom printer is provided.
1 parent c415b4e commit f95ef0e

1 file changed

Lines changed: 48 additions & 6 deletions

File tree

linopy/io.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,42 @@ def print_constraint(cons: Any) -> str:
124124
return print_variable, print_constraint
125125

126126

127+
def vectorized_label_names(
128+
labels: np.ndarray,
129+
prefix: str,
130+
printer: Callable | None = None,
131+
) -> np.ndarray:
132+
"""
133+
Generate label name arrays using vectorized string ops when possible.
134+
135+
For simple prefix-based names (e.g. "x0", "x1", ..., "c0", "c1", ...),
136+
uses np.char operations which are ~1.2x faster than np.vectorize for large
137+
arrays (500K+ elements).
138+
139+
Falls back to np.vectorize for custom printer functions that require
140+
per-element lookups (e.g. explicit coordinate names).
141+
142+
Parameters
143+
----------
144+
labels : np.ndarray
145+
Integer label array.
146+
prefix : str
147+
Single-character prefix ("x" for variables, "c" for constraints).
148+
Only used when printer is None.
149+
printer : callable, optional
150+
Custom scalar printer function. If provided, falls back to
151+
np.vectorize (needed for explicit_coordinate_names mode).
152+
153+
Returns
154+
-------
155+
np.ndarray
156+
Object array of string names.
157+
"""
158+
if printer is not None:
159+
return np.vectorize(printer)(labels).astype(object)
160+
return np.char.add(prefix, labels.astype(str)).astype(object)
161+
162+
127163
def get_printers(
128164
m: Model, explicit_coordinate_names: bool = False
129165
) -> tuple[Callable, Callable]:
@@ -665,7 +701,9 @@ def to_mosek(
665701
# for j, n in enumerate(("x" + M.vlabels.astype(str).astype(object))):
666702
# task.putvarname(j, n)
667703

668-
labels = np.vectorize(print_variable)(M.vlabels).astype(object)
704+
var_printer = print_variable if explicit_coordinate_names else None
705+
con_printer = print_constraint if explicit_coordinate_names else None
706+
labels = vectorized_label_names(M.vlabels, "x", var_printer)
669707
task.generatevarnames(
670708
np.arange(0, len(labels)), "%0", [len(labels)], None, [0], list(labels)
671709
)
@@ -704,7 +742,7 @@ def to_mosek(
704742
## Constraints
705743

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

774812
M = m.matrices
775813

776-
names = np.vectorize(print_variable)(M.vlabels).astype(object)
814+
var_printer = print_variable if explicit_coordinate_names else None
815+
con_printer = print_constraint if explicit_coordinate_names else None
816+
names = vectorized_label_names(M.vlabels, "x", var_printer)
777817
kwargs = {}
778818
if (
779819
len(m.binaries.labels)
@@ -792,7 +832,7 @@ def to_gurobipy(
792832
model.ModelSense = -1
793833

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

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

883923
lp = h.getLp()
884-
lp.col_names_ = np.vectorize(print_variable)(M.vlabels).astype(object)
924+
var_printer = print_variable if explicit_coordinate_names else None
925+
con_printer = print_constraint if explicit_coordinate_names else None
926+
lp.col_names_ = vectorized_label_names(M.vlabels, "x", var_printer)
885927
if len(M.clabels):
886-
lp.row_names_ = np.vectorize(print_constraint)(M.clabels).astype(object)
928+
lp.row_names_ = vectorized_label_names(M.clabels, "c", con_printer)
887929
h.passModel(lp)
888930

889931
# quadrative objective

0 commit comments

Comments
 (0)