Repository: Pyomo/pyomo

Issue:
Error with Cplex 20.1.0.0
Cplex just released its 20.1.0.0 version.

When I use Cplex 20.1.0.0 to solve a small MILP example, the following results showed up.

Example code:

```
from pyomo.solvers.tests.models.MILP_simple import MILP_simple
a = MILP_simple()
a.generate_model()
model = a.model
model.pprint()
opt = SolverFactory('cplex')
results = opt.solve(model,tee=True) #,warmstart=True)
print(results)
```


Output:

```
Welcome to IBM(R) ILOG(R) CPLEX(R) Interactive Optimizer 20.1.0.0
  with Simplex, Mixed Integer & Barrier Optimizers
5725-A06 5725-A29 5724-Y48 5724-Y49 5724-Y54 5724-Y55 5655-Y21
Copyright IBM Corp. 1988, 2020.  All Rights Reserved.

Type 'help' for a list of available commands.
Type 'help' followed by a command name for more
information on commands.

CPLEX> Logfile 'cplex.log' closed.
Logfile '/var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmp3ca49s13.cplex.log' open.
CPLEX> Problem '/var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmpsefek9qr.pyomo.lp' read.
Read time = 0.00 sec. (0.00 ticks)
CPLEX> Problem name         : /var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmpsefek9qr.pyomo.lp
Objective sense      : Minimize
Variables            :       3  [Nneg: 2,  Binary: 1]
Objective nonzeros   :       2
Linear constraints   :       4  [Less: 1,  Greater: 2,  Equal: 1]
  Nonzeros           :       6
  RHS nonzeros       :       4

Variables            : Min LB: 0.000000         Max UB: 1.000000       
Objective nonzeros   : Min   : 1.000000         Max   : 3.000000       
Linear constraints   :
  Nonzeros           : Min   : 1.000000         Max   : 1.000000       
  RHS nonzeros       : Min   : 1.000000         Max   : 10.00000       
CPLEX> Version identifier: 20.1.0.0 | 2020-11-10 | 9bedb6d68
Found incumbent of value 13.000000 after 0.00 sec. (0.00 ticks)
Tried aggregator 1 time.
MIP Presolve eliminated 4 rows and 3 columns.
All rows and columns eliminated.
Presolve time = 0.00 sec. (0.00 ticks)

Root node processing (before b&c):
  Real time             =    0.00 sec. (0.00 ticks)
Parallel b&c, 12 threads:
  Real time             =    0.00 sec. (0.00 ticks)
  Sync time (average)   =    0.00 sec.
  Wait time (average)   =    0.00 sec.
                          ------------
Total (root+branch&cut) =    0.00 sec. (0.00 ticks)

Solution pool: 2 solutions saved.

MIP - Integer optimal solution:  Objective =  6.0000000000e+00
Solution time =    0.00 sec.  Iterations = 0  Nodes = 0 (1)
Deterministic time = 0.00 ticks  (1.79 ticks/sec)

CPLEX> Incumbent solution written to file '/var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmp8xwq6lvo.cplex.sol'.
CPLEX> Traceback (most recent call last):
  File "/Users/zedongpeng/Github/pyomo-MINLP-benchmarking/test.py", line 115, in <module>
    results = opt.solve(model,tee=True) #,warmstart=True)
  File "/Users/zedongpeng/Github/pyomo/pyomo/opt/base/solvers.py", line 603, in solve
    result = self._postsolve()
  File "/Users/zedongpeng/Github/pyomo/pyomo/solvers/plugins/solvers/CPLEX.py", line 894, in _postsolve
    results = ILMLicensedSystemCallSolver._postsolve(self)
  File "/Users/zedongpeng/Github/pyomo/pyomo/opt/solver/shellcmd.py", line 269, in _postsolve
    results = self.process_output(self._rc)
  File "/Users/zedongpeng/Github/pyomo/pyomo/opt/solver/shellcmd.py", line 331, in process_output
    self.process_soln_file(results)
  File "/Users/zedongpeng/Github/pyomo/pyomo/solvers/plugins/solvers/CPLEX.py", line 780, in process_soln_file
    objective_value = (tokens[0].split('=')[1].strip()).lstrip("\"").rstrip("\"")
IndexError: list index out of range
```
I think the I/O has changed in Cplex 20.1.0.0. Maybe some modifications need to be added to adapt to Cplex 20.1.0.0 .


Error with Cplex 20.1.0.0
Cplex just released its 20.1.0.0 version.

When I use Cplex 20.1.0.0 to solve a small MILP example, the following results showed up.

Example code:

```
from pyomo.solvers.tests.models.MILP_simple import MILP_simple
a = MILP_simple()
a.generate_model()
model = a.model
model.pprint()
opt = SolverFactory('cplex')
results = opt.solve(model,tee=True) #,warmstart=True)
print(results)
```


