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