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 ( "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