@@ -638,30 +638,61 @@ def constr_get_expr(self: "SolverHighs", constr: "mip.Constr") -> "mip.LinExpr":
638
638
)
639
639
640
640
# - second, to get the coefficients in pre-allocated arrays.
641
- matrix_start = ffi .new ("int[]" , 1 )
642
- matrix_index = ffi .new ("int[]" , num_nz [0 ])
643
- matrix_value = ffi .new ("double[]" , num_nz [0 ])
644
- check (
645
- self ._lib .Highs_getRowsByRange (
646
- self ._model ,
647
- row ,
648
- row ,
649
- num_row ,
650
- lower ,
651
- upper ,
652
- num_nz ,
653
- matrix_start ,
654
- matrix_index ,
655
- matrix_value ,
641
+ if num_nz [0 ] == 0 :
642
+ # early exit for empty expressions
643
+ expr = mip .xsum ([])
644
+ else :
645
+ matrix_start = ffi .new ("int[]" , 1 )
646
+ matrix_index = ffi .new ("int[]" , num_nz [0 ])
647
+ matrix_value = ffi .new ("double[]" , num_nz [0 ])
648
+ check (
649
+ self ._lib .Highs_getRowsByRange (
650
+ self ._model ,
651
+ row ,
652
+ row ,
653
+ num_row ,
654
+ lower ,
655
+ upper ,
656
+ num_nz ,
657
+ matrix_start ,
658
+ matrix_index ,
659
+ matrix_value ,
660
+ )
661
+ )
662
+ expr = mip .xsum (
663
+ matrix_value [i ] * self .model .vars [i ] for i in range (num_nz [0 ])
656
664
)
657
- )
658
665
659
- return mip .xsum (matrix_value [i ] * self .model .vars [i ] for i in range (num_nz ))
666
+ # Also set sense and constant
667
+ lhs , rhs = lower [0 ], upper [0 ]
668
+ if rhs < mip .INF :
669
+ expr -= rhs
670
+ if lhs > - mip .INF :
671
+ assert lhs == rhs
672
+ expr .sense = mip .EQUAL
673
+ else :
674
+ expr .sense = mip .LESS_OR_EQUAL
675
+ else :
676
+ if lhs > - mip .INF :
677
+ expr -= lhs
678
+ expr .sense = mip .GREATER_OR_EQUAL
679
+ else :
680
+ raise ValueError ("Unbounded constraint?!" )
681
+ return expr
660
682
661
683
def constr_set_expr (
662
684
self : "SolverHighs" , constr : "mip.Constr" , value : "mip.LinExpr"
663
685
) -> "mip.LinExpr" :
664
- raise NotImplementedError ()
686
+ # We also have to set to 0 all coefficients of the old row, so we
687
+ # fetch that first.
688
+ coeffs = {var : 0.0 for var in constr .expr }
689
+
690
+ # Then we fetch the new coefficients and overwrite.
691
+ coeffs .update (value .expr .items ())
692
+
693
+ # Finally, we change the coeffs in HiGHS' matrix one-by-one.
694
+ for var , coef in coeffs .items ():
695
+ self ._change_coef (constr .idx , var .idx , coef )
665
696
666
697
def constr_get_rhs (self : "SolverHighs" , idx : int ) -> numbers .Real :
667
698
# fetch both lower and upper bound
@@ -731,7 +762,19 @@ def constr_get_pi(self: "SolverHighs", constr: "mip.Constr") -> numbers.Real:
731
762
return self ._pi [constr .idx ]
732
763
733
764
def constr_get_slack (self : "SolverHighs" , constr : "mip.Constr" ) -> numbers .Real :
734
- raise NotImplementedError ()
765
+ expr = constr .expr
766
+ activity = sum (coef * var .x for var , coef in expr .expr .items ())
767
+ rhs = - expr .const
768
+ slack = rhs - activity
769
+ assert False
770
+ if expr .sense == mip .LESS_OR_EQUAL :
771
+ return slack
772
+ elif expr .sense == mip .GREATER_OR_EQUAL :
773
+ return - slack
774
+ elif expr .sense == mip .EQUAL :
775
+ return - abs (slack )
776
+ else :
777
+ raise ValueError (f"Invalid constraint sense: { expr .sense } " )
735
778
736
779
def remove_constrs (self : "SolverHighs" , constrsList : List [int ]):
737
780
set_ = ffi .new ("int[]" , constrsList )
0 commit comments