Repository: Pyomo/pyomo

Issue:
CBC plugin error when gap returned by solver is "1.#J"
Looks like the CBC solver returns `1.#J` if the gap is infinity/NaN, which isn't handled correctly by the `CBCplugin.py`, as that string cannot be converted to a float:

https://github.com/Pyomo/pyomo/blob/7a018c52591a9534e32f4a5d62b714f89da21536/pyomo/solvers/plugins/solvers/CBCplugin.py#L563

When this happens, we get a `ValueError: could not convert string to float: '1.#J'`

Also mentioned in this question: https://or.stackexchange.com/questions/3652/pyomo-cbc-1-j-error 


Discussion:

Resolving patch:
diff --git a/pyomo/solvers/plugins/solvers/CBCplugin.py b/pyomo/solvers/plugins/solvers/CBCplugin.py
--- a/pyomo/solvers/plugins/solvers/CBCplugin.py
+++ b/pyomo/solvers/plugins/solvers/CBCplugin.py
@@ -448,12 +448,12 @@ def process_logfile(self):
             if n_tokens > 1:
                 # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L3769
                 if n_tokens > 4 and tokens[:4] == ('Continuous', 'objective', 'value', 'is'):
-                    lower_bound = float(tokens[4])
+                    lower_bound = _float(tokens[4])
                 # Search completed - best objective %g, took %d iterations and %d nodes
                 elif n_tokens > 12 and tokens[1:3] == ('Search', 'completed') \
                         and tokens[4:6] == ('best', 'objective') and tokens[9] == 'iterations' \
                         and tokens[12] == 'nodes':
-                    optim_value = float(tokens[6][:-1])
+                    optim_value = _float(tokens[6][:-1])
                     results.solver.statistics.black_box.number_of_iterations = int(tokens[8])
                     nodes = int(tokens[11])
                 elif tokens[1] == 'Exiting' and n_tokens > 4:
@@ -467,7 +467,7 @@ def process_logfile(self):
                     #     # We might want to handle this case
                 # Integer solution of %g found...
                 elif n_tokens >= 4 and tokens[1:4] == ('Integer', 'solution', 'of'):
-                    optim_value = float(tokens[4])
+                    optim_value = _float(tokens[4])
                     try:
                         results.solver.statistics.black_box.number_of_iterations = \
                             int(tokens[tokens.index('iterations') - 1])
@@ -478,15 +478,15 @@ def process_logfile(self):
                 elif n_tokens > 15 and tokens[1:3] == ('Partial', 'search') \
                         and tokens[4:6] == ('best', 'objective') and tokens[7:9] == ('(best', 'possible') \
                         and tokens[12] == 'iterations' and tokens[15] == 'nodes':
-                    optim_value = float(tokens[6])
-                    lower_bound = float(tokens[9][:-2])
+                    optim_value = _float(tokens[6])
+                    lower_bound = _float(tokens[9][:-2])
                     results.solver.statistics.black_box.number_of_iterations = int(tokens[11])
                     nodes = int(tokens[14])
                 elif n_tokens > 12 and tokens[1] == 'After' and tokens[3] == 'nodes,' \
                         and tokens[8:10] == ('best', 'solution,') and tokens[10:12] == ('best', 'possible'):
                     nodes = int(tokens[2])
-                    optim_value = float(tokens[7])
-                    lower_bound = float(tokens[12])
+                    optim_value = _float(tokens[7])
+                    lower_bound = _float(tokens[12])
                 elif tokens[0] == "Current" and n_tokens == 10 and tokens[1] == "default" and tokens[2] == "(if" \
                         and results.problem.name is None:
                     results.problem.name = tokens[-1]
@@ -548,21 +548,21 @@ def process_logfile(self):
                     # perhaps from https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L12318
                     elif n_tokens > 3 and tokens[2] == "Finished":
                         soln.status = SolutionStatus.optimal
-                        optim_value = float(tokens[4])
+                        optim_value = _float(tokens[4])
                 # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7904
                 elif n_tokens >= 3 and tokens[:2] == ('Objective', 'value:'):
                     # parser for log file generetated with discrete variable
