12
12
import itertools
13
13
import logging
14
14
15
- from pyomo .common .collections import ComponentMap
15
+ from pyomo .common .collections import ComponentMap , ComponentSet
16
16
from pyomo .common .config import ConfigDict , ConfigValue
17
17
from pyomo .common .gc_manager import PauseGC
18
18
from pyomo .common .modeling import unique_component_name
@@ -310,9 +310,12 @@ def _transform_disjunctionData(self, obj, index, parent_disjunct, root_disjunct)
310
310
311
311
arg_Ms = self ._config .bigM if self ._config .bigM is not None else {}
312
312
313
+ # ESJ: I am relying on the fact that the ComponentSet is going to be
314
+ # ordered here, but using a set because I will remove infeasible
315
+ # Disjuncts from it if I encounter them calculating M's.
316
+ active_disjuncts = ComponentSet (disj for disj in obj .disjuncts if disj .active )
313
317
# First handle the bound constraints if we are dealing with them
314
318
# separately
315
- active_disjuncts = [disj for disj in obj .disjuncts if disj .active ]
316
319
transformed_constraints = set ()
317
320
if self ._config .reduce_bound_constraints :
318
321
transformed_constraints = self ._transform_bound_constraints (
@@ -585,7 +588,7 @@ def _calculate_missing_M_values(
585
588
):
586
589
if disjunct is other_disjunct :
587
590
continue
588
- if id (other_disjunct ) in scratch_blocks :
591
+ elif id (other_disjunct ) in scratch_blocks :
589
592
scratch = scratch_blocks [id (other_disjunct )]
590
593
else :
591
594
scratch = scratch_blocks [id (other_disjunct )] = Block ()
@@ -631,15 +634,21 @@ def _calculate_missing_M_values(
631
634
scratch .obj .expr = constraint .body - constraint .lower
632
635
scratch .obj .sense = minimize
633
636
lower_M = self ._solve_disjunct_for_M (
634
- other_disjunct , scratch , unsuccessful_solve_msg
637
+ other_disjunct ,
638
+ scratch ,
639
+ unsuccessful_solve_msg ,
640
+ active_disjuncts ,
635
641
)
636
642
if constraint .upper is not None and upper_M is None :
637
643
# last resort: calculate
638
644
if upper_M is None :
639
645
scratch .obj .expr = constraint .body - constraint .upper
640
646
scratch .obj .sense = maximize
641
647
upper_M = self ._solve_disjunct_for_M (
642
- other_disjunct , scratch , unsuccessful_solve_msg
648
+ other_disjunct ,
649
+ scratch ,
650
+ unsuccessful_solve_msg ,
651
+ active_disjuncts ,
643
652
)
644
653
arg_Ms [constraint , other_disjunct ] = (lower_M , upper_M )
645
654
transBlock ._mbm_values [constraint , other_disjunct ] = (lower_M , upper_M )
@@ -651,9 +660,18 @@ def _calculate_missing_M_values(
651
660
return arg_Ms
652
661
653
662
def _solve_disjunct_for_M (
654
- self , other_disjunct , scratch_block , unsuccessful_solve_msg
663
+ self , other_disjunct , scratch_block , unsuccessful_solve_msg , active_disjuncts
655
664
):
665
+ if not other_disjunct .active :
666
+ # If a Disjunct is infeasible, we will discover that and deactivate
667
+ # it when we are calculating the M values. We remove that disjunct
668
+ # from active_disjuncts inside of the loop in
669
+ # _calculate_missing_M_values. So that means that we might have
670
+ # deactivated Disjuncts here that we should skip over.
671
+ return 0
672
+
656
673
solver = self ._config .solver
674
+
657
675
results = solver .solve (other_disjunct , load_solutions = False )
658
676
if results .solver .termination_condition is TerminationCondition .infeasible :
659
677
# [2/18/24]: TODO: After the solver rewrite is complete, we will not
@@ -669,6 +687,7 @@ def _solve_disjunct_for_M(
669
687
"Disjunct '%s' is infeasible, deactivating." % other_disjunct .name
670
688
)
671
689
other_disjunct .deactivate ()
690
+ active_disjuncts .remove (other_disjunct )
672
691
M = 0
673
692
else :
674
693
# This is a solver that might report
0 commit comments