Output:

```
Welcome to IBM(R) ILOG(R) CPLEX(R) Interactive Optimizer 20.1.0.0
  with Simplex, Mixed Integer & Barrier Optimizers
5725-A06 5725-A29 5724-Y48 5724-Y49 5724-Y54 5724-Y55 5655-Y21
Copyright IBM Corp. 1988, 2020.  All Rights Reserved.

Type 'help' for a list of available commands.
Type 'help' followed by a command name for more
information on commands.

CPLEX> Logfile 'cplex.log' closed.
Logfile '/var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmp3ca49s13.cplex.log' open.
CPLEX> Problem '/var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmpsefek9qr.pyomo.lp' read.
Read time = 0.00 sec. (0.00 ticks)
CPLEX> Problem name         : /var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmpsefek9qr.pyomo.lp
Objective sense      : Minimize
Variables            :       3  [Nneg: 2,  Binary: 1]
Objective nonzeros   :       2
Linear constraints   :       4  [Less: 1,  Greater: 2,  Equal: 1]
  Nonzeros           :       6
  RHS nonzeros       :       4

Variables            : Min LB: 0.000000         Max UB: 1.000000       
Objective nonzeros   : Min   : 1.000000         Max   : 3.000000       
Linear constraints   :
  Nonzeros           : Min   : 1.000000         Max   : 1.000000       
  RHS nonzeros       : Min   : 1.000000         Max   : 10.00000       
CPLEX> Version identifier: 20.1.0.0 | 2020-11-10 | 9bedb6d68
Found incumbent of value 13.000000 after 0.00 sec. (0.00 ticks)
Tried aggregator 1 time.
MIP Presolve eliminated 4 rows and 3 columns.
All rows and columns eliminated.
Presolve time = 0.00 sec. (0.00 ticks)

Root node processing (before b&c):
  Real time             =    0.00 sec. (0.00 ticks)
Parallel b&c, 12 threads:
  Real time             =    0.00 sec. (0.00 ticks)
  Sync time (average)   =    0.00 sec.
  Wait time (average)   =    0.00 sec.
                          ------------
Total (root+branch&cut) =    0.00 sec. (0.00 ticks)

Solution pool: 2 solutions saved.

MIP - Integer optimal solution:  Objective =  6.0000000000e+00
Solution time =    0.00 sec.  Iterations = 0  Nodes = 0 (1)
Deterministic time = 0.00 ticks  (1.79 ticks/sec)

CPLEX> Incumbent solution written to file '/var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmp8xwq6lvo.cplex.sol'.
CPLEX> Traceback (most recent call last):
  File "/Users/zedongpeng/Github/pyomo-MINLP-benchmarking/test.py", line 115, in <module>
    results = opt.solve(model,tee=True) #,warmstart=True)
  File "/Users/zedongpeng/Github/pyomo/pyomo/opt/base/solvers.py", line 603, in solve
    result = self._postsolve()
  File "/Users/zedongpeng/Github/pyomo/pyomo/solvers/plugins/solvers/CPLEX.py", line 894, in _postsolve
    results = ILMLicensedSystemCallSolver._postsolve(self)
  File "/Users/zedongpeng/Github/pyomo/pyomo/opt/solver/shellcmd.py", line 269, in _postsolve
    results = self.process_output(self._rc)
  File "/Users/zedongpeng/Github/pyomo/pyomo/opt/solver/shellcmd.py", line 331, in process_output
    self.process_soln_file(results)
  File "/Users/zedongpeng/Github/pyomo/pyomo/solvers/plugins/solvers/CPLEX.py", line 780, in process_soln_file
    objective_value = (tokens[0].split('=')[1].strip()).lstrip("\"").rstrip("\"")
IndexError: list index out of range
```
I think the I/O has changed in Cplex 20.1.0.0. Maybe some modifications need to be added to adapt to Cplex 20.1.0.0 .




Discussion:
@ZedongPeng can you attach the `.sol` file generated by CPLEX to this ticket?  It looks like we need to update the the .sol parser.
Here is the `.sol` file.

