Repository: Pyomo/pyomo

Issue:
Return values feature in parmest is broken
@jialuw96 found this bug in parmest. Here is the MWE:

```
import numpy as np
import pandas as pd
from itertools import product
import pyomo.contrib.parmest.parmest as parmest
from rooney_biegler import rooney_biegler_model

### Parameter estimation

# Vars to estimate
theta_names = ['asymptote', 'rate_constant']

# Data
data = pd.DataFrame(data=[[1,8.3],[2,10.3],[3,19.0],
                          [4,16.0],[5,15.6],[7,19.8]],
                    columns=['hour', 'y'])

# Sum of squared error function
def SSE(model, data):  
    expr = sum((data.y[i] - model.response_function[data.hour[i]])**2 for i in data.index)
    return expr


solver_options = {"max_iter": 6000}  # not really needed in this case

pest = parmest.Estimator(rooney_biegler_model, data, theta_names, SSE, solver_options)
obj, theta = pest.theta_est(return_values=['response_function'])
print(obj)
print(theta)
```

And here is the output/error message:
```
Ipopt 3.12.10: 

******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
 Ipopt is released as open source code under the Eclipse Public License (EPL).
         For more information visit http://projects.coin-or.org/Ipopt
******************************************************************************

This is Ipopt version 3.12.10, running with linear solver mumps.
NOTE: Other linear solvers might be more efficient (see Ipopt documentation).

Number of nonzeros in equality constraint Jacobian...:       24
Number of nonzeros in inequality constraint Jacobian.:        0
Number of nonzeros in Lagrangian Hessian.............:       18

Total number of variables............................:       14
                     variables with only lower bounds:        0
                variables with lower and upper bounds:        0
                     variables with only upper bounds:        0
Total number of equality constraints.................:       12
Total number of inequality constraints...............:        0
        inequality constraints with only lower bounds:        0
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  1.6754267e+01 1.50e+01 7.48e+00  -1.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  4.4025852e+00 1.11e-16 2.09e+00  -1.0 1.91e+01    -  1.00e+00 1.00e+00f  1
   2  4.3387273e+00 0.00e+00 5.38e-01  -1.0 1.95e-01    -  1.00e+00 1.00e+00f  1
   3  4.3317515e+00 0.00e+00 5.35e-02  -1.7 1.28e-01    -  1.00e+00 1.00e+00f  1
   4  4.3317112e+00 0.00e+00 2.78e-04  -2.5 6.94e-03    -  1.00e+00 1.00e+00f  1
   5  4.3317112e+00 0.00e+00 1.27e-08  -5.7 5.68e-05    -  1.00e+00 1.00e+00f  1
   6  4.3317112e+00 0.00e+00 8.88e-15  -9.0 1.88e-09    -  1.00e+00 1.00e+00f  1

Number of Iterations....: 6

                                   (scaled)                 (unscaled)
Objective...............:   4.3317112136568863e+00    4.3317112136568863e+00
Dual infeasibility......:   8.8817841970012523e-15    8.8817841970012523e-15
Constraint violation....:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   0.0000000000000000e+00    0.0000000000000000e+00
Overall NLP error.......:   8.8817841970012523e-15    8.8817841970012523e-15


Number of objective function evaluations             = 7
Number of objective gradient evaluations             = 7
Number of equality constraint evaluations            = 7
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 7
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 6
Total CPU secs in IPOPT (w/o function evaluations)   =      0.024
Total CPU secs in NLP function evaluations           =      0.001

EXIT: Optimal Solution Found.
Traceback (most recent call last):
  File "parmest_return_values_fail.py", line 28, in <module>
    obj, theta = pest.theta_est(return_values=['response_function'])
  File "/Users/adowling/DowlingLab/pyomo/pyomo/contrib/parmest/parmest.py", line 798, in theta_est
    return self._Q_opt(solver=solver, return_values=return_values,
  File "/Users/adowling/DowlingLab/pyomo/pyomo/contrib/parmest/parmest.py", line 557, in _Q_opt
    for exp_i in stsolver.ef_instance.component_objects(Block, descend_into=False):
AttributeError: 'StochSolver' object has no attribute 'ef_instance'
```

I am about to create/link a PR that includes this MWE.


Discussion:
@kaklise @DLWoodruff Any ideas?
Here is the example: https://github.com/adowling2/pyomo/blob/parmest-return-values-fix/pyomo/contrib/parmest/examples/rooney_biegler/parmest_return_values_fail.py
I will take a look.
OK. I have a fix, but it will take me a day to find time to add an example to the documentation and add a test; also, there are more fixes needed than I at first thought.

Resolving patch:
diff --git a/pyomo/contrib/parmest/parmest.py b/pyomo/contrib/parmest/parmest.py
--- a/pyomo/contrib/parmest/parmest.py
+++ b/pyomo/contrib/parmest/parmest.py
@@ -554,11 +554,11 @@ def _Q_opt(self, ThetaVals=None, solver="ef_ipopt",
             
             if len(return_values) > 0:
                 var_values = []
-                for exp_i in stsolver.ef_instance.component_objects(Block, descend_into=False):
+                for exp_i in self.ef_instance.component_objects(Block, descend_into=False):
                     vals = {}
                     for var in return_values:
                         exp_i_var = eval('exp_i.'+ str(var))
-                        temp = [_.value for _ in exp_i_var.itervalues()]
+                        temp = [pyo.value(_) for _ in exp_i_var.itervalues()]
                         if len(temp) == 1:
                             vals[var] = temp[0]
                         else:


Test patch:
diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py
--- a/pyomo/contrib/parmest/tests/test_parmest.py
+++ b/pyomo/contrib/parmest/tests/test_parmest.py
@@ -272,7 +272,7 @@ def test_theta_est_cov(self):
         
 
 @unittest.skipIf(not imports_present, "Cannot test parmest: required dependencies are missing")
-@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available")
+@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available")
 class parmest_object_Tester_reactor_design(unittest.TestCase):
     
     def setUp(self):
@@ -320,6 +320,13 @@ def test_theta_est(self):
         self.assertAlmostEqual(thetavals['k1'], 5.0/6.0, places=4) 
         self.assertAlmostEqual(thetavals['k2'], 5.0/3.0, places=4) 
         self.assertAlmostEqual(thetavals['k3'], 1.0/6000.0, places=7) 
+
+    def test_return_values(self):
+        objval, thetavals, data_rec =\
+            self.pest.theta_est(return_values=['ca', 'cb', 'cc', 'cd', 'caf'])
+        self.assertAlmostEqual(data_rec["cc"].loc[18], 893.84924, places=3)
+
+        
         
 @unittest.skipIf(not parmest.parmest_available,
                  "Cannot test parmest: required dependencies are missing")