def make_nested_block_model(self): """For the next two tests: Has BooleanVar on model, but LogicalConstraints on a Block and a Block nested on that Block.""" m = ConcreteModel() m.b = Block() m.Y = BooleanVar([1, 2]) m.b.logical = LogicalConstraint(expr=~m.Y[1]) m.b.b = Block() m.b.b.logical = LogicalConstraint(expr=m.Y[1].xor(m.Y[2])) return m
def test_constant_True(self): m = ConcreteModel() with self.assertRaisesRegex(ValueError, "LogicalConstraint 'p' is always True."): m.p = LogicalConstraint(expr=True) TransformationFactory('core.logical_to_linear').apply_to(m) self.assertIsNone(m.component('logic_to_linear'))
def test_literal(self): m = ConcreteModel() m.Y = BooleanVar() m.p = LogicalConstraint(expr=m.Y) TransformationFactory('core.logical_to_linear').apply_to(m) _constrs_contained_within(self, [(1, m.Y.get_associated_binary(), 1)], m.logic_to_linear.transformed_constraints)
def test_transformed_components_on_parent_block(self): m = ConcreteModel() m.b = Block() m.b.s = RangeSet(3) m.b.Y = BooleanVar(m.b.s) m.b.p = LogicalConstraint( expr=m.b.Y[1].implies(lor(m.b.Y[2], m.b.Y[3]))) TransformationFactory('core.logical_to_linear').apply_to(m) boolean_var = m.b.component("Y_asbinary") self.assertIsInstance(boolean_var, Var) notAVar = m.component("Y_asbinary") self.assertIsNone(notAVar) transBlock = m.b.component("logic_to_linear") self.assertIsInstance(transBlock, Block) notAThing = m.component("logic_to_linear") self.assertIsNone(notAThing) # check the constraints on the transBlock _constrs_contained_within( self, [ (1, m.b.Y[2].get_associated_binary() + \ m.b.Y[3].get_associated_binary() + (1 - m.b.Y[1].get_associated_binary()), None) ], m.b.logic_to_linear.transformed_constraints)
def _generate_boolean_model(nvars): m = ConcreteModel() m.s = RangeSet(nvars) m.Y = BooleanVar(m.s) # make sure all the variables are used in at least one logical constraint m.constraint = LogicalConstraint(expr=exactly(2, m.Y)) return m
def test_xfrm_atleast_nested(self): m = _generate_boolean_model(4) m.p = LogicalConstraint(expr=atleast(1, atleast(2, m.Y[1], m.Y[1].lor(m.Y[2]), m.Y[2]).lor(m.Y[3]), m.Y[4])) TransformationFactory('core.logical_to_linear').apply_to(m) Y_aug = m.logic_to_linear.augmented_vars self.assertEqual(len(Y_aug), 3) _constrs_contained_within( self, [ (1, Y_aug[1].get_associated_binary() + m.Y[4].get_associated_binary(), None), (1, 1 - Y_aug[2].get_associated_binary() + Y_aug[1].get_associated_binary(), None), (1, 1 - m.Y[3].get_associated_binary() + Y_aug[1].get_associated_binary(), None), (1, Y_aug[2].get_associated_binary() + m.Y[3].get_associated_binary() + 1 - Y_aug[1].get_associated_binary(), None), (1, 1 - m.Y[1].get_associated_binary() + Y_aug[3].get_associated_binary(), None), (1, 1 - m.Y[2].get_associated_binary() + Y_aug[3].get_associated_binary(), None), (1, m.Y[1].get_associated_binary() + m.Y[2].get_associated_binary() + 1 - Y_aug[3].get_associated_binary(), None), (None, 2 - 2 * (1 - Y_aug[2].get_associated_binary()) - (m.Y[1].get_associated_binary() + Y_aug[3].get_associated_binary() + m.Y[2].get_associated_binary()), 0), (None, m.Y[1].get_associated_binary() + Y_aug[3].get_associated_binary() + m.Y[2].get_associated_binary() - (1 + 2 * Y_aug[2].get_associated_binary()), 0) ], m.logic_to_linear.transformed_constraints)
def test_construct(self): model = self.create_model() def rule(model): return model.x model.p = LogicalConstraint(rule=rule) self.assertIs(model.p.body, model.x)
def test_implies(self): m = ConcreteModel() m.x = BooleanVar() m.y = BooleanVar() m.p = LogicalConstraint(expr=m.x.implies(m.y)) TransformationFactory('core.logical_to_linear').apply_to(m) _constrs_contained_within( self, [(1, (1 - m.x.get_associated_binary()) + m.y.get_associated_binary(), None)], m.logic_to_linear.transformed_constraints)
def test_RIC_strip_pack_maxBinary_logical_constraints(self): """Test RIC with strip packing using max_binary initialization and including logical constraints.""" exfile = import_file( join(exdir, 'strip_packing', 'strip_packing_concrete.py')) strip_pack = exfile.build_rect_strip_packing_model() # add logical constraints strip_pack.Rec3AboveOrBelowRec1 = LogicalConstraint( expr=strip_pack.no_overlap[1,3].disjuncts[2].indicator_var.lor( strip_pack.no_overlap[1,3].disjuncts[3].indicator_var)) strip_pack.Rec3RightOrLeftOfRec2 = LogicalConstraint( expr=strip_pack.no_overlap[2,3].disjuncts[0].indicator_var.lor( strip_pack.no_overlap[2,3].disjuncts[1].indicator_var)) SolverFactory('gdpopt').solve( strip_pack, strategy='RIC', init_strategy='max_binary', mip_solver=mip_solver, nlp_solver=nlp_solver) self.assertTrue( fabs(value(strip_pack.total_length.expr) - 13) <= 1E-2)
def test_GLOA_strip_pack_default_init_logical_constraints(self): """Test logic-based outer approximation with strip packing.""" exfile = import_file( join(exdir, 'strip_packing', 'strip_packing_concrete.py')) strip_pack = exfile.build_rect_strip_packing_model() # add logical constraints strip_pack.Rec3AboveOrBelowRec1 = LogicalConstraint( expr=strip_pack.no_overlap[1,3].disjuncts[2].indicator_var.lor( strip_pack.no_overlap[1,3].disjuncts[3].indicator_var)) strip_pack.Rec3RightOrLeftOfRec2 = LogicalConstraint( expr=strip_pack.no_overlap[2,3].disjuncts[0].indicator_var.lor( strip_pack.no_overlap[2,3].disjuncts[1].indicator_var)) SolverFactory('gdpopt').solve( strip_pack, strategy='GLOA', mip_solver=mip_solver, nlp_solver=global_nlp_solver, nlp_solver_args=global_nlp_solver_args) self.assertTrue( fabs(value(strip_pack.total_length.expr) - 13) <= 1E-2)
def test_xfrm_exactly_statement(self): m = ConcreteModel() m.s = RangeSet(3) m.Y = BooleanVar(m.s) m.p = LogicalConstraint(expr=exactly(2, m.Y[1], m.Y[2], m.Y[3])) TransformationFactory('core.logical_to_linear').apply_to(m) _constrs_contained_within(self, [(2, m.Y[1].get_associated_binary() + m.Y[2].get_associated_binary() + m.Y[3].get_associated_binary(), 2)], m.logic_to_linear.transformed_constraints)
def test_longer_statement(self): m = ConcreteModel() m.s = RangeSet(3) m.Y = BooleanVar(m.s) m.p = LogicalConstraint(expr=m.Y[1].implies(lor(m.Y[2], m.Y[3]))) TransformationFactory('core.logical_to_linear').apply_to(m) _constrs_contained_within( self, [(1, m.Y[2].get_associated_binary() + m.Y[3].get_associated_binary() + (1 - m.Y[1].get_associated_binary()), None)], m.logic_to_linear.transformed_constraints)
def test_logical_constraints_transformed(self): """It is expected that the result of this transformation is a MI(N)LP, so check that LogicalConstraints are handeled correctly""" m = ConcreteModel() m.x = Var(bounds=(0, 10)) m.d1 = Disjunct() m.d2 = Disjunct() m.d2.c = Constraint() m.d = Disjunction(expr=[m.d1, m.d2]) m.another = Disjunction(expr=[[m.x == 3], [m.x == 0]]) m.Y = BooleanVar() m.global_logical = LogicalConstraint(expr=m.Y.xor(m.d1.indicator_var)) m.d1.logical = LogicalConstraint( expr=implies(~m.Y, m.another.disjuncts[0].indicator_var)) m.obj = Objective(expr=m.x) m.d1.indicator_var.set_value(True) m.d2.indicator_var.set_value(False) m.another.disjuncts[0].indicator_var.set_value(True) m.another.disjuncts[1].indicator_var.set_value(False) TransformationFactory('gdp.fix_disjuncts').apply_to(m) # Make sure there are no active LogicalConstraints self.assertEqual( len( list( m.component_data_objects(LogicalConstraint, active=True, descend_into=(Block, Disjunct)))), 0) # See that it solves as expected SolverFactory('gurobi').solve(m) self.assertTrue(value(m.d1.indicator_var)) self.assertFalse(value(m.d2.indicator_var)) self.assertTrue(value(m.another.disjuncts[0].indicator_var)) self.assertFalse(value(m.another.disjuncts[1].indicator_var)) self.assertEqual(value(m.Y.get_associated_binary()), 0) self.assertEqual(value(m.x), 3)
def test_link_with_gdp_indicators(self): m = _generate_boolean_model(4) m.d1 = Disjunct() m.d2 = Disjunct() m.x = Var() m.dd = Disjunct([1, 2]) m.d1.c = Constraint(expr=m.x >= 2) m.d2.c = Constraint(expr=m.x <= 10) m.dd[1].c = Constraint(expr=m.x >= 5) m.dd[2].c = Constraint(expr=m.x <= 6) m.Y[1].associate_binary_var(m.d1.indicator_var) m.Y[2].associate_binary_var(m.d2.indicator_var) m.Y[3].associate_binary_var(m.dd[1].indicator_var) m.Y[4].associate_binary_var(m.dd[2].indicator_var) m.p = LogicalConstraint(expr=m.Y[1].implies(lor(m.Y[3], m.Y[4]))) m.p2 = LogicalConstraint(expr=atmost(2, *m.Y[:])) TransformationFactory('core.logical_to_linear').apply_to(m) _constrs_contained_within( self, [ (1, m.dd[1].indicator_var + m.dd[2].indicator_var + 1 - m.d1.indicator_var, None), (None, m.d1.indicator_var + m.d2.indicator_var + m.dd[1].indicator_var + m.dd[2].indicator_var, 2) ], m.logic_to_linear.transformed_constraints)
def test_backmap_hierarchical_model(self): m = _generate_boolean_model(3) m.b = Block() m.b.Y = BooleanVar() m.b.lc = LogicalConstraint(expr=m.Y[1].lor(m.b.Y)) TransformationFactory('core.logical_to_linear').apply_to(m) m.Y_asbinary[1].value = 1 m.Y_asbinary[2].value = 0 m.b.Y.get_associated_binary().value = 1 update_boolean_vars_from_binary(m) self.assertTrue(m.Y[1].value) self.assertFalse(m.Y[2].value) self.assertIsNone(m.Y[3].value) self.assertTrue(m.b.Y.value)
def build_eight_process_flowsheet(): """Build flowsheet for the 8 process problem.""" m = ConcreteModel(name='DuranEx3 Disjunctive') """Set declarations""" m.streams = RangeSet(2, 25, doc="process streams") m.units = RangeSet(1, 8, doc="process units") no_unit_zero_flows = { 1: (2, 3), 2: (4, 5), 3: (9, ), 4: (12, 14), 5: (15, ), 6: (19, 20), 7: (21, 22), 8: (10, 17, 18), } """Parameter and initial point declarations""" # FIXED COST INVESTMENT COEFF FOR PROCESS UNITS # Format: process #: cost fixed_cost = {1: 5, 2: 8, 3: 6, 4: 10, 5: 6, 6: 7, 7: 4, 8: 5} m.CF = Param(m.units, initialize=fixed_cost) def fixed_cost_bounds(m, unit): return (0, m.CF[unit]) m.yCF = Var(m.units, initialize=0, bounds=fixed_cost_bounds) # VARIABLE COST COEFF FOR PROCESS UNITS - STREAMS # Format: stream #: cost variable_cost = { 3: -10, 5: -15, 9: -40, 19: 25, 21: 35, 25: -35, 17: 80, 14: 15, 10: 15, 2: 1, 4: 1, 18: -65, 20: -60, 22: -80 } CV = m.CV = Param(m.streams, initialize=variable_cost, default=0) # initial point information for stream flows initX = { 2: 2, 3: 1.5, 6: 0.75, 7: 0.5, 8: 0.5, 9: 0.75, 11: 1.5, 12: 1.34, 13: 2, 14: 2.5, 17: 2, 18: 0.75, 19: 2, 20: 1.5, 23: 1.7, 24: 1.5, 25: 0.5 } """Variable declarations""" # FLOWRATES OF PROCESS STREAMS m.flow = Var(m.streams, domain=NonNegativeReals, initialize=initX, bounds=(0, 10)) # OBJECTIVE FUNCTION CONSTANT TERM CONSTANT = m.constant = Param(initialize=122.0) """Constraint definitions""" # INPUT-OUTPUT RELATIONS FOR process units 1 through 8 @m.Disjunct(m.units) def use_unit(disj, unit): disj.impose_fixed_cost = Constraint(expr=m.yCF[unit] == m.CF[unit]) disj.io_relation = ConstraintList(doc="Input-Output relationship") @m.Disjunct(m.units) def no_unit(disj, unit): disj.no_flow = ConstraintList() for stream in no_unit_zero_flows[unit]: disj.no_flow.add(expr=m.flow[stream] == 0) @m.Disjunction(m.units) def use_unit_or_not(m, unit): return [m.use_unit[unit], m.no_unit[unit]] # Note: this could be done in an automated manner by indexed construction. # Below is just for illustration m.use_unit[1].io_relation.add(expr=exp(m.flow[3]) - 1 == m.flow[2]) m.use_unit[2].io_relation.add(expr=exp(m.flow[5] / 1.2) - 1 == m.flow[4]) m.use_unit[3].io_relation.add(expr=1.5 * m.flow[9] + m.flow[10] == m.flow[8]) m.use_unit[4].io_relation.add(expr=1.25 * (m.flow[12] + m.flow[14]) == m.flow[13]) m.use_unit[5].io_relation.add(expr=m.flow[15] == 2 * m.flow[16]) m.use_unit[6].io_relation.add(expr=exp(m.flow[20] / 1.5) - 1 == m.flow[19]) m.use_unit[7].io_relation.add(expr=exp(m.flow[22]) - 1 == m.flow[21]) m.use_unit[8].io_relation.add(expr=exp(m.flow[18]) - 1 == m.flow[10] + m.flow[17]) m.no_unit[3].bypass = Constraint(expr=m.flow[10] == m.flow[8]) # Mass balance equations m.massbal1 = Constraint(expr=m.flow[13] == m.flow[19] + m.flow[21]) m.massbal2 = Constraint(expr=m.flow[17] == m.flow[9] + m.flow[16] + m.flow[25]) m.massbal3 = Constraint(expr=m.flow[11] == m.flow[12] + m.flow[15]) m.massbal4 = Constraint(expr=m.flow[3] + m.flow[5] == m.flow[6] + m.flow[11]) m.massbal5 = Constraint(expr=m.flow[6] == m.flow[7] + m.flow[8]) m.massbal6 = Constraint(expr=m.flow[23] == m.flow[20] + m.flow[22]) m.massbal7 = Constraint(expr=m.flow[23] == m.flow[14] + m.flow[24]) # process specifications m.specs1 = Constraint(expr=m.flow[10] <= 0.8 * m.flow[17]) m.specs2 = Constraint(expr=m.flow[10] >= 0.4 * m.flow[17]) m.specs3 = Constraint(expr=m.flow[12] <= 5 * m.flow[14]) m.specs4 = Constraint(expr=m.flow[12] >= 2 * m.flow[14]) m.Y = Reference(m.use_unit[:].indicator_bool) # logical propositions m.use1or2 = LogicalConstraint(expr=m.Y[1].xor(m.Y[2])) m.use1or2implies345 = LogicalConstraint( expr=lor(m.Y[1], m.Y[2]).implies(lor(m.Y[3], m.Y[4], m.Y[5]))) m.use4implies6or7 = LogicalConstraint( expr=m.Y[4].implies(lor(m.Y[6], m.Y[7]))) m.use3implies8 = LogicalConstraint(expr=m.Y[3].implies(m.Y[8])) m.use6or7implies4 = LogicalConstraint( expr=lor(m.Y[6], m.Y[7]).implies(m.Y[4])) m.use6or7 = LogicalConstraint(expr=m.Y[6].xor(m.Y[7])) """Profit (objective) function definition""" m.profit = Objective(expr=sum(m.yCF[unit] for unit in m.units) + sum(m.flow[stream] * CV[stream] for stream in m.streams) + CONSTANT, sense=minimize) """Bound definitions""" # x (flow) upper bounds x_ubs = {3: 2, 5: 2, 9: 2, 10: 1, 14: 1, 17: 2, 19: 2, 21: 2, 25: 3} for i, x_ub in x_ubs.item(): m.flow[i].setub(x_ub) # Optimal solution uses units 2, 4, 6, 8 with objective value 68. return m
def make_indexed_logical_constraint_model(self): m = _generate_boolean_model(3) m.cons = LogicalConstraint([1, 2]) m.cons[1] = exactly(2, m.Y) m.cons[2] = m.Y[1].implies(lor(m.Y[2], m.Y[3])) return m
def test_xfrm_special_atoms_nonroot(self): m = ConcreteModel() m.s = RangeSet(3) m.Y = BooleanVar(m.s) m.p = LogicalConstraint( expr=m.Y[1].implies(atleast(2, m.Y[1], m.Y[2], m.Y[3]))) TransformationFactory('core.logical_to_linear').apply_to(m) Y_aug = m.logic_to_linear.augmented_vars self.assertEqual(len(Y_aug), 1) self.assertEqual(Y_aug[1].domain, BooleanSet) _constrs_contained_within( self, [(None, sum(m.Y[:].get_associated_binary()) - (1 + 2 * Y_aug[1].get_associated_binary()), 0), (1, (1 - m.Y[1].get_associated_binary()) + Y_aug[1].get_associated_binary(), None), (None, 2 - 2 * (1 - Y_aug[1].get_associated_binary()) - sum(m.Y[:].get_associated_binary()), 0)], m.logic_to_linear.transformed_constraints) m = ConcreteModel() m.s = RangeSet(3) m.Y = BooleanVar(m.s) m.p = LogicalConstraint( expr=m.Y[1].implies(atmost(2, m.Y[1], m.Y[2], m.Y[3]))) TransformationFactory('core.logical_to_linear').apply_to(m) Y_aug = m.logic_to_linear.augmented_vars self.assertEqual(len(Y_aug), 1) self.assertEqual(Y_aug[1].domain, BooleanSet) _constrs_contained_within( self, [(None, sum(m.Y[:].get_associated_binary()) - (1 - Y_aug[1].get_associated_binary() + 2), 0), (1, (1 - m.Y[1].get_associated_binary()) + Y_aug[1].get_associated_binary(), None), (None, 3 - 3 * Y_aug[1].get_associated_binary() - sum(m.Y[:].get_associated_binary()), 0)], m.logic_to_linear.transformed_constraints) m = ConcreteModel() m.s = RangeSet(3) m.Y = BooleanVar(m.s) m.p = LogicalConstraint( expr=m.Y[1].implies(exactly(2, m.Y[1], m.Y[2], m.Y[3]))) TransformationFactory('core.logical_to_linear').apply_to(m) Y_aug = m.logic_to_linear.augmented_vars self.assertEqual(len(Y_aug), 3) self.assertEqual(Y_aug[1].domain, BooleanSet) _constrs_contained_within(self, [ (1, (1 - m.Y[1].get_associated_binary()) + Y_aug[1].get_associated_binary(), None), (None, sum(m.Y[:].get_associated_binary()) - (1 - Y_aug[1].get_associated_binary() + 2), 0), (None, 2 - 2 * (1 - Y_aug[1].get_associated_binary()) - sum(m.Y[:].get_associated_binary()), 0), (1, sum(Y_aug[:].get_associated_binary()), None), (None, sum(m.Y[:].get_associated_binary()) - (1 + 2 * (1 - Y_aug[2].get_associated_binary())), 0), (None, 3 - 3 * (1 - Y_aug[3].get_associated_binary()) - sum(m.Y[:].get_associated_binary()), 0), ], m.logic_to_linear.transformed_constraints) # Note: x is now a variable m = ConcreteModel() m.s = RangeSet(3) m.Y = BooleanVar(m.s) m.x = Var(bounds=(1, 3)) m.p = LogicalConstraint( expr=m.Y[1].implies(exactly(m.x, m.Y[1], m.Y[2], m.Y[3]))) TransformationFactory('core.logical_to_linear').apply_to(m) Y_aug = m.logic_to_linear.augmented_vars self.assertEqual(len(Y_aug), 3) self.assertEqual(Y_aug[1].domain, BooleanSet) _constrs_contained_within(self, [ (1, (1 - m.Y[1].get_associated_binary()) + Y_aug[1].get_associated_binary(), None), (None, sum(m.Y[:].get_associated_binary()) - (m.x + 2 * (1 - Y_aug[1].get_associated_binary())), 0), (None, m.x - 3 * (1 - Y_aug[1].get_associated_binary()) - sum(m.Y[:].get_associated_binary()), 0), (1, sum(Y_aug[:].get_associated_binary()), None), (None, sum(m.Y[:].get_associated_binary()) - (m.x - 1 + 3 * (1 - Y_aug[2].get_associated_binary())), 0), (None, m.x + 1 - 4 * (1 - Y_aug[3].get_associated_binary()) - sum(m.Y[:].get_associated_binary()), 0), ], m.logic_to_linear.transformed_constraints)
def test_nothing_to_do(self): m = ConcreteModel() m.p = LogicalConstraint() TransformationFactory('core.logical_to_linear').apply_to(m) self.assertIsNone(m.component('logic_to_linear'))
def build_model(): m = ConcreteModel() seed(1) # Fix seed to generate same parameters and solution every time m.T_max = randint(10, 10) m.T = RangeSet(m.T_max) # Variables m.s = Var(m.T, domain=NonNegativeReals, bounds=(0, 10000), doc='stock') m.x = Var(m.T, domain=NonNegativeReals, bounds=(0, 10000), doc='purchased') m.c = Var(m.T, domain=NonNegativeReals, bounds=(0, 10000), doc='cost') m.f = Var(m.T, domain=NonNegativeReals, bounds=(0, 10000), doc='feed') m.max_q_idx = RangeSet(m.T_max) # Randomly generated parameters m.D = Param(m.T, doc='demand', initialize=dict((t, randint(50, 100)) for t in m.T)) m.alpha = Param(m.T, doc='storage cost', initialize=dict((t, randint(5, 20)) for t in m.T)) m.gamma = Param(m.T, doc='base buying cost', initialize=dict((t, randint(10, 30)) for t in m.T)) m.beta_B = Param(m.T, doc='bulk discount', initialize=dict((t, randint(50, 500)/1000) for t in m.T)) m.F_B_lo = Param(m.T, doc='bulk minimum purchase amount', initialize=dict((t, randint(50, 100)) for t in m.T)) m.beta_L = Param(m.T, m.max_q_idx, initialize=dict(((t, q), randint(10, 999)/1000) for t in m.T for q in m.max_q_idx), doc='long-term discount') m.F_L_lo = Param(m.T, m.max_q_idx, initialize=dict(((t, q), randint(50, 100)) for t in m.T for q in m.max_q_idx), doc='long-term minimum purchase amount') # Contract choices 'standard', 'bulk' and long term contracts '0','1',... time_time_choices = [(t1, str(t2)) for t1, t2 in m.T * m.T if t2 <= m.T_max - t1] time_special_choices = [(t, s) for t in m.T for s in {'S', 'B', '0'}] m.contract_time_choices = Set(initialize=time_time_choices + time_special_choices) m.disjunct_choices = Set( initialize=['S', 'B', *[str(t) for t in range(m.T_max)]]) m.disjuncts = Disjunct(m.contract_time_choices) m.Y = BooleanVar(m.contract_time_choices) for t, c in m.contract_time_choices: m.Y[t, c].associate_binary_var(m.disjuncts[t, c].indicator_var) # Create disjuncts for contracts in each timeset for t in m.T: m.disjuncts[t, 'S'].cost = Constraint(expr=m.c[t] == m.gamma[t]*m.x[t]) m.disjuncts[t, 'B'].cost = Constraint( expr=m.c[t] == (1-m.beta_B[t])*m.gamma[t]*m.x[t]) m.disjuncts[t, 'B'].amount = Constraint( expr=m.x[t] >= m.F_B_lo[t]) m.disjuncts[t, '0'].c = Constraint(expr=0 <= m.c[t]) for q in range(1, m.T_max-t+1): m.disjuncts[t, str(q)].t_idx = RangeSet(t, t+q) m.disjuncts[t, str(q)].cost = Constraint(m.disjuncts[t, str(q)].t_idx) m.disjuncts[t, str(q)].amount = Constraint(m.disjuncts[t, str(q)].t_idx) for t_ in m.disjuncts[t, str(q)].t_idx: m.disjuncts[t, str(q)].cost[t_] =\ m.c[t_] == (1-m.beta_L[t, q])*m.gamma[t]*m.x[t_] m.disjuncts[t, str(q)].amount[t_] =\ m.x[t_] >= m.F_L_lo[t, q] # Create disjunctions @m.Disjunction(m.T, xor=True) def disjunctions(m, t): return [m.disjuncts[t, 'S'], m.disjuncts[t, 'B'], m.disjuncts[t, '0'], *[m.disjuncts[t, str(q)] for q in range(1, m.T_max-t+1)]] # Connect the disjuncts indicator variables using logical expressions m.logical_blocks = Block(range(1, m.T_max+1)) # Enforce absence of existing long-term contract m.logical_blocks[1].not_y_1_0 = LogicalConstraint(expr=~m.Y[1, '0'], doc="no pre-existing long-term contract") # Long-term contract implies '0'-disjunct in following timesteps for t in range(2, m.T_max+1): m.logical_blocks[t].equiv = LogicalConstraint( expr=m.Y[t, '0'].equivalent_to(lor(m.Y[t_, str(q)] for t_ in range(1, t) for q in range(t - t_, m.T_max - t_ + 1))) ) # Objective function m.objective = Objective(expr=sum(m.alpha[t]*m.s[t]+m.c[t] for t in m.T)) # Global constraints m.demand_satisfaction = Constraint(m.T) for t in m.T: m.demand_satisfaction[t] = m.f[t] >= m.D[t] m.material_balance = Constraint(m.T) for t in m.T: m.material_balance[t]=m.s[t] == (m.s[t-1] if t>1 else 0) + m.x[t] - m.f[t] return m