2525from linopy import solvers
2626from linopy .constants import CONCAT_DIM
2727from linopy .objective import Objective
28+ from linopy .scaling import ScalingContext
2829
2930if TYPE_CHECKING :
3031 from highspy .highs import Highs
3132
33+ from linopy .matrices import MatrixAccessor
3234 from linopy .model import Model
35+ from linopy .scaling import ScaledMatrices
3336
3437
3538logger = logging .getLogger (__name__ )
@@ -162,6 +165,7 @@ def objective_to_file(
162165 f : BufferedWriter ,
163166 progress : bool = False ,
164167 explicit_coordinate_names : bool = False ,
168+ scaling : ScalingContext | None = None ,
165169) -> None :
166170 """
167171 Write out the objective of a model to a lp file.
@@ -176,6 +180,16 @@ def objective_to_file(
176180 sense = m .objective .sense
177181 f .write (f"{ sense } \n \n obj:\n \n " .encode ())
178182 df = m .objective .to_polars ()
183+ if scaling is not None and getattr (scaling , "col_inv" , None ) is not None :
184+ scale_map = pl .DataFrame (
185+ {"vars" : pl .Series (scaling .matrices .vlabels ), "col_inv" : scaling .col_inv }
186+ )
187+ df = (
188+ df .join (scale_map , on = "vars" , how = "left" )
189+ .fill_null (1.0 )
190+ .with_columns (pl .col ("coeffs" ) * pl .col ("col_inv" ))
191+ .drop ("col_inv" )
192+ )
179193
180194 if m .is_linear :
181195 objective_write_linear_terms (f , df , print_variable )
@@ -200,6 +214,7 @@ def bounds_to_file(
200214 progress : bool = False ,
201215 slice_size : int = 2_000_000 ,
202216 explicit_coordinate_names : bool = False ,
217+ scaling : ScalingContext | None = None ,
203218) -> None :
204219 """
205220 Write out variables of a model to a lp file.
@@ -224,6 +239,24 @@ def bounds_to_file(
224239 var = m .variables [name ]
225240 for var_slice in var .iterate_slices (slice_size ):
226241 df = var_slice .to_polars ()
242+ if scaling is not None :
243+ scale_map = pl .DataFrame (
244+ {
245+ "labels" : pl .Series (scaling .matrices .vlabels ),
246+ "col_scale" : scaling .col_scale ,
247+ }
248+ )
249+ df = (
250+ df .join (scale_map , on = "labels" , how = "left" )
251+ .fill_null (1.0 )
252+ .with_columns (
253+ [
254+ pl .col ("lower" ) * pl .col ("col_scale" ),
255+ pl .col ("upper" ) * pl .col ("col_scale" ),
256+ ]
257+ )
258+ .drop ("col_scale" )
259+ )
227260
228261 columns = [
229262 pl .when (pl .col ("lower" ) >= 0 ).then (pl .lit ("+" )).otherwise (pl .lit ("" )),
@@ -334,6 +367,7 @@ def constraints_to_file(
334367 lazy : bool = False ,
335368 slice_size : int = 2_000_000 ,
336369 explicit_coordinate_names : bool = False ,
370+ scaling : ScalingContext | None = None ,
337371) -> None :
338372 if not len (m .constraints ):
339373 return
@@ -358,6 +392,32 @@ def constraints_to_file(
358392 for con_slice in con .iterate_slices (slice_size ):
359393 df = con_slice .to_polars ()
360394
395+ if scaling is not None :
396+ row_map = pl .DataFrame (
397+ {
398+ "labels" : pl .Series (scaling .matrices .clabels ),
399+ "row_scale" : scaling .row_scale ,
400+ }
401+ )
402+ col_map = pl .DataFrame (
403+ {
404+ "vars" : pl .Series (scaling .matrices .vlabels ),
405+ "col_inv" : scaling .col_inv ,
406+ }
407+ )
408+ df = (
409+ df .join (row_map , on = "labels" , how = "left" )
410+ .join (col_map , on = "vars" , how = "left" )
411+ .fill_null (1.0 )
412+ .with_columns (
413+ [
414+ pl .col ("coeffs" ) * pl .col ("row_scale" ) * pl .col ("col_inv" ),
415+ pl .col ("rhs" ) * pl .col ("row_scale" ),
416+ ]
417+ )
418+ .drop (["row_scale" , "col_inv" ])
419+ )
420+
361421 if df .height == 0 :
362422 continue
363423
@@ -428,26 +488,33 @@ def to_lp_file(
428488 slice_size : int = 2_000_000 ,
429489 progress : bool = True ,
430490 explicit_coordinate_names : bool = False ,
491+ scaling : ScalingContext | None = None ,
431492) -> None :
432493 with open (fn , mode = "wb" ) as f :
433494 start = time .time ()
434495
435496 objective_to_file (
436- m , f , progress = progress , explicit_coordinate_names = explicit_coordinate_names
497+ m ,
498+ f ,
499+ progress = progress ,
500+ explicit_coordinate_names = explicit_coordinate_names ,
501+ scaling = scaling ,
437502 )
438503 constraints_to_file (
439504 m ,
440505 f = f ,
441506 progress = progress ,
442507 slice_size = slice_size ,
443508 explicit_coordinate_names = explicit_coordinate_names ,
509+ scaling = scaling ,
444510 )
445511 bounds_to_file (
446512 m ,
447513 f = f ,
448514 progress = progress ,
449515 slice_size = slice_size ,
450516 explicit_coordinate_names = explicit_coordinate_names ,
517+ scaling = scaling ,
451518 )
452519 binaries_to_file (
453520 m ,
@@ -477,6 +544,7 @@ def to_file(
477544 slice_size : int = 2_000_000 ,
478545 progress : bool | None = None ,
479546 explicit_coordinate_names : bool = False ,
547+ scaling : ScalingContext | None = None ,
480548) -> Path :
481549 """
482550 Write out a model to a lp or mps file.
@@ -502,6 +570,7 @@ def to_file(
502570 slice_size = slice_size ,
503571 progress = progress ,
504572 explicit_coordinate_names = explicit_coordinate_names ,
573+ scaling = scaling ,
505574 )
506575
507576 elif io_api == "mps" :
@@ -512,7 +581,10 @@ def to_file(
512581
513582 # Use very fast highspy implementation
514583 # Might be replaced by custom writer, however needs C/Rust bindings for performance
515- h = m .to_highspy (explicit_coordinate_names = explicit_coordinate_names )
584+ h = m .to_highspy (
585+ explicit_coordinate_names = explicit_coordinate_names ,
586+ matrices = scaling .matrices if scaling else None ,
587+ )
516588 h .writeModel (str (fn ))
517589 else :
518590 raise ValueError (
@@ -523,7 +595,10 @@ def to_file(
523595
524596
525597def to_mosek (
526- m : Model , task : Any | None = None , explicit_coordinate_names : bool = False
598+ m : Model ,
599+ task : Any | None = None ,
600+ explicit_coordinate_names : bool = False ,
601+ matrices : MatrixAccessor | ScaledMatrices | None = None ,
527602) -> Any :
528603 """
529604 Export model to MOSEK.
@@ -552,7 +627,7 @@ def to_mosek(
552627 task .appendvars (m .nvars )
553628 task .appendcons (m .ncons )
554629
555- M = m .matrices
630+ M = m .matrices if matrices is None else matrices
556631 # for j, n in enumerate(("x" + M.vlabels.astype(str).astype(object))):
557632 # task.putvarname(j, n)
558633
@@ -635,7 +710,10 @@ def to_mosek(
635710
636711
637712def to_gurobipy (
638- m : Model , env : Any | None = None , explicit_coordinate_names : bool = False
713+ m : Model ,
714+ env : Any | None = None ,
715+ explicit_coordinate_names : bool = False ,
716+ matrices : MatrixAccessor | ScaledMatrices | None = None ,
639717) -> Any :
640718 """
641719 Export the model to gurobipy.
@@ -662,7 +740,7 @@ def to_gurobipy(
662740 m .constraints .sanitize_missings ()
663741 model = gurobipy .Model (env = env )
664742
665- M = m .matrices
743+ M = m .matrices if matrices is None else matrices
666744
667745 names = np .vectorize (print_variable )(M .vlabels ).astype (object )
668746 kwargs = {}
@@ -687,7 +765,11 @@ def to_gurobipy(
687765 return model
688766
689767
690- def to_highspy (m : Model , explicit_coordinate_names : bool = False ) -> Highs :
768+ def to_highspy (
769+ m : Model ,
770+ explicit_coordinate_names : bool = False ,
771+ matrices : MatrixAccessor | ScaledMatrices | None = None ,
772+ ) -> Highs :
691773 """
692774 Export the model to highspy.
693775
@@ -710,7 +792,7 @@ def to_highspy(m: Model, explicit_coordinate_names: bool = False) -> Highs:
710792 m , explicit_coordinate_names = explicit_coordinate_names
711793 )
712794
713- M = m .matrices
795+ M = m .matrices if matrices is None else matrices
714796 h = highspy .Highs ()
715797 h .addVars (len (M .vlabels ), M .lb , M .ub )
716798 if len (m .binaries ) + len (m .integers ):
0 commit comments