140
140
HighsInt Highs_setBoolOptionValue(
141
141
void* highs, const char* option, const bool value
142
142
);
143
+ HighsInt Highs_getSolution(
144
+ const void* highs, double* col_value, double* col_dual,
145
+ double* row_value, double* row_dual
146
+ );
147
+ HighsInt Highs_deleteRowsBySet(
148
+ void* highs, const HighsInt num_set_entries, const HighsInt* set
149
+ );
150
+ HighsInt Highs_deleteColsBySet(
151
+ void* highs, const HighsInt num_set_entries, const HighsInt* set
152
+ );
143
153
"""
144
154
145
155
if has_highs :
@@ -173,6 +183,11 @@ def __init__(self, model: mip.Model, name: str, sense: str):
173
183
self ._cons_col : Dict [str , int ] = {}
174
184
self ._num_int : int = 0
175
185
186
+ # Also store solution (when available)
187
+ self ._x = []
188
+ self ._rc = []
189
+ self ._pi = []
190
+
176
191
def __del__ (self ):
177
192
self ._lib .Highs_destroy (self ._model )
178
193
@@ -355,6 +370,24 @@ def optimize(
355
370
# TODO: handle relax (need to remember and reset integrality?!
356
371
raise NotImplementedError ()
357
372
status = self ._lib .Highs_run (self ._model )
373
+
374
+ # store solution values for later access
375
+ if self ._has_primal_solution ():
376
+ # TODO: also handle primal/dual rays?
377
+ n , m = self .num_cols (), self .num_rows ()
378
+ col_value = ffi .new ("double[]" , n )
379
+ col_dual = ffi .new ("double[]" , n )
380
+ row_value = ffi .new ("double[]" , m )
381
+ row_dual = ffi .new ("double[]" , m )
382
+ status = self ._lib .Highs_getSolution (
383
+ self ._model , col_value , col_dual , row_value , row_dual
384
+ )
385
+ self ._x = [col_value [j ] for j in range (n )]
386
+ self ._rc = [col_dual [j ] for j in range (n )]
387
+
388
+ if self ._has_dual_solution ():
389
+ self ._pi = [row_dual [i ] for i in range (m )]
390
+
358
391
return self .get_status ()
359
392
360
393
def get_objective_value (self : "SolverHighs" ) -> numbers .Real :
@@ -506,33 +539,96 @@ def set_verbose(self: "SolverHighs", verbose: int):
506
539
# Constraint-related getters/setters
507
540
508
541
def constr_get_expr (self : "SolverHighs" , constr : "mip.Constr" ) -> "mip.LinExpr" :
509
- pass
542
+ row = constr .idx
543
+ # Call method twice:
544
+ # - first, to get the sizes for coefficients,
545
+ num_row = ffi .new ("int*" )
546
+ lower = ffi .new ("double[]" , 1 )
547
+ upper = ffi .new ("double[]" , 1 )
548
+ num_nz = ffi .new ("int*" )
549
+ status = self ._lib .Highs_getRowsByRange (
550
+ self ._model , row , row , lower , upper , num_nz , ffi .NULL , ffi .NULL , ffi .NULL
551
+ )
552
+
553
+ # - second, to get the coefficients in pre-allocated arrays.
554
+ matrix_start = ffi .new ("int[]" , 1 )
555
+ matrix_index = ffi .new ("int[]" , num_nz [0 ])
556
+ matrix_value = ffi .new ("double[]" , num_nz [0 ])
557
+ status = self ._lib .Highs_getRowsByRange (
558
+ self ._model ,
559
+ row ,
560
+ row ,
561
+ lower ,
562
+ upper ,
563
+ num_nz ,
564
+ matrix_start ,
565
+ matrix_index ,
566
+ matrix_value ,
567
+ )
568
+ assert matrix [0 ] == 0
569
+
570
+ return mip .xsum (matrix_value [i ] * self .model .vars [i ] for i in range (num_nz ))
510
571
511
572
def constr_set_expr (
512
573
self : "SolverHighs" , constr : "mip.Constr" , value : "mip.LinExpr"
513
574
) -> "mip.LinExpr" :
514
- pass
575
+ raise NotImplementedError ()
515
576
516
577
def constr_get_rhs (self : "SolverHighs" , idx : int ) -> numbers .Real :
517
- pass
578
+ # fetch both lower and upper bound
579
+ row = constr .idx
580
+ num_row = ffi .new ("int*" )
581
+ lower = ffi .new ("double[]" , 1 )
582
+ upper = ffi .new ("double[]" , 1 )
583
+ num_nz = ffi .new ("int*" )
584
+ status = self ._lib .Highs_getRowsByRange (
585
+ self ._model , row , row , lower , upper , num_nz , ffi .NULL , ffi .NULL , ffi .NULL
586
+ )
587
+
588
+ # case distinction for sense
589
+ if lower [0 ] == - mip .INF :
590
+ return - upper [0 ]
591
+ if upper [0 ] == mip .INF :
592
+ return - lower [0 ]
593
+ assert lower [0 ] == upper [0 ]
594
+ return - lower [0 ]
518
595
519
596
def constr_set_rhs (self : "SolverHighs" , idx : int , rhs : numbers .Real ):
520
- pass
597
+ # first need to figure out which bound to change (lower or upper)
598
+ num_row = ffi .new ("int*" )
599
+ lower = ffi .new ("double[]" , 1 )
600
+ upper = ffi .new ("double[]" , 1 )
601
+ num_nz = ffi .new ("int*" )
602
+ status = self ._lib .Highs_getRowsByRange (
603
+ self ._model , idx , idx , lower , upper , num_nz , ffi .NULL , ffi .NULL , ffi .NULL
604
+ )
605
+
606
+ # update bounds as needed
607
+ lb , ub = lower [0 ], upper [0 ]
608
+ if lb != - mip .INF :
609
+ lb = - rhs
610
+ if ub != mip .INF :
611
+ ub = - rhs
612
+
613
+ # set new bounds
614
+ status = self ._lib .Highs_changeRowBounds (self ._model , idx , lb , ub )
521
615
522
616
def constr_get_name (self : "SolverHighs" , idx : int ) -> str :
523
- pass
617
+ return self . _cons_name ( idx )
524
618
525
619
def constr_get_pi (self : "SolverHighs" , constr : "mip.Constr" ) -> numbers .Real :
526
- pass
620
+ if self ._pi :
621
+ return self ._pi [constr .idx ]
527
622
528
623
def constr_get_slack (self : "SolverHighs" , constr : "mip.Constr" ) -> numbers .Real :
529
- pass
624
+ raise NotImplementedError ()
530
625
531
626
def remove_constrs (self : "SolverHighs" , constrsList : List [int ]):
532
- pass
627
+ set_ = ffi .new ("int[]" , constrsList )
628
+ status = self ._lib .Highs_deleteRowsBySet (self ._model , len (constrsList ), set_ )
533
629
534
630
def constr_get_index (self : "SolverHighs" , name : str ) -> int :
535
- pass
631
+ return self . _cons_col ( name )
536
632
537
633
# Variable-related getters/setters
538
634
@@ -545,49 +641,115 @@ def var_set_branch_priority(
545
641
raise NotImplementedError ()
546
642
547
643
def var_get_lb (self : "SolverHighs" , var : "mip.Var" ) -> numbers .Real :
548
- pass
644
+ num_col = ffi .new ("int*" )
645
+ costs = ffi .new ("double[]" , 1 )
646
+ lower = ffi .new ("double[]" , 1 )
647
+ upper = ffi .new ("double[]" , 1 )
648
+ num_nz = ffi .new ("int*" )
649
+ status = self ._lib .Highs_getColsByRange (
650
+ self ._model ,
651
+ var .idx , # from_col
652
+ var .idx , # to_col
653
+ num_col ,
654
+ costs ,
655
+ lower ,
656
+ upper ,
657
+ num_nz ,
658
+ ffi .NULL , # matrix_start
659
+ ffi .NULL , # matrix_index
660
+ ffi .NULL , # matrix_value
661
+ )
662
+ return lower [0 ]
549
663
550
664
def var_set_lb (self : "SolverHighs" , var : "mip.Var" , value : numbers .Real ):
551
- pass
665
+ # can only set both bounds, so we just set the old upper bound
666
+ old_upper = self .var_get_ub (var )
667
+ status = self ._lib .Highs_changeColBounds (self ._model , var .idx , value , old_upper )
552
668
553
669
def var_get_ub (self : "SolverHighs" , var : "mip.Var" ) -> numbers .Real :
554
- pass
670
+ num_col = ffi .new ("int*" )
671
+ costs = ffi .new ("double[]" , 1 )
672
+ lower = ffi .new ("double[]" , 1 )
673
+ upper = ffi .new ("double[]" , 1 )
674
+ num_nz = ffi .new ("int*" )
675
+ status = self ._lib .Highs_getColsByRange (
676
+ self ._model ,
677
+ var .idx , # from_col
678
+ var .idx , # to_col
679
+ num_col ,
680
+ costs ,
681
+ lower ,
682
+ upper ,
683
+ num_nz ,
684
+ ffi .NULL , # matrix_start
685
+ ffi .NULL , # matrix_index
686
+ ffi .NULL , # matrix_value
687
+ )
688
+ return upper [0 ]
555
689
556
690
def var_set_ub (self : "SolverHighs" , var : "mip.Var" , value : numbers .Real ):
557
- pass
691
+ # can only set both bounds, so we just set the old lower bound
692
+ old_lower = self .var_get_lb (var )
693
+ status = self ._lib .Highs_changeColBounds (self ._model , var .idx , old_lower , value )
558
694
559
695
def var_get_obj (self : "SolverHighs" , var : "mip.Var" ) -> numbers .Real :
560
- pass
696
+ num_col = ffi .new ("int*" )
697
+ costs = ffi .new ("double[]" , 1 )
698
+ lower = ffi .new ("double[]" , 1 )
699
+ upper = ffi .new ("double[]" , 1 )
700
+ num_nz = ffi .new ("int*" )
701
+ status = self ._lib .Highs_getColsByRange (
702
+ self ._model ,
703
+ var .idx , # from_col
704
+ var .idx , # to_col
705
+ num_col ,
706
+ costs ,
707
+ lower ,
708
+ upper ,
709
+ num_nz ,
710
+ ffi .NULL , # matrix_start
711
+ ffi .NULL , # matrix_index
712
+ ffi .NULL , # matrix_value
713
+ )
714
+ return costs [0 ]
561
715
562
716
def var_set_obj (self : "SolverHighs" , var : "mip.Var" , value : numbers .Real ):
563
- pass
717
+ status = self . _lib . Highs_changeColCost ( self . _model , var . idx , value )
564
718
565
719
def var_get_var_type (self : "SolverHighs" , var : "mip.Var" ) -> str :
566
- pass
720
+ # TODO: store var type separately?
721
+ raise NotImplementedError ()
567
722
568
723
def var_set_var_type (self : "SolverHighs" , var : "mip.Var" , value : str ):
569
- pass
724
+ # TODO: store var type separately?
725
+ raise NotImplementedError ()
570
726
571
727
def var_get_column (self : "SolverHighs" , var : "mip.Var" ) -> "Column" :
572
- pass
728
+ # TODO
729
+ raise NotImplementedError ()
573
730
574
731
def var_set_column (self : "SolverHighs" , var : "mip.Var" , value : "Column" ):
575
- pass
732
+ # TODO
733
+ raise NotImplementedError ()
576
734
577
735
def var_get_rc (self : "SolverHighs" , var : "mip.Var" ) -> numbers .Real :
578
- pass
736
+ # TODO: double-check this!
737
+ if self ._rc :
738
+ self ._rc [var .idx ]
579
739
580
740
def var_get_x (self : "SolverHighs" , var : "mip.Var" ) -> numbers .Real :
581
- pass
741
+ if self ._x :
742
+ return self ._x [var .idx ]
582
743
583
744
def var_get_xi (self : "SolverHighs" , var : "mip.Var" , i : int ) -> numbers .Real :
584
- pass
745
+ raise NotImplementedError ()
585
746
586
747
def var_get_name (self : "SolverHighs" , idx : int ) -> str :
587
748
return self ._var_name [idx ]
588
749
589
750
def remove_vars (self : "SolverHighs" , varsList : List [int ]):
590
- pass
751
+ set_ = ffi .new ("int[]" , varsList )
752
+ status = self ._lib .Highs_deleteColsBySet (self ._model , len (varsList ), set_ )
591
753
592
754
def var_get_index (self : "SolverHighs" , name : str ) -> int :
593
755
return self ._var_col [name ]
@@ -599,17 +761,19 @@ def set_problem_name(self: "SolverHighs", name: str):
599
761
self ._name = name
600
762
601
763
def _get_primal_solution_status (self : "SolverHighs" ):
602
- sol_status = ffi .new ("int*" )
603
- status = self ._lib .Highs_getIntInfoValue (
604
- self ._model , "primal_solution_status" , sol_status
605
- )
606
- return sol_status [0 ]
764
+ return self ._get_int_info_value ("primal_solution_status" )
607
765
608
766
def _has_primal_solution (self : "SolverHighs" ):
609
767
return (
610
768
self ._get_primal_solution_status () == self ._lib .kHighsSolutionStatusFeasible
611
769
)
612
770
771
+ def _get_dual_solution_status (self : "SolverHighs" ):
772
+ return self ._get_int_info_value ("dual_solution_status" )
773
+
774
+ def _has_dual_solution (self : "SolverHighs" ):
775
+ return self ._get_dual_solution_status () == self ._lib .kHighsSolutionStatusFeasible
776
+
613
777
def get_status (self : "SolverHighs" ) -> mip .OptimizationStatus :
614
778
OS = mip .OptimizationStatus
615
779
status_map = {
0 commit comments