```
<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<CPLEXSolution version="1.2">
 <header
   problemName="/var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmpvke0cu0k.pyomo.lp"
   solutionName="incumbent"
   solutionIndex="-1"
   objectiveValue="6"
   solutionTypeValue="3"
   solutionTypeString="primal"
   solutionStatusValue="101"
   solutionStatusString="integer optimal solution"
   solutionMethodString="mip"
   primalFeasible="1"
   dualFeasible="1"
   MIPNodes="0"
   MIPIterations="0"
   writeLevel="1"/>
 <quality
   epInt="1.0000000000000001e-05"
   epRHS="9.9999999999999995e-07"
   maxIntInfeas="0"
   maxPrimalInfeas="0"
   maxX="3"
   maxSlack="8"/>
 <linearConstraints>
  <constraint name="c_l_x4_" index="0" slack="0"/>
  <constraint name="r_l_x5_" index="1" slack="0"/>
  <constraint name="r_u_x5_" index="2" slack="8"/>
  <constraint name="c_e_ONE_VAR_CONSTANT" index="3" slack="0"/>
 </linearConstraints>
 <variables>
  <variable name="x1" index="0" value="3"/>
  <variable name="x2" index="1" value="1"/>
  <variable name="ONE_VAR_CONSTANT" index="2" value="1"/>
 </variables>
 <objectiveValues>
  <objective index="0" name="x3" value="6"/>
 </objectiveValues>
</CPLEXSolution>
```
@ZedongPeng can you attach the `.sol` file generated by CPLEX to this ticket?  It looks like we need to update the the .sol parser.
Here is the `.sol` file.

```
<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<CPLEXSolution version="1.2">
 <header
   problemName="/var/folders/pj/1113b24j7b3083zbt19pjxqm0000gn/T/tmpvke0cu0k.pyomo.lp"
   solutionName="incumbent"
   solutionIndex="-1"
   objectiveValue="6"
   solutionTypeValue="3"
   solutionTypeString="primal"
   solutionStatusValue="101"
   solutionStatusString="integer optimal solution"
   solutionMethodString="mip"
   primalFeasible="1"
   dualFeasible="1"
   MIPNodes="0"
   MIPIterations="0"
   writeLevel="1"/>
 <quality
   epInt="1.0000000000000001e-05"
   epRHS="9.9999999999999995e-07"
   maxIntInfeas="0"
   maxPrimalInfeas="0"
   maxX="3"
   maxSlack="8"/>
 <linearConstraints>
  <constraint name="c_l_x4_" index="0" slack="0"/>
  <constraint name="r_l_x5_" index="1" slack="0"/>
  <constraint name="r_u_x5_" index="2" slack="8"/>
  <constraint name="c_e_ONE_VAR_CONSTANT" index="3" slack="0"/>
 </linearConstraints>
 <variables>
  <variable name="x1" index="0" value="3"/>
  <variable name="x2" index="1" value="1"/>
  <variable name="ONE_VAR_CONSTANT" index="2" value="1"/>
 </variables>
 <objectiveValues>
  <objective index="0" name="x3" value="6"/>
 </objectiveValues>
</CPLEXSolution>
```

Resolving patch:
diff --git a/pyomo/core/kernel/block.py b/pyomo/core/kernel/block.py
--- a/pyomo/core/kernel/block.py
+++ b/pyomo/core/kernel/block.py
@@ -458,7 +458,9 @@ def load_solution(self,
                 raise KeyError("Objective associated with symbol '%s' "
                                 "is not found on this block"
                                 % (label))
-            unseen_var_ids.remove(id(obj))
+            # Because of __default_objective__, an objective might
+            # appear twice in the objective dictionary.
+            unseen_var_ids.discard(id(obj))
             for _attr_key, attr_value in six.iteritems(entry):
                 attr_key = _attr_key[0].lower() + _attr_key[1:]
                 if attr_key in valid_import_suffixes:
diff --git a/pyomo/solvers/plugins/solvers/CPLEX.py b/pyomo/solvers/plugins/solvers/CPLEX.py
--- a/pyomo/solvers/plugins/solvers/CPLEX.py
+++ b/pyomo/solvers/plugins/solvers/CPLEX.py
@@ -776,9 +776,21 @@ def process_soln_file(self,results):
                     break
                 tINPUT.close()
 
-            elif tokens[0].startswith("objectiveValue"):
+            elif tokens[0].startswith("objectiveValue") and tokens[0] != 'objectiveValues':
+                # prior to 12.10.0, the objective value came back as an
+                # attribute on the <header> tag
                 objective_value = (tokens[0].split('=')[1].strip()).lstrip("\"").rstrip("\"")
                 soln.objective['__default_objective__']['Value'] = float(objective_value)
+
+            elif tokens[0] == "objective":
+                # beginning in 12.10.0, CPLEX supports multiple
+                # objectives in an <objectiveValue> tag
+                fields = {}
+                for field in tokens[1:]:
+                    k,v = field.split('=')
+                    fields[k] = v.strip('"')
+                soln.objective.setdefault(fields['name'], {})['Value'] = float(fields['value'])
+
             elif tokens[0].startswith("solutionStatusValue"):
                pieces = tokens[0].split("=")
                solution_status = eval(pieces[1])


Test patch: