Repository: Pyomo/pyomo

Issue:
How to access the dual solution of Baron in Pyomo?
I tried a small example to access the dual solution of Baron, but it doesn't work.
The code is the following:

```
>>> from pyomo.environ import *
>>> m = ConcreteModel()
>>> m.x = Var()
>>> m.obj = Objective(expr=m.x ** 2)
>>> m.con = Constraint(expr=m.x >= 2)
>>> m.dual = Suffix(direction=Suffix.IMPORT)
>>> result = SolverFactory('baron').solve(m)
>>> m.dual.pprint()
dual : Direction=Suffix.IMPORT, Datatype=Suffix.FLOAT
    Key : Value

```
The dual is empty.

When I change the solver to GUROBI, the output is as expected.

```
>>> result = SolverFactory('gurobi').solve(m)
>>> m.dual.pprint()
dual : Direction=Suffix.IMPORT, Datatype=Suffix.FLOAT
    Key : Value
    con :   4.0
```


Discussion:
I believe I have seen for some small test cases that Baron will not return a dual solution when the problem is solved within its presolve phase. This might be the case.

> On Jun 29, 2020, at 9:35 PM, Zedong <notifications@github.com> wrote:
> 
> ﻿
> I tried a small example to access the dual solution of Baron, but it doesn't work.
> The code is the following:
> 
> >>> from pyomo.environ import *
> >>> m = ConcreteModel()
> >>> m.x = Var()
> >>> m.obj = Objective(expr=m.x ** 2)
> >>> m.con = Constraint(expr=m.x >= 2)
> >>> m.dual = Suffix(direction=Suffix.IMPORT)
> >>> result = SolverFactory('baron').solve(m)
> >>> m.dual.pprint()
> dual : Direction=Suffix.IMPORT, Datatype=Suffix.FLOAT
>     Key : Value
> 
> The dual is empty.
> 
> When I change the solver to GUROBI, the output is as expected.
> 
> >>> result = SolverFactory('gurobi').solve(m)
> >>> m.dual.pprint()
> dual : Direction=Suffix.IMPORT, Datatype=Suffix.FLOAT
>     Key : Value
>     con :   4.0
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub, or unsubscribe.

> I believe I have seen for some small test cases that Baron will not return a dual solution when the problem is solved within its presolve phase. This might be the case.

Thanks for your advice. Just now I tried several cases, `rsyn0805h, fac1, fac2, fac3, batchs201210m, ball_mk2_10, clay0203h, batch` in [MINLPlib](http://minlplib.org/instances.html). Only the batch example successfully returns the duals. Some of the other examples are not solved within its presolve phase but still didn't return the duals. It's a little uncontrollable and wired.
I checked the manual of Baron. There is one option to control the calculation of duals. However, it cannot guarantee that a dual solution will always be available.

![image](https://user-images.githubusercontent.com/15854830/86076050-66552a80-ba57-11ea-835d-0ecdf39e7fac.png)


Resolving patch:
diff --git a/pyomo/solvers/plugins/solvers/BARON.py b/pyomo/solvers/plugins/solvers/BARON.py
--- a/pyomo/solvers/plugins/solvers/BARON.py
+++ b/pyomo/solvers/plugins/solvers/BARON.py
@@ -251,6 +251,11 @@ def _convert_problem(self,
             else:
                 solver_options[key] = self.options[key]
 
+        for suffix in self._suffixes:
+            if re.match(suffix, 'dual') or re.match(suffix, 'rc'):
+                solver_options['WantDual'] = 1
+                break
+
         if 'solver_options' in kwds:
             raise ValueError("Baron solver options should be set "
                              "using the options object on this "


Test patch:
diff --git a/pyomo/solvers/tests/testcases.py b/pyomo/solvers/tests/testcases.py
--- a/pyomo/solvers/tests/testcases.py
+++ b/pyomo/solvers/tests/testcases.py
@@ -269,76 +269,82 @@
 #
 # BARON
 #
+#
+# The following were necessary before we started adding the 'WantDual'
+# option when a user explicitly defines a 'dual' or 'rc' suffix to
+# "force" Baron to run a local solve (if necessary) and always return
+# dual nformation.
+#
 
-# Known to fail through 18.11.15, but was resolved by 19.12.7
-ExpectedFailures['baron', 'bar', 'MILP_unbounded'] = (
-    lambda v: v <= (18,11,15),
-    ['dual'],
-    "Baron fails to report a MILP model as unbounded")
-
-# Known to work through 18.11.15, and fail in 19.12.7
-MissingSuffixFailures['baron', 'bar', 'LP_piecewise'] = (
-    lambda v: v <= (15,0,0,0) or v > (18,11,15),
-    ['dual'],
-    "Baron will not return dual solution when a solution is "
-    "found during preprocessing.")
-
-# Marking this test suffixes as fragile: Baron 20.4.14 will
-# intermittently return suffixes.
-MissingSuffixFailures['baron', 'bar', 'QP_simple'] = (
-    lambda v: v <= (15,2,0,0) or v > (18,11,15),
-    {'dual': (False, {}), 'rc': (False, {})},
-    "Baron will intermittently return dual solution when "
-    "a solution is found during preprocessing.")
-
-# Known to fail through 17.4.1, but was resolved by 18.5.9
-MissingSuffixFailures['baron', 'bar', 'QCP_simple'] = (
-    lambda v: v <= (17,4,1) or v > (18,11,15),
-    ['dual','rc'],
-    "Baron will not return dual solution when a solution is "
-    "found during preprocessing.")
-
-# Known to work through 18.11.15, and fail in 19.12.7
-MissingSuffixFailures['baron', 'bar', 'LP_block'] = (
-    lambda v: v > (18,11,15),
-    ['dual'],
-    "Baron will not return dual solution when a solution is "
-    "found during preprocessing.")
-
-# Known to work through 18.11.15, and fail in 19.12.7
-MissingSuffixFailures['baron', 'bar', 'LP_inactive_index'] = (
-    lambda v: v > (18,11,15),
-    ['dual'],
-    "Baron will not return dual solution when a solution is "
-    "found during preprocessing.")
-
-# Known to work through 18.11.15, and fail in 19.12.7
-MissingSuffixFailures['baron', 'bar', 'LP_simple'] = (
-    lambda v: v > (18,11,15),
-    ['dual'],
-    "Baron will not return dual solution when a solution is "
-    "found during preprocessing.")
-
-# Known to work through 18.11.15, and fail in 19.12.7
-MissingSuffixFailures['baron', 'bar', 'LP_trivial_constraints'] = (
-    lambda v: v > (18,11,15),
-    ['dual'],
-    "Baron will not return dual solution when a solution is "
-    "found during preprocessing.")
-
-# Known to work through 19.12.7, and fail in 20.4.14
-MissingSuffixFailures['baron', 'bar', 'LP_duals_minimize'] = (
-    lambda v: v > (19,12,7),
-    ['dual','rc'],
-    "Baron will not return dual solution when a solution is "
-    "found during preprocessing.")
-
-# Known to work through 19.12.7, and fail in 20.4.14
-MissingSuffixFailures['baron', 'bar', 'LP_duals_maximize'] = (
-    lambda v: v > (19,12,7),
-    ['dual','rc'],
-    "Baron will not return dual solution when a solution is "
-    "found during preprocessing.")
+# # Known to fail through 18.11.15, but was resolved by 19.12.7
+# ExpectedFailures['baron', 'bar', 'MILP_unbounded'] = (
+#     lambda v: v <= (18,11,15),
+#     ['dual'],
+#     "Baron fails to report a MILP model as unbounded")
+
+# # Known to work through 18.11.15, and fail in 19.12.7
+# MissingSuffixFailures['baron', 'bar', 'LP_piecewise'] = (
+#     lambda v: v <= (15,0,0,0) or v > (18,11,15),
+#     ['dual'],
+#     "Baron will not return dual solution when a solution is "
+#     "found during preprocessing.")
+
+# # Marking this test suffixes as fragile: Baron 20.4.14 will
+# # intermittently return suffixes.
+# MissingSuffixFailures['baron', 'bar', 'QP_simple'] = (
+#     lambda v: v <= (15,2,0,0) or v > (18,11,15),
+#     {'dual': (False, {}), 'rc': (False, {})},
+#     "Baron will intermittently return dual solution when "
+#     "a solution is found during preprocessing.")
+
+# # Known to fail through 17.4.1, but was resolved by 18.5.9
+# MissingSuffixFailures['baron', 'bar', 'QCP_simple'] = (
+#     lambda v: v <= (17,4,1) or v > (18,11,15),
+#     ['dual','rc'],
+#     "Baron will not return dual solution when a solution is "
+#     "found during preprocessing.")
+
+# # Known to work through 18.11.15, and fail in 19.12.7
+# MissingSuffixFailures['baron', 'bar', 'LP_block'] = (
+#     lambda v: v > (18,11,15),
+#     ['dual'],
+#     "Baron will not return dual solution when a solution is "
+#     "found during preprocessing.")
+
+# # Known to work through 18.11.15, and fail in 19.12.7
+# MissingSuffixFailures['baron', 'bar', 'LP_inactive_index'] = (
+#     lambda v: v > (18,11,15),
+#     ['dual'],
+#     "Baron will not return dual solution when a solution is "
+#     "found during preprocessing.")
+
+# # Known to work through 18.11.15, and fail in 19.12.7
+# MissingSuffixFailures['baron', 'bar', 'LP_simple'] = (
+#     lambda v: v > (18,11,15),
+#     ['dual'],
+#     "Baron will not return dual solution when a solution is "
+#     "found during preprocessing.")
+
+# # Known to work through 18.11.15, and fail in 19.12.7
+# MissingSuffixFailures['baron', 'bar', 'LP_trivial_constraints'] = (
+#     lambda v: v > (18,11,15),
+#     ['dual'],
+#     "Baron will not return dual solution when a solution is "
+#     "found during preprocessing.")
+
+# # Known to work through 19.12.7, and fail in 20.4.14
+# MissingSuffixFailures['baron', 'bar', 'LP_duals_minimize'] = (
+#     lambda v: v > (19,12,7),
+#     ['dual','rc'],
+#     "Baron will not return dual solution when a solution is "
+#     "found during preprocessing.")
+
+# # Known to work through 19.12.7, and fail in 20.4.14
+# MissingSuffixFailures['baron', 'bar', 'LP_duals_maximize'] = (
+#     lambda v: v > (19,12,7),
+#     ['dual','rc'],
+#     "Baron will not return dual solution when a solution is "
+#     "found during preprocessing.")