@@ -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+
127163def 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