-                    optim_value = float(tokens[2])
+                    optim_value = _float(tokens[2])
                 # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7904
                 elif n_tokens >= 4 and tokens[:4] == ('No', 'feasible', 'solution', 'found'):
                     soln.status = SolutionStatus.infeasible
                 elif n_tokens > 2 and tokens[:2] == ('Lower', 'bound:'):
                     if lower_bound is None:  # Only use if not already found since this is to less decimal places
-                        results.problem.lower_bound = float(tokens[2])
+                        results.problem.lower_bound = _float(tokens[2])
                 # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7918
                 elif tokens[0] == 'Gap:':
                     # This is relative and only to 2 decimal places - could calculate explicitly using lower bound
-                    gap = float(tokens[1])
+                    gap = _float(tokens[1])
                 # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7923
                 elif n_tokens > 2 and tokens[:2] == ('Enumerated', 'nodes:'):
                     nodes = int(tokens[2])
@@ -571,34 +571,34 @@ def process_logfile(self):
                     results.solver.statistics.black_box.number_of_iterations = int(tokens[2])
                 # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7930
                 elif n_tokens > 3 and tokens[:3] == ('Time', '(CPU', 'seconds):'):
-                    results.solver.system_time = float(tokens[3])
+                    results.solver.system_time = _float(tokens[3])
                 # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L7933
                 elif n_tokens > 3 and tokens[:3] == ('Time', '(Wallclock', 'Seconds):'):
-                    results.solver.wallclock_time = float(tokens[3])
+                    results.solver.wallclock_time = _float(tokens[3])
                 # https://projects.coin-or.org/Cbc/browser/trunk/Cbc/src/CbcSolver.cpp?rev=2497#L10477
                 elif n_tokens > 4 and tokens[:4] == ('Total', 'time', '(CPU', 'seconds):'):
-                    results.solver.system_time = float(tokens[4])
+                    results.solver.system_time = _float(tokens[4])
                     if n_tokens > 7 and tokens[5:7] == ('(Wallclock', 'seconds):'):
-                        results.solver.wallclock_time = float(tokens[7])
+                        results.solver.wallclock_time = _float(tokens[7])
                 elif tokens[0] == "Optimal":
                     if n_tokens > 4 and tokens[2] == "objective" and tokens[4] != "and":
                         # parser for log file generetated without discrete variable
                         # see pull request #339: last check avoids lines like "Optimal - objective gap and
                         # complementarity gap both smallish and small steps"
                         soln.status = SolutionStatus.optimal
-                        optim_value = float(tokens[4])
+                        optim_value = _float(tokens[4])
                     elif n_tokens > 5 and tokens[1] == 'objective' and tokens[5] == 'iterations':
                         soln.status = SolutionStatus.optimal
-                        optim_value = float(tokens[2])
+                        optim_value = _float(tokens[2])
                         results.solver.statistics.black_box.number_of_iterations = int(tokens[4])
                 elif tokens[0] == "sys" and n_tokens == 2:
-                    results.solver.system_time = float(tokens[1])
+                    results.solver.system_time = _float(tokens[1])
                 elif tokens[0] == "user" and n_tokens == 2:
-                    results.solver.user_time = float(tokens[1])
+                    results.solver.user_time = _float(tokens[1])
                 elif n_tokens == 10 and "Presolve" in tokens and \
                         "iterations" in tokens and tokens[0] == "Optimal" and "objective" == tokens[1]:
                     soln.status = SolutionStatus.optimal
-                    optim_value = float(tokens[2])
+                    optim_value = _float(tokens[2])
                 results.solver.user_time = -1.0  # Why is this set to -1?
 
         if results.problem.name is None:
@@ -724,7 +724,7 @@ def process_soln_file(self, results):
                     results.solver.termination_message = "Model was solved to optimality (subject to tolerances), " \
                                                          "and an optimal solution is available."
                     solution.status = SolutionStatus.optimal
-                    optim_value = float(tokens[-1])
+                    optim_value = _float(tokens[-1])
                 elif tokens[0] in ('Infeasible', 'PrimalInfeasible') or (
                         n_tokens > 1 and tokens[0:2] == ('Integer', 'infeasible')):
                     results.solver.termination_message = "Model was proven to be infeasible."
@@ -743,7 +743,7 @@ def process_soln_file(self, results):
                     INPUT.close()
                     return
                 elif n_tokens > 2 and tokens[0:2] == ('Stopped', 'on'):
-                    optim_value = float(tokens[-1])
+                    optim_value = _float(tokens[-1])
                     solution.gap = None
                     results.solver.status = SolverStatus.aborted
                     solution.status = SolutionStatus.stoppedByLimit
@@ -815,8 +815,8 @@ def process_soln_file(self, results):
                     raise RuntimeError("Unexpected line format encountered in CBC solution file - line="+line)
 
                 constraint = tokens[1]
-                constraint_ax = float(tokens[2]) # CBC reports the constraint row times the solution vector - not the slack.
-                constraint_dual = float(tokens[3])
+                constraint_ax = _float(tokens[2]) # CBC reports the constraint row times the solution vector - not the slack.
+                constraint_dual = _float(tokens[3])
                 if results.problem.sense == ProblemSense.maximize and self.version() < (2, 10, 2):
                     constraint_dual *= -1
                 if constraint[:2] == 'c_':
@@ -846,10 +846,10 @@ def process_soln_file(self, results):
                                        "in CBC solution file - line="+line)
 
                 variable_name = tokens[1]
-                variable_value = float(tokens[2])
+                variable_value = _float(tokens[2])
                 variable = solution.variable[variable_name] = {"Value" : variable_value}
                 if extract_reduced_costs is True:
-                    variable_reduced_cost = float(tokens[3]) # currently ignored.
+                    variable_reduced_cost = _float(tokens[3]) # currently ignored.
                     if results.problem.sense == ProblemSense.maximize and self.version() < (2, 10, 2):
                         variable_reduced_cost *= -1
                     variable["Rc"] = variable_reduced_cost
@@ -887,6 +887,10 @@ def _postsolve(self):
         return results
 
 
+def _float(x):
+    return float(x) if x != '1.#J' else float('inf')
+
+
 @SolverFactory.register('_mock_cbc')
 class MockCBC(CBCSHELL,MockMIP):
     """A Mock CBC solver used for testing


Test patch:
diff --git a/pyomo/solvers/tests/checks/data/test5_timeout_cbc.txt b/pyomo/solvers/tests/checks/data/test5_timeout_cbc.txt
new file mode 100644
--- /dev/null
+++ b/pyomo/solvers/tests/checks/data/test5_timeout_cbc.txt
@@ -0,0 +1,110 @@
+Welcome to the CBC MILP Solver 
+Version: 2.10.5 
+Build Date: Aug 12 2020 
+
+command line - cbc -sec 2 -import test5.mps -solve (default strategy 1)
+seconds was changed from 1e+100 to 2
+At line 15 NAME          BELL5
+At line 16 ROWS
+At line 109 COLUMNS
+At line 308 RHS
+At line 325 BOUNDS
+At line 384 ENDATA
+Problem BELL5 has 91 rows, 104 columns and 266 elements
+Coin0008I BELL5 read with 0 errors
+Continuous objective value is 8.60842e+06 - 0.00 seconds
+Cgl0003I 0 fixed, 0 tightened bounds, 1 strengthened rows, 0 substitutions
+Cgl0003I 0 fixed, 0 tightened bounds, 1 strengthened rows, 0 substitutions
+Cgl0004I processed model has 85 rows, 101 columns (57 integer (29 of which binary)) and 253 elements
+Cbc0038I Initial state - 25 integers unsatisfied sum - 4.65627
+Cbc0038I Pass   1: suminf.    3.23205 (23) obj. 8.67066e+06 iterations 9
+Cbc0038I Pass   2: suminf.    3.53339 (12) obj. 8.98674e+06 iterations 22
+Cbc0038I Solution found of 8.98674e+06
+Cbc0038I Branch and bound needed to clear up 12 general integers
+Cbc0038I Full problem 85 rows 101 columns, reduced to 45 rows 52 columns
+Cbc0038I Cleaned solution of 9.09197e+06
+Cbc0038I Before mini branch and bound, 27 integers at bound fixed and 18 continuous of which 1 were internal integer and 0 internal continuous
+Cbc0038I Full problem 85 rows 101 columns, reduced to 22 rows 27 columns
+Cbc0038I Mini branch and bound improved solution from 9.09197e+06 to 9.05759e+06 (0.10 seconds)
+Cbc0038I Round again with cutoff of 9.01267e+06
+Cbc0038I Pass   3: suminf.    3.23205 (23) obj. 8.67066e+06 iterations 0
+Cbc0038I Pass   4: suminf.    3.70006 (15) obj. 8.97531e+06 iterations 21
+Cbc0038I Solution found of 8.97531e+06
+Cbc0038I Branch and bound needed to clear up 15 general integers
+Cbc0038I Mini branch and bound could not fix general integers
+Cbc0038I No solution found this major pass
+Cbc0038I Before mini branch and bound, 25 integers at bound fixed and 16 continuous of which 1 were internal integer and 0 internal continuous
+Cbc0038I Full problem 85 rows 101 columns, reduced to 30 rows 34 columns
+Cbc0038I Mini branch and bound improved solution from 9.05759e+06 to 9.04677e+06 (0.15 seconds)
+Cbc0038I Round again with cutoff of 8.93182e+06
+Cbc0038I Pass   5: suminf.    3.23205 (23) obj. 8.67066e+06 iterations 0
+Cbc0038I Pass   6: suminf.    3.58661 (14) obj. 8.92637e+06 iterations 23
+Cbc0038I Solution found of 8.92637e+06
+Cbc0038I Branch and bound needed to clear up 14 general integers
+Cbc0038I Mini branch and bound could not fix general integers
+Cbc0038I No solution found this major pass
+Cbc0038I Before mini branch and bound, 26 integers at bound fixed and 15 continuous of which 1 were internal integer and 0 internal continuous
+Cbc0038I Full problem 85 rows 101 columns, reduced to 31 rows 36 columns
+Cbc0038I Mini branch and bound did not improve solution (0.16 seconds)
+Cbc0038I After 0.16 seconds - Feasibility pump exiting with objective of 9.04677e+06 - took 0.16 seconds
+Cbc0012I Integer solution of 9046771.5 found by feasibility pump after 0 iterations and 0 nodes (0.16 seconds)
+Cbc0038I Full problem 85 rows 101 columns, reduced to 62 rows 63 columns
+Cbc0012I Integer solution of 8986007 found by DiveCoefficient after 495 iterations and 0 nodes (0.35 seconds)
+Cbc0031I 14 added rows had average density of 26.571429
+Cbc0013I At root node, 14 cuts changed objective from 8608417.9 to 8690152.6 in 100 passes
+Cbc0014I Cut generator 0 (Probing) - 13 row cuts average 11.3 elements, 3 column cuts (3 active)  in 0.031 seconds - new frequency is -100
+Cbc0014I Cut generator 1 (Gomory) - 436 row cuts average 66.0 elements, 0 column cuts (0 active)  in 0.025 seconds - new frequency is 1
+Cbc0014I Cut generator 2 (Knapsack) - 0 row cuts average 0.0 elements, 0 column cuts (0 active)  in 0.011 seconds - new frequency is -100
+Cbc0014I Cut generator 3 (Clique) - 0 row cuts average 0.0 elements, 0 column cuts (0 active)  in 0.001 seconds - new frequency is -100
+Cbc0014I Cut generator 4 (MixedIntegerRounding2) - 9 row cuts average 6.8 elements, 0 column cuts (0 active)  in 0.012 seconds - new frequency is -100
+Cbc0014I Cut generator 5 (FlowCover) - 0 row cuts average 0.0 elements, 0 column cuts (0 active)  in 0.023 seconds - new frequency is -100
+Cbc0014I Cut generator 6 (TwoMirCuts) - 64 row cuts average 29.1 elements, 0 column cuts (0 active)  in 0.016 seconds - new frequency is 1
+Cbc0010I After 0 nodes, 1 on tree, 8986007 best solution, best possible 8911402.1 (0.36 seconds)
+Cbc0038I Full problem 85 rows 101 columns, reduced to 45 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 47 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 44 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 47 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 45 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 44 rows 50 columns
+Cbc0012I Integer solution of 8980056.8 found by DiveCoefficient after 2998 iterations and 742 nodes (1.06 seconds)
+Cbc0012I Integer solution of 8971675.1 found by DiveCoefficient after 3059 iterations and 742 nodes (1.07 seconds)
+Cbc0016I Integer solution of 8971402.6 found by strong branching after 3184 iterations and 751 nodes (1.18 seconds)
+Cbc0016I Integer solution of 8970555.8 found by strong branching after 3186 iterations and 755 nodes (1.30 seconds)
+Cbc0016I Integer solution of 8968748.2 found by strong branching after 3229 iterations and 767 nodes (1.36 seconds)
+Cbc0016I Integer solution of 8968741 found by strong branching after 3229 iterations and 768 nodes (1.40 seconds)
+Cbc0016I Integer solution of 8968086.2 found by strong branching after 3230 iterations and 771 nodes (1.51 seconds)
+Cbc0038I Full problem 85 rows 101 columns, reduced to 37 rows 42 columns
+Cbc0016I Integer solution of 8967731.6 found by strong branching after 3694 iterations and 911 nodes (1.73 seconds)
+Cbc0016I Integer solution of 8967724.4 found by strong branching after 3699 iterations and 914 nodes (1.76 seconds)
+Cbc0010I After 1000 nodes, 7 on tree, 8967724.4 best solution, best possible 8911402.1 (1.88 seconds)
+Cbc0038I Full problem 85 rows 101 columns, reduced to 37 rows 42 columns
+Cbc0012I Integer solution of 8966940.9 found by DiveCoefficient after 4081 iterations and 1008 nodes (1.91 seconds)
+Cbc0016I Integer solution of 8966818.2 found by strong branching after 4102 iterations and 1022 nodes (1.99 seconds)
+Cbc0016I Integer solution of 8966413.7 found by strong branching after 4103 iterations and 1023 nodes (2.00 seconds)
+Cbc0020I Exiting on maximum time
+Cbc0005I Partial search - best objective 8966413.7 (best possible 8911402.1), took 50364 iterations and 34776 nodes (2.00 seconds)
+Cbc0032I Strong branching done 3384 times (6873 iterations), fathomed 148 nodes and fixed 207 variables
+Cbc0041I Maximum depth 22, 543 variables fixed on reduced cost (complete fathoming 237 times, 33753 nodes taking 46260 iterations)
+Cuts at root node changed objective from 8.60842e+06 to 8.9114e+06
+Probing was tried 100 times and created 16 cuts of which 0 were active after adding rounds of cuts (0.031 seconds)
+Gomory was tried 498 times and created 1186 cuts of which 0 were active after adding rounds of cuts (0.054 seconds)
+Knapsack was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.011 seconds)
+Clique was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.001 seconds)
+MixedIntegerRounding2 was tried 100 times and created 9 cuts of which 0 were active after adding rounds of cuts (0.012 seconds)
+FlowCover was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.023 seconds)
+TwoMirCuts was tried 498 times and created 159 cuts of which 0 were active after adding rounds of cuts (0.045 seconds)
+ZeroHalf was tried 1 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
+ImplicationCuts was tried 504 times and created 37 cuts of which 0 were active after adding rounds of cuts (0.007 seconds)
+
+Result - Stopped on time limit
+
+Objective value:                8966413.70538000
+Lower bound:                    8911402.113
+Gap:                            0.01
+Enumerated nodes:               34776
+Total iterations:               50364
+Time (CPU seconds):             2.01
+Time (Wallclock seconds):       2.34
+
+Total time (CPU seconds):       2.01   (Wallclock seconds):       2.35
+
diff --git a/pyomo/solvers/tests/checks/data/test5_timeout_cbc_gap.txt b/pyomo/solvers/tests/checks/data/test5_timeout_cbc_gap.txt
new file mode 100644
--- /dev/null
+++ b/pyomo/solvers/tests/checks/data/test5_timeout_cbc_gap.txt
@@ -0,0 +1,110 @@
+Welcome to the CBC MILP Solver 
+Version: 2.10.5 
+Build Date: Aug 12 2020 
+
+command line - cbc -sec 2 -import test5.mps -solve (default strategy 1)
+seconds was changed from 1e+100 to 2
+At line 15 NAME          BELL5
+At line 16 ROWS
+At line 109 COLUMNS
+At line 308 RHS
+At line 325 BOUNDS
+At line 384 ENDATA
+Problem BELL5 has 91 rows, 104 columns and 266 elements
+Coin0008I BELL5 read with 0 errors
+Continuous objective value is 8.60842e+06 - 0.00 seconds
+Cgl0003I 0 fixed, 0 tightened bounds, 1 strengthened rows, 0 substitutions
+Cgl0003I 0 fixed, 0 tightened bounds, 1 strengthened rows, 0 substitutions
+Cgl0004I processed model has 85 rows, 101 columns (57 integer (29 of which binary)) and 253 elements
+Cbc0038I Initial state - 25 integers unsatisfied sum - 4.65627
+Cbc0038I Pass   1: suminf.    3.23205 (23) obj. 8.67066e+06 iterations 9
+Cbc0038I Pass   2: suminf.    3.53339 (12) obj. 8.98674e+06 iterations 22
+Cbc0038I Solution found of 8.98674e+06
+Cbc0038I Branch and bound needed to clear up 12 general integers
+Cbc0038I Full problem 85 rows 101 columns, reduced to 45 rows 52 columns
+Cbc0038I Cleaned solution of 9.09197e+06
+Cbc0038I Before mini branch and bound, 27 integers at bound fixed and 18 continuous of which 1 were internal integer and 0 internal continuous
+Cbc0038I Full problem 85 rows 101 columns, reduced to 22 rows 27 columns
+Cbc0038I Mini branch and bound improved solution from 9.09197e+06 to 9.05759e+06 (0.10 seconds)
+Cbc0038I Round again with cutoff of 9.01267e+06
+Cbc0038I Pass   3: suminf.    3.23205 (23) obj. 8.67066e+06 iterations 0
+Cbc0038I Pass   4: suminf.    3.70006 (15) obj. 8.97531e+06 iterations 21
+Cbc0038I Solution found of 8.97531e+06
+Cbc0038I Branch and bound needed to clear up 15 general integers
+Cbc0038I Mini branch and bound could not fix general integers
+Cbc0038I No solution found this major pass
+Cbc0038I Before mini branch and bound, 25 integers at bound fixed and 16 continuous of which 1 were internal integer and 0 internal continuous
+Cbc0038I Full problem 85 rows 101 columns, reduced to 30 rows 34 columns
+Cbc0038I Mini branch and bound improved solution from 9.05759e+06 to 9.04677e+06 (0.15 seconds)
+Cbc0038I Round again with cutoff of 8.93182e+06
+Cbc0038I Pass   5: suminf.    3.23205 (23) obj. 8.67066e+06 iterations 0
+Cbc0038I Pass   6: suminf.    3.58661 (14) obj. 8.92637e+06 iterations 23
+Cbc0038I Solution found of 8.92637e+06
+Cbc0038I Branch and bound needed to clear up 14 general integers
+Cbc0038I Mini branch and bound could not fix general integers
+Cbc0038I No solution found this major pass
+Cbc0038I Before mini branch and bound, 26 integers at bound fixed and 15 continuous of which 1 were internal integer and 0 internal continuous
+Cbc0038I Full problem 85 rows 101 columns, reduced to 31 rows 36 columns
+Cbc0038I Mini branch and bound did not improve solution (0.16 seconds)
+Cbc0038I After 0.16 seconds - Feasibility pump exiting with objective of 9.04677e+06 - took 0.16 seconds
+Cbc0012I Integer solution of 9046771.5 found by feasibility pump after 0 iterations and 0 nodes (0.16 seconds)
+Cbc0038I Full problem 85 rows 101 columns, reduced to 62 rows 63 columns
+Cbc0012I Integer solution of 8986007 found by DiveCoefficient after 495 iterations and 0 nodes (0.35 seconds)
+Cbc0031I 14 added rows had average density of 26.571429
+Cbc0013I At root node, 14 cuts changed objective from 8608417.9 to 8690152.6 in 100 passes
+Cbc0014I Cut generator 0 (Probing) - 13 row cuts average 11.3 elements, 3 column cuts (3 active)  in 0.031 seconds - new frequency is -100
+Cbc0014I Cut generator 1 (Gomory) - 436 row cuts average 66.0 elements, 0 column cuts (0 active)  in 0.025 seconds - new frequency is 1
+Cbc0014I Cut generator 2 (Knapsack) - 0 row cuts average 0.0 elements, 0 column cuts (0 active)  in 0.011 seconds - new frequency is -100
+Cbc0014I Cut generator 3 (Clique) - 0 row cuts average 0.0 elements, 0 column cuts (0 active)  in 0.001 seconds - new frequency is -100
+Cbc0014I Cut generator 4 (MixedIntegerRounding2) - 9 row cuts average 6.8 elements, 0 column cuts (0 active)  in 0.012 seconds - new frequency is -100
+Cbc0014I Cut generator 5 (FlowCover) - 0 row cuts average 0.0 elements, 0 column cuts (0 active)  in 0.023 seconds - new frequency is -100
+Cbc0014I Cut generator 6 (TwoMirCuts) - 64 row cuts average 29.1 elements, 0 column cuts (0 active)  in 0.016 seconds - new frequency is 1
+Cbc0010I After 0 nodes, 1 on tree, 8986007 best solution, best possible 8911402.1 (0.36 seconds)
+Cbc0038I Full problem 85 rows 101 columns, reduced to 45 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 47 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 44 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 47 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 45 rows 50 columns
+Cbc0038I Full problem 85 rows 101 columns, reduced to 44 rows 50 columns
+Cbc0012I Integer solution of 8980056.8 found by DiveCoefficient after 2998 iterations and 742 nodes (1.06 seconds)
+Cbc0012I Integer solution of 8971675.1 found by DiveCoefficient after 3059 iterations and 742 nodes (1.07 seconds)
+Cbc0016I Integer solution of 8971402.6 found by strong branching after 3184 iterations and 751 nodes (1.18 seconds)
+Cbc0016I Integer solution of 8970555.8 found by strong branching after 3186 iterations and 755 nodes (1.30 seconds)
+Cbc0016I Integer solution of 8968748.2 found by strong branching after 3229 iterations and 767 nodes (1.36 seconds)
+Cbc0016I Integer solution of 8968741 found by strong branching after 3229 iterations and 768 nodes (1.40 seconds)
+Cbc0016I Integer solution of 8968086.2 found by strong branching after 3230 iterations and 771 nodes (1.51 seconds)
+Cbc0038I Full problem 85 rows 101 columns, reduced to 37 rows 42 columns
+Cbc0016I Integer solution of 8967731.6 found by strong branching after 3694 iterations and 911 nodes (1.73 seconds)
+Cbc0016I Integer solution of 8967724.4 found by strong branching after 3699 iterations and 914 nodes (1.76 seconds)
+Cbc0010I After 1000 nodes, 7 on tree, 8967724.4 best solution, best possible 8911402.1 (1.88 seconds)
+Cbc0038I Full problem 85 rows 101 columns, reduced to 37 rows 42 columns
+Cbc0012I Integer solution of 8966940.9 found by DiveCoefficient after 4081 iterations and 1008 nodes (1.91 seconds)
+Cbc0016I Integer solution of 8966818.2 found by strong branching after 4102 iterations and 1022 nodes (1.99 seconds)
+Cbc0016I Integer solution of 8966413.7 found by strong branching after 4103 iterations and 1023 nodes (2.00 seconds)
+Cbc0020I Exiting on maximum time
+Cbc0005I Partial search - best objective 8966413.7 (best possible 8911402.1), took 50364 iterations and 34776 nodes (2.00 seconds)
+Cbc0032I Strong branching done 3384 times (6873 iterations), fathomed 148 nodes and fixed 207 variables
+Cbc0041I Maximum depth 22, 543 variables fixed on reduced cost (complete fathoming 237 times, 33753 nodes taking 46260 iterations)
+Cuts at root node changed objective from 8.60842e+06 to 8.9114e+06
+Probing was tried 100 times and created 16 cuts of which 0 were active after adding rounds of cuts (0.031 seconds)
+Gomory was tried 498 times and created 1186 cuts of which 0 were active after adding rounds of cuts (0.054 seconds)
+Knapsack was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.011 seconds)
+Clique was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.001 seconds)
+MixedIntegerRounding2 was tried 100 times and created 9 cuts of which 0 were active after adding rounds of cuts (0.012 seconds)
+FlowCover was tried 100 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.023 seconds)
+TwoMirCuts was tried 498 times and created 159 cuts of which 0 were active after adding rounds of cuts (0.045 seconds)
+ZeroHalf was tried 1 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
+ImplicationCuts was tried 504 times and created 37 cuts of which 0 were active after adding rounds of cuts (0.007 seconds)
+
+Result - Stopped on time limit
+
+Objective value:                8966413.70538000
+Lower bound:                    8911402.113
+Gap:                            1.#J
+Enumerated nodes:               34776
+Total iterations:               50364
+Time (CPU seconds):             2.01
+Time (Wallclock seconds):       2.34
+
+Total time (CPU seconds):       2.01   (Wallclock seconds):       2.35
+
diff --git a/pyomo/solvers/tests/checks/test_CBCplugin.py b/pyomo/solvers/tests/checks/test_CBCplugin.py
--- a/pyomo/solvers/tests/checks/test_CBCplugin.py
+++ b/pyomo/solvers/tests/checks/test_CBCplugin.py
@@ -20,6 +20,7 @@
                            maximize, minimize)
 from pyomo.opt import (SolverFactory, ProblemSense,
                        TerminationCondition, SolverStatus)
+from pyomo.solvers.plugins.solvers.CBCplugin import CBCSHELL
 
 cbc_available = SolverFactory('cbc', solver_io='lp').available(exception_flag=False)
 
@@ -343,6 +344,24 @@ def test_fix_parsing_bug(self):
         self.assertEqual(results.solver.statistics.branch_and_bound.number_of_created_subproblems, 0)
         self.assertEqual(results.solver.statistics.black_box.number_of_iterations, 0)
 
+    def test_process_logfile(self):
+        cbc_shell = CBCSHELL()
+        cbc_shell._log_file = os.path.join(data_dir, 'test5_timeout_cbc.txt')
+        results = cbc_shell.process_logfile()
+        self.assertEqual(results.solution.gap, 0.01)
+        self.assertEqual(results.solver.statistics.black_box.number_of_iterations, 50364)
+        self.assertEqual(results.solver.system_time, 2.01)
+        self.assertEqual(results.solver.statistics.branch_and_bound.number_of_created_subproblems, 34776)
+
+    def test_process_logfile_gap_inf(self):
+        cbc_shell = CBCSHELL()
+        cbc_shell._log_file = os.path.join(data_dir, 'test5_timeout_cbc_gap.txt')
+        results = cbc_shell.process_logfile()
+        self.assertEqual(results.solution.gap, float('inf'))
+        self.assertEqual(results.solver.statistics.black_box.number_of_iterations, 50364)
+        self.assertEqual(results.solver.system_time, 2.01)
+        self.assertEqual(results.solver.statistics.branch_and_bound.number_of_created_subproblems, 34776)
+
 
 if __name__ == "__main__":
     unittest.main()