@@ -2066,8 +2066,9 @@ cdef class Model:
2066
2066
return SCIPgetRowObjParallelism(self ._scip, row.scip_row)
2067
2067
2068
2068
def getRowParallelism (self , Row row1 , Row row2 , orthofunc = 101 ):
2069
- """ Returns the degree of parallelism between hyplerplanes. 1 if perfectly parallel, 0 if orthognal.
2070
- 101 in this case is an 'e' (euclidean) in ASCII. The other accpetable input is 100 (d for discrete)."""
2069
+ """ Returns the degree of parallelism between hyplerplanes. 1 if perfectly parallel, 0 if orthogonal.
2070
+ For two row vectors v, w the parallelism is calculated as: |v*w|/(|v|*|w|).
2071
+ 101 in this case is an 'e' (euclidean) in ASCII. The other acceptable input is 100 (d for discrete)."""
2071
2072
return SCIProwGetParallelism(row1.scip_row, row2.scip_row, orthofunc)
2072
2073
2073
2074
def getRowDualSol (self , Row row ):
@@ -5697,6 +5698,255 @@ cdef class Model:
5697
5698
""" Get an estimation of the final tree size """
5698
5699
return SCIPgetTreesizeEstimation(self ._scip)
5699
5700
5701
+
5702
+ def getBipartiteGraphRepresentation (self , prev_col_features = None , prev_edge_features = None , prev_row_features = None ,
5703
+ static_only = False , suppress_warnings = False ):
5704
+ """ This function generates the bipartite graph representation of an LP, which was first used in
5705
+ the following paper:
5706
+ @inproceedings{conf/nips/GasseCFCL19,
5707
+ title={Exact Combinatorial Optimization with Graph Convolutional Neural Networks},
5708
+ author={Gasse, Maxime and Chételat, Didier and Ferroni, Nicola and Charlin, Laurent and Lodi, Andrea},
5709
+ booktitle={Advances in Neural Information Processing Systems 32},
5710
+ year={2019}
5711
+ }
5712
+ The exact features have been modified compared to the original implementation.
5713
+ This function is used mainly in the machine learning community for MIP.
5714
+ A user can only call it during the solving process, when there is an LP object. This means calling it
5715
+ from some user defined plugin on the Python side.
5716
+ An example plugin is a branching rule or an event handler, which is exclusively created to call this function.
5717
+ The user must then make certain to return the appropriate SCIP_RESULT (e.g. DIDNOTRUN)
5718
+
5719
+ :param prev_col_features: The list of column features previously returned by this function
5720
+ :param prev_edge_features: The list of edge features previously returned by this function
5721
+ :param prev_row_features: The list of row features previously returned by this function
5722
+ :param static_only: Whether exclusively static features should be generated
5723
+ :param suppress_warnings: Whether warnings should be suppressed
5724
+ """
5725
+
5726
+ cdef SCIP* scip = self ._scip
5727
+ cdef int i, j, k, col_i
5728
+ cdef SCIP_VARTYPE vtype
5729
+ cdef SCIP_Real sim, prod
5730
+
5731
+ # Check if SCIP is in the correct stage
5732
+ if SCIPgetStage(scip) != SCIP_STAGE_SOLVING:
5733
+ raise Warning (" This functionality can only been called in SCIP_STAGE SOLVING. The row and column"
5734
+ " information is then accessible" )
5735
+
5736
+ # Generate column features
5737
+ cdef SCIP_COL** cols = SCIPgetLPCols(scip)
5738
+ cdef int ncols = SCIPgetNLPCols(scip)
5739
+
5740
+ if static_only:
5741
+ n_col_features = 5
5742
+ col_feature_map = {" continuous" : 0 , " binary" : 1 , " integer" : 2 , " implicit_integer" : 3 , " obj_coef" : 4 }
5743
+ else :
5744
+ n_col_features = 19
5745
+ col_feature_map = {" continuous" : 0 , " binary" : 1 , " integer" : 2 , " implicit_integer" : 3 , " obj_coef" : 4 ,
5746
+ " has_lb" : 5 , " has_ub" : 6 , " sol_at_lb" : 7 , " sol_at_ub" : 8 , " sol_val" : 9 , " sol_frac" : 10 ,
5747
+ " red_cost" : 11 , " basis_lower" : 12 , " basis_basic" : 13 , " basis_upper" : 14 ,
5748
+ " basis_zero" : 15 , " best_incumbent_val" : 16 , " avg_incumbent_val" : 17 , " age" : 18 }
5749
+
5750
+ if prev_col_features is None :
5751
+ col_features = [[0 for _ in range (n_col_features)] for _ in range (ncols)]
5752
+ else :
5753
+ assert len (prev_col_features) > 0 , " Previous column features is empty"
5754
+ col_features = prev_col_features
5755
+ if len (prev_col_features) != ncols:
5756
+ if not suppress_warnings:
5757
+ raise Warning (f" The number of columns has changed. Previous column data being ignored" )
5758
+ else :
5759
+ col_features = [[0 for _ in range (n_col_features)] for _ in range (ncols)]
5760
+ prev_col_features = None
5761
+ if len (prev_col_features[0 ]) != n_col_features:
5762
+ raise Warning (f" Dimension mismatch in provided previous features and new features:"
5763
+ f" {len(prev_col_features[0])} != {n_col_features}" )
5764
+
5765
+ cdef SCIP_SOL* sol = SCIPgetBestSol(scip)
5766
+ cdef SCIP_VAR* var
5767
+ cdef SCIP_Real lb, ub, solval
5768
+ cdef SCIP_BASESTAT basis_status
5769
+ for i in range (ncols):
5770
+ col_i = SCIPcolGetLPPos(cols[i])
5771
+ var = SCIPcolGetVar(cols[i])
5772
+
5773
+ lb = SCIPcolGetLb(cols[i])
5774
+ ub = SCIPcolGetUb(cols[i])
5775
+ solval = SCIPcolGetPrimsol(cols[i])
5776
+
5777
+ # Collect the static features first (don't need to changed if previous features are passed)
5778
+ if prev_col_features is None :
5779
+ # Variable types
5780
+ vtype = SCIPvarGetType(var)
5781
+ if vtype == SCIP_VARTYPE_BINARY:
5782
+ col_features[col_i][col_feature_map[" binary" ]] = 1
5783
+ elif vtype == SCIP_VARTYPE_INTEGER:
5784
+ col_features[col_i][col_feature_map[" integer" ]] = 1
5785
+ elif vtype == SCIP_VARTYPE_CONTINUOUS:
5786
+ col_features[col_i][col_feature_map[" continuous" ]] = 1
5787
+ elif vtype == SCIP_VARTYPE_IMPLINT:
5788
+ col_features[col_i][col_feature_map[" implicit_integer" ]] = 1
5789
+ # Objective coefficient
5790
+ col_features[col_i][col_feature_map[" obj_coef" ]] = SCIPcolGetObj(cols[i])
5791
+
5792
+ # Collect the dynamic features
5793
+ if not static_only:
5794
+ # Lower bound
5795
+ if not SCIPisInfinity(scip, abs (lb)):
5796
+ col_features[col_i][col_feature_map[" has_lb" ]] = 1
5797
+
5798
+ # Upper bound
5799
+ if not SCIPisInfinity(scip, abs (ub)):
5800
+ col_features[col_i][col_feature_map[" has_ub" ]] = 1
5801
+
5802
+ # Basis status
5803
+ basis_status = SCIPcolGetBasisStatus(cols[i])
5804
+ if basis_status == SCIP_BASESTAT_LOWER:
5805
+ col_features[col_i][col_feature_map[" basis_lower" ]] = 1
5806
+ elif basis_status == SCIP_BASESTAT_BASIC:
5807
+ col_features[col_i][col_feature_map[" basis_basic" ]] = 1
5808
+ elif basis_status == SCIP_BASESTAT_UPPER:
5809
+ col_features[col_i][col_feature_map[" basis_upper" ]] = 1
5810
+ elif basis_status == SCIP_BASESTAT_ZERO:
5811
+ col_features[col_i][col_feature_map[" basis_zero" ]] = 1
5812
+
5813
+ # Reduced cost
5814
+ col_features[col_i][col_feature_map[" red_cost" ]] = SCIPgetColRedcost(scip, cols[i])
5815
+
5816
+ # Age
5817
+ col_features[col_i][col_feature_map[" age" ]] = SCIPcolGetAge(cols[i])
5818
+
5819
+ # LP solution value
5820
+ col_features[col_i][col_feature_map[" sol_val" ]] = solval
5821
+ col_features[col_i][col_feature_map[" sol_frac" ]] = SCIPfeasFrac(scip, solval)
5822
+ col_features[col_i][col_feature_map[" sol_at_lb" ]] = int (SCIPisEQ(scip, solval, lb))
5823
+ col_features[col_i][col_feature_map[" sol_at_ub" ]] = int (SCIPisEQ(scip, solval, ub))
5824
+
5825
+ # Incumbent solution value
5826
+ if sol is NULL :
5827
+ col_features[col_i][col_feature_map[" best_incumbent_val" ]] = None
5828
+ col_features[col_i][col_feature_map[" avg_incumbent_val" ]] = None
5829
+ else :
5830
+ col_features[col_i][col_feature_map[" best_incumbent_val" ]] = SCIPgetSolVal(scip, sol, var)
5831
+ col_features[col_i][col_feature_map[" avg_incumbent_val" ]] = SCIPvarGetAvgSol(var)
5832
+
5833
+ # Generate row features
5834
+ cdef int nrows = SCIPgetNLPRows(scip)
5835
+ cdef SCIP_ROW** rows = SCIPgetLPRows(scip)
5836
+
5837
+ if static_only:
5838
+ n_row_features = 6
5839
+ row_feature_map = {" has_lhs" : 0 , " has_rhs" : 1 , " n_non_zeros" : 2 , " obj_cosine" : 3 , " bias" : 4 , " norm" : 5 }
5840
+ else :
5841
+ n_row_features = 14
5842
+ row_feature_map = {" has_lhs" : 0 , " has_rhs" : 1 , " n_non_zeros" : 2 , " obj_cosine" : 3 , " bias" : 4 , " norm" : 5 ,
5843
+ " sol_at_lhs" : 6 , " sol_at_rhs" : 7 , " dual_sol" : 8 , " age" : 9 ,
5844
+ " basis_lower" : 10 , " basis_basic" : 11 , " basis_upper" : 12 , " basis_zero" : 13 }
5845
+
5846
+ if prev_row_features is None :
5847
+ row_features = [[0 for _ in range (n_row_features)] for _ in range (nrows)]
5848
+ else :
5849
+ assert len (prev_row_features) > 0 , " Previous row features is empty"
5850
+ row_features = prev_row_features
5851
+ if len (prev_row_features) != nrows:
5852
+ if not suppress_warnings:
5853
+ raise Warning (f" The number of rows has changed. Previous row data being ignored" )
5854
+ else :
5855
+ row_features = [[0 for _ in range (n_row_features)] for _ in range (nrows)]
5856
+ prev_row_features = None
5857
+ if len (prev_row_features[0 ]) != n_row_features:
5858
+ raise Warning (f" Dimension mismatch in provided previous features and new features:"
5859
+ f" {len(prev_row_features[0])} != {n_row_features}" )
5860
+
5861
+ cdef int nnzrs = 0
5862
+ cdef SCIP_Real lhs, rhs, cst
5863
+ for i in range (nrows):
5864
+
5865
+ # lhs <= activity + cst <= rhs
5866
+ lhs = SCIProwGetLhs(rows[i])
5867
+ rhs = SCIProwGetRhs(rows[i])
5868
+ cst = SCIProwGetConstant(rows[i])
5869
+ activity = SCIPgetRowLPActivity(scip, rows[i])
5870
+
5871
+ if prev_row_features is None :
5872
+ # number of coefficients
5873
+ row_features[i][row_feature_map[" n_non_zeros" ]] = SCIProwGetNLPNonz(rows[i])
5874
+ nnzrs += row_features[i][row_feature_map[" n_non_zeros" ]]
5875
+
5876
+ # left-hand-side
5877
+ if not SCIPisInfinity(scip, abs (lhs)):
5878
+ row_features[i][row_feature_map[" has_lhs" ]] = 1
5879
+
5880
+ # right-hand-side
5881
+ if not SCIPisInfinity(scip, abs (rhs)):
5882
+ row_features[i][row_feature_map[" has_rhs" ]] = 1
5883
+
5884
+ # bias
5885
+ row_features[i][row_feature_map[" bias" ]] = cst
5886
+
5887
+ # Objective cosine similarity
5888
+ row_features[i][row_feature_map[" obj_cosine" ]] = SCIPgetRowObjParallelism(scip, rows[i])
5889
+
5890
+ # L2 norm
5891
+ row_features[i][row_feature_map[" norm" ]] = SCIProwGetNorm(rows[i])
5892
+
5893
+ if not static_only:
5894
+
5895
+ # Dual solution
5896
+ row_features[i][row_feature_map[" dual_sol" ]] = SCIProwGetDualsol(rows[i])
5897
+
5898
+ # Basis status
5899
+ basis_status = SCIProwGetBasisStatus(rows[i])
5900
+ if basis_status == SCIP_BASESTAT_LOWER:
5901
+ row_features[i][row_feature_map[" basis_lower" ]] = 1
5902
+ elif basis_status == SCIP_BASESTAT_BASIC:
5903
+ row_features[i][row_feature_map[" basis_basic" ]] = 1
5904
+ elif basis_status == SCIP_BASESTAT_UPPER:
5905
+ row_features[i][row_feature_map[" basis_upper" ]] = 1
5906
+ elif basis_status == SCIP_BASESTAT_ZERO:
5907
+ row_features[i][row_feature_map[" basis_zero" ]] = 1
5908
+
5909
+ # Age
5910
+ row_features[i][row_feature_map[" age" ]] = SCIProwGetAge(rows[i])
5911
+
5912
+ # Is tight
5913
+ row_features[i][row_feature_map[" sol_at_lhs" ]] = int (SCIPisEQ(scip, activity, lhs))
5914
+ row_features[i][row_feature_map[" sol_at_rhs" ]] = int (SCIPisEQ(scip, activity, rhs))
5915
+
5916
+ # Generate edge (coefficient) features
5917
+ cdef SCIP_COL** row_cols
5918
+ cdef SCIP_Real * row_vals
5919
+ n_edge_features = 3
5920
+ edge_feature_map = {" col_idx" : 0 , " row_idx" : 1 , " coef" : 2 }
5921
+ if prev_edge_features is None :
5922
+ edge_features = [[0 for _ in range (n_edge_features)] for _ in range (nnzrs)]
5923
+ j = 0
5924
+ for i in range (nrows):
5925
+ # coefficient indexes and values
5926
+ row_cols = SCIProwGetCols(rows[i])
5927
+ row_vals = SCIProwGetVals(rows[i])
5928
+ for k in range (row_features[i][row_feature_map[" n_non_zeros" ]]):
5929
+ edge_features[j][edge_feature_map[" col_idx" ]] = SCIPcolGetLPPos(row_cols[k])
5930
+ edge_features[j][edge_feature_map[" row_idx" ]] = i
5931
+ edge_features[j][edge_feature_map[" coef" ]] = row_vals[k]
5932
+ j += 1
5933
+ else :
5934
+ assert len (prev_edge_features) > 0 , " Previous edge features is empty"
5935
+ edge_features = prev_edge_features
5936
+ if len (prev_edge_features) != nnzrs:
5937
+ if not suppress_warnings:
5938
+ raise Warning (f" The number of coefficients in the LP has changed. Previous edge data being ignored" )
5939
+ else :
5940
+ edge_features = [[0 for _ in range (3 )] for _ in range (nnzrs)]
5941
+ prev_edge_features = None
5942
+ if len (prev_edge_features[0 ]) != 3 :
5943
+ raise Warning (f" Dimension mismatch in provided previous features and new features:"
5944
+ f" {len(prev_edge_features[0])} != 3" )
5945
+
5946
+
5947
+ return (col_features, edge_features, row_features,
5948
+ {" col" : col_feature_map, " edge" : edge_feature_map, " row" : row_feature_map})
5949
+
5700
5950
@dataclass
5701
5951
class Statistics :
5702
5952
"""
0 commit comments