Repository: Pyomo/pyomo

Issue:
Avoid PyROS master feasibility problem unit consistency checks
## Summary/Motivation:
Avoid unit inconsistency checks invoked while initializing PyROS master feasibility problems. We leave unit consistency checks to the owner of the deterministic model.

## Changes proposed in this PR:
- avoid master feasibility problem unit consistency checks

### Legal Acknowledgement

By contributing to this software project, I have read the [contribution guide](https://pyomo.readthedocs.io/en/stable/contribution_guide.html) and agree to the following terms and conditions for my contribution:

1. I agree my contributions are submitted under the BSD license.
2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.



Discussion:
@shermanjasonaf just FYI the failing NEOS tests are unrelated to your changes and should be resolved soon
# [Codecov](https://codecov.io/gh/Pyomo/pyomo/pull/2459?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo) Report
> Merging [#2459](https://codecov.io/gh/Pyomo/pyomo/pull/2459?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo) (7306831) into [main](https://codecov.io/gh/Pyomo/pyomo/commit/80465f9588b1795830988cfa84898cae68c4bf7a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo) (80465f9) will **decrease** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##             main    #2459      +/-   ##
==========================================
- Coverage   86.24%   86.24%   -0.01%     
==========================================
  Files         709      709              
  Lines       78287    78287              
==========================================
- Hits        67520    67516       -4     
- Misses      10767    10771       +4     
```

| Flag | Coverage Δ | |
|---|---|---|
| linux | `83.10% <33.33%> (-0.01%)` | :arrow_down: |
| osx | `73.45% <33.33%> (ø)` | |
| other | `83.29% <33.33%> (ø)` | |
| win | `80.19% <33.33%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/Pyomo/pyomo/pull/2459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo) | Coverage Δ | |
|---|---|---|
| [pyomo/contrib/pyros/master\_problem\_methods.py](https://codecov.io/gh/Pyomo/pyomo/pull/2459/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo#diff-cHlvbW8vY29udHJpYi9weXJvcy9tYXN0ZXJfcHJvYmxlbV9tZXRob2RzLnB5) | `83.50% <100.00%> (ø)` | |
| [pyomo/contrib/pyros/pyros.py](https://codecov.io/gh/Pyomo/pyomo/pull/2459/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo#diff-cHlvbW8vY29udHJpYi9weXJvcy9weXJvcy5weQ==) | `93.71% <100.00%> (ø)` | |
| [pyomo/contrib/mindtpy/iterate.py](https://codecov.io/gh/Pyomo/pyomo/pull/2459/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo#diff-cHlvbW8vY29udHJpYi9taW5kdHB5L2l0ZXJhdGUucHk=) | `81.73% <0.00%> (-1.83%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/Pyomo/pyomo/pull/2459?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/Pyomo/pyomo/pull/2459?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo). Last update [80465f9...7306831](https://codecov.io/gh/Pyomo/pyomo/pull/2459?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pyomo).


Resolving patch:
diff --git a/pyomo/contrib/pyros/pyros.py b/pyomo/contrib/pyros/pyros.py
--- a/pyomo/contrib/pyros/pyros.py
+++ b/pyomo/contrib/pyros/pyros.py
@@ -45,7 +45,7 @@
 from pyomo.contrib.pyros.uncertainty_sets import uncertainty_sets
 from pyomo.core.base import Constraint
 
-__version__ =  "1.1.3"
+__version__ =  "1.1.4"
 
 def NonNegIntOrMinusOne(obj):
     '''
diff --git a/pyomo/contrib/pyros/separation_problem_methods.py b/pyomo/contrib/pyros/separation_problem_methods.py
--- a/pyomo/contrib/pyros/separation_problem_methods.py
+++ b/pyomo/contrib/pyros/separation_problem_methods.py
@@ -199,7 +199,8 @@ def get_index_of_max_violation(model_data, config, solve_data_list):
         indices_of_violating_realizations.extend(i for i,x in enumerate(solve_data_list) if x[idx_j].found_violation==True)
 
     if matrix_dim == 0:
-        return 0, 0 # Just a dummy index...
+        # no violating realizations
+        return None, None
 
     matrix_of_violations = np.zeros(shape=(matrix_dim, len(model_data.separation_model.util.performance_constraints)))
     violation_dict = {}
@@ -332,12 +333,19 @@ def solve_separation_problem(model_data, config):
                 separation_obj.deactivate()
 
         # Do we return?
-        # If their are multiple violations in this bucket, pick the worst-case
-        idx_i, idx_j = get_index_of_max_violation(model_data=model_data, config=config,
-                                                              solve_data_list=solve_data_list)
+        # If there are multiple violations in this bucket, pick the worst-case
+        idx_i, idx_j = get_index_of_max_violation(
+            model_data=model_data,
+            config=config,
+            solve_data_list=solve_data_list,
+        )
 
-        violating_realizations = [v for v in solve_data_list[idx_i][idx_j].violating_param_realization]
-        violations = solve_data_list[idx_i][idx_j].list_of_scaled_violations
+        if (idx_i, idx_j) != (None, None):
+            violating_realizations = [v for v in solve_data_list[idx_i][idx_j].violating_param_realization]
+            violations = solve_data_list[idx_i][idx_j].list_of_scaled_violations
+        else:
+            violating_realizations = []
+            violations = []
 
         if any(s.found_violation for solve_list in solve_data_list for s in solve_list):
             #config.progress_logger.info(


Test patch:
diff --git a/pyomo/contrib/pyros/tests/test_grcs.py b/pyomo/contrib/pyros/tests/test_grcs.py
--- a/pyomo/contrib/pyros/tests/test_grcs.py
+++ b/pyomo/contrib/pyros/tests/test_grcs.py
@@ -1394,6 +1394,75 @@ def test_add_bounds_on_uncertain_parameters(self):
         self.assertNotEqual(m.util.uncertain_param_vars[1].lb, None, "Bounds not added correctly for DiscreteScenarioSet")
         self.assertNotEqual(m.util.uncertain_param_vars[1].ub, None, "Bounds not added correctly for DiscreteScenarioSet")
 
+    @unittest.skipUnless(SolverFactory('baron').license_is_valid(),
+                         "Global NLP solver is not available and licensed.")
+    def test_two_stg_model_discrete_set_single_scenario(self):
+        """
+        Test two-stage model under discrete uncertainty with
+        a single scenario.
+        """
+        m = ConcreteModel()
+
+        # model params
+        m.u1 = Param(initialize=1.125, mutable=True)
+        m.u2 = Param(initialize=1, mutable=True)
+
+        # model vars
+        m.x1 = Var(initialize=0, bounds=(0, None))
+        m.x2 = Var(initialize=0, bounds=(0, None))
+        m.x3 = Var(initialize=0, bounds=(None, None))
+
+        # model constraints
+        m.con1 = Constraint(expr=m.x1 * m.u1**(0.5) - m.x2 * m.u1 <= 2)
+        m.con2 = Constraint(expr=m.x1 ** 2 - m.x2 ** 2 * m.u1 == m.x3)
+
+        m.obj = Objective(expr=(m.x1 - 4) ** 2 + (m.x2 - m.u2) ** 2)
+
+        # uncertainty set
+        discrete_set = DiscreteScenarioSet(
+            scenarios=[(1.125, 1)],
+        )
+
+        # Instantiate PyROS solver
+        pyros_solver = SolverFactory("pyros")
+
+        # Define subsolvers utilized in the algorithm
+        local_subsolver = SolverFactory('baron')
+        global_subsolver = SolverFactory("baron")
+
+        # Call the PyROS solver
+        results = pyros_solver.solve(
+            model=m,
+            first_stage_variables=[m.x1],
+            second_stage_variables=[m.x2],
+            uncertain_params=[m.u1, m.u2],
+            uncertainty_set=discrete_set,
+            local_solver=local_subsolver,
+            global_solver=global_subsolver,
+            options={
+                "objective_focus": ObjectiveType.worst_case,
+                "solve_master_globally": True,
+            }
+        )
+
+        # check successful termination
+        self.assertEqual(
+            results.pyros_termination_condition,
+            pyrosTerminationCondition.robust_optimal,
+            msg="Did not identify robust optimal solution to problem instance."
+        )
+
+        # only one iteration required
+        self.assertEqual(
+            results.iterations,
+            1,
+            msg=(
+                "PyROS was unable to solve a singleton discrete set instance "
+                " successfully within a single iteration."
+            )
+        )
+
+
 class testFactorModelUncertaintySetClass(unittest.TestCase):
     '''
     FactorModelSet uncertainty sets. Required inputs are psi_matrix, number_of_factors, origin and beta.