this repo has no description
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