Repository: Pyomo/pyomo

Issue:
GDPopt doesn't remove the dummy objective it adds when the model doesn't have any objectives
## Summary

GDPopt adds a dummy objective (and issues a warning) if the model it's solving doesn't have an active objective. It then fails to remove it, so if you add one and then solve again, you trigger an error about there being multiple active objectives. 

### Steps to reproduce the issue

```
from pyomo.environ import *
from pyomo.gdp import *

m = ConcreteModel()
m.x = Var(bounds=(0,10))
m.disjunction = Disjunction(expr=[[m.x == 0], [m.x >= 5, m.x <= 7]])

ric = SolverFactory('gdpopt.ric')

ric.solve(m)

m.obj = Objective(expr=m.x)

ric.solve(m)
```

### Error Message

```
WARNING: Model has no active objectives. Adding dummy objective.
WARNING: Constant objective detected, replacing with a placeholder to prevent
    solver failure.
WARNING: Constant objective detected, replacing with a placeholder to prevent
    solver failure.
Traceback (most recent call last):
  File "/home/esjohn/src/pyomo/pyomo/contrib/gdpopt/algorithm_base_class.py", line 114, in solve
    results = self._gather_problem_info_and_solve_non_gdps(
  File "/home/esjohn/src/pyomo/pyomo/contrib/gdpopt/algorithm_base_class.py", line 185, in _gather_problem_info_and_solve_non_gdps
    self._create_pyomo_results_object_with_problem_info(model, config)
  File "/home/esjohn/src/pyomo/pyomo/contrib/gdpopt/algorithm_base_class.py", line 420, in _create_pyomo_results_object_with_problem_info
    raise ValueError('Model has multiple active objectives.')
ValueError: Model has multiple active objectives.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "argh.py", line 14, in <module>
    ric.solve(m)
  File "/home/esjohn/src/pyomo/pyomo/contrib/gdpopt/algorithm_base_class.py", line 132, in solve
    self._delete_original_model_util_block()
  File "/home/esjohn/src/pyomo/pyomo/contrib/gdpopt/algorithm_base_class.py", line 454, in _delete_original_model_util_block
    blk.parent_block().del_component(blk)
AttributeError: 'NoneType' object has no attribute 'del_component'
```

### Information on your system

Pyomo version: main branch
Python version:
Operating system:
How Pyomo was installed (PyPI, conda, source):
Solver (if applicable):



Discussion:

Resolving patch:
diff --git a/pyomo/contrib/gdpopt/algorithm_base_class.py b/pyomo/contrib/gdpopt/algorithm_base_class.py
--- a/pyomo/contrib/gdpopt/algorithm_base_class.py
+++ b/pyomo/contrib/gdpopt/algorithm_base_class.py
@@ -56,6 +56,7 @@ def __init__(self, **kwds):
         self.incumbent_continuous_soln = None
 
         self.original_obj = None
+        self._dummy_obj = None
         self.original_util_block = None
 
         self.log_formatter = ('{:>9}   {:>15}   {:>11.5f}   {:>11.5f}   '
@@ -412,7 +413,7 @@ def _create_pyomo_results_object_with_problem_info(self, original_model,
         if number_of_objectives == 0:
             config.logger.warning(
                 'Model has no active objectives. Adding dummy objective.')
-            discrete_obj = Objective(expr=1)
+            self._dummy_obj = discrete_obj = Objective(expr=1)
             original_model.add_component(unique_component_name(original_model,
                                                                'dummy_obj'),
                                          discrete_obj)
@@ -456,6 +457,9 @@ def _delete_original_model_util_block(self):
         # prior one.
         if self.original_obj is not None:
             self.original_obj.activate()
+        if self._dummy_obj is not None:
+            self._dummy_obj.parent_block().del_component(self._dummy_obj)
+            self._dummy_obj = None
 
     def _get_final_pyomo_results_object(self):
         """


Test patch:
diff --git a/pyomo/contrib/gdpopt/tests/test_gdpopt.py b/pyomo/contrib/gdpopt/tests/test_gdpopt.py
--- a/pyomo/contrib/gdpopt/tests/test_gdpopt.py
+++ b/pyomo/contrib/gdpopt/tests/test_gdpopt.py
@@ -156,6 +156,11 @@ def test_no_objective(self):
             self.assertIn("Model has no active objectives. Adding dummy "
                           "objective.", output.getvalue().strip())
 
+        # check that the dummy objective is removed after the solve (else
+        # repeated solves result in the error about multiple active objectives
+        # on the model)
+        self.assertIsNone(m.component("dummy_obj"))
+
     def test_multiple_objectives(self):
         m = ConcreteModel()
         m.x = Var()