this repo has no description
at develop 28 kB view raw
1## This class compares and summarizes instance solution results 2## for a given set of optimization/satisfiability instances. 3## Looks for contradictions. Heuristic performance indicators. 4## (c) Gleb Belov@monash.edu 2017 5 6from collections import OrderedDict 7# import prettytable 8import utils, io ## functools 9from utils import strNL 10 11##################################################################################### 12################### class CompareLogs ################## 13##################################################################################### 14 15## It receives a list of dictionaries, keyed by instances files, 16## containing values of solutions status etc., and performs the checks + summary. 17class CompareLogs: 18 def __init__( self ): 19 self.lResLogs = [] ## empty list of logs/methods to compare 20 self.hdrSummary = [ ## These are column headers for overall per-method summary 21 ( "s_MethodName", "logfile/test name", "Logfile and possibly test alias" ), 22 ( "n_Reported", "Nout", "Total number of instances" ), 23 ( "n_CheckFailed","Nbad", "Number of failed solution checks" ), 24 ( "n_ErrorsBackend", "NerrB", "Number of backend errors. TODO need still to consider feasible solutions if ERROR status" ), 25 ( "n_ErrorsLogical", "NerrL", "Number of logical errors, such as different solver and MZN obj values" ), 26 ( "n_OPT", "Nopt", "Number of reported optimal" ), 27 ( "n_FEAS", "Nfea", "Number of reported feasible" ), 28 ( "n_SATALL", "NsatA", "Number of reported SAT-COMPLETE" ), 29 ( "n_SAT", "Nsat", "Number of reported SAT" ), 30 ( "n_INFEAS", "Ninfeas", "Number of reported UNSAT" ), 31 ( "n_NOFZN", "NoFZN", "Number of failed flattenings" ), 32 ( "n_UNKNOWN", "Nunkn", "Number of unknown results" ), 33 ( "t_Flatten", "TFlt", "Total flattening time" ) 34 ] 35 self.hdrRanking = [ ## These are column headers for ranking analysis 36 ## ( "nmMeth", "logfile/test/method name" ), 37 ( "OOpt", "Number of instances where ONLY this method is OPTIMAL" ), 38 ( "OSaC", "Number of instances where ONLY this method is SAT-COMPLETE" ), 39 ( "OFeas", "Number of instances where ONLY this method is FEASIBLE and none is optimal" ), 40 ( "OSat", "Number of instances where ONLY this method is SAT and none is optimal" ), 41 ( "OInfeas","Number of instances where ONLY this method is INFeasible" ), 42 ( "BPri", "Number of instances where this method has a better PRIMAL BOUND" ), 43 ( "BDua", "Number of instances where this method has a better DUAL BOUND" ) 44 ## TODO: ranks here ( "n_UNKNOWN", "Nunkn" ) 45 ] 46 47 hdrTable = OrderedDict( [ ## Possible headers for table printout 48 ( "stt", "The solver status" ), ## TODO an error should be separate flag, not a status 49 ( "chk", "The solution checking status" ), 50 ( "objMZN", "The MZN obj value" ), 51 ( "objSLV", "The solver obj value" ), 52 ( "bnd", "The solver dual bound" ), 53 ( "tAll", "Total running wall time" ), 54 ( "tFlt", "Flattening time" ), 55 ( "tBest", "A best solution's finding time" ), 56 ( "sns", "Model sense (min/max/sat)" ), 57 ( "errH", "Solver errors" ), 58 ( "errL", "Logical errors" ) 59 ] ) 60 61 ## which of those to print for each method 62 hdrTable2P = "stt objMZN objSLV bnd tFlt tBest" 63 hdrTable2P_spl = hdrTable2P.split( " " ) 64 65 mapStatShort = { ## Short status names 66 2: "OPT", 67 1: "FEAS", 68 4: "SATA", 69 3: "SAT", 70 0: "UNKN", 71 -1: "UNSAT", 72 -2: "UNBND", 73 -3: "UNSorUNB", 74 -4: "ERR_H" 75 } 76 77 mapProblemSense = { 78 1: "max", 79 0: "sat", 80 -1: "min", 81 -2: "nosns" 82 } 83 84 ## Add a method's log 85 def addLog( self, sName ): 86 self.lResLogs.append( ( OrderedDict(), [ sName, '' ] ) ) ## a tuple of dict and list of names ([filename, nick]) 87 return self.getLastLog() 88 89 def getLastLog( self ): 90 assert 0<len( self.lResLogs ) 91 return self.lResLogs[-1] 92 93 def getMethodName( self, lNames ): ## produce a sigle string from a list to create a method name 94 return " ".join( lNames ) 95 96 ## Return the union of all instances in all logs 97 def getInstanceUnion( self ): 98 ## return list( functools.reduce(set.union, (set(d[0].keys()) for d in self.lResLogs)) ) 99 r0 = OrderedDict() 100 for d in self.lResLogs: 101 r0.update( OrderedDict.fromkeys( d[0] ) ) 102 return r0.keys() 103 104 ## Return the intersection of all instances in all logs 105 def getInstanceIntersection( self ): 106 assert False 107 return [] ## TODO 108 109 ## This compares all instances specified in the list of pairs (full name, short name) 110 ## If comparing by-instance from outside, follow this pattern 111 def compareAllInstances( self, lInstances ): 112 self.initListComparison() 113 for sInst in lInstances: 114 self.compareInstance( sInst ) 115 self.summarizeCmp() 116 self.summarizeFinalHdr() 117 self.summarize() 118 119 ## Init stats etc. 120 def initListComparison( self ): 121 self.nInstCompared = 0 122 self.nInstWithOptSense = 0 123 ## Using warning strings... self.lInstContradOptSense = [] 124 self.lCmpVecs = [] # List of summary vectors for each method 125 self.mCmpVecVals = {} # Maps to the "quantity" parts of those 126 self.mCmpVecQual = {} # Maps to the "quality" parts 127 print( "" ) ## Newline 128 print( "=============== PER-INSTANCE RESULTS TABLE, HEADERS: ===============" ) 129 for hdrLine in self.hdrTable.items(): 130 print( " ", hdrLine ) 131 print( "====================================================================" ) 132 print( "No.\tinst", end='\t') 133 for mLog, lN in self.lResLogs: ## Select method and its name list 134 lNames = self.getMethodName(lN) 135 av = OrderedDict({ "s_MethodName": lNames }) 136 aq = OrderedDict({ "s_MethodName": lNames }) 137 self.lCmpVecs.append( ( lNames, av, aq ) ) 138 self.mCmpVecVals[ lNames ] = av 139 self.mCmpVecQual[ lNames ] = aq 140 for hdr in self.hdrTable2P_spl: ## Print table headers 141 print( hdr, end='\t' ) 142 143 print( "" ) ## Newline 144 145 self.mInfeas, self.mNoFZN, self.mFail, self.mError = {}, {}, {}, {} 146 self.nContrStatus, self.nContrOptVal, self.nContrBounds = 0, 0, 0 147 self.matrRanking = utils.MatrixByStr( 148 [ ( self.getMethodName( lNames ), "No long name" ) for mLog, lNames in self.lResLogs ], 149 self.hdrRanking ) 150 self.matrRankingMsg = utils.MatrixByStr( 151 [ ( self.getMethodName( lNames ), "No long name" ) for mLog, lNames in self.lResLogs ], 152 self.hdrRanking, [] ) 153 154 self.nNoOptAndAtLeast2Feas = 0 155 156 ############################## Output strings. TODO into a map 157 self.ioContrSense = io.StringIO() 158 self.ioContrStatus = io.StringIO() 159 self.ioContrObjValMZN = io.StringIO() 160 self.ioBadObjValueStatusOpt = io.StringIO() 161 self.ioBadObjValueStatusFeas = io.StringIO() 162 self.ioContrOptVal = io.StringIO() 163 self.ioContrBounds = io.StringIO() 164 self.ioBadChecks = io.StringIO() 165 self.ioErrors = io.StringIO() 166 167 ## Compare methods on the given instance 168 def compareInstance( self, sInst ): 169 self.initInstanceComparison( sInst[0] ) 170 self.tryFindProblemSense( sInst[0] ) 171 self.compileInstanceResults( sInst[0] ) 172 self.checkContradictions( sInst[0] ) 173 self.rankPerformance( sInst[0] ) 174 self.doInstanceSummary( sInst ) 175 176 ## Summarize up to current instance 177 def summarizeCurrent( self, lLogNames ): 178 lNames = self.getMethodName(lLogNames) 179 mCmpVals = self.mCmpVecVals[lNames] 180 for hdr in self.hdrSummary: 181 print( hdr[1], ":", 182 mCmpVals[hdr[0]] if hdr[0] in mCmpVals else 0, 183 sep='', end=' ' ) 184 185 ## Summarize comparisons / ranking 186 def summarizeCmp( self ): 187 print( 188 self.matrRankingMsg.stringifyLists( " METHODS' STAND-OUTS", 189 " METHOD", 190 " PARAMETER" ) + \ 191 "\n------------------ SOLUTION CHECKS FAILURES ------------------\n\n" + \ 192 self.ioBadChecks.getvalue() + \ 193 "\n\n------------------ OBJECTIVE SENSE CONTRADICTIONS ------------------\n\n" + \ 194 self.ioContrSense.getvalue() + \ 195 "\n\n------------------ OBJECTIVE VALUE MZN / SOLVER CONTRADICTIONS ------------------\n\n" + \ 196 self.ioContrObjValMZN.getvalue() + \ 197 "\n\n------------------ OBJECTIVE VALUE BAD, STATUS OPTIMAL ------------------\n\n" + \ 198 self.ioBadObjValueStatusOpt.getvalue() + \ 199 "\n\n------------------ OBJECTIVE VALUE BAD, STATUS FEASIBLE ------------------\n\n" + \ 200 self.ioBadObjValueStatusFeas.getvalue() + \ 201 "\n\n------------------ STATUS CONTRADICTIONS ------------------\n\n" + \ 202 self.ioContrStatus.getvalue() + \ 203 "\n\n------------------ OBJECTIVE VALUE CONTRADICTIONS ------------------\n\n" + \ 204 self.ioContrOptVal.getvalue() + \ 205 "\n\n------------------ DUAL BOUND CONTRADICTIONS ------------------\n\n" + \ 206 self.ioContrBounds.getvalue() + \ 207 "\n\n------------------ ERRORS REPORTED BY SOLVERS ------------------\n\n" + \ 208 self.ioErrors.getvalue() + "\n" + \ 209 "\n\n------------------ RANKING ------------------\n\n" + \ 210 "\n".join( [ " " + str( hl ) for hl in self.hdrRanking ] ) + \ 211 "\n---------------------------------------------\n" + \ 212 self.matrRanking.stringify2D() 213 ) 214 215 ## Summary headers 216 def summarizeFinalHdr( self ): 217 return \ 218 "\n".join( [ " " + str((hdrLine[1], hdrLine[2])) for hdrLine in self.hdrSummary ] ) + \ 219 "\n==================================================" 220 221 ## Summarize 222 def summarize( self ): 223 return \ 224 utils.MyTab().tabulate( 225 [ [ lcv[1][hdr[0]] if hdr[0] in lcv[1] else 0 226 for hdr in self.hdrSummary ] 227 for lcv in self.lCmpVecs ], 228 [ pr[1] for pr in self.hdrSummary ] 229 ) 230 231############################################################################################### 232####################### LEVEL 2 ######################### 233############################################################################################### 234 def initInstanceComparison( self, sInst ): 235 self.lOpt, self.lSatAll, self.lFeas, self.lSat, self.lInfeas = [], [], [], [], [] 236 self.mOptVal, self.lOptVal, self.lPrimBnd, self.lDualBnd = OrderedDict(), [], [], [] 237 self.nInstCompared += 1 238 self.nReported = 0 ## How many methods reported for this instances 239 ## Detailed table line for this instance 240 self.aDetThisInst = { self.getMethodName( ll[1] ) : {} for ll in self.lResLogs } 241 242 def tryFindProblemSense( self, sInst ): 243 self.sSenses = {} 244 self.nOptSenseGiven = -2; 245 for mLog, lNames in self.lResLogs: 246 if sInst in mLog: 247 mSlv = mLog[ sInst ][ "__SOLVE__" ] ## __SOLVE__ always there??? 248 if "Problem_Sense" in mSlv: 249 self.sSenses[ mSlv["Problem_Sense"][0] ] = lNames # mSlv["Problem_Sense"][1] 250 if 1<len( self.sSenses ): 251 print( "WARNING: DIFFERENT OBJ SENSES REPORTED for the instance ", sInst, 252 ": ", self.sSenses, sep='', file=self.ioContrSense ) 253 elif 1==len( self.sSenses ): 254 self.nOptSenseGiven = list(self.sSenses.keys())[0] 255 256 def compileInstanceResults( self, sInst ): 257 for mLog, lN in self.lResLogs: ## Select method and its name list 258 lNames = self.getMethodName(lN) 259 aDetThis = self.aDetThisInst[ lNames ] ## Result table line section 260 if sInst in mLog: 261 self.nReported += 1 262 aResultThisInst = OrderedDict({ "n_Reported": 1 }) 263 aResultThisInst[ "n_CheckFailed" ] = 0 264 mRes = mLog[ sInst ] ## The method's entry for this instance 265 aDetThis[ "chk" ] = "ok" 266 if "SOLUTION_CHECKS_FAILED" in mRes and \ 267 0<mRes["SOLUTION_CHECKS_FAILED"]: 268 aResultThisInst[ "n_CheckFailed" ] = 1 269 aDetThis[ "chk" ] = "BAD" 270 utils.addMapValues( self.mCmpVecVals[lNames], aResultThisInst ) 271 print( "WARNING: SOLUTION CHECK(S) FAILED for the instance ", sInst, 272 ", method '", lNames, "'.", sep='', file = self.ioBadChecks ) 273 continue ## TODO. Param? 274 aResultThisInst[ "n_ErrorsBackend" ] = 0 275 aResultThisInst[ "n_ErrorsLogical" ] = 0 276 aDetThis [ "errH" ] = 0 277 aDetThis [ "errL" ] = 0 278 mSlv = mRes[ "__SOLVE__" ] 279 dObj_MZN = utils.try_float( mSlv.get( "ObjVal_MZN" ) ) 280 aDetThis [ "objMZN" ] = dObj_MZN 281 dObj_SLV = utils.try_float( mSlv.get( "ObjVal_Solver" ) ) 282 aDetThis [ "objSLV" ] = dObj_SLV 283 dBnd_SLV = utils.try_float( mSlv.get( "DualBnd_Solver" ) ) 284 aDetThis [ "bnd" ] = dBnd_SLV 285 dTime_All = utils.try_float( mSlv.get( "TimeReal_All" ) ) 286 aDetThis [ "tAll" ] = dTime_All 287 dTime_Flt = utils.try_float( mSlv.get( "Time_Flt" ) ) 288 aResultThisInst[ "t_Flatten" ] = dTime_Flt if dTime_Flt is not None else dTime_All ##?? Assume flattening stopped 289 aDetThis [ "tFlt" ] = dTime_Flt 290 dTime_Last = utils.try_float( mSlv.get( "TimeReal_LastStatus" ) ) 291 aDetThis [ "tBest" ] = dTime_Last 292 ## Compare obj vals 293 dObj, bObj_MZN = (dObj_MZN, True) if \ 294 None!=dObj_MZN and abs( dObj_MZN ) < 1e45 else (mSlv.get("ObjVal_MZN"), False) 295 ## Assuming solver value is better if different. WHY? Well it' happened both ways 296 dObj, bObj_SLV = (dObj_SLV, True) if \ 297 None!=dObj_SLV and abs( dObj_SLV ) < 1e45 else (dObj, False) 298 if bObj_MZN and bObj_SLV: 299 if abs( dObj_MZN-dObj_SLV ) > 1e-6 * max( abs(dObj_MZN), abs(dObj_SLV) ): 300 aResultThisInst[ "n_ErrorsLogical" ] += 1 301 aDetThis [ "errL" ] += 1 302 print ( " WARNING: DIFFERENT MZN / SOLVER OBJ VALUES for the instance ", sInst, 303 ", method '", lNames, "' : ", 304 dObj_MZN, " / ", dObj_SLV, sep='', file=self.ioContrObjValMZN) 305 ## Retrieve solution status 306 if "Sol_Status" in mSlv: 307 n_SolStatus = mSlv[ "Sol_Status" ][0] 308 else: 309 n_SolStatus = 0 310 ## Retrieve dual bound 311 dBnd = None 312 if None!=dBnd_SLV and abs( dBnd_SLV ) < 1e45: 313 dBnd = dBnd_SLV 314 self.lDualBnd.append( ( dBnd_SLV, lNames ) ) ## Even infeas instances can have dual bound? 315 ## Trying to deduce opt sense if not given: 316 if 1==len(self.sSenses): 317 nSense = next(iter(self.sSenses.keys())) 318 else: 319 nSense = -2 ## ?? 320 aDetThis[ "sns" ] = self.mapProblemSense[ nSense ] 321 self.bOptProblem = True if 0!=nSense else False ## or (None!=dBnd or None!=dObj) 322 ### ... here assumed it's an opt problem by default... why... need to check bounds first?? 323 ## Handle optimality / SAT completed 324 if 2==n_SolStatus: 325 if not self.bOptProblem: 326 self.lSatAll.append( lNames ) 327 aResultThisInst[ "n_SATALL" ] = 1 328 aDetThis[ "stt" ] = self.mapStatShort[ 4 ] 329 else: ## Assume it's an optimization problem????? TODO 330 self.lOpt.append( lNames ) ## Append the optimal method list 331 aResultThisInst[ "n_OPT" ] = 1 332 aDetThis[ "stt" ] = self.mapStatShort[ 2 ] 333 if None==dObj or abs( dObj ) >= 1e45: 334 aResultThisInst[ "n_ErrorsLogical" ] += 1 335 aDetThis [ "errL" ] += 1 336 print ( " WARNING: OPTIMAL STATUS BUT BAD OBJ VALUE, instance ", sInst, 337 ", method '", lNames, "': '", 338 ( "" if None==dObj else str(dObj) ), "', result record: ", # mRes, 339 ",, dObj_MZN: ", dObj_MZN, sep='', file=self.ioBadObjValueStatusOpt ) 340 else: 341 self.mOptVal[ dObj ] = lNames ## Could have used OrderedDict 342 self.lOptVal.append( (dObj, lNames) ) ## To have both a map and the order 343 self.lPrimBnd.append( (dObj, lNames) ) 344 ## Handle feasibility / SAT 345 elif 1==n_SolStatus: 346 if not self.bOptProblem: 347 self.lSat.append( lNames ) 348 aResultThisInst[ "n_SAT" ] = 1 349 aDetThis[ "stt" ] = self.mapStatShort[ 3 ] 350 else: ## Assume it's an optimization problem????? TODO 351 self.lFeas.append( lNames ) ## Append the optimal method list 352 aResultThisInst[ "n_FEAS" ] = 1 353 aDetThis[ "stt" ] = self.mapStatShort[ 1 ] 354 if None==dObj or abs( dObj ) >= 1e45: 355 aResultThisInst[ "n_ErrorsLogical" ] += 1 356 aDetThis [ "errL" ] += 1 357 print ( " WARNING: feasible status but bad obj value, instance ", sInst, 358 ", method '", lNames, "' :'", 359 ( "" if None==dObj else str(dObj) ), "', result record: ", # mRes, 360 sep='', file=self.ioBadObjValueStatusFeas ) 361 else: 362 self.lPrimBnd.append( (dObj, lNames) ) 363 ## Handle infeasibility 364 elif -1>=n_SolStatus and -3<=n_SolStatus: 365 self.lInfeas.append( lNames ) 366 aResultThisInst[ "n_INFEAS" ] = 1 367 aDetThis[ "stt" ] = self.mapStatShort[ n_SolStatus ] 368 self.mInfeas. setdefault( sInst, [] ) 369 self.mInfeas[ sInst ].append( lNames ) 370 ## Handle ERROR? 371 elif -4==n_SolStatus: 372 aResultThisInst[ "n_ErrorsBackend" ] = 1 373 aDetThis [ "errH" ] += 1 374 aDetThis[ "stt" ] = self.mapStatShort[ n_SolStatus ] ## Should not happen TODO 375 self.mError. setdefault( sInst, [] ).append( lNames ) 376 print( "ERROR REPORTED for the instance ", sInst, ", method '", lNames, 377 "', result record: ", ## mRes, 378 sep='', file=self.ioErrors ) 379 else: 380 aResultThisInst[ "n_UNKNOWN" ] = 1 381 aDetThis[ "stt" ] = self.mapStatShort[ 0 ] 382 ## Handle NOFZN 383 if None==dTime_Flt: 384 aResultThisInst[ "n_NOFZN" ] = 1 385 self.mNoFZN. setdefault( sInst, [] ).append( lNames ) 386 ## Handle FAIL??? 387 # LAST: 388 utils.addMapValues( self.mCmpVecVals[lNames], aResultThisInst ) 389 390 391 ### 392 ### Now compare between differen methods: CONTRADICTIONS 393 ### 394 def checkContradictions( self, sInst ): 395 self.fContr = False 396 if len(self.lOpt)+len(self.lFeas)+len(self.lSatAll)+len(self.lSat) > 0 and len(self.lInfeas) > 0: 397 self.nContrStatus += 1 398 self.fContr = True 399 print( "CONTRADICTION of STATUS: instance " + str(sInst) + ": " + \ 400 "\n OPTIMAL: " + strNL( "\n ", self.lOpt) + \ 401 "\n FEAS: " + strNL( "\n ", self.lFeas) + \ 402 "\n SAT_COMPLETE: " + strNL( "\n ", self.lSatAll) + \ 403 "\n SAT: " + strNL( "\n ", self.lSat) + \ 404 "\n INFEAS: " + strNL( "\n ", self.lInfeas), file= self.ioContrStatus ) 405 if len(self.mOptVal) > 1: 406 self.nContrOptVal += 1 407 self.fContr = True 408 print( "CONTRADICTION of OPTIMAL VALUES: " + str(sInst) + \ 409 ": " + strNL( "\n ", self.mOptVal.items()), file=self.ioContrOptVal ) 410 self.nOptSense=0; ## Take as SAT by default 411 if len(self.lPrimBnd)>0 and len(self.lDualBnd)>0 and len(self.lOpt)<self.nReported: 412 lKeysP, lValP = zip(*self.lPrimBnd) 413 lKeysD, lValD = zip(*self.lDualBnd) 414 nPMin, nPMax, nDMin, nDMax = \ 415 min(lKeysP), max(lKeysP), \ 416 min(lKeysD), max(lKeysD) 417 if nPMax <= nDMin + 1e-6 and nPMin < nDMax - 1e-6: 418 self.nOptSense=1 ## maximize 419 elif nPMin >= nDMax - 1e-6 and nPMax > nDMin + 1e-6: 420 self.nOptSense=-1 ## minimize 421 elif nPMax > nDMin + 1e-6 and nPMin < nDMax - 1e-6 or \ 422 nPMin < nDMax - 1e-6 and nPMax > nDMin + 1e-6: 423 self.nContrBounds += 1 424 self.fContr = True 425 print( "CONTRADICTION of BOUNDS: instance " + str(sInst) + \ 426 ":\n PRIMALS: " + strNL( "\n ", self.lPrimBnd) + \ 427 ",\n DUALS: " + strNL( "\n ", self.lDualBnd), 428 file = self.ioContrBounds ) 429 else: 430 self.nOptSense=0 ## SAT 431 if 1==len(self.sSenses) and self.nOptSense!=0: 432 if self.nOptSense!=self.nOptSenseGiven: ## access the 'given' opt sense 433 print( "CONTRADICITON of IMPLIED OBJ SENSE: Instance "+ str(sInst) + \ 434 ": primal bounds " + strNL( "\n ", self.lPrimBnd) + \ 435 " and dual bounds "+ strNL( "\n ", self.lDualBnd) + \ 436 " together imply opt sense " + str(self.nOptSense) + \ 437 ", while result logs say "+ str(self.nOptSenseGiven), file=self.ioContrBounds ) 438 ## else accepting nOptSense as it is 439 440 441 442 ### 443 ### Now compare between differen methods: DIFFERENCES AND RANKING 444 ### 445 def rankPerformance( self, sInst ): 446 ### Accepting the opt sense from result tables, if given 447 if self.nOptSenseGiven!=-2: 448 self.nOptSense = self.nOptSenseGiven 449 ### Compare methods on this instance: 450 if not self.fContr and self.nReported == len(self.lResLogs): 451 if len(self.lOpt) == 1: 452 self.matrRanking[self.lOpt[0], "OOpt"] += 1 453 self.matrRankingMsg[self.lOpt[0], "OOpt"].append( \ 454 str(sInst) + ": the ONLY OPTIMAL") 455 elif len(self.lSatAll) == 1: 456 self.matrRanking[self.lSatAll[0], "OSaC"] += 1 457 self.matrRankingMsg[self.lSatAll[0], "OSaC"].append( \ 458 str(sInst) + ": the ONLY SAT-COMPLETE") 459 elif len(self.lOpt) == 0 and len(self.lFeas) == 1: 460 self.matrRanking[self.lFeas[0], "OFeas"] += 1 461 self.matrRankingMsg[self.lFeas[0], "OFeas"].append( \ 462 str(sInst) + ": the ONLY FEASIBLE") 463 elif len(self.lSatAll) == 0 and len(self.lSat) == 1: 464 self.matrRanking[self.lSat[0], "OSat"] += 1 465 self.matrRankingMsg[self.lSat[0], "OSat"].append( \ 466 str(sInst) + ": the ONLY SAT") 467 elif len(self.lOpt) == 0 and len(self.lFeas) > 1: 468 self.nNoOptAndAtLeast2Feas += 1 469 elif len(self.lInfeas) == 1: 470 self.matrRanking[self.lInfeas[0], "OInfeas"] += 1 471 self.matrRankingMsg[self.lInfeas[0], "OInfeas"].append( \ 472 str(sInst) + ": the ONLY INFeasible") 473 if not self.fContr \ 474 and 0==len(self.lInfeas) and 1<len(self.lFeas) and 0!=self.nOptSense: 475 self.lPrimBnd.sort() 476 if self.nOptSense>0: 477 self.lPrimBnd.reverse() 478 dBnd, dNM = zip(*self.lPrimBnd) 479 dBetter = (dBnd[0]-dBnd[1]) * self.nOptSense 480 if 1e-2 < dBetter: ## Param? TODO 481 self.matrRanking[dNM[0], "BPri"] += 1 482 self.matrRankingMsg[dNM[0], "BPri"].append( str(sInst) \ 483 + ": the best OBJ VALUE by " + str(dBetter) \ 484 + "\n PRIMAL BOUNDS AVAILABLE: " + strNL( "\n ", self.lPrimBnd)) 485 if not self.fContr \ 486 and 0==len(self.lInfeas) and 1<len(self.lDualBnd) and 0!=self.nOptSense: 487 self.lDualBnd.sort() 488 if self.nOptSense<0: 489 self.lDualBnd.reverse() 490 dBnd, dNM = zip(*self.lDualBnd) 491 dBetter = (dBnd[1]-dBnd[0]) * self.nOptSense 492 if 1e-2 < dBetter: ## Param/adaptive? TODO 493 self.matrRanking[dNM[0], "BDua"] += 1 494 self.matrRankingMsg[dNM[0], "BDua"].append( str(sInst) \ 495 + ": the best DUAL BOUND by " + str(dBetter) \ 496 + "\n DUAL BOUNDS AVAILABLE: " + strNL( "\n ", self.lDualBnd)) 497 498 ### 499 ### Now print a table line summarizing the instance for different methods 500 ### 501 def doInstanceSummary( self, sInst ): 502 lIL = [ sInst[1] ] ## The short instance name 503 print( self.nInstCompared, ".\t", sInst[1], sep='', end='\t' ) 504 for mLog, lN in self.lResLogs: ## Select method and its name list 505 lNames = self.getMethodName(lN) 506 aDetThis = self.aDetThisInst[ lNames ] ## Result table line section 507 if sInst[0] in mLog: 508 for hdr in self.hdrTable2P_spl: 509 if hdr not in self.hdrTable: 510 print( '?', end='\t' ) 511 elif aDetThis.get( hdr ) is None: 512 print( "-", end='\t' ) 513 else: 514 print( aDetThis.get(hdr), end='\t' ) 515 else: 516 for hdr in self.hdrTable2P_spl: 517 print( '-' if hdr in self.hdrTable else '?', end='\t' ) 518 print( "" ) ## Newline 519