From 0473db7a0976a2e1c1bed9ed1a651893d707a2c8 Mon Sep 17 00:00:00 2001 From: Vincent Reinbold Date: Wed, 27 Feb 2019 15:39:40 +0100 Subject: [PATCH 01/38] issue882 : adding Port.Conservative rule and tests --- pyomo/network/port.py | 80 ++++++++++++- pyomo/network/tests/test_arc.py | 200 +++++++++++++++++++++++++++++++- 2 files changed, 276 insertions(+), 4 deletions(-) diff --git a/pyomo/network/port.py b/pyomo/network/port.py index e2124b89112..ac6af4a3b7f 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -2,8 +2,8 @@ # # Pyomo: Python Optimization Modeling Objects # Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and -# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ @@ -205,6 +205,10 @@ def is_extensive(self, name): """Return True if the rule for this port member is Port.Extensive""" return self.rule_for(name) is Port.Extensive + def is_conservative(self, name): + """Return True if the rule for this port member is Port.Conservative""" + return self.rule_for(name) is Port.Conservative + def fix(self): """ Fix all variables in the port at their current values. @@ -497,6 +501,27 @@ def Extensive(port, name, index_set, include_splitfrac=False, include_splitfrac=include_splitfrac, write_var_sum=write_var_sum) in_vars = Port._Combine(port, name, index_set) + @staticmethod + def Conservative(port, name, index_set): + """ + Arc Expansion procedure for conservative variable properties. + + This procedure is the rule to use when variable quantities should + be conserved without fixing a split for inlets or fixing combinations + for outlet. + + :param port: + :param name: + :param index_set: + :param include_splitfrac: + :param write_var_sum: + :return: + """ + + port_parent = port.parent_block() + out_vars = Port._Split_Conservative(port, name, index_set) + in_vars = Port._Combine(port, name, index_set) + @staticmethod def _Combine(port, name, index_set): port_parent = port.parent_block() @@ -655,6 +680,56 @@ def rule(m, *args): return out_vars + @staticmethod + def _Split_Conservative(port, name, index_set): + port_parent = port.parent_block() + var = port.vars[name] + out_vars = [] + no_splitfrac = False + dests = port.dests(active=True) + + if not len(dests): + return out_vars + + if len(dests) == 1: + # No need for splitting on one outlet. + # Make sure they do not try to fix splitfrac not at 1. + splitfracspec = port.get_split_fraction(dests[0]) + if splitfracspec is not None: + if splitfracspec[0] != 1 and splitfracspec[1] is True: + raise ValueError( + "Cannot fix splitfrac not at 1 for port '%s' with a " + "single dest '%s'" % (port.name, dests[0].name)) + + if len(dests[0].destination.sources(active=True)) == 1: + # This is a 1-to-1 connection, no need for evar, just equality. + arc = dests[0] + Port._add_equality_constraint(arc, name, index_set) + return out_vars + + for arc in dests: + eblock = arc.expanded_block + + # Make and record new variables for every arc with this member. + evar = Port._create_evar(port.vars[name], name, eblock, index_set) + out_vars.append(evar) + + # Create var total sum constraint: var == sum of evars + # Need to alphanum port name in case it is indexed. + cname = unique_component_name(port_parent, "%s_%s_outsum" % + (alphanum_label_from_name(port.local_name), name)) + + def rule(m, *args): + if len(args): + return sum(evar[args] for evar in out_vars) == var[args] + else: + return sum(evar for evar in out_vars) == var + + con = Constraint(index_set, rule=rule) + port_parent.add_component(cname, con) + + return out_vars + @staticmethod def _add_equality_constraint(arc, name, index_set): # This function will add the equality constraint if it doesn't exist. @@ -694,4 +769,3 @@ def __init__(self, *args, **kwd): class IndexedPort(Port): pass - diff --git a/pyomo/network/tests/test_arc.py b/pyomo/network/tests/test_arc.py index 9d7128f7106..4b610798be2 100644 --- a/pyomo/network/tests/test_arc.py +++ b/pyomo/network/tests/test_arc.py @@ -984,6 +984,81 @@ def test_inactive(self): 1 Declarations: v_equality """) + def test_conservative_single_var(self): + m = ConcreteModel() + m.x = Var() + m.y = Var() + m.z = Var() + m.p1 = Port(initialize={'v': (m.x, Port.Conservative)}) + m.p2 = Port(initialize={'v': (m.y, Port.Conservative)}) + m.p3 = Port(initialize={'v': (m.z, Port.Conservative)}) + m.a1 = Arc(source=m.p1, destination=m.p2) + m.a2 = Arc(source=m.p1, destination=m.p3) + + TransformationFactory('network.expand_arcs').apply_to(m) + + os = StringIO() + m.pprint(ostream=os) + self.assertEqual(os.getvalue(), +"""3 Var Declarations + x : Size=1, Index=None + Key : Lower : Value : Upper : Fixed : Stale : Domain + None : None : None : None : False : True : Reals + y : Size=1, Index=None + Key : Lower : Value : Upper : Fixed : Stale : Domain + None : None : None : None : False : True : Reals + z : Size=1, Index=None + Key : Lower : Value : Upper : Fixed : Stale : Domain + None : None : None : None : False : True : Reals + +3 Constraint Declarations + p1_v_outsum : Size=1, Index=None, Active=True + Key : Lower : Body : Upper : Active + None : 0.0 : a1_expanded.v + a2_expanded.v - x : 0.0 : True + p2_v_insum : Size=1, Index=None, Active=True + Key : Lower : Body : Upper : Active + None : 0.0 : a1_expanded.v - y : 0.0 : True + p3_v_insum : Size=1, Index=None, Active=True + Key : Lower : Body : Upper : Active + None : 0.0 : a2_expanded.v - z : 0.0 : True + +2 Block Declarations + a1_expanded : Size=1, Index=None, Active=True + 1 Var Declarations + v : Size=1, Index=None + Key : Lower : Value : Upper : Fixed : Stale : Domain + None : None : None : None : False : True : Reals + + 1 Declarations: v + a2_expanded : Size=1, Index=None, Active=True + 1 Var Declarations + v : Size=1, Index=None + Key : Lower : Value : Upper : Fixed : Stale : Domain + None : None : None : None : False : True : Reals + + 1 Declarations: v + +2 Arc Declarations + a1 : Size=1, Index=None, Active=False + Key : Ports : Directed : Active + None : (p1, p2) : True : False + a2 : Size=1, Index=None, Active=False + Key : Ports : Directed : Active + None : (p1, p3) : True : False + +3 Port Declarations + p1 : Size=1, Index=None + Key : Name : Size : Variable + None : v : 1 : x + p2 : Size=1, Index=None + Key : Name : Size : Variable + None : v : 1 : y + p3 : Size=1, Index=None + Key : Name : Size : Variable + None : v : 1 : z + +13 Declarations: x y z p1 p2 p3 a1 a2 a1_expanded a2_expanded p1_v_outsum p2_v_insum p3_v_insum +""") def test_extensive_single_var(self): m = ConcreteModel() @@ -1061,6 +1136,129 @@ def test_extensive_single_var(self): 13 Declarations: x y z p1 p2 p3 a1 a2 a1_expanded a2_expanded p1_v_outsum p2_v_insum p3_v_insum """) + def test_conservative_expansion(self): + m = ConcreteModel() + m.time = Set(initialize=[1, 2, 3]) + + m.source = Block() + m.load1 = Block() + m.load2 = Block() + + def source_block(b): + b.t = Set(initialize=[1, 2, 3]) + b.p_out = Var(b.t) + b.outlet = Port(initialize={'p': (b.p_out, Port.Conservative)}) + + def load_block(b): + b.t = Set(initialize=[1, 2, 3]) + b.p_in = Var(b.t) + b.inlet = Port(initialize={'p': (b.p_in, Port.Conservative)}) + + source_block(m.source) + load_block(m.load1) + load_block(m.load2) + + m.cs1 = Arc(source=m.source.outlet, destination=m.load1.inlet) + m.cs2 = Arc(source=m.source.outlet, destination=m.load2.inlet) + + TransformationFactory("network.expand_arcs").apply_to(m) + + os = StringIO() + m.pprint(ostream=os) + self.assertEqual(os.getvalue(), + '1 Set Declarations\n' + ' time : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3)\n' + ' [1, 2, 3]\n\n5 Block Declarations\n' + ' cs1_expanded : Size=1, Index=None, Active=True\n' + ' 1 Var Declarations\n' + ' p : Size=3, Index=source.t\n' + ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' + ' 1 : None : None : None : False : True : Reals\n' + ' 2 : None : None : None : False : True : Reals\n' + ' 3 : None : None : None : False : True : Reals\n\n' + ' 1 Declarations: p\n' + ' cs2_expanded : Size=1, Index=None, Active=True\n' + ' 1 Var Declarations\n' + ' p : Size=3, Index=source.t\n' + ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' + ' 1 : None : None : None : False : True : Reals\n' + ' 2 : None : None : None : False : True : Reals\n' + ' 3 : None : None : None : False : True : Reals\n\n' + ' 1 Declarations: p\n' + ' load1 : Size=1, Index=None, Active=True\n' + ' 1 Set Declarations\n' + ' t : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3)\n' + ' [1, 2, 3]\n\n' + ' 1 Var Declarations\n p_in : Size=3, Index=load1.t\n' + ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' + ' 1 : None : None : None : False : True : Reals\n' + ' 2 : None : None : None : False : True : Reals\n' + ' 3 : None : None : None : False : True : Reals\n\n' + ' 1 Constraint Declarations\n' + ' inlet_p_insum : Size=3, Index=source.t, Active=True\n' + ' Key : Lower : Body : Upper : Active\n' + ' 1 : 0.0 : cs1_expanded.p[1] - load1.p_in[1] : 0.0 : True\n' + ' 2 : 0.0 : cs1_expanded.p[2] - load1.p_in[2] : 0.0 : True\n' + ' 3 : 0.0 : cs1_expanded.p[3] - load1.p_in[3] : 0.0 : True\n\n' + ' 1 Port Declarations\n' + ' inlet : Size=1, Index=None\n' + ' Key : Name : Size : Variable\n' + ' None : p : 3 : load1.p_in\n\n' + ' 4 Declarations: t p_in inlet inlet_p_insum\n' + ' load2 : Size=1, Index=None, Active=True\n' + ' 1 Set Declarations\n' + ' t : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3)\n' + ' [1, 2, 3]\n\n' + ' 1 Var Declarations\n' + ' p_in : Size=3, Index=load2.t\n' + ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' + ' 1 : None : None : None : False : True : Reals\n' + ' 2 : None : None : None : False : True : Reals\n' + ' 3 : None : None : None : False : True : Reals\n\n' + ' 1 Constraint Declarations\n' + ' inlet_p_insum : Size=3, Index=source.t, Active=True\n' + ' Key : Lower : Body : Upper : Active\n' + ' 1 : 0.0 : cs2_expanded.p[1] - load2.p_in[1] : 0.0 : True\n' + ' 2 : 0.0 : cs2_expanded.p[2] - load2.p_in[2] : 0.0 : True\n' + ' 3 : 0.0 : cs2_expanded.p[3] - load2.p_in[3] : 0.0 : True\n\n' + ' 1 Port Declarations\n' + ' inlet : Size=1, Index=None\n' + ' Key : Name : Size : Variable\n' + ' None : p : 3 : load2.p_in\n\n' + ' 4 Declarations: t p_in inlet inlet_p_insum\n' + ' source : Size=1, Index=None, Active=True\n' + ' 1 Set Declarations\n' + ' t : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3)\n' + ' [1, 2, 3]\n\n' + ' 1 Var Declarations\n' + ' p_out : Size=3, Index=source.t\n' + ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' + ' 1 : None : None : None : False : True : Reals\n' + ' 2 : None : None : None : False : True : Reals\n' + ' 3 : None : None : None : False : True : Reals\n\n' + ' 1 Constraint Declarations\n' + ' outlet_p_outsum : Size=3, Index=source.t, Active=True\n' + ' Key : Lower : Body' + ' : Upper : Active\n' + ' 1 : 0.0 : cs1_expanded.p[1]' + ' + cs2_expanded.p[1] - source.p_out[1] : 0.0 : True\n' + ' 2 : 0.0 : cs1_expanded.p[2]' + ' + cs2_expanded.p[2] - source.p_out[2] : 0.0 : True\n' + ' 3 : 0.0 : cs1_expanded.p[3]' + ' + cs2_expanded.p[3] - source.p_out[3] : 0.0 : True\n\n' + ' 1 Port Declarations\n' + ' outlet : Size=1, Index=None\n' + ' Key : Name : Size : Variable\n' + ' None : p : 3 : source.p_out\n\n' + ' 4 Declarations: t p_out outlet outlet_p_outsum\n\n' + '2 Arc Declarations\n' + ' cs1 : Size=1, Index=None, Active=False\n' + ' Key : Ports : Directed : Active\n' + ' None : (source.outlet, load1.inlet) : True : False\n' + ' cs2 : Size=1, Index=None, Active=False\n' + ' Key : Ports : Directed : Active\n' + ' None : (source.outlet, load2.inlet) : True : False\n\n' + '8 Declarations: time source load1 load2 cs1 cs2 cs1_expanded cs2_expanded\n') def test_extensive_expansion(self): m = ConcreteModel() @@ -1675,4 +1873,4 @@ def test_extensive_expansion(self): if __name__ == "__main__": - unittest.main() + unittest.main() \ No newline at end of file From 23640557b573e8a3157b74fbb156a5d924b4dff8 Mon Sep 17 00:00:00 2001 From: REINBOLD Vincent Date: Mon, 18 Nov 2019 11:17:09 +0100 Subject: [PATCH 02/38] delete bounds and domain copies for variable replication --- pyomo/network/port.py | 8 ++------ pyomo/network/util.py | 23 ++++++++++++----------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/pyomo/network/port.py b/pyomo/network/port.py index ac6af4a3b7f..d117a8b4fb4 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -510,12 +510,8 @@ def Conservative(port, name, index_set): be conserved without fixing a split for inlets or fixing combinations for outlet. - :param port: - :param name: - :param index_set: - :param include_splitfrac: - :param write_var_sum: - :return: + It acts like Extensive but does not introduces a split variable + nor a split constraint. """ port_parent = port.parent_block() diff --git a/pyomo/network/util.py b/pyomo/network/util.py index b062d86427d..c4b3f25a054 100644 --- a/pyomo/network/util.py +++ b/pyomo/network/util.py @@ -25,14 +25,14 @@ def replicate_var(comp, name, block, index_set=None): index_set = UnindexedComponent_set var_args = {} - try: - var_args['domain'] = comp.domain - except AttributeError: - pass - try: - var_args['bounds'] = comp.bounds - except AttributeError: - pass + # try: + # var_args['domain'] = comp.domain + # except AttributeError: + # pass + # try: + # var_args['bounds'] = comp.bounds + # except AttributeError: + # pass new_var = Var(index_set, **var_args) block.add_component(name, new_var) @@ -40,9 +40,10 @@ def replicate_var(comp, name, block, index_set=None): for i in index_set: try: # set bounds for every member in case they differ - new_var[i].domain = comp[i].domain - new_var[i].setlb(comp[i].lb) - new_var[i].setub(comp[i].ub) + pass + # new_var[i].domain = comp[i].domain + # new_var[i].setlb(comp[i].lb) + # new_var[i].setub(comp[i].ub) except AttributeError: break From f9d768fb0a5452d55f6f3ee60c2d5773fd078ed1 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Wed, 22 Jan 2020 20:39:45 -0500 Subject: [PATCH 03/38] incremental improvements to GDPopt --- pyomo/contrib/gdpopt/GDPopt.py | 7 +++- pyomo/contrib/gdpopt/branch_and_bound.py | 41 ++++++++++++++++++++++-- pyomo/contrib/gdpopt/iterate.py | 5 +-- pyomo/contrib/gdpopt/mip_solve.py | 12 +++++-- pyomo/contrib/gdpopt/nlp_solve.py | 40 ++++++++++++++++++++--- 5 files changed, 94 insertions(+), 11 deletions(-) diff --git a/pyomo/contrib/gdpopt/GDPopt.py b/pyomo/contrib/gdpopt/GDPopt.py index 3be11d88380..4ac581020ad 100644 --- a/pyomo/contrib/gdpopt/GDPopt.py +++ b/pyomo/contrib/gdpopt/GDPopt.py @@ -1,6 +1,11 @@ # -*- coding: utf-8 -*- """Main driver module for GDPopt solver. +20.1.22 changes: +- improved subsolver time limit support for GAMS interface +- add maxTimeLimit exit condition for GDPopt-LBB +- add token Big M for reactivated constraints in GDPopt-LBB +- activate fbbt for branch-and-bound nodes 20.1.15 changes: - internal cleanup of codebase - merge GDPbb capabilities (logic-based branch and bound) @@ -43,7 +48,7 @@ setup_solver_environment) from pyomo.opt.base import SolverFactory -__version__ = (20, 1, 15) # Note: date-based version number +__version__ = (20, 1, 22) # Note: date-based version number @SolverFactory.register( diff --git a/pyomo/contrib/gdpopt/branch_and_bound.py b/pyomo/contrib/gdpopt/branch_and_bound.py index 2361eac293a..bed740da6fd 100644 --- a/pyomo/contrib/gdpopt/branch_and_bound.py +++ b/pyomo/contrib/gdpopt/branch_and_bound.py @@ -2,7 +2,9 @@ from collections import namedtuple from heapq import heappush, heappop -from pyomo.contrib.gdpopt.util import copy_var_list_values, SuppressInfeasibleWarning +from pyomo.common.errors import InfeasibleConstraintException +from pyomo.contrib.fbbt.fbbt import fbbt +from pyomo.contrib.gdpopt.util import copy_var_list_values, SuppressInfeasibleWarning, get_main_elapsed_time from pyomo.contrib.satsolver.satsolver import satisfiable from pyomo.core import minimize, Suffix, Constraint, ComponentMap, TransformationFactory from pyomo.opt import SolverFactory, SolverStatus @@ -117,6 +119,25 @@ def _perform_branch_and_bound(solve_data): node_data, node_model = heappop(queue) config.logger.info("Nodes: %s LB %.10g Unbranched %s" % ( solve_data.explored_nodes, node_data.obj_lb, node_data.num_unbranched_disjunctions)) + + # Check time limit + elapsed = get_main_elapsed_time(solve_data.timing) + if elapsed >= config.time_limit: + config.logger.info( + 'GDPopt-LBB unable to converge bounds ' + 'before time limit of {} seconds. ' + 'Elapsed: {} seconds' + .format(config.time_limit, elapsed)) + no_feasible_soln = float('inf') + solve_data.LB = node_data.obj_lb if solve_data.objective_sense == minimize else -no_feasible_soln + solve_data.UB = no_feasible_soln if solve_data.objective_sense == minimize else -node_data.obj_lb + config.logger.info( + 'Final bound values: LB: {} UB: {}'. + format(solve_data.LB, solve_data.UB)) + solve_data.results.solver.termination_condition = tc.maxTimeLimit + return True + + # Handle current node if not node_data.is_screened: # Node has not been evaluated. solve_data.explored_nodes += 1 @@ -177,6 +198,7 @@ def _branch_on_node(node_data, node_model, solve_data): fixed_True_disjunct = child_unfixed_disjuncts[disjunct_index_to_fix_True] for constr in child_model.GDPopt_utils.disjunct_to_nonlinear_constraints.get(fixed_True_disjunct, ()): constr.activate() + child_model.BigM[constr] = 1 # set arbitrary BigM (ok, because we fix corresponding Y=True) del child_model.GDPopt_utils.disjunction_to_unfixed_disjuncts[child_disjunction_to_branch] for child_disjunct in child_unfixed_disjuncts: @@ -243,7 +265,22 @@ def _solve_rnGDP_subproblem(model, solve_data): try: with SuppressInfeasibleWarning(): - result = SolverFactory(config.minlp_solver).solve(subproblem, **config.minlp_solver_args) + try: + fbbt(subproblem, integer_tol=config.integer_tolerance) + except InfeasibleConstraintException: + copy_var_list_values( # copy variable values, even if errored + from_list=subproblem.GDPopt_utils.variable_list, + to_list=model.GDPopt_utils.variable_list, + config=config, ignore_integrality=True + ) + return float('inf'), float('inf') + minlp_args = dict(config.minlp_solver_args) + if config.minlp_solver == 'gams': + elapsed = get_main_elapsed_time(solve_data.timing) + remaining = max(config.time_limit - elapsed, 1) + minlp_args['add_options'] = minlp_args.get('add_options', []) + minlp_args['add_options'].append('option reslim=%s;' % remaining) + result = SolverFactory(config.minlp_solver).solve(subproblem, **minlp_args) except RuntimeError as e: config.logger.warning( "Solver encountered RuntimeError. Treating as infeasible. " diff --git a/pyomo/contrib/gdpopt/iterate.py b/pyomo/contrib/gdpopt/iterate.py index 0c2c8cc0448..cc9e09a25c3 100644 --- a/pyomo/contrib/gdpopt/iterate.py +++ b/pyomo/contrib/gdpopt/iterate.py @@ -90,12 +90,13 @@ def algorithm_should_terminate(solve_data, config): return True # Check time limit - if get_main_elapsed_time(solve_data.timing) >= config.time_limit: + elapsed = get_main_elapsed_time(solve_data.timing) + if elapsed >= config.time_limit: config.logger.info( 'GDPopt unable to converge bounds ' 'before time limit of {} seconds. ' 'Elapsed: {} seconds' - .format(config.time_limit, get_main_elapsed_time(solve_data.timing))) + .format(config.time_limit, elapsed)) config.logger.info( 'Final bound values: LB: {} UB: {}'. format(solve_data.LB, solve_data.UB)) diff --git a/pyomo/contrib/gdpopt/mip_solve.py b/pyomo/contrib/gdpopt/mip_solve.py index 93171d25838..786914b34bd 100644 --- a/pyomo/contrib/gdpopt/mip_solve.py +++ b/pyomo/contrib/gdpopt/mip_solve.py @@ -7,7 +7,7 @@ from pyomo.common.errors import InfeasibleConstraintException from pyomo.contrib.fbbt.fbbt import fbbt from pyomo.contrib.gdpopt.data_class import MasterProblemResult -from pyomo.contrib.gdpopt.util import SuppressInfeasibleWarning, _DoNothing +from pyomo.contrib.gdpopt.util import SuppressInfeasibleWarning, _DoNothing, get_main_elapsed_time from pyomo.core import (Block, Expression, Objective, TransformationFactory, Var, minimize, value, Constraint) from pyomo.gdp import Disjunct @@ -75,8 +75,16 @@ def solve_linear_GDP(linear_GDP_model, solve_data, config): try: with SuppressInfeasibleWarning(): + mip_args = dict(config.mip_solver_args) + elapsed = get_main_elapsed_time(solve_data.timing) + remaining = max(config.time_limit - elapsed, 1) + if config.mip_solver == 'gams': + mip_args['add_options'] = mip_args.get('add_options', []) + mip_args['add_options'].append('option reslim=%s;' % remaining) + elif config.mip_solver == 'multisolve': + mip_args['time_limit'] = min(mip_args.get('time_limit', float('inf')), remaining) results = SolverFactory(config.mip_solver).solve( - m, **config.mip_solver_args) + m, **mip_args) except RuntimeError as e: if 'GAMS encountered an error during solve.' in str(e): config.logger.warning("GAMS encountered an error in solve. Treating as infeasible.") diff --git a/pyomo/contrib/gdpopt/nlp_solve.py b/pyomo/contrib/gdpopt/nlp_solve.py index 1108d69f60c..78f4be9cfa7 100644 --- a/pyomo/contrib/gdpopt/nlp_solve.py +++ b/pyomo/contrib/gdpopt/nlp_solve.py @@ -7,7 +7,7 @@ from pyomo.contrib.fbbt.fbbt import fbbt from pyomo.contrib.gdpopt.data_class import SubproblemResult from pyomo.contrib.gdpopt.util import (SuppressInfeasibleWarning, - is_feasible) + is_feasible, get_main_elapsed_time) from pyomo.core import Constraint, TransformationFactory, minimize, value, Objective from pyomo.core.expr import current as EXPR from pyomo.core.kernel.component_set import ComponentSet @@ -41,7 +41,15 @@ def solve_linear_subproblem(mip_model, solve_data, config): if not mip_solver.available(): raise RuntimeError("MIP solver %s is not available." % config.mip_solver) with SuppressInfeasibleWarning(): - results = mip_solver.solve(mip_model, **config.mip_solver_args) + mip_args = dict(config.mip_solver_args) + elapsed = get_main_elapsed_time(solve_data.timing) + remaining = max(config.time_limit - elapsed, 1) + if config.mip_solver == 'gams': + mip_args['add_options'] = mip_args.get('add_options', []) + mip_args['add_options'].append('option reslim=%s;' % remaining) + elif config.mip_solver == 'multisolve': + mip_args['time_limit'] = min(mip_args.get('time_limit', float('inf')), remaining) + results = mip_solver.solve(mip_model, **mip_args) subprob_result = SubproblemResult() subprob_result.feasible = True @@ -96,7 +104,15 @@ def solve_NLP(nlp_model, solve_data, config): config.nlp_solver) with SuppressInfeasibleWarning(): try: - results = nlp_solver.solve(nlp_model, **config.nlp_solver_args) + nlp_args = dict(config.nlp_solver_args) + elapsed = get_main_elapsed_time(solve_data.timing) + remaining = max(config.time_limit - elapsed, 1) + if config.nlp_solver == 'gams': + nlp_args['add_options'] = nlp_args.get('add_options', []) + nlp_args['add_options'].append('option reslim=%s;' % remaining) + elif config.nlp_solver == 'multisolve': + nlp_args['time_limit'] = min(nlp_args.get('time_limit', float('inf')), remaining) + results = nlp_solver.solve(nlp_model, **nlp_args) except ValueError as err: if 'Cannot load a SolverResults object with bad status: error' in str(err): results = SolverResults() @@ -187,7 +203,15 @@ def solve_MINLP(model, solve_data, config): raise RuntimeError("MINLP solver %s is not available." % config.minlp_solver) with SuppressInfeasibleWarning(): - results = minlp_solver.solve(model, **config.minlp_solver_args) + minlp_args = dict(config.minlp_solver_args) + elapsed = get_main_elapsed_time(solve_data.timing) + remaining = max(config.time_limit - elapsed, 1) + if config.minlp_solver == 'gams': + minlp_args['add_options'] = minlp_args.get('add_options', []) + minlp_args['add_options'].append('option reslim=%s;' % remaining) + elif config.minlp_solver == 'multisolve': + minlp_args['time_limit'] = min(minlp_args.get('time_limit', float('inf')), remaining) + results = minlp_solver.solve(model, **minlp_args) subprob_result = SubproblemResult() subprob_result.feasible = True @@ -214,6 +238,14 @@ def solve_MINLP(model, solve_data, config): 'Using potentially suboptimal feasible solution.') else: subprob_result.feasible = False + elif term_cond == tc.maxTimeLimit: + config.logger.info('MINLP subproblem failed to converge within time limit.') + if is_feasible(model, config): + config.logger.info( + 'MINLP solution is still feasible. ' + 'Using potentially suboptimal feasible solution.') + else: + subprob_result.feasible = False elif term_cond == tc.intermediateNonInteger: config.logger.info( "MINLP solver could not find feasible integer solution: %s" % results.solver.message) From edb99d50a4044328b467d1a2aa87e13f3cee5b6d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 30 Jan 2020 00:28:24 -0700 Subject: [PATCH 04/38] Remove Port.Conservative, be more selective for evar bounds This removes the Conservative rule and its helper methods and instead changes the handling of include_splitfrac to that specifying include_splitfrac=False will prevent the split fraction variables from being created. This also changes how expanded arc variables' domains and bounds are set, so that the domain is only tightened when the arc has a single source or single destination. --- pyomo/network/port.py | 110 ++++++++------------------------ pyomo/network/tests/test_arc.py | 70 ++++++++++---------- pyomo/network/util.py | 67 ++++++++++++------- 3 files changed, 107 insertions(+), 140 deletions(-) diff --git a/pyomo/network/port.py b/pyomo/network/port.py index 93f94461ff3..4f12fce20f8 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -30,7 +30,7 @@ IPyomoScriptModifyInstance, TransformationFactory from pyomo.core.kernel.component_map import ComponentMap -from pyomo.network.util import replicate_var +from pyomo.network.util import create_var, tighten_var_domain logger = logging.getLogger('pyomo.network') @@ -205,10 +205,6 @@ def is_extensive(self, name): """Return True if the rule for this port member is Port.Extensive""" return self.rule_for(name) is Port.Extensive - def is_conservative(self, name): - """Return True if the rule for this port member is Port.Conservative""" - return self.rule_for(name) is Port.Conservative - def fix(self): """ Fix all variables in the port at their current values. @@ -385,13 +381,23 @@ def _add_from_container(self, port, items): if type(items) is dict: for key, val in iteritems(items): if type(val) is tuple: - port.add(val[0], key, val[1]) + if len(val) == 2: + obj, rule = val + port.add(obj, key, rule) + else: + obj, rule, kwds = val + port.add(obj, key, rule, **kwds) else: port.add(val, key) else: for val in self._initialize: if type(val) is tuple: - port.add(val[0], rule=val[1]) + if len(val) == 2: + obj, rule = val + port.add(obj, rule=rule) + else: + obj, rule, kwds = val + port.add(obj, rule=rule, **kwds) else: port.add(val) @@ -452,7 +458,7 @@ def Equality(port, name, index_set): Port._add_equality_constraint(arc, name, index_set) @staticmethod - def Extensive(port, name, index_set, include_splitfrac=False, + def Extensive(port, name, index_set, include_splitfrac=None, write_var_sum=True): """ Arc Expansion procedure for extensive variable properties @@ -501,23 +507,6 @@ def Extensive(port, name, index_set, include_splitfrac=False, include_splitfrac=include_splitfrac, write_var_sum=write_var_sum) in_vars = Port._Combine(port, name, index_set) - @staticmethod - def Conservative(port, name, index_set): - """ - Arc Expansion procedure for conservative variable properties. - - This procedure is the rule to use when variable quantities should - be conserved without fixing a split for inlets or fixing combinations - for outlet. - - It acts like Extensive but does not introduces a split variable - nor a split constraint. - """ - - port_parent = port.parent_block() - out_vars = Port._Split_Conservative(port, name, index_set) - in_vars = Port._Combine(port, name, index_set) - @staticmethod def _Combine(port, name, index_set): port_parent = port.parent_block() @@ -541,6 +530,9 @@ def _Combine(port, name, index_set): evar = Port._create_evar(port.vars[name], name, eblock, index_set) in_vars.append(evar) + if len(sources) == 1: + tighten_var_domain(port.vars[name], in_vars[0], index_set) + # Create constraint: var == sum of evars # Same logic as Port._Split cname = unique_component_name(port_parent, "%s_%s_insum" % @@ -556,12 +548,11 @@ def rule(m, *args): return in_vars @staticmethod - def _Split(port, name, index_set, include_splitfrac=False, + def _Split(port, name, index_set, include_splitfrac=None, write_var_sum=True): port_parent = port.parent_block() var = port.vars[name] out_vars = [] - no_splitfrac = False dests = port.dests(active=True) if not len(dests): @@ -577,7 +568,8 @@ def _Split(port, name, index_set, include_splitfrac=False, "Cannot fix splitfrac not at 1 for port '%s' with a " "single dest '%s'" % (port.name, dests[0].name)) - no_splitfrac = True + if include_splitfrac is not True: + include_splitfrac = False if len(dests[0].destination.sources(active=True)) == 1: # This is a 1-to-1 connection, no need for evar, just equality. @@ -592,7 +584,7 @@ def _Split(port, name, index_set, include_splitfrac=False, evar = Port._create_evar(port.vars[name], name, eblock, index_set) out_vars.append(evar) - if no_splitfrac: + if include_splitfrac is False: continue # Create and potentially initialize split fraction variables. @@ -627,7 +619,7 @@ def _Split(port, name, index_set, include_splitfrac=False, "splitfracs, please pass the " " include_splitfrac=True argument." % (port.name, arc.name)) - no_splitfrac = True + include_splitfrac = False continue eblock.splitfrac = Var() @@ -647,6 +639,9 @@ def rule(m, *args): con = Constraint(index_set, rule=rule) eblock.add_component(cname, con) + if len(dests) == 1: + tighten_var_domain(port.vars[name], out_vars[0], index_set) + if write_var_sum: # Create var total sum constraint: var == sum of evars # Need to alphanum port name in case it is indexed. @@ -661,7 +656,7 @@ def rule(m, *args): port_parent.add_component(cname, con) else: # OR create constraint on splitfrac vars: sum == 1 - if no_splitfrac: + if include_splitfrac is False: raise ValueError( "Cannot choose to write split fraction sum constraint for " "ports with a single destination or a single Extensive " @@ -676,56 +671,6 @@ def rule(m, *args): return out_vars - @staticmethod - def _Split_Conservative(port, name, index_set): - port_parent = port.parent_block() - var = port.vars[name] - out_vars = [] - no_splitfrac = False - dests = port.dests(active=True) - - if not len(dests): - return out_vars - - if len(dests) == 1: - # No need for splitting on one outlet. - # Make sure they do not try to fix splitfrac not at 1. - splitfracspec = port.get_split_fraction(dests[0]) - if splitfracspec is not None: - if splitfracspec[0] != 1 and splitfracspec[1] is True: - raise ValueError( - "Cannot fix splitfrac not at 1 for port '%s' with a " - "single dest '%s'" % (port.name, dests[0].name)) - - if len(dests[0].destination.sources(active=True)) == 1: - # This is a 1-to-1 connection, no need for evar, just equality. - arc = dests[0] - Port._add_equality_constraint(arc, name, index_set) - return out_vars - - for arc in dests: - eblock = arc.expanded_block - - # Make and record new variables for every arc with this member. - evar = Port._create_evar(port.vars[name], name, eblock, index_set) - out_vars.append(evar) - - # Create var total sum constraint: var == sum of evars - # Need to alphanum port name in case it is indexed. - cname = unique_component_name(port_parent, "%s_%s_outsum" % - (alphanum_label_from_name(port.local_name), name)) - - def rule(m, *args): - if len(args): - return sum(evar[args] for evar in out_vars) == var[args] - else: - return sum(evar for evar in out_vars) == var - - con = Constraint(index_set, rule=rule) - port_parent.add_component(cname, con) - - return out_vars - @staticmethod def _add_equality_constraint(arc, name, index_set): # This function will add the equality constraint if it doesn't exist. @@ -751,10 +696,9 @@ def _create_evar(member, name, eblock, index_set): # before making a new one. evar = eblock.component(name) if evar is None: - evar = replicate_var(member, name, eblock, index_set) + evar = create_var(member, name, eblock, index_set) return evar - class SimplePort(Port, _PortData): def __init__(self, *args, **kwd): diff --git a/pyomo/network/tests/test_arc.py b/pyomo/network/tests/test_arc.py index 4b610798be2..19375af5217 100644 --- a/pyomo/network/tests/test_arc.py +++ b/pyomo/network/tests/test_arc.py @@ -984,14 +984,14 @@ def test_inactive(self): 1 Declarations: v_equality """) - def test_conservative_single_var(self): + def test_extensive_no_splitfrac_single_var(self): m = ConcreteModel() m.x = Var() m.y = Var() m.z = Var() - m.p1 = Port(initialize={'v': (m.x, Port.Conservative)}) - m.p2 = Port(initialize={'v': (m.y, Port.Conservative)}) - m.p3 = Port(initialize={'v': (m.z, Port.Conservative)}) + m.p1 = Port(initialize={'v': (m.x, Port.Extensive, {'include_splitfrac':False})}) + m.p2 = Port(initialize={'v': (m.y, Port.Extensive, {'include_splitfrac':False})}) + m.p3 = Port(initialize={'v': (m.z, Port.Extensive, {'include_splitfrac':False})}) m.a1 = Arc(source=m.p1, destination=m.p2) m.a2 = Arc(source=m.p1, destination=m.p3) @@ -1136,7 +1136,7 @@ def test_extensive_single_var(self): 13 Declarations: x y z p1 p2 p3 a1 a2 a1_expanded a2_expanded p1_v_outsum p2_v_insum p3_v_insum """) - def test_conservative_expansion(self): + def test_extensive_no_splitfrac_expansion(self): m = ConcreteModel() m.time = Set(initialize=[1, 2, 3]) @@ -1147,12 +1147,12 @@ def test_conservative_expansion(self): def source_block(b): b.t = Set(initialize=[1, 2, 3]) b.p_out = Var(b.t) - b.outlet = Port(initialize={'p': (b.p_out, Port.Conservative)}) + b.outlet = Port(initialize={'p': (b.p_out, Port.Extensive, {'include_splitfrac':False})}) def load_block(b): b.t = Set(initialize=[1, 2, 3]) b.p_in = Var(b.t) - b.inlet = Port(initialize={'p': (b.p_in, Port.Conservative)}) + b.inlet = Port(initialize={'p': (b.p_in, Port.Extensive, {'include_splitfrac':False})}) source_block(m.source) load_block(m.load1) @@ -1530,9 +1530,9 @@ def test_extensive_expansion(self): 3 Var Declarations flow : Size=3, Index=comp Key : Lower : Value : Upper : Fixed : Stale : Domain - a : 0 : None : None : False : True : NonNegativeReals - b : 0 : None : None : False : True : NonNegativeReals - c : 0 : None : None : False : True : NonNegativeReals + a : None : None : None : False : True : Reals + b : None : None : None : False : True : Reals + c : None : None : None : False : True : Reals mass : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals @@ -1558,9 +1558,9 @@ def test_extensive_expansion(self): 3 Var Declarations flow : Size=3, Index=comp Key : Lower : Value : Upper : Fixed : Stale : Domain - a : 0 : None : None : False : True : NonNegativeReals - b : 0 : None : None : False : True : NonNegativeReals - c : 0 : None : None : False : True : NonNegativeReals + a : None : None : None : False : True : Reals + b : None : None : None : False : True : Reals + c : None : None : None : False : True : Reals mass : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals @@ -1586,9 +1586,9 @@ def test_extensive_expansion(self): 3 Var Declarations flow : Size=3, Index=comp Key : Lower : Value : Upper : Fixed : Stale : Domain - a : 0 : None : None : False : True : NonNegativeReals - b : 0 : None : None : False : True : NonNegativeReals - c : 0 : None : None : False : True : NonNegativeReals + a : None : None : None : False : True : Reals + b : None : None : None : False : True : Reals + c : None : None : None : False : True : Reals mass : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals @@ -1614,9 +1614,9 @@ def test_extensive_expansion(self): 3 Var Declarations flow : Size=3, Index=comp Key : Lower : Value : Upper : Fixed : Stale : Domain - a : 0 : None : None : False : True : NonNegativeReals - b : 0 : None : None : False : True : NonNegativeReals - c : 0 : None : None : False : True : NonNegativeReals + a : 0 : None : None : False : True : Reals + b : 0 : None : None : False : True : Reals + c : 0 : None : None : False : True : Reals mass : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals @@ -1657,9 +1657,9 @@ def test_extensive_expansion(self): 3 Var Declarations flow : Size=3, Index=comp Key : Lower : Value : Upper : Fixed : Stale : Domain - a : 0 : None : None : False : True : NonNegativeReals - b : 0 : None : None : False : True : NonNegativeReals - c : 0 : None : None : False : True : NonNegativeReals + a : None : None : None : False : True : Reals + b : None : None : None : False : True : Reals + c : None : None : None : False : True : Reals mass : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals @@ -1685,9 +1685,9 @@ def test_extensive_expansion(self): 2 Var Declarations flow : Size=3, Index=comp Key : Lower : Value : Upper : Fixed : Stale : Domain - a : 0 : None : None : False : True : NonNegativeReals - b : 0 : None : None : False : True : NonNegativeReals - c : 0 : None : None : False : True : NonNegativeReals + a : 0 : None : None : False : True : Reals + b : 0 : None : None : False : True : Reals + c : 0 : None : None : False : True : Reals mass : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals @@ -1702,9 +1702,9 @@ def test_extensive_expansion(self): 3 Var Declarations flow : Size=3, Index=comp Key : Lower : Value : Upper : Fixed : Stale : Domain - a : 0 : None : None : False : True : NonNegativeReals - b : 0 : None : None : False : True : NonNegativeReals - c : 0 : None : None : False : True : NonNegativeReals + a : None : None : None : False : True : Reals + b : None : None : None : False : True : Reals + c : None : None : None : False : True : Reals mass : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals @@ -1730,9 +1730,9 @@ def test_extensive_expansion(self): 3 Var Declarations flow : Size=3, Index=comp Key : Lower : Value : Upper : Fixed : Stale : Domain - a : 0 : None : None : False : True : NonNegativeReals - b : 0 : None : None : False : True : NonNegativeReals - c : 0 : None : None : False : True : NonNegativeReals + a : None : None : None : False : True : Reals + b : None : None : None : False : True : Reals + c : None : None : None : False : True : Reals mass : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals @@ -1758,9 +1758,9 @@ def test_extensive_expansion(self): 3 Var Declarations flow : Size=3, Index=comp Key : Lower : Value : Upper : Fixed : Stale : Domain - a : 0 : None : None : False : True : NonNegativeReals - b : 0 : None : None : False : True : NonNegativeReals - c : 0 : None : None : False : True : NonNegativeReals + a : None : None : None : False : True : Reals + b : None : None : None : False : True : Reals + c : None : None : None : False : True : Reals mass : Size=1, Index=None Key : Lower : Value : Upper : Fixed : Stale : Domain None : None : None : None : False : True : Reals @@ -1873,4 +1873,4 @@ def test_extensive_expansion(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/pyomo/network/util.py b/pyomo/network/util.py index c4b3f25a054..73cb0bc6ef7 100644 --- a/pyomo/network/util.py +++ b/pyomo/network/util.py @@ -11,40 +11,63 @@ from pyomo.core import Var from pyomo.core.base.indexed_component import UnindexedComponent_set -def replicate_var(comp, name, block, index_set=None): - """ - Create a new variable that will have the same indexing set, domain, - and bounds as the provided component, and add it to the given block. - Optionally pass an index set to use that to build the variable, but - this set must be symmetric to comp's index set. - """ +def create_var(comp, name, block, index_set=None): if index_set is None: if comp.is_indexed(): index_set = comp.index_set() else: index_set = UnindexedComponent_set - var_args = {} - # try: - # var_args['domain'] = comp.domain - # except AttributeError: - # pass - # try: - # var_args['bounds'] = comp.bounds - # except AttributeError: - # pass - - new_var = Var(index_set, **var_args) + new_var = Var(index_set) block.add_component(name, new_var) + return new_var + +def _tighten(src, dest): + starting_lb = dest.lb + starting_ub = dest.ub + if not src.is_continuous(): + dest.domain = src.domain + if src.lb is not None: + if starting_lb is None: + dest.setlb(src.lb) + else: + dest.setlb(max(starting_lb, src.lb)) + if src.ub is not None: + if starting_ub is None: + dest.setub(src.ub) + else: + dest.setub(min(starting_ub, src.ub)) + +def tighten_var_domain(comp, new_var, index_set=None): + if index_set is None: + if comp.is_indexed(): + index_set = comp.index_set() + else: + index_set = UnindexedComponent_set + if comp.is_indexed(): for i in index_set: try: # set bounds for every member in case they differ - pass - # new_var[i].domain = comp[i].domain - # new_var[i].setlb(comp[i].lb) - # new_var[i].setub(comp[i].ub) + _tighten(comp[i], new_var[i]) except AttributeError: break + else: + try: + # set bounds for every member in case they differ + _tighten(comp, new_var) + except AttributeError: + pass return new_var + +def replicate_var(comp, name, block, index_set=None): + """ + Create a new variable that will have the same indexing set, domain, + and bounds as the provided component, and add it to the given block. + Optionally pass an index set to use that to build the variable, but + this set must be symmetric to comp's index set. + """ + new_var = create_var(comp, name, block, index_set) + tighten_var_domain(comp, new_var, index_set) + return new_var From db90846ebd2e9a1b595bd4b69f4cc5e6f19051be Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Mon, 10 Feb 2020 21:50:03 -0500 Subject: [PATCH 05/38] Changing GDP examples to use build_model() function instead of direct import-time execution. --- examples/gdp/batchProcessing.py | 438 ++++---- examples/gdp/disease_model.py | 228 ++-- examples/gdp/jobshop-nodisjuncts.py | 62 +- examples/gdp/jobshop.py | 84 +- examples/gdp/medTermPurchasing_Literal.py | 1167 +++++++++++---------- examples/gdp/simple1.py | 53 +- examples/gdp/simple2.py | 42 +- examples/gdp/simple3.py | 58 +- 8 files changed, 1076 insertions(+), 1056 deletions(-) diff --git a/examples/gdp/batchProcessing.py b/examples/gdp/batchProcessing.py index 160cf17e722..c574bbc5914 100644 --- a/examples/gdp/batchProcessing.py +++ b/examples/gdp/batchProcessing.py @@ -10,221 +10,223 @@ because the _opt file is different (It has hard-coded bigM parameters so that each constraint has the "optimal" bigM).''' -model = AbstractModel() - -# TODO: it looks like they set a bigM for each j. Which I need to look up how to do... -model.BigM = Suffix(direction=Suffix.LOCAL) -model.BigM[None] = 1000 - - -## Constants from GAMS -StorageTankSizeFactor = 2*5 # btw, I know 2*5 is 10... I don't know why it's written this way in GAMS? -StorageTankSizeFactorByProd = 3 -MinFlow = -log(10000) -VolumeLB = log(300) -VolumeUB = log(3500) -StorageTankSizeLB = log(100) -StorageTankSizeUB = log(15000) -UnitsInPhaseUB = log(6) -UnitsOutOfPhaseUB = log(6) -# TODO: YOU ARE HERE. YOU HAVEN'T ACTUALLY MADE THESE THE BOUNDS YET, NOR HAVE YOU FIGURED OUT WHOSE -# BOUNDS THEY ARE. AND THERE ARE MORE IN GAMS. - - -########## -# Sets -########## - -model.PRODUCTS = Set() -model.STAGES = Set(ordered=True) -model.PARALLELUNITS = Set(ordered=True) - -# TODO: this seems like an over-complicated way to accomplish this task... -def filter_out_last(model, j): - return j != model.STAGES.last() -model.STAGESExceptLast = Set(initialize=model.STAGES, filter=filter_out_last) - - -# TODO: these aren't in the formulation?? -#model.STORAGE_TANKS = Set() - - -############### -# Parameters -############### - -model.HorizonTime = Param() -model.Alpha1 = Param() -model.Alpha2 = Param() -model.Beta1 = Param() -model.Beta2 = Param() - -model.ProductionAmount = Param(model.PRODUCTS) -model.ProductSizeFactor = Param(model.PRODUCTS, model.STAGES) -model.ProcessingTime = Param(model.PRODUCTS, model.STAGES) - -# These are hard-coded in the GAMS file, hence the defaults -model.StorageTankSizeFactor = Param(model.STAGES, default=StorageTankSizeFactor) -model.StorageTankSizeFactorByProd = Param(model.PRODUCTS, model.STAGES, - default=StorageTankSizeFactorByProd) - -# TODO: bonmin wasn't happy and I think it might have something to do with this? -# or maybe issues with convexity or a lack thereof... I don't know yet. -# I made PRODUCTS ordered so I could do this... Is that bad? And it does index -# from 1, right? -def get_log_coeffs(model, k): - return log(model.PARALLELUNITS.ord(k)) - -model.LogCoeffs = Param(model.PARALLELUNITS, initialize=get_log_coeffs) - -# bounds -model.volumeLB = Param(model.STAGES, default=VolumeLB) -model.volumeUB = Param(model.STAGES, default=VolumeUB) -model.storageTankSizeLB = Param(model.STAGES, default=StorageTankSizeLB) -model.storageTankSizeUB = Param(model.STAGES, default=StorageTankSizeUB) -model.unitsInPhaseUB = Param(model.STAGES, default=UnitsInPhaseUB) -model.unitsOutOfPhaseUB = Param(model.STAGES, default=UnitsOutOfPhaseUB) - - -################ -# Variables -################ - -# TODO: right now these match the formulation. There are more in GAMS... - -# unit size of stage j -# model.volume = Var(model.STAGES) -# # TODO: GAMS has a batch size indexed just by products that isn't in the formulation... I'm going -# # to try to avoid it for the moment... -# # batch size of product i at stage j -# model.batchSize = Var(model.PRODUCTS, model.STAGES) -# # TODO: this is different in GAMS... They index by stages too? -# # cycle time of product i divided by batch size of product i -# model.cycleTime = Var(model.PRODUCTS) -# # number of units in parallel out-of-phase (or in phase) at stage j -# model.unitsOutOfPhase = Var(model.STAGES) -# model.unitsInPhase = Var(model.STAGES) -# # TODO: what are we going to do as a boundary condition here? For that last stage? -# # size of intermediate storage tank between stage j and j+1 -# model.storageTankSize = Var(model.STAGES) - -# variables for convexified problem -# TODO: I am beginning to think these are my only variables actually. -# GAMS never un-logs them, I don't think. And I think the GAMs ones -# must be the log ones. -def get_volume_bounds(model, j): - return (model.volumeLB[j], model.volumeUB[j]) -model.volume_log = Var(model.STAGES, bounds=get_volume_bounds) -model.batchSize_log = Var(model.PRODUCTS, model.STAGES) -model.cycleTime_log = Var(model.PRODUCTS) -def get_unitsOutOfPhase_bounds(model, j): - return (0, model.unitsOutOfPhaseUB[j]) -model.unitsOutOfPhase_log = Var(model.STAGES, bounds=get_unitsOutOfPhase_bounds) -def get_unitsInPhase_bounds(model, j): - return (0, model.unitsInPhaseUB[j]) -model.unitsInPhase_log = Var(model.STAGES, bounds=get_unitsInPhase_bounds) -def get_storageTankSize_bounds(model, j): - return (model.storageTankSizeLB[j], model.storageTankSizeUB[j]) -# TODO: these bounds make it infeasible... -model.storageTankSize_log = Var(model.STAGES, bounds=get_storageTankSize_bounds) - -# binary variables for deciding number of parallel units in and out of phase -model.outOfPhase = Var(model.STAGES, model.PARALLELUNITS, within=Binary) -model.inPhase = Var(model.STAGES, model.PARALLELUNITS, within=Binary) - -############### -# Objective -############### - -def get_cost_rule(model): - return model.Alpha1 * sum(exp(model.unitsInPhase_log[j] + model.unitsOutOfPhase_log[j] + \ - model.Beta1 * model.volume_log[j]) for j in model.STAGES) +\ - model.Alpha2 * sum(exp(model.Beta2 * model.storageTankSize_log[j]) for j in model.STAGESExceptLast) -model.min_cost = Objective(rule=get_cost_rule) - - -############## -# Constraints -############## - -def processing_capacity_rule(model, j, i): - return model.volume_log[j] >= log(model.ProductSizeFactor[i, j]) + model.batchSize_log[i, j] - \ - model.unitsInPhase_log[j] -model.processing_capacity = Constraint(model.STAGES, model.PRODUCTS, rule=processing_capacity_rule) - -def processing_time_rule(model, j, i): - return model.cycleTime_log[i] >= log(model.ProcessingTime[i, j]) - model.batchSize_log[i, j] - \ - model.unitsOutOfPhase_log[j] -model.processing_time = Constraint(model.STAGES, model.PRODUCTS, rule=processing_time_rule) - -def finish_in_time_rule(model): - return model.HorizonTime >= sum(model.ProductionAmount[i]*exp(model.cycleTime_log[i]) \ - for i in model.PRODUCTS) -model.finish_in_time = Constraint(rule=finish_in_time_rule) - - -############### -# Disjunctions -############### - -def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): - model = disjunct.model() - def volume_stage_j_rule(disjunct, i): - return model.storageTankSize_log[j] >= log(model.StorageTankSizeFactor[j]) + \ - model.batchSize_log[i, j] - def volume_stage_jPlus1_rule(disjunct, i): - return model.storageTankSize_log[j] >= log(model.StorageTankSizeFactor[j]) + \ - model.batchSize_log[i, j+1] - def batch_size_rule(disjunct, i): - return -log(model.StorageTankSizeFactorByProd[i,j]) <= model.batchSize_log[i,j] - \ - model.batchSize_log[i, j+1] <= log(model.StorageTankSizeFactorByProd[i,j]) - def no_batch_rule(disjunct, i): - return model.batchSize_log[i,j] - model.batchSize_log[i,j+1] == 0 - - if selectStorageTank: - disjunct.volume_stage_j = Constraint(model.PRODUCTS, rule=volume_stage_j_rule) - disjunct.volume_stage_jPlus1 = Constraint(model.PRODUCTS, - rule=volume_stage_jPlus1_rule) - disjunct.batch_size = Constraint(model.PRODUCTS, rule=batch_size_rule) - else: - # The formulation says 0, but GAMS has this constant. - # 04/04: Francisco says volume should be free: - # disjunct.no_volume = Constraint(expr=model.storageTankSize_log[j] == MinFlow) - disjunct.no_batch = Constraint(model.PRODUCTS, rule=no_batch_rule) -model.storage_tank_selection_disjunct = Disjunct([0,1], model.STAGESExceptLast, - rule=storage_tank_selection_disjunct_rule) - -def select_storage_tanks_rule(model, j): - return [model.storage_tank_selection_disjunct[selectTank, j] for selectTank in [0,1]] -model.select_storage_tanks = Disjunction(model.STAGESExceptLast, rule=select_storage_tanks_rule) - -# though this is a disjunction in the GAMs model, it is more efficiently formulated this way: -# TODO: what on earth is k? -def units_out_of_phase_rule(model, j): - return model.unitsOutOfPhase_log[j] == sum(model.LogCoeffs[k] * model.outOfPhase[j,k] \ - for k in model.PARALLELUNITS) -model.units_out_of_phase = Constraint(model.STAGES, rule=units_out_of_phase_rule) - -def units_in_phase_rule(model, j): - return model.unitsInPhase_log[j] == sum(model.LogCoeffs[k] * model.inPhase[j,k] \ - for k in model.PARALLELUNITS) -model.units_in_phase = Constraint(model.STAGES, rule=units_in_phase_rule) - -# and since I didn't do the disjunction as a disjunction, we need the XORs: -def units_out_of_phase_xor_rule(model, j): - return sum(model.outOfPhase[j,k] for k in model.PARALLELUNITS) == 1 -model.units_out_of_phase_xor = Constraint(model.STAGES, rule=units_out_of_phase_xor_rule) - -def units_in_phase_xor_rule(model, j): - return sum(model.inPhase[j,k] for k in model.PARALLELUNITS) == 1 -model.units_in_phase_xor = Constraint(model.STAGES, rule=units_in_phase_xor_rule) - - -# instance = model.create_instance('batchProcessing1.dat') -# solver = SolverFactory('baron') -# TransformationFactory('gdp.bigm').apply_to(instance) -# TransformationFactory('core.add_slack_variables').apply_to(instance) -# results = solver.solve(instance) -# instance.display() -# instance.solutions.store_to(results) -# print results +def build_model(): + model = AbstractModel() + + # TODO: it looks like they set a bigM for each j. Which I need to look up how to do... + model.BigM = Suffix(direction=Suffix.LOCAL) + model.BigM[None] = 1000 + + + ## Constants from GAMS + StorageTankSizeFactor = 2*5 # btw, I know 2*5 is 10... I don't know why it's written this way in GAMS? + StorageTankSizeFactorByProd = 3 + MinFlow = -log(10000) + VolumeLB = log(300) + VolumeUB = log(3500) + StorageTankSizeLB = log(100) + StorageTankSizeUB = log(15000) + UnitsInPhaseUB = log(6) + UnitsOutOfPhaseUB = log(6) + # TODO: YOU ARE HERE. YOU HAVEN'T ACTUALLY MADE THESE THE BOUNDS YET, NOR HAVE YOU FIGURED OUT WHOSE + # BOUNDS THEY ARE. AND THERE ARE MORE IN GAMS. + + + ########## + # Sets + ########## + + model.PRODUCTS = Set() + model.STAGES = Set(ordered=True) + model.PARALLELUNITS = Set(ordered=True) + + # TODO: this seems like an over-complicated way to accomplish this task... + def filter_out_last(model, j): + return j != model.STAGES.last() + model.STAGESExceptLast = Set(initialize=model.STAGES, filter=filter_out_last) + + + # TODO: these aren't in the formulation?? + #model.STORAGE_TANKS = Set() + + + ############### + # Parameters + ############### + + model.HorizonTime = Param() + model.Alpha1 = Param() + model.Alpha2 = Param() + model.Beta1 = Param() + model.Beta2 = Param() + + model.ProductionAmount = Param(model.PRODUCTS) + model.ProductSizeFactor = Param(model.PRODUCTS, model.STAGES) + model.ProcessingTime = Param(model.PRODUCTS, model.STAGES) + + # These are hard-coded in the GAMS file, hence the defaults + model.StorageTankSizeFactor = Param(model.STAGES, default=StorageTankSizeFactor) + model.StorageTankSizeFactorByProd = Param(model.PRODUCTS, model.STAGES, + default=StorageTankSizeFactorByProd) + + # TODO: bonmin wasn't happy and I think it might have something to do with this? + # or maybe issues with convexity or a lack thereof... I don't know yet. + # I made PRODUCTS ordered so I could do this... Is that bad? And it does index + # from 1, right? + def get_log_coeffs(model, k): + return log(model.PARALLELUNITS.ord(k)) + + model.LogCoeffs = Param(model.PARALLELUNITS, initialize=get_log_coeffs) + + # bounds + model.volumeLB = Param(model.STAGES, default=VolumeLB) + model.volumeUB = Param(model.STAGES, default=VolumeUB) + model.storageTankSizeLB = Param(model.STAGES, default=StorageTankSizeLB) + model.storageTankSizeUB = Param(model.STAGES, default=StorageTankSizeUB) + model.unitsInPhaseUB = Param(model.STAGES, default=UnitsInPhaseUB) + model.unitsOutOfPhaseUB = Param(model.STAGES, default=UnitsOutOfPhaseUB) + + + ################ + # Variables + ################ + + # TODO: right now these match the formulation. There are more in GAMS... + + # unit size of stage j + # model.volume = Var(model.STAGES) + # # TODO: GAMS has a batch size indexed just by products that isn't in the formulation... I'm going + # # to try to avoid it for the moment... + # # batch size of product i at stage j + # model.batchSize = Var(model.PRODUCTS, model.STAGES) + # # TODO: this is different in GAMS... They index by stages too? + # # cycle time of product i divided by batch size of product i + # model.cycleTime = Var(model.PRODUCTS) + # # number of units in parallel out-of-phase (or in phase) at stage j + # model.unitsOutOfPhase = Var(model.STAGES) + # model.unitsInPhase = Var(model.STAGES) + # # TODO: what are we going to do as a boundary condition here? For that last stage? + # # size of intermediate storage tank between stage j and j+1 + # model.storageTankSize = Var(model.STAGES) + + # variables for convexified problem + # TODO: I am beginning to think these are my only variables actually. + # GAMS never un-logs them, I don't think. And I think the GAMs ones + # must be the log ones. + def get_volume_bounds(model, j): + return (model.volumeLB[j], model.volumeUB[j]) + model.volume_log = Var(model.STAGES, bounds=get_volume_bounds) + model.batchSize_log = Var(model.PRODUCTS, model.STAGES) + model.cycleTime_log = Var(model.PRODUCTS) + def get_unitsOutOfPhase_bounds(model, j): + return (0, model.unitsOutOfPhaseUB[j]) + model.unitsOutOfPhase_log = Var(model.STAGES, bounds=get_unitsOutOfPhase_bounds) + def get_unitsInPhase_bounds(model, j): + return (0, model.unitsInPhaseUB[j]) + model.unitsInPhase_log = Var(model.STAGES, bounds=get_unitsInPhase_bounds) + def get_storageTankSize_bounds(model, j): + return (model.storageTankSizeLB[j], model.storageTankSizeUB[j]) + # TODO: these bounds make it infeasible... + model.storageTankSize_log = Var(model.STAGES, bounds=get_storageTankSize_bounds) + + # binary variables for deciding number of parallel units in and out of phase + model.outOfPhase = Var(model.STAGES, model.PARALLELUNITS, within=Binary) + model.inPhase = Var(model.STAGES, model.PARALLELUNITS, within=Binary) + + ############### + # Objective + ############### + + def get_cost_rule(model): + return model.Alpha1 * sum(exp(model.unitsInPhase_log[j] + model.unitsOutOfPhase_log[j] + \ + model.Beta1 * model.volume_log[j]) for j in model.STAGES) +\ + model.Alpha2 * sum(exp(model.Beta2 * model.storageTankSize_log[j]) for j in model.STAGESExceptLast) + model.min_cost = Objective(rule=get_cost_rule) + + + ############## + # Constraints + ############## + + def processing_capacity_rule(model, j, i): + return model.volume_log[j] >= log(model.ProductSizeFactor[i, j]) + model.batchSize_log[i, j] - \ + model.unitsInPhase_log[j] + model.processing_capacity = Constraint(model.STAGES, model.PRODUCTS, rule=processing_capacity_rule) + + def processing_time_rule(model, j, i): + return model.cycleTime_log[i] >= log(model.ProcessingTime[i, j]) - model.batchSize_log[i, j] - \ + model.unitsOutOfPhase_log[j] + model.processing_time = Constraint(model.STAGES, model.PRODUCTS, rule=processing_time_rule) + + def finish_in_time_rule(model): + return model.HorizonTime >= sum(model.ProductionAmount[i]*exp(model.cycleTime_log[i]) \ + for i in model.PRODUCTS) + model.finish_in_time = Constraint(rule=finish_in_time_rule) + + + ############### + # Disjunctions + ############### + + def storage_tank_selection_disjunct_rule(disjunct, selectStorageTank, j): + model = disjunct.model() + def volume_stage_j_rule(disjunct, i): + return model.storageTankSize_log[j] >= log(model.StorageTankSizeFactor[j]) + \ + model.batchSize_log[i, j] + def volume_stage_jPlus1_rule(disjunct, i): + return model.storageTankSize_log[j] >= log(model.StorageTankSizeFactor[j]) + \ + model.batchSize_log[i, j+1] + def batch_size_rule(disjunct, i): + return -log(model.StorageTankSizeFactorByProd[i,j]) <= model.batchSize_log[i,j] - \ + model.batchSize_log[i, j+1] <= log(model.StorageTankSizeFactorByProd[i,j]) + def no_batch_rule(disjunct, i): + return model.batchSize_log[i,j] - model.batchSize_log[i,j+1] == 0 + + if selectStorageTank: + disjunct.volume_stage_j = Constraint(model.PRODUCTS, rule=volume_stage_j_rule) + disjunct.volume_stage_jPlus1 = Constraint(model.PRODUCTS, + rule=volume_stage_jPlus1_rule) + disjunct.batch_size = Constraint(model.PRODUCTS, rule=batch_size_rule) + else: + # The formulation says 0, but GAMS has this constant. + # 04/04: Francisco says volume should be free: + # disjunct.no_volume = Constraint(expr=model.storageTankSize_log[j] == MinFlow) + disjunct.no_batch = Constraint(model.PRODUCTS, rule=no_batch_rule) + model.storage_tank_selection_disjunct = Disjunct([0,1], model.STAGESExceptLast, + rule=storage_tank_selection_disjunct_rule) + + def select_storage_tanks_rule(model, j): + return [model.storage_tank_selection_disjunct[selectTank, j] for selectTank in [0,1]] + model.select_storage_tanks = Disjunction(model.STAGESExceptLast, rule=select_storage_tanks_rule) + + # though this is a disjunction in the GAMs model, it is more efficiently formulated this way: + # TODO: what on earth is k? + def units_out_of_phase_rule(model, j): + return model.unitsOutOfPhase_log[j] == sum(model.LogCoeffs[k] * model.outOfPhase[j,k] \ + for k in model.PARALLELUNITS) + model.units_out_of_phase = Constraint(model.STAGES, rule=units_out_of_phase_rule) + + def units_in_phase_rule(model, j): + return model.unitsInPhase_log[j] == sum(model.LogCoeffs[k] * model.inPhase[j,k] \ + for k in model.PARALLELUNITS) + model.units_in_phase = Constraint(model.STAGES, rule=units_in_phase_rule) + + # and since I didn't do the disjunction as a disjunction, we need the XORs: + def units_out_of_phase_xor_rule(model, j): + return sum(model.outOfPhase[j,k] for k in model.PARALLELUNITS) == 1 + model.units_out_of_phase_xor = Constraint(model.STAGES, rule=units_out_of_phase_xor_rule) + + def units_in_phase_xor_rule(model, j): + return sum(model.inPhase[j,k] for k in model.PARALLELUNITS) == 1 + model.units_in_phase_xor = Constraint(model.STAGES, rule=units_in_phase_xor_rule) + + + # instance = model.create_instance('batchProcessing1.dat') + # solver = SolverFactory('baron') + # TransformationFactory('gdp.bigm').apply_to(instance) + # TransformationFactory('core.add_slack_variables').apply_to(instance) + # results = solver.solve(instance) + # instance.display() + # instance.solutions.store_to(results) + # print results + return model diff --git a/examples/gdp/disease_model.py b/examples/gdp/disease_model.py index 7d47f5a3182..87183884ade 100644 --- a/examples/gdp/disease_model.py +++ b/examples/gdp/disease_model.py @@ -24,116 +24,118 @@ from data_set import * #from new_data_set import * -# declare model name -model = AbstractModel() - -# declare constants -bpy = 26 # biweeks per year -years = 15 # years of data -bigM = 50.0 # big M for disjunction constraints - -# declare sets -model.S_meas = RangeSet(1,bpy*years) -model.S_meas_small = RangeSet(1,bpy*years-1) -model.S_beta = RangeSet(1,bpy) - -# define variable bounds -def _gt_zero(m,i): - return (0.0,1e7) -def _beta_bounds(m): - return (None,5.0) - -# define variables - -# log of estimated cases -#model.logI = Var(model.S_meas, bounds=_gt_zero) -model.logI = Var(model.S_meas, bounds=(0.001,1e7)) -# log of transmission parameter beta -#model.logbeta = Var(model.S_beta, bounds=_gt_zero) -model.logbeta = Var(model.S_beta, bounds=(0.0001,5)) -# binary variable y over all betas -#model.y = Var(model.S_beta, within=Binary) -# low value of beta -#model.logbeta_low = Var(bounds=_beta_bounds) -model.logbeta_low = Var(bounds=(0.0001,5)) -# high value of beta -#model.logbeta_high = Var(bounds=_beta_bounds) -model.logbeta_high = Var(bounds=(0.0001,5)) -# dummy variables -model.p = Var(model.S_meas, bounds=_gt_zero) -model.n = Var(model.S_meas, bounds=_gt_zero) - -# define indexed constants - -# log of measured cases after adjusting for underreporting -logIstar = logIstar -# changes in susceptible population profile from susceptible reconstruction -deltaS = deltaS -# mean susceptibles -#meanS = 1.04e6 -meanS = 8.65e5 -# log of measured population -logN = pop -# define index for beta over all measurements -beta_set = beta_set - -# define objective -def _obj_rule(m): - expr = sum(m.p[i] + m.n[i] for i in m.S_meas) - return expr -model.obj = Objective(rule=_obj_rule, sense=minimize) - -# define constraints -def _logSIR(m,i): - expr = m.logI[i+1] - ( m.logbeta[beta_set[i-1]] + m.logI[i] + math.log(deltaS[i-1] + meanS) - logN[i-1] ) - return (0.0, expr) -model.logSIR = Constraint(model.S_meas_small, rule=_logSIR) - -# objective function constraint -def _p_n_const(m,i): - expr = logIstar[i-1] - m.logI[i] - m.p[i] + m.n[i] - return (0.0, expr) -model.p_n_const = Constraint(model.S_meas,rule=_p_n_const) - -# disjuncts - -model.BigM = Suffix() -model.y = RangeSet(0,1) -def _high_low(disjunct, i, y): - model = disjunct.model() - if y: - disjunct.c = Constraint(expr=model.logbeta_high - model.logbeta[i]== 0.0) - else: - disjunct.c = Constraint(expr=model.logbeta[i] - model.logbeta_low == 0.0) - model.BigM[disjunct.c] = bigM -model.high_low = Disjunct(model.S_beta, model.y, rule=_high_low) - -# disjunctions -def _disj(model, i): - return [model.high_low[i,j] for j in model.y] -model.disj = Disjunction(model.S_beta, rule=_disj) - - -""" -# high beta disjuncts -def highbeta_L(m,i): - expr = m.logbeta[i] - m.logbeta_high + bigM*(1-m.y[i]) - return (0.0, expr, None) -model.highbeta_L = Constraint(model.S_beta, rule=highbeta_L) - -def highbeta_U(m,i): - expr = m.logbeta[i] - m.logbeta_high - return (None, expr, 0.0) -model.highbeta_U = Constraint(model.S_beta, rule=highbeta_U) - -# low beta disjuncts -def lowbeta_U(m,i): - expr = m.logbeta[i] - m.logbeta_low - bigM*(m.y[i]) - return (None, expr, 0.0) -model.lowbeta_U = Constraint(model.S_beta, rule=lowbeta_U) - -def lowbeta_L(m,i): - expr = m.logbeta[i] - m.logbeta_low - return (0.0, expr, None) -model.lowbeta_L = Constraint(model.S_beta, rule=lowbeta_L) -""" +def build_model(): + # declare model name + model = AbstractModel() + + # declare constants + bpy = 26 # biweeks per year + years = 15 # years of data + bigM = 50.0 # big M for disjunction constraints + + # declare sets + model.S_meas = RangeSet(1,bpy*years) + model.S_meas_small = RangeSet(1,bpy*years-1) + model.S_beta = RangeSet(1,bpy) + + # define variable bounds + def _gt_zero(m,i): + return (0.0,1e7) + def _beta_bounds(m): + return (None,5.0) + + # define variables + + # log of estimated cases + #model.logI = Var(model.S_meas, bounds=_gt_zero) + model.logI = Var(model.S_meas, bounds=(0.001,1e7)) + # log of transmission parameter beta + #model.logbeta = Var(model.S_beta, bounds=_gt_zero) + model.logbeta = Var(model.S_beta, bounds=(0.0001,5)) + # binary variable y over all betas + #model.y = Var(model.S_beta, within=Binary) + # low value of beta + #model.logbeta_low = Var(bounds=_beta_bounds) + model.logbeta_low = Var(bounds=(0.0001,5)) + # high value of beta + #model.logbeta_high = Var(bounds=_beta_bounds) + model.logbeta_high = Var(bounds=(0.0001,5)) + # dummy variables + model.p = Var(model.S_meas, bounds=_gt_zero) + model.n = Var(model.S_meas, bounds=_gt_zero) + + # define indexed constants + + # log of measured cases after adjusting for underreporting + logIstar = logIstar + # changes in susceptible population profile from susceptible reconstruction + deltaS = deltaS + # mean susceptibles + #meanS = 1.04e6 + meanS = 8.65e5 + # log of measured population + logN = pop + # define index for beta over all measurements + beta_set = beta_set + + # define objective + def _obj_rule(m): + expr = sum(m.p[i] + m.n[i] for i in m.S_meas) + return expr + model.obj = Objective(rule=_obj_rule, sense=minimize) + + # define constraints + def _logSIR(m,i): + expr = m.logI[i+1] - ( m.logbeta[beta_set[i-1]] + m.logI[i] + math.log(deltaS[i-1] + meanS) - logN[i-1] ) + return (0.0, expr) + model.logSIR = Constraint(model.S_meas_small, rule=_logSIR) + + # objective function constraint + def _p_n_const(m,i): + expr = logIstar[i-1] - m.logI[i] - m.p[i] + m.n[i] + return (0.0, expr) + model.p_n_const = Constraint(model.S_meas,rule=_p_n_const) + + # disjuncts + + model.BigM = Suffix() + model.y = RangeSet(0,1) + def _high_low(disjunct, i, y): + model = disjunct.model() + if y: + disjunct.c = Constraint(expr=model.logbeta_high - model.logbeta[i]== 0.0) + else: + disjunct.c = Constraint(expr=model.logbeta[i] - model.logbeta_low == 0.0) + model.BigM[disjunct.c] = bigM + model.high_low = Disjunct(model.S_beta, model.y, rule=_high_low) + + # disjunctions + def _disj(model, i): + return [model.high_low[i,j] for j in model.y] + model.disj = Disjunction(model.S_beta, rule=_disj) + + + """ + # high beta disjuncts + def highbeta_L(m,i): + expr = m.logbeta[i] - m.logbeta_high + bigM*(1-m.y[i]) + return (0.0, expr, None) + model.highbeta_L = Constraint(model.S_beta, rule=highbeta_L) + + def highbeta_U(m,i): + expr = m.logbeta[i] - m.logbeta_high + return (None, expr, 0.0) + model.highbeta_U = Constraint(model.S_beta, rule=highbeta_U) + + # low beta disjuncts + def lowbeta_U(m,i): + expr = m.logbeta[i] - m.logbeta_low - bigM*(m.y[i]) + return (None, expr, 0.0) + model.lowbeta_U = Constraint(model.S_beta, rule=lowbeta_U) + + def lowbeta_L(m,i): + expr = m.logbeta[i] - m.logbeta_low + return (0.0, expr, None) + model.lowbeta_L = Constraint(model.S_beta, rule=lowbeta_L) + """ + return model diff --git a/examples/gdp/jobshop-nodisjuncts.py b/examples/gdp/jobshop-nodisjuncts.py index 354f6a39e29..ba7db448e2c 100644 --- a/examples/gdp/jobshop-nodisjuncts.py +++ b/examples/gdp/jobshop-nodisjuncts.py @@ -29,40 +29,42 @@ # Aldo Vecchietti, LogMIP User's Manual, http://www.logmip.ceride.gov.ar/, 2007 # -model = AbstractModel() +def build_model(): + model = AbstractModel() -model.JOBS = Set(ordered=True) -model.STAGES = Set(ordered=True) -model.I_BEFORE_K = RangeSet(0,1) + model.JOBS = Set(ordered=True) + model.STAGES = Set(ordered=True) + model.I_BEFORE_K = RangeSet(0,1) -# Task durations -model.tau = Param(model.JOBS, model.STAGES, default=0) + # Task durations + model.tau = Param(model.JOBS, model.STAGES, default=0) -# Total Makespan (this will be the objective) -model.ms = Var() -# Start time of each job -def t_bounds(model, I): - return (0, sum(value(model.tau[idx]) for idx in model.tau)) -model.t = Var( model.JOBS, within=NonNegativeReals, bounds=t_bounds ) + # Total Makespan (this will be the objective) + model.ms = Var() + # Start time of each job + def t_bounds(model, I): + return (0, sum(value(model.tau[idx]) for idx in model.tau)) + model.t = Var( model.JOBS, within=NonNegativeReals, bounds=t_bounds ) -# Auto-generate the L set (potential collisions between 2 jobs at any stage. -def _L_filter(model, I, K, J): - return I < K and model.tau[I,J] and model.tau[K,J] -model.L = Set( initialize=model.JOBS * model.JOBS * model.STAGES, - dimen=3, filter=_L_filter) + # Auto-generate the L set (potential collisions between 2 jobs at any stage. + def _L_filter(model, I, K, J): + return I < K and model.tau[I,J] and model.tau[K,J] + model.L = Set( initialize=model.JOBS * model.JOBS * model.STAGES, + dimen=3, filter=_L_filter) -# Makespan is greater than the start time of every job + that job's -# total duration -def _feas(model, I): - return model.ms >= model.t[I] + sum(model.tau[I,M] for M in model.STAGES) -model.Feas = Constraint(model.JOBS, rule=_feas) + # Makespan is greater than the start time of every job + that job's + # total duration + def _feas(model, I): + return model.ms >= model.t[I] + sum(model.tau[I,M] for M in model.STAGES) + model.Feas = Constraint(model.JOBS, rule=_feas) -# Define the disjunctions: either job I occurs before K or K before I -def _disj(model, I, K, J): - lhs = model.t[I] + sum([M= model.t[I] + sum(model.tau[I,M] for M in model.STAGES) -model.Feas = Constraint(model.JOBS, rule=_feas) + # Auto-generate the L set (potential collisions between 2 jobs at any stage. + def _L_filter(model, I, K, J): + return I < K and model.tau[I,J] and model.tau[K,J] + model.L = Set( initialize=model.JOBS * model.JOBS * model.STAGES, + dimen=3, filter=_L_filter) -# Disjunctions to prevent clashes at a stage: This creates a set of -# disjunct pairs: one if job I occurs before job K and the other if job -# K occurs before job I. -def _NoClash(disjunct, I, K, J, IthenK): - model = disjunct.model() - lhs = model.t[I] + sum([M= model.t[I] + sum(model.tau[I,M] for M in model.STAGES) + model.Feas = Constraint(model.JOBS, rule=_feas) -# Define the disjunctions: either job I occurs before K or K before I -def _disj(model, I, K, J): - return [model.NoClash[I,K,J,IthenK] for IthenK in model.I_BEFORE_K] -model.disj = Disjunction(model.L, rule=_disj) + # Disjunctions to prevent clashes at a stage: This creates a set of + # disjunct pairs: one if job I occurs before job K and the other if job + # K occurs before job I. + def _NoClash(disjunct, I, K, J, IthenK): + model = disjunct.model() + lhs = model.t[I] + sum([M= model.DemandLB[j,t] -model.demand_LB = Constraint(model.Products, model.TimePeriods, rule=demand_LB_rule) - - -# FIXED PRICE CONTRACT - -# Disjunction for Fixed Price contract buying options -def FP_contract_disjunct_rule(disjunct, j, t, buy): - model = disjunct.model() - if buy: - disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] <= MAX_AMOUNT_FP) - else: - disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] == 0) -model.FP_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyFPContract, rule=FP_contract_disjunct_rule) - -# Fixed price disjunction -def FP_contract_rule(model, j, t): - return [model.FP_contract_disjunct[j,t,buy] for buy in model.BuyFPContract] -model.FP_disjunction = Disjunction(model.RawMaterials, model.TimePeriods, - rule=FP_contract_rule) - -# cost constraint for fixed price contract (independent contraint) -def FP_contract_cost_rule(model, j, t): - return model.Cost_FP[j,t] == model.AmountPurchased_FP[j,t] * \ - model.Prices[j,t] -model.FP_contract_cost = Constraint(model.RawMaterials, model.TimePeriods, - rule=FP_contract_cost_rule) - - -# DISCOUNT CONTRACT - -# Disjunction for Discount contract -def discount_contract_disjunct_rule(disjunct, j, t, buy): - model = disjunct.model() - if buy == 'BelowMin': - disjunct.belowMin = Constraint( - expr=model.AmountPurchasedBelowMin_Discount[j,t] <= \ - model.MinAmount_Discount[j,t]) - disjunct.aboveMin = Constraint( - expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) - elif buy == 'AboveMin': - disjunct.belowMin = Constraint( - expr=model.AmountPurchasedBelowMin_Discount[j,t] == \ - model.MinAmount_Discount[j,t]) - disjunct.aboveMin = Constraint( - expr=model.AmountPurchasedAboveMin_Discount[j,t] >= 0) - elif buy == 'NotSelected': - disjunct.belowMin = Constraint( - expr=model.AmountPurchasedBelowMin_Discount[j,t] == 0) - disjunct.aboveMin = Constraint( - expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) - else: - raise RuntimeError("Unrecognized choice for discount contract: %s" % buy) -model.discount_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyDiscountContract, rule=discount_contract_disjunct_rule) - -# Discount contract disjunction -def discount_contract_rule(model, j, t): - return [model.discount_contract_disjunct[j,t,buy] \ - for buy in model.BuyDiscountContract] -model.discount_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=discount_contract_rule) - -# cost constraint for discount contract (independent constraint) -def discount_cost_rule(model, j, t): - return model.Cost_Discount[j,t] == model.RegPrice_Discount[j,t] * \ - model.AmountPurchasedBelowMin_Discount[j,t] + \ - model.DiscountPrice_Discount[j,t] * model.AmountPurchasedAboveMin_Discount[j,t] -model.discount_cost = Constraint(model.RawMaterials, model.TimePeriods, - rule=discount_cost_rule) - - -# BULK CONTRACT - -# Bulk contract buying options disjunct -def bulk_contract_disjunct_rule(disjunct, j, t, buy): - model = disjunct.model() - if buy == 'BelowMin': - disjunct.amount = Constraint( - expr=model.AmountPurchased_Bulk[j,t] <= model.MinAmount_Bulk[j,t]) - disjunct.price = Constraint( - expr=model.Cost_Bulk[j,t] == model.RegPrice_Bulk[j,t] * \ - model.AmountPurchased_Bulk[j,t]) - elif buy == 'AboveMin': - disjunct.amount = Constraint( - expr=model.AmountPurchased_Bulk[j,t] >= model.MinAmount_Bulk[j,t]) - disjunct.price = Constraint( - expr=model.Cost_Bulk[j,t] == model.DiscountPrice_Bulk[j,t] * \ - model.AmountPurchased_Bulk[j,t]) - elif buy == 'NotSelected': - disjunct.amount = Constraint(expr=model.AmountPurchased_Bulk[j,t] == 0) - disjunct.price = Constraint(expr=model.Cost_Bulk[j,t] == 0) - else: - raise RuntimeError("Unrecognized choice for bulk contract: %s" % buy) -model.bulk_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyBulkContract, rule=bulk_contract_disjunct_rule) - -# Bulk contract disjunction -def bulk_contract_rule(model, j, t): - return [model.bulk_contract_disjunct[j,t,buy] for buy in model.BuyBulkContract] -model.bulk_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=bulk_contract_rule) - - -# FIXED DURATION CONTRACT - -def FD_1mo_contract(disjunct, j, t): - model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - MIN_AMOUNT_FD_1MONTH) - disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,1,t] * model.AmountPurchased_FD[j,t]) -model.FD_1mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_1mo_contract) - -def FD_2mo_contract(disjunct, j, t): - model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - model.MinAmount_Length[j,2]) - disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j,t]) - # only enforce these if we aren't in the last time period - if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j, t+1] >= \ +def build_model(): + model = AbstractModel() + + # Constants (data that was hard-coded in GAMS model) + AMOUNT_UB = 1000 + COST_UB = 1e4 + MAX_AMOUNT_FP = 1000 + MIN_AMOUNT_FD_1MONTH = 0 + + RandomConst_Line264 = 0.17 + RandomConst_Line265 = 0.83 + + ################### + # Sets + ################### + + # T + # t in GAMS + model.TimePeriods = Set(ordered=True) + + # Available length contracts + # p in GAMS + model.Contracts_Length = Set() + + # JP + # final(j) in GAMS + # Finished products + model.Products = Set() + + # JM + # rawmat(J) in GAMS + # Set of Raw Materials-- raw materials, intermediate products, and final products partition J + model.RawMaterials = Set() + + # C + # c in GAMS + model.Contracts = Set() + + # I + # i in GAMS + model.Processes = Set() + + # J + # j in GAMS + model.Streams = Set() + + + ################## + # Parameters + ################## + + # Q_it + # excap(i) in GAMS + model.Capacity = Param(model.Processes) + + # u_ijt + # cov(i) in GAMS + model.ProcessConstants = Param(model.Processes) + + # a_jt^U and d_jt^U + # spdm(j,t) in GAMS + model.SupplyAndDemandUBs = Param(model.Streams, model.TimePeriods, default=0) + + # d_jt^L + # lbdm(j, t) in GAMS + model.DemandLB = Param(model.Streams, model.TimePeriods, default=0) + + # delta_it + # delta(i, t) in GAMS + # operating cost of process i at time t + model.OperatingCosts = Param(model.Processes, model.TimePeriods) + + # prices of raw materials under FP contract and selling prices of products + # pf(j, t) in GAMS + # omega_jt and pf_jt + model.Prices = Param(model.Streams, model.TimePeriods, default=0) + + # Price for quantities less than min amount under discount contract + # pd1(j, t) in GAMS + model.RegPrice_Discount = Param(model.Streams, model.TimePeriods) + # Discounted price for the quantity purchased exceeding the min amount + # pd2(j,t0 in GAMS + model.DiscountPrice_Discount = Param(model.Streams, model.TimePeriods) + + # Price for quantities below min amount + # pb1(j,t) in GAMS + model.RegPrice_Bulk = Param(model.Streams, model.TimePeriods) + # Price for quantities aboce min amount + # pb2(j, t) in GAMS + model.DiscountPrice_Bulk = Param(model.Streams, model.TimePeriods) + + # prices with length contract + # pl(j, p, t) in GAMS + model.Prices_Length = Param(model.Streams, model.Contracts_Length, model.TimePeriods, default=0) + + # sigmad_jt + # sigmad(j, t) in GAMS + # Minimum quantity of chemical j that must be bought before recieving a Discount under discount contract + model.MinAmount_Discount = Param(model.Streams, model.TimePeriods, default=0) + + # min quantity to recieve discount under bulk contract + # sigmab(j, t) in GAMS + model.MinAmount_Bulk = Param(model.Streams, model.TimePeriods, default=0) + + # min quantity to recieve discount under length contract + # sigmal(j, p) in GAMS + model.MinAmount_Length = Param(model.Streams, model.Contracts_Length, default=0) + + # main products of process i + # These are 1 (true) if stream j is the main product of process i, false otherwise. + # jm(j, i) in GAMS + model.MainProducts = Param(model.Streams, model.Processes, default=0) + + # theta_jt + # psf(j, t) in GAMS + # Shortfall penalty of product j at time t + model.ShortfallPenalty = Param(model.Products, model.TimePeriods) + + # shortfall upper bound + # sfub(j, t) in GAMS + model.ShortfallUB = Param(model.Products, model.TimePeriods, default=0) + + # epsilon_jt + # cinv(j, t) in GAMS + # inventory cost of material j at time t + model.InventoryCost = Param(model.Streams, model.TimePeriods) + + # invub(j, t) in GAMS + # inventory upper bound + model.InventoryLevelUB = Param(model.Streams, model.TimePeriods, default=0) + + ## UPPER BOUNDS HARDCODED INTO GAMS MODEL + + # All of these upper bounds are hardcoded. So I am just leaving them that way. + # This means they all have to be the same as each other right now. + def getAmountUBs(model, j, t): + return AMOUNT_UB + + def getCostUBs(model, j, t): + return COST_UB + + model.AmountPurchasedUB_FP = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedUB_Discount = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedBelowMinUB_Discount = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedAboveMinUB_Discount = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedUB_FD = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedUB_Bulk = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + + model.CostUB_FP = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) + model.CostUB_FD = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) + model.CostUB_Discount = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) + model.CostUB_Bulk = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) + + + #################### + #VARIABLES + #################### + + # prof in GAMS + # will be objective + model.Profit = Var() + + # f(j, t) in GAMS + # mass flow rates in tons per time interval t + model.FlowRate = Var(model.Streams, model.TimePeriods, within=NonNegativeReals) + + # V_jt + # inv(j, t) in GAMS + # inventory level of chemical j at time period t + def getInventoryBounds(model, i, j): + return (0, model.InventoryLevelUB[i,j]) + model.InventoryLevel = Var(model.Streams, model.TimePeriods, + bounds=getInventoryBounds) + + # SF_jt + # sf(j, t) in GAMS + # Shortfall of demand for chemical j at time period t + def getShortfallBounds(model, i, j): + return(0, model.ShortfallUB[i,j]) + model.Shortfall = Var(model.Products, model.TimePeriods, + bounds=getShortfallBounds) + + + # amounts purchased under different contracts + + # spf(j, t) in GAMS + # Amount of raw material j bought under fixed price contract at time period t + def get_FP_bounds(model, j, t): + return (0, model.AmountPurchasedUB_FP[j,t]) + model.AmountPurchased_FP = Var(model.Streams, model.TimePeriods, + bounds=get_FP_bounds) + + # spd(j, t) in GAMS + def get_Discount_Total_bounds(model, j, t): + return (0, model.AmountPurchasedUB_Discount[j,t]) + model.AmountPurchasedTotal_Discount = Var(model.Streams, model.TimePeriods, + bounds=get_Discount_Total_bounds) + + # Amount purchased below min amount for discount under discount contract + # spd1(j, t) in GAMS + def get_Discount_BelowMin_bounds(model, j, t): + return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) + model.AmountPurchasedBelowMin_Discount = Var(model.Streams, + model.TimePeriods, bounds=get_Discount_BelowMin_bounds) + + # spd2(j, t) in GAMS + # Amount purchased above min amount for discount under discount contract + def get_Discount_AboveMin_bounds(model, j, t): + return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) + model.AmountPurchasedAboveMin_Discount = Var(model.Streams, + model.TimePeriods, bounds=get_Discount_AboveMin_bounds) + + # Amount purchased under bulk contract + # spb(j, t) in GAMS + def get_bulk_bounds(model, j, t): + return (0, model.AmountPurchasedUB_Bulk[j,t]) + model.AmountPurchased_Bulk = Var(model.Streams, model.TimePeriods, + bounds=get_bulk_bounds) + + # spl(j, t) in GAMS + # Amount purchased under Fixed Duration contract + def get_FD_bounds(model, j, t): + return (0, model.AmountPurchasedUB_FD[j,t]) + model.AmountPurchased_FD = Var(model.Streams, model.TimePeriods, + bounds=get_FD_bounds) + + + # costs + + # costpl(j, t) in GAMS + # cost of variable length contract + def get_CostUBs_FD(model, j, t): + return (0, model.CostUB_FD[j,t]) + model.Cost_FD = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FD) + + # costpf(j, t) in GAMS + # cost of fixed duration contract + def get_CostUBs_FP(model, j, t): + return (0, model.CostUB_FP[j,t]) + model.Cost_FP = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FP) + + # costpd(j, t) in GAMS + # cost of discount contract + def get_CostUBs_Discount(model, j, t): + return (0, model.CostUB_Discount[j,t]) + model.Cost_Discount = Var(model.Streams, model.TimePeriods, + bounds=get_CostUBs_Discount) + + # costpb(j, t) in GAMS + # cost of bulk contract + def get_CostUBs_Bulk(model, j, t): + return (0, model.CostUB_Bulk[j,t]) + model.Cost_Bulk = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_Bulk) + + + # binary variables + + model.BuyFPContract = RangeSet(0,1) + model.BuyDiscountContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected')) + model.BuyBulkContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected')) + model.BuyFDContract = Set(initialize=('1Month', '2Month', '3Month', 'NotSelected')) + + + ################ + # CONSTRAINTS + ################ + + # Objective: maximize profit + def profit_rule(model): + salesIncome = sum(model.Prices[j,t] * model.FlowRate[j,t] + for j in model.Products for t in model.TimePeriods) + purchaseCost = sum(model.Cost_FD[j,t] + for j in model.RawMaterials for t in model.TimePeriods) + \ + sum(model.Cost_Discount[j,t] + for j in model.RawMaterials for t in model.TimePeriods) + \ + sum(model.Cost_Bulk[j,t] + for j in model.RawMaterials for t in model.TimePeriods) + \ + sum(model.Cost_FP[j,t] + for j in model.RawMaterials for t in model.TimePeriods) + productionCost = sum(model.OperatingCosts[i,t] * sum(model.FlowRate[j,t] + for j in model.Streams if model.MainProducts[j,i]) + for i in model.Processes for t in model.TimePeriods) + shortfallCost = sum(model.Shortfall[j,t] * model.ShortfallPenalty[j, t] + for j in model.Products for t in model.TimePeriods) + inventoryCost = sum(model.InventoryCost[j,t] * model.InventoryLevel[j,t] + for j in model.Products for t in model.TimePeriods) + return salesIncome - purchaseCost - productionCost - inventoryCost - shortfallCost + model.profit = Objective(rule=profit_rule, sense=maximize) + + # flow of raw materials is the total amount purchased (accross all contracts) + def raw_material_flow_rule(model, j, t): + return model.FlowRate[j,t] == model.AmountPurchased_FD[j,t] + \ + model.AmountPurchased_FP[j,t] + model.AmountPurchased_Bulk[j,t] + \ + model.AmountPurchasedTotal_Discount[j,t] + model.raw_material_flow = Constraint(model.RawMaterials, model.TimePeriods, + rule=raw_material_flow_rule) + + def discount_amount_total_rule(model, j, t): + return model.AmountPurchasedTotal_Discount[j,t] == \ + model.AmountPurchasedBelowMin_Discount[j,t] + \ + model.AmountPurchasedAboveMin_Discount[j,t] + model.discount_amount_total_rule = Constraint(model.RawMaterials, model.TimePeriods, + rule=discount_amount_total_rule) + + # mass balance equations for each node + # these are specific to the process network in this example. + def mass_balance_rule1(model, t): + return model.FlowRate[1, t] == model.FlowRate[2, t] + model.FlowRate[3, t] + model.mass_balance1 = Constraint(model.TimePeriods, rule=mass_balance_rule1) + + def mass_balance_rule2(model, t): + return model.FlowRate[5, t] == model.FlowRate[4, t] + model.FlowRate[8,t] + model.mass_balance2 = Constraint(model.TimePeriods, rule=mass_balance_rule2) + + def mass_balance_rule3(model, t): + return model.FlowRate[6, t] == model.FlowRate[7, t] + model.mass_balance3 = Constraint(model.TimePeriods, rule=mass_balance_rule3) + + def mass_balance_rule4(model, t): + return model.FlowRate[3, t] == 10*model.FlowRate[5, t] + model.mass_balance4 = Constraint(model.TimePeriods, rule=mass_balance_rule4) + + # process input/output constraints + # these are also totally specific to the process network + def process_balance_rule1(model, t): + return model.FlowRate[9, t] == model.ProcessConstants[1] * model.FlowRate[2, t] + model.process_balance1 = Constraint(model.TimePeriods, rule=process_balance_rule1) + + def process_balance_rule2(model, t): + return model.FlowRate[10, t] == model.ProcessConstants[2] * \ + (model.FlowRate[5, t] + model.FlowRate[3, t]) + model.process_balance2 = Constraint(model.TimePeriods, rule=process_balance_rule2) + + def process_balance_rule3(model, t): + return model.FlowRate[8, t] == RandomConst_Line264 * \ + model.ProcessConstants[3] * model.FlowRate[7, t] + model.process_balance3 = Constraint(model.TimePeriods, rule=process_balance_rule3) + + def process_balance_rule4(model, t): + return model.FlowRate[11, t] == RandomConst_Line265 * \ + model.ProcessConstants[3] * model.FlowRate[7, t] + model.process_balance4 = Constraint(model.TimePeriods, rule=process_balance_rule4) + + # process capacity contraints + # these are hardcoded based on the three processes and the process flow structure + def process_capacity_rule1(model, t): + return model.FlowRate[9, t] <= model.Capacity[1] + model.process_capacity1 = Constraint(model.TimePeriods, rule=process_capacity_rule1) + + def process_capacity_rule2(model, t): + return model.FlowRate[10, t] <= model.Capacity[2] + model.process_capacity2 = Constraint(model.TimePeriods, rule=process_capacity_rule2) + + def process_capacity_rule3(model, t): + return model.FlowRate[11, t] + model.FlowRate[8, t] <= model.Capacity[3] + model.process_capacity3 = Constraint(model.TimePeriods, rule=process_capacity_rule3) + + # Inventory balance of final products + # again, these are hardcoded. + + def inventory_balance1(model, t): + prev = 0 if t == min(model.TimePeriods) else model.InventoryLevel[12, t-1] + return prev + model.FlowRate[9, t] == model.FlowRate[12, t] + model.InventoryLevel[12,t] + model.inventory_balance1 = Constraint(model.TimePeriods, rule=inventory_balance1) + + def inventory_balance_rule2(model, t): + if t != 1: + return Constraint.Skip + return model.FlowRate[10, t] + model.FlowRate[11, t] == \ + model.InventoryLevel[13,t] + model.FlowRate[13, t] + model.inventory_balance2 = Constraint(model.TimePeriods, rule=inventory_balance_rule2) + + def inventory_balance_rule3(model, t): + if t <= 1: + return Constraint.Skip + return model.InventoryLevel[13, t-1] + model.FlowRate[10, t] + \ + model.FlowRate[11,t] == model.InventoryLevel[13, t] + model.FlowRate[13, t] + model.inventory_balance3 = Constraint(model.TimePeriods, rule=inventory_balance_rule3) + + # Max capacities of inventories + def inventory_capacity_rule(model, j, t): + return model.InventoryLevel[j,t] <= model.InventoryLevelUB[j,t] + model.inventory_capacity_rule = Constraint(model.Products, model.TimePeriods, rule=inventory_capacity_rule) + + # Shortfall calculation + def shortfall_rule(model, j, t): + return model.Shortfall[j, t] == model.SupplyAndDemandUBs[j, t] - model.FlowRate[j,t] + model.shortfall = Constraint(model.Products, model.TimePeriods, rule=shortfall_rule) + + # maximum shortfall allowed + def shortfall_max_rule(model, j, t): + return model.Shortfall[j, t] <= model.ShortfallUB[j, t] + model.shortfall_max = Constraint(model.Products, model.TimePeriods, rule=shortfall_max_rule) + + # maxiumum capacities of suppliers + def supplier_capacity_rule(model, j, t): + return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j, t] + model.supplier_capacity = Constraint(model.RawMaterials, model.TimePeriods, rule=supplier_capacity_rule) + + # demand upper bound + def demand_UB_rule(model, j, t): + return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j,t] + model.demand_UB = Constraint(model.Products, model.TimePeriods, rule=demand_UB_rule) + # demand lower bound + def demand_LB_rule(model, j, t): + return model.FlowRate[j, t] >= model.DemandLB[j,t] + model.demand_LB = Constraint(model.Products, model.TimePeriods, rule=demand_LB_rule) + + + # FIXED PRICE CONTRACT + + # Disjunction for Fixed Price contract buying options + def FP_contract_disjunct_rule(disjunct, j, t, buy): + model = disjunct.model() + if buy: + disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] <= MAX_AMOUNT_FP) + else: + disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] == 0) + model.FP_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, + model.BuyFPContract, rule=FP_contract_disjunct_rule) + + # Fixed price disjunction + def FP_contract_rule(model, j, t): + return [model.FP_contract_disjunct[j,t,buy] for buy in model.BuyFPContract] + model.FP_disjunction = Disjunction(model.RawMaterials, model.TimePeriods, + rule=FP_contract_rule) + + # cost constraint for fixed price contract (independent contraint) + def FP_contract_cost_rule(model, j, t): + return model.Cost_FP[j,t] == model.AmountPurchased_FP[j,t] * \ + model.Prices[j,t] + model.FP_contract_cost = Constraint(model.RawMaterials, model.TimePeriods, + rule=FP_contract_cost_rule) + + + # DISCOUNT CONTRACT + + # Disjunction for Discount contract + def discount_contract_disjunct_rule(disjunct, j, t, buy): + model = disjunct.model() + if buy == 'BelowMin': + disjunct.belowMin = Constraint( + expr=model.AmountPurchasedBelowMin_Discount[j,t] <= \ + model.MinAmount_Discount[j,t]) + disjunct.aboveMin = Constraint( + expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) + elif buy == 'AboveMin': + disjunct.belowMin = Constraint( + expr=model.AmountPurchasedBelowMin_Discount[j,t] == \ + model.MinAmount_Discount[j,t]) + disjunct.aboveMin = Constraint( + expr=model.AmountPurchasedAboveMin_Discount[j,t] >= 0) + elif buy == 'NotSelected': + disjunct.belowMin = Constraint( + expr=model.AmountPurchasedBelowMin_Discount[j,t] == 0) + disjunct.aboveMin = Constraint( + expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) + else: + raise RuntimeError("Unrecognized choice for discount contract: %s" % buy) + model.discount_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, + model.BuyDiscountContract, rule=discount_contract_disjunct_rule) + + # Discount contract disjunction + def discount_contract_rule(model, j, t): + return [model.discount_contract_disjunct[j,t,buy] \ + for buy in model.BuyDiscountContract] + model.discount_contract = Disjunction(model.RawMaterials, model.TimePeriods, + rule=discount_contract_rule) + + # cost constraint for discount contract (independent constraint) + def discount_cost_rule(model, j, t): + return model.Cost_Discount[j,t] == model.RegPrice_Discount[j,t] * \ + model.AmountPurchasedBelowMin_Discount[j,t] + \ + model.DiscountPrice_Discount[j,t] * model.AmountPurchasedAboveMin_Discount[j,t] + model.discount_cost = Constraint(model.RawMaterials, model.TimePeriods, + rule=discount_cost_rule) + + + # BULK CONTRACT + + # Bulk contract buying options disjunct + def bulk_contract_disjunct_rule(disjunct, j, t, buy): + model = disjunct.model() + if buy == 'BelowMin': + disjunct.amount = Constraint( + expr=model.AmountPurchased_Bulk[j,t] <= model.MinAmount_Bulk[j,t]) + disjunct.price = Constraint( + expr=model.Cost_Bulk[j,t] == model.RegPrice_Bulk[j,t] * \ + model.AmountPurchased_Bulk[j,t]) + elif buy == 'AboveMin': + disjunct.amount = Constraint( + expr=model.AmountPurchased_Bulk[j,t] >= model.MinAmount_Bulk[j,t]) + disjunct.price = Constraint( + expr=model.Cost_Bulk[j,t] == model.DiscountPrice_Bulk[j,t] * \ + model.AmountPurchased_Bulk[j,t]) + elif buy == 'NotSelected': + disjunct.amount = Constraint(expr=model.AmountPurchased_Bulk[j,t] == 0) + disjunct.price = Constraint(expr=model.Cost_Bulk[j,t] == 0) + else: + raise RuntimeError("Unrecognized choice for bulk contract: %s" % buy) + model.bulk_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, + model.BuyBulkContract, rule=bulk_contract_disjunct_rule) + + # Bulk contract disjunction + def bulk_contract_rule(model, j, t): + return [model.bulk_contract_disjunct[j,t,buy] for buy in model.BuyBulkContract] + model.bulk_contract = Disjunction(model.RawMaterials, model.TimePeriods, + rule=bulk_contract_rule) + + + # FIXED DURATION CONTRACT + + def FD_1mo_contract(disjunct, j, t): + model = disjunct.model() + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ + MIN_AMOUNT_FD_1MONTH) + disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ + model.Prices_Length[j,1,t] * model.AmountPurchased_FD[j,t]) + model.FD_1mo_contract = Disjunct( + model.RawMaterials, model.TimePeriods, rule=FD_1mo_contract) + + def FD_2mo_contract(disjunct, j, t): + model = disjunct.model() + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ model.MinAmount_Length[j,2]) - disjunct.price2 = Constraint(expr=model.Cost_FD[j,t+1] == \ - model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j, t+1]) -model.FD_2mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_2mo_contract) - -def FD_3mo_contract(disjunct, j, t): - model = disjunct.model() - # NOTE: I think there is a mistake in the GAMS file in line 327. - # they use the bulk minamount rather than the length one. - #I am doing the same here for validation purposes. - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - model.MinAmount_Bulk[j,3]) - disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t]) - # check we aren't in one of the last two time periods - if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] >= \ - model.MinAmount_Length[j,3]) - disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+1]) - if t < model.TimePeriods[-2]: - disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] >= \ - model.MinAmount_Length[j,3]) - disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+2]) -model.FD_3mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_3mo_contract) - -def FD_no_contract(disjunct, j, t): - model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] == 0) - disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == 0) - if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] == 0) - disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == 0) - if t < model.TimePeriods[-2]: - disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] == 0) - disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == 0) -model.FD_no_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_no_contract) - -def FD_contract(model, j, t): - return [ model.FD_1mo_contract[j,t], model.FD_2mo_contract[j,t], - model.FD_3mo_contract[j,t], model.FD_no_contract[j,t], ] -model.FD_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=FD_contract) + disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ + model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j,t]) + # only enforce these if we aren't in the last time period + if t < model.TimePeriods[-1]: + disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j, t+1] >= \ + model.MinAmount_Length[j,2]) + disjunct.price2 = Constraint(expr=model.Cost_FD[j,t+1] == \ + model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j, t+1]) + model.FD_2mo_contract = Disjunct( + model.RawMaterials, model.TimePeriods, rule=FD_2mo_contract) + + def FD_3mo_contract(disjunct, j, t): + model = disjunct.model() + # NOTE: I think there is a mistake in the GAMS file in line 327. + # they use the bulk minamount rather than the length one. + #I am doing the same here for validation purposes. + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ + model.MinAmount_Bulk[j,3]) + disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == \ + model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t]) + # check we aren't in one of the last two time periods + if t < model.TimePeriods[-1]: + disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] >= \ + model.MinAmount_Length[j,3]) + disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == \ + model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+1]) + if t < model.TimePeriods[-2]: + disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] >= \ + model.MinAmount_Length[j,3]) + disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == \ + model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+2]) + model.FD_3mo_contract = Disjunct( + model.RawMaterials, model.TimePeriods, rule=FD_3mo_contract) + + def FD_no_contract(disjunct, j, t): + model = disjunct.model() + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] == 0) + disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == 0) + if t < model.TimePeriods[-1]: + disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] == 0) + disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == 0) + if t < model.TimePeriods[-2]: + disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] == 0) + disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == 0) + model.FD_no_contract = Disjunct( + model.RawMaterials, model.TimePeriods, rule=FD_no_contract) + + def FD_contract(model, j, t): + return [ model.FD_1mo_contract[j,t], model.FD_2mo_contract[j,t], + model.FD_3mo_contract[j,t], model.FD_no_contract[j,t], ] + model.FD_contract = Disjunction(model.RawMaterials, model.TimePeriods, + rule=FD_contract) + + return model diff --git a/examples/gdp/simple1.py b/examples/gdp/simple1.py index 0536a86212c..2073a5bc4f3 100644 --- a/examples/gdp/simple1.py +++ b/examples/gdp/simple1.py @@ -7,28 +7,31 @@ from pyomo.core import * from pyomo.gdp import * -model = ConcreteModel() - -# x >= 0 _|_ y>=0 -model.x = Var(bounds=(0,None)) -model.y = Var(bounds=(0,None)) - -# Two conditions -def _d(disjunct, flag): - model = disjunct.model() - if flag: - # x == 0 - disjunct.c = Constraint(expr=model.x == 0) - else: - # y == 0 - disjunct.c = Constraint(expr=model.y == 0) -model.d = Disjunct([0,1], rule=_d) - -# Define the disjunction -def _c(model): - return [model.d[0], model.d[1]] -model.c = Disjunction(rule=_c) - -model.C = Constraint(expr=model.x+model.y <= 1) - -model.o = Objective(expr=2*model.x+3*model.y, sense=maximize) +def build_model(): + + model = ConcreteModel() + + # x >= 0 _|_ y>=0 + model.x = Var(bounds=(0,None)) + model.y = Var(bounds=(0,None)) + + # Two conditions + def _d(disjunct, flag): + model = disjunct.model() + if flag: + # x == 0 + disjunct.c = Constraint(expr=model.x == 0) + else: + # y == 0 + disjunct.c = Constraint(expr=model.y == 0) + model.d = Disjunct([0,1], rule=_d) + + # Define the disjunction + def _c(model): + return [model.d[0], model.d[1]] + model.c = Disjunction(rule=_c) + + model.C = Constraint(expr=model.x+model.y <= 1) + + model.o = Objective(expr=2*model.x+3*model.y, sense=maximize) + return model diff --git a/examples/gdp/simple2.py b/examples/gdp/simple2.py index 7bbcfd96b22..fbb3ffa190c 100644 --- a/examples/gdp/simple2.py +++ b/examples/gdp/simple2.py @@ -6,28 +6,30 @@ from pyomo.core import * from pyomo.gdp import * -model = ConcreteModel() +def build_model(): + model = ConcreteModel() -# x >= 0 _|_ y>=0 -model.x = Var(bounds=(0,100)) -model.y = Var(bounds=(0,100)) + # x >= 0 _|_ y>=0 + model.x = Var(bounds=(0,100)) + model.y = Var(bounds=(0,100)) -# Two conditions -def _d(disjunct, flag): - model = disjunct.model() - if flag: - # x == 0 - disjunct.c = Constraint(expr=model.x == 0) - else: - # y == 0 - disjunct.c = Constraint(expr=model.y == 0) -model.d = Disjunct([0,1], rule=_d) + # Two conditions + def _d(disjunct, flag): + model = disjunct.model() + if flag: + # x == 0 + disjunct.c = Constraint(expr=model.x == 0) + else: + # y == 0 + disjunct.c = Constraint(expr=model.y == 0) + model.d = Disjunct([0,1], rule=_d) -# Define the disjunction -def _c(model): - return [model.d[0], model.d[1]] -model.c = Disjunction(rule=_c) + # Define the disjunction + def _c(model): + return [model.d[0], model.d[1]] + model.c = Disjunction(rule=_c) -model.C = Constraint(expr=model.x+model.y <= 1) + model.C = Constraint(expr=model.x+model.y <= 1) -model.o = Objective(expr=2*model.x+3*model.y, sense=maximize) + model.o = Objective(expr=2*model.x+3*model.y, sense=maximize) + return model \ No newline at end of file diff --git a/examples/gdp/simple3.py b/examples/gdp/simple3.py index 4c90d646e71..73dc27be6a2 100644 --- a/examples/gdp/simple3.py +++ b/examples/gdp/simple3.py @@ -6,31 +6,33 @@ from pyomo.core import * from pyomo.gdp import * -model = ConcreteModel() - -# x >= 0 _|_ y>=0 -model.x = Var(bounds=(0,None)) -model.y = Var(bounds=(0,None)) - -# Two conditions -def _d(disjunct, flag): - model = disjunct.model() - if flag: - # x == 0 - disjunct.c = Constraint(expr=model.x == 0) - else: - # y == 0 - disjunct.c = Constraint(expr=model.y == 0) - disjunct.BigM = Suffix() - disjunct.BigM[disjunct.c] = 1 -model.d = Disjunct([0,1], rule=_d) - -# Define the disjunction -def _c(model): - return [model.d[0], model.d[1]] -model.c = Disjunction(rule=_c) - -model.C = Constraint(expr=model.x+model.y <= 1) - -model.o = Objective(expr=2*model.x+3*model.y, sense=maximize) - +def build_model(): + model = ConcreteModel() + + # x >= 0 _|_ y>=0 + model.x = Var(bounds=(0,None)) + model.y = Var(bounds=(0,None)) + + # Two conditions + def _d(disjunct, flag): + model = disjunct.model() + if flag: + # x == 0 + disjunct.c = Constraint(expr=model.x == 0) + else: + # y == 0 + disjunct.c = Constraint(expr=model.y == 0) + disjunct.BigM = Suffix() + disjunct.BigM[disjunct.c] = 1 + model.d = Disjunct([0,1], rule=_d) + + # Define the disjunction + def _c(model): + return [model.d[0], model.d[1]] + model.c = Disjunction(rule=_c) + + model.C = Constraint(expr=model.x+model.y <= 1) + + model.o = Objective(expr=2*model.x+3*model.y, sense=maximize) + + return model From 1004e91a9121068edaccc54d2f47403f99424dbf Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Tue, 11 Feb 2020 15:22:43 -0500 Subject: [PATCH 06/38] bug squishing --- examples/gdp/batchProcessing.py | 21 +- examples/gdp/data_set.py | 21 - examples/gdp/disease_model.py | 78 +- examples/gdp/jobshop.py | 19 +- examples/gdp/medTermPurchasing_Literal.py | 1221 +++++++++-------- .../gdp/medTermPurchasing_Literal_Chull.dat | 1066 +++++++------- 6 files changed, 1217 insertions(+), 1209 deletions(-) delete mode 100644 examples/gdp/data_set.py diff --git a/examples/gdp/batchProcessing.py b/examples/gdp/batchProcessing.py index c574bbc5914..d1363aeda2f 100644 --- a/examples/gdp/batchProcessing.py +++ b/examples/gdp/batchProcessing.py @@ -177,8 +177,9 @@ def volume_stage_jPlus1_rule(disjunct, i): return model.storageTankSize_log[j] >= log(model.StorageTankSizeFactor[j]) + \ model.batchSize_log[i, j+1] def batch_size_rule(disjunct, i): - return -log(model.StorageTankSizeFactorByProd[i,j]) <= model.batchSize_log[i,j] - \ - model.batchSize_log[i, j+1] <= log(model.StorageTankSizeFactorByProd[i,j]) + return inequality(-log(model.StorageTankSizeFactorByProd[i,j]), + model.batchSize_log[i,j] - model.batchSize_log[i, j+1], + log(model.StorageTankSizeFactorByProd[i,j])) def no_batch_rule(disjunct, i): return model.batchSize_log[i,j] - model.batchSize_log[i,j+1] == 0 @@ -220,13 +221,11 @@ def units_in_phase_xor_rule(model, j): return sum(model.inPhase[j,k] for k in model.PARALLELUNITS) == 1 model.units_in_phase_xor = Constraint(model.STAGES, rule=units_in_phase_xor_rule) + return model.create_instance('batchProcessing1.dat') - # instance = model.create_instance('batchProcessing1.dat') - # solver = SolverFactory('baron') - # TransformationFactory('gdp.bigm').apply_to(instance) - # TransformationFactory('core.add_slack_variables').apply_to(instance) - # results = solver.solve(instance) - # instance.display() - # instance.solutions.store_to(results) - # print results - return model + +if __name__ == "__main__": + m = build_model() + TransformationFactory('gdp.bigm').apply_to(m) + SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) + m.min_cost.display() diff --git a/examples/gdp/data_set.py b/examples/gdp/data_set.py deleted file mode 100644 index c338f915023..00000000000 --- a/examples/gdp/data_set.py +++ /dev/null @@ -1,21 +0,0 @@ -# ___________________________________________________________________________ -# -# Pyomo: Python Optimization Modeling Objects -# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and -# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain -# rights in this software. -# This software is distributed under the 3-clause BSD License. -# ___________________________________________________________________________ - -#def data(): - -pop = [ 15.881351, 15.881339, 15.881320, 15.881294, 15.881261, 15.881223, 15.881180, 15.881132, 15.881079, 15.881022, 15.880961, 15.880898, 15.880832, 15.880764, 15.880695, 15.880624, 15.880553, 15.880480, 15.880409, 15.880340, 15.880270, 15.880203, 15.880138, 15.880076, 15.880016, 15.879960, 15.879907, 15.879852, 15.879799, 15.879746, 15.879693, 15.879638, 15.879585, 15.879531, 15.879477, 15.879423, 15.879370, 15.879315, 15.879262, 15.879209, 15.879155, 15.879101, 15.879048, 15.878994, 15.878940, 15.878886, 15.878833, 15.878778, 15.878725, 15.878672, 15.878618, 15.878564, 15.878510, 15.878457, 15.878402, 15.878349, 15.878295, 15.878242, 15.878187, 15.878134, 15.878081, 15.878026, 15.877973, 15.877919, 15.877864, 15.877811, 15.877758, 15.877704, 15.877650, 15.877596, 15.877543, 15.877488, 15.877435, 15.877381, 15.877326, 15.877273, 15.877220, 15.877166, 15.877111, 15.877058, 15.877005, 15.876950, 15.876896, 15.876843, 15.876789, 15.876735, 15.876681, 15.876628, 15.876573, 15.876520, 15.876466, 15.876411, 15.876358, 15.876304, 15.876251, 15.876196, 15.876143, 15.876089, 15.876034, 15.875981, 15.875927, 15.875872, 15.875819, 15.875765, 15.875712, 15.875657, 15.875604, 15.875550, 15.875495, 15.875442, 15.875388, 15.875335, 15.875280, 15.875226, 15.875173, 15.875118, 15.875064, 15.875011, 15.874956, 15.874902, 15.874849, 15.874795, 15.874740, 15.874687, 15.874633, 15.874578, 15.874525, 15.874471, 15.874416, 15.874363, 15.874309, 15.874256, 15.874201, 15.874147, 15.874094, 15.874039, 15.873985, 15.873931, 15.873878, 15.873823, 15.873769, 15.873716, 15.873661, 15.873607, 15.873554, 15.873499, 15.873445, 15.873391, 15.873338, 15.873283, 15.873229, 15.873175, 15.873121, 15.873067, 15.873013, 15.872960, 15.872905, 15.872851, 15.872797, 15.872742, 15.872689, 15.872635, 15.872580, 15.872526, 15.872473, 15.872419, 15.872364, 15.872310, 15.872256, 15.872202, 15.872148, 15.872094, 15.872039, 15.871985, 15.871932, 15.871878, 15.871823, 15.871769, 15.871715, 15.871660, 15.871607, 15.871553, 15.871499, 15.871444, 15.871390, 15.871337, 15.871282, 15.871228, 15.871174, 15.871119, 15.871065, 15.871012, 15.870958, 15.870903, 15.870849, 15.870795, 15.870740, 15.870686, 15.870633, 15.870577, 15.870524, 15.870470, 15.870416, 15.870361, 15.870307, 15.870253, 15.870198, 15.870144, 15.870091, 15.870037, 15.869982, 15.869928, 15.869874, 15.869819, 15.869765, 15.869711, 15.869656, 15.869602, 15.869548, 15.869495, 15.869439, 15.869386, 15.869332, 15.869277, 15.869223, 15.869169, 15.869114, 15.869060, 15.869006, 15.868952, 15.868897, 15.868843, 15.868789, 15.868734, 15.868679, 15.868618, 15.868556, 15.868489, 15.868421, 15.868351, 15.868280, 15.868208, 15.868134, 15.868063, 15.867991, 15.867921, 15.867852, 15.867785, 15.867721, 15.867659, 15.867601, 15.867549, 15.867499, 15.867455, 15.867416, 15.867383, 15.867357, 15.867338, 15.867327, 15.867321, 15.867327, 15.867338, 15.867359, 15.867386, 15.867419, 15.867459, 15.867505, 15.867555, 15.867610, 15.867671, 15.867734, 15.867801, 15.867869, 15.867941, 15.868012, 15.868087, 15.868161, 15.868236, 15.868310, 15.868384, 15.868457, 15.868527, 15.868595, 15.868661, 15.868722, 15.868780, 15.868837, 15.868892, 15.868948, 15.869005, 15.869061, 15.869116, 15.869173, 15.869229, 15.869284, 15.869341, 15.869397, 15.869452, 15.869509, 15.869565, 15.869620, 15.869677, 15.869733, 15.869788, 15.869845, 15.869901, 15.869956, 15.870012, 15.870069, 15.870124, 15.870180, 15.870237, 15.870292, 15.870348, 15.870405, 15.870461, 15.870516, 15.870572, 15.870629, 15.870684, 15.870740, 15.870796, 15.870851, 15.870908, 15.870964, 15.871019, 15.871076, 15.871132, 15.871187, 15.871243, 15.871300, 15.871355, 15.871411, 15.871467, 15.871522, 15.871579, 15.871635, 15.871691, 15.871746, 15.871802, 15.871859, 15.871914, 15.871970, 15.872026, 15.872081, 15.872138, 15.872194, 15.872249, 15.872305, 15.872361, 15.872416, 15.872473, 15.872529, 15.872584, 15.872640, 15.872696, 15.872751, 15.872807, 15.872864, 15.872919, 15.872975, 15.873031, 15.873087, 15.873142, 15.873198, 15.873255, 15.873310, 15.873366, 15.873422, 15.873477, 15.873533, 15.873589, 15.873644, 15.873700, 15.873757, 15.873811, 15.873868, 15.873924, 15.873979, 15.874035, 15.874091, 15.874146, 15.874202, 15.874258, 15.874313, 15.874369, 15.874425, 15.874481, 15.874536, 15.874592] - -logIstar = [7.943245, 8.269994, 8.517212, 8.814208, 9.151740, 9.478472, 9.559847, 9.664087, 9.735378, 9.852583, 9.692265, 9.498807, 9.097634, 8.388878, 7.870516, 7.012956, 6.484941, 5.825368, 5.346815, 5.548361, 5.706732, 5.712617, 5.709714, 5.696888, 5.530087, 5.826563, 6.643563, 7.004292, 7.044663, 7.190259, 7.335926, 7.516861, 7.831779, 8.188895, 8.450204, 8.801436, 8.818379, 8.787658, 8.601685, 8.258338, 7.943364, 7.425585, 7.062834, 6.658307, 6.339600, 6.526984, 6.679178, 6.988758, 7.367331, 7.746694, 8.260558, 8.676522, 9.235582, 9.607778, 9.841917, 10.081571, 10.216090, 10.350366, 10.289668, 10.248842, 10.039504, 9.846343, 9.510392, 9.190923, 8.662465, 7.743221, 7.128458, 5.967898, 5.373883, 5.097497, 4.836570, 5.203345, 5.544798, 5.443047, 5.181152, 5.508669, 6.144130, 6.413744, 6.610423, 6.748885, 6.729511, 6.789841, 6.941034, 7.093516, 7.307039, 7.541077, 7.644803, 7.769145, 7.760187, 7.708017, 7.656795, 7.664983, 7.483828, 6.887324, 6.551093, 6.457449, 6.346064, 6.486300, 6.612378, 6.778753, 6.909477, 7.360570, 8.150303, 8.549044, 8.897572, 9.239323, 9.538751, 9.876531, 10.260911, 10.613536, 10.621510, 10.661115, 10.392899, 10.065536, 9.920090, 9.933097, 9.561691, 8.807713, 8.263463, 7.252184, 6.669083, 5.877763, 5.331878, 5.356563, 5.328469, 5.631146, 6.027497, 6.250717, 6.453919, 6.718444, 7.071636, 7.348905, 7.531528, 7.798226, 8.197941, 8.578809, 8.722964, 8.901152, 8.904370, 8.889865, 8.881902, 8.958903, 8.721281, 8.211509, 7.810624, 7.164607, 6.733688, 6.268503, 5.905983, 5.900432, 5.846547, 6.245427, 6.786271, 7.088480, 7.474295, 7.650063, 7.636703, 7.830990, 8.231516, 8.584816, 8.886908, 9.225216, 9.472778, 9.765505, 9.928623, 10.153033, 10.048574, 9.892620, 9.538818, 8.896100, 8.437584, 7.819738, 7.362598, 6.505880, 5.914972, 6.264584, 6.555019, 6.589319, 6.552029, 6.809771, 7.187616, 7.513918, 8.017712, 8.224957, 8.084474, 8.079148, 8.180991, 8.274269, 8.413748, 8.559599, 8.756090, 9.017927, 9.032720, 9.047983, 8.826873, 8.366489, 8.011876, 7.500830, 7.140406, 6.812626, 6.538719, 6.552218, 6.540129, 6.659927, 6.728530, 7.179692, 7.989210, 8.399173, 8.781128, 9.122303, 9.396378, 9.698512, 9.990104, 10.276543, 10.357284, 10.465869, 10.253833, 10.018503, 9.738407, 9.484367, 9.087025, 8.526409, 8.041126, 7.147168, 6.626706, 6.209446, 5.867231, 5.697439, 5.536769, 5.421413, 5.238297, 5.470136, 5.863007, 6.183083, 6.603569, 6.906278, 7.092324, 7.326612, 7.576052, 7.823430, 7.922775, 8.041677, 8.063403, 8.073229, 8.099726, 8.168522, 8.099041, 8.011404, 7.753147, 6.945211, 6.524244, 6.557723, 6.497742, 6.256247, 5.988794, 6.268093, 6.583316, 7.106842, 8.053929, 8.508237, 8.938915, 9.311863, 9.619753, 9.931745, 10.182361, 10.420978, 10.390829, 10.389230, 10.079342, 9.741479, 9.444561, 9.237448, 8.777687, 7.976436, 7.451502, 6.742856, 6.271545, 5.782289, 5.403089, 5.341954, 5.243509, 5.522993, 5.897001, 6.047042, 6.100738, 6.361727, 6.849562, 7.112544, 7.185346, 7.309412, 7.423746, 7.532142, 7.510318, 7.480175, 7.726362, 8.061117, 8.127072, 8.206166, 8.029634, 7.592953, 7.304869, 7.005394, 6.750019, 6.461377, 6.226432, 6.287047, 6.306452, 6.783694, 7.450957, 7.861692, 8.441530, 8.739626, 8.921994, 9.168961, 9.428077, 9.711664, 10.032714, 10.349937, 10.483985, 10.647475, 10.574038, 10.522431, 10.192246, 9.756246, 9.342511, 8.872072, 8.414189, 7.606582, 7.084701, 6.149903, 5.517257, 5.839429, 6.098090, 6.268935, 6.475965, 6.560543, 6.598942, 6.693938, 6.802531, 6.934345, 7.078370, 7.267736, 7.569640, 7.872204, 8.083603, 8.331226, 8.527144, 8.773523, 8.836599, 8.894303, 8.808326, 8.641717, 8.397901, 7.849034, 7.482899, 7.050252, 6.714103, 6.900603, 7.050765, 7.322905, 7.637986, 8.024340, 8.614505, 8.933591, 9.244008, 9.427410, 9.401385, 9.457744, 9.585068, 9.699673, 9.785478, 9.884559, 9.769732, 9.655075, 9.423071, 9.210198, 8.786654, 8.061787, 7.560976, 6.855829, 6.390707, 5.904006, 5.526631, 5.712303, 5.867027, 5.768367, 5.523352, 5.909118, 6.745543, 6.859218 ] - -deltaS = [ 9916.490263 ,12014.263380 ,13019.275755 ,12296.373612 ,8870.995603 ,1797.354574 ,-6392.880771 ,-16150.825387 ,-27083.245106 ,-40130.421462 ,-50377.169958 ,-57787.717468 ,-60797.223427 ,-59274.041897 ,-55970.213230 ,-51154.650927 ,-45877.841034 ,-40278.553775 ,-34543.967175 ,-28849.633641 ,-23192.776605 ,-17531.130740 ,-11862.021829 ,-6182.456792 ,-450.481090 ,5201.184400 ,10450.773882 ,15373.018272 ,20255.699431 ,24964.431669 ,29470.745887 ,33678.079947 ,37209.808930 ,39664.432393 ,41046.735479 ,40462.982011 ,39765.070209 ,39270.815830 ,39888.077002 ,42087.276604 ,45332.012929 ,49719.128772 ,54622.190928 ,59919.718626 ,65436.341097 ,70842.911460 ,76143.747430 ,81162.358574 ,85688.102884 ,89488.917734 ,91740.108470 ,91998.787916 ,87875.986012 ,79123.877908 ,66435.611045 ,48639.250610 ,27380.282817 ,2166.538464 ,-21236.428084 ,-43490.803535 ,-60436.624080 ,-73378.401966 ,-80946.278268 ,-84831.969493 ,-84696.627286 ,-81085.365407 ,-76410.847049 ,-70874.415387 ,-65156.276464 ,-59379.086883 ,-53557.267619 ,-47784.164830 ,-42078.001172 ,-36340.061427 ,-30541.788202 ,-24805.281435 ,-19280.817165 ,-13893.690606 ,-8444.172221 ,-3098.160839 ,2270.908649 ,7594.679295 ,12780.079247 ,17801.722109 ,22543.091206 ,26897.369814 ,31051.285734 ,34933.809557 ,38842.402859 ,42875.230152 ,47024.395356 ,51161.516122 ,55657.298307 ,60958.155424 ,66545.635029 ,72202.930397 ,77934.761905 ,83588.207792 ,89160.874522 ,94606.115027 ,99935.754968 ,104701.404975 ,107581.670606 ,108768.440311 ,107905.700480 ,104062.148863 ,96620.281684 ,83588.443029 ,61415.088182 ,27124.031692 ,-7537.285321 ,-43900.451653 ,-70274.062783 ,-87573.481475 ,-101712.148408 ,-116135.719087 ,-124187.225446 ,-124725.278371 ,-122458.145590 ,-117719.918256 ,-112352.138605 ,-106546.806030 ,-100583.803012 ,-94618.253238 ,-88639.090897 ,-82725.009842 ,-76938.910669 ,-71248.957807 ,-65668.352795 ,-60272.761991 ,-55179.538428 ,-50456.021161 ,-46037.728058 ,-42183.912670 ,-39522.184006 ,-38541.255303 ,-38383.665728 ,-39423.998130 ,-40489.466130 ,-41450.406768 ,-42355.156592 ,-43837.562085 ,-43677.262972 ,-41067.896944 ,-37238.628465 ,-32230.392026 ,-26762.766062 ,-20975.163308 ,-15019.218554 ,-9053.105545 ,-3059.663132 ,2772.399618 ,8242.538397 ,13407.752291 ,18016.047539 ,22292.125752 ,26616.583347 ,30502.564253 ,33153.890890 ,34216.684448 ,33394.220786 ,29657.417791 ,23064.375405 ,12040.831532 ,-2084.921068 ,-21390.235970 ,-38176.615985 ,-51647.714482 ,-59242.564959 ,-60263.150854 ,-58599.245165 ,-54804.972560 ,-50092.112608 ,-44465.812552 ,-38533.096297 ,-32747.104307 ,-27130.082610 ,-21529.632955 ,-15894.611939 ,-10457.566933 ,-5429.042583 ,-903.757828 ,2481.947589 ,5173.789976 ,8358.768202 ,11565.584635 ,14431.147931 ,16951.619820 ,18888.807708 ,20120.884465 ,20222.141242 ,18423.168124 ,16498.668271 ,14442.624242 ,14070.038273 ,16211.370808 ,19639.815904 ,24280.360465 ,29475.380079 ,35030.793540 ,40812.325095 ,46593.082382 ,52390.906885 ,58109.310860 ,63780.896094 ,68984.456561 ,72559.442320 ,74645.487900 ,74695.219755 ,72098.143876 ,66609.929889 ,56864.971296 ,41589.295266 ,19057.032104 ,-5951.329863 ,-34608.796853 ,-56603.801584 ,-72678.838057 ,-83297.070856 ,-90127.593511 ,-92656.040614 ,-91394.995510 ,-88192.056842 ,-83148.833075 ,-77582.587173 ,-71750.440823 ,-65765.369857 ,-59716.101820 ,-53613.430067 ,-47473.832358 ,-41287.031890 ,-35139.919259 ,-29097.671507 ,-23178.836760 ,-17486.807388 ,-12046.775779 ,-6802.483422 ,-1867.556171 ,2644.380534 ,6615.829501 ,10332.557518 ,13706.737038 ,17017.991307 ,20303.136670 ,23507.386461 ,26482.194102 ,29698.585356 ,33196.305757 ,37385.914179 ,42872.996212 ,48725.617879 ,54564.488527 ,60453.841604 ,66495.146265 ,72668.620416 ,78723.644870 ,84593.136677 ,89974.936239 ,93439.798630 ,95101.207834 ,94028.126381 ,89507.925620 ,80989.846001 ,66944.274744 ,47016.422041 ,19932.783790 ,-6198.433172 ,-32320.379400 ,-49822.852084 ,-60517.553414 ,-66860.548269 ,-70849.714105 ,-71058.721556 ,-67691.947812 ,-63130.703822 ,-57687.607311 ,-51916.952488 ,-45932.054982 ,-39834.909941 ,-33714.535713 ,-27564.443333 ,-21465.186188 ,-15469.326408 ,-9522.358787 ,-3588.742161 ,2221.802073 ,7758.244339 ,13020.269708 ,18198.562827 ,23211.338588 ,28051.699645 ,32708.577247 ,37413.795242 ,42181.401920 ,46462.499633 ,49849.582315 ,53026.578940 ,55930.600705 ,59432.642178 ,64027.356857 ,69126.843653 ,74620.328837 ,80372.056070 ,86348.152766 ,92468.907239 ,98568.998246 ,104669.511588 ,110445.790143 ,115394.348973 ,119477.553152 ,121528.574511 ,121973.674087 ,121048.017786 ,118021.473181 ,112151.993711 ,102195.999157 ,85972.731130 ,61224.719621 ,31949.279603 ,-3726.022971 ,-36485.298619 ,-67336.469799 ,-87799.366129 ,-98865.713558 ,-104103.651120 ,-105068.402300 ,-103415.820781 ,-99261.356633 ,-94281.850081 ,-88568.701325 ,-82625.711921 ,-76766.776770 ,-70998.803524 ,-65303.404499 ,-59719.198305 ,-54182.230439 ,-48662.904657 ,-43206.731668 ,-37732.701095 ,-32375.478519 ,-27167.508567 ,-22197.211891 ,-17722.869502 ,-13925.135219 ,-10737.893027 ,-8455.327914 ,-7067.008358 ,-7086.991191 ,-7527.693561 ,-8378.025732 ,-8629.383998 ,-7854.586079 ,-5853.040657 ,-1973.225485 ,2699.850783 ,8006.098287 ,13651.734934 ,19139.318072 ,24476.645420 ,29463.480336 ,33899.078820 ,37364.528796 ,38380.214949 ,37326.585649 ,33428.470616 ,27441.000494 ,21761.126583 ,15368.408081 ,7224.234078 ,-2702.217396 ,-14109.682505 ,-27390.915614 ,-38569.562393 ,-47875.155339 ,-53969.121872 ,-57703.473001 ,-57993.198171 ,-54908.391840 ,-50568.410328 ,-45247.622563 ,-39563.224328 ,-33637.786521 ,-27585.345413 ,-21572.074797 ,-15597.363909 ,-9577.429076 ,-3475.770622 ,2520.378408 ,8046.881775 ,13482.345595 ] - -beta_set = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26] - -# return beta_set, deltaS, logIstar, pop diff --git a/examples/gdp/disease_model.py b/examples/gdp/disease_model.py index 87183884ade..8695c850023 100644 --- a/examples/gdp/disease_model.py +++ b/examples/gdp/disease_model.py @@ -2,8 +2,8 @@ # # Pyomo: Python Optimization Modeling Objects # Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and -# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ @@ -16,17 +16,24 @@ # ============================================ # import packages -from pyomo.core import * +from pyomo.environ import * from pyomo.gdp import * import math -# import data -from data_set import * -#from new_data_set import * - def build_model(): + # import data + pop = [ 15.881351, 15.881339, 15.881320, 15.881294, 15.881261, 15.881223, 15.881180, 15.881132, 15.881079, 15.881022, 15.880961, 15.880898, 15.880832, 15.880764, 15.880695, 15.880624, 15.880553, 15.880480, 15.880409, 15.880340, 15.880270, 15.880203, 15.880138, 15.880076, 15.880016, 15.879960, 15.879907, 15.879852, 15.879799, 15.879746, 15.879693, 15.879638, 15.879585, 15.879531, 15.879477, 15.879423, 15.879370, 15.879315, 15.879262, 15.879209, 15.879155, 15.879101, 15.879048, 15.878994, 15.878940, 15.878886, 15.878833, 15.878778, 15.878725, 15.878672, 15.878618, 15.878564, 15.878510, 15.878457, 15.878402, 15.878349, 15.878295, 15.878242, 15.878187, 15.878134, 15.878081, 15.878026, 15.877973, 15.877919, 15.877864, 15.877811, 15.877758, 15.877704, 15.877650, 15.877596, 15.877543, 15.877488, 15.877435, 15.877381, 15.877326, 15.877273, 15.877220, 15.877166, 15.877111, 15.877058, 15.877005, 15.876950, 15.876896, 15.876843, 15.876789, 15.876735, 15.876681, 15.876628, 15.876573, 15.876520, 15.876466, 15.876411, 15.876358, 15.876304, 15.876251, 15.876196, 15.876143, 15.876089, 15.876034, 15.875981, 15.875927, 15.875872, 15.875819, 15.875765, 15.875712, 15.875657, 15.875604, 15.875550, 15.875495, 15.875442, 15.875388, 15.875335, 15.875280, 15.875226, 15.875173, 15.875118, 15.875064, 15.875011, 15.874956, 15.874902, 15.874849, 15.874795, 15.874740, 15.874687, 15.874633, 15.874578, 15.874525, 15.874471, 15.874416, 15.874363, 15.874309, 15.874256, 15.874201, 15.874147, 15.874094, 15.874039, 15.873985, 15.873931, 15.873878, 15.873823, 15.873769, 15.873716, 15.873661, 15.873607, 15.873554, 15.873499, 15.873445, 15.873391, 15.873338, 15.873283, 15.873229, 15.873175, 15.873121, 15.873067, 15.873013, 15.872960, 15.872905, 15.872851, 15.872797, 15.872742, 15.872689, 15.872635, 15.872580, 15.872526, 15.872473, 15.872419, 15.872364, 15.872310, 15.872256, 15.872202, 15.872148, 15.872094, 15.872039, 15.871985, 15.871932, 15.871878, 15.871823, 15.871769, 15.871715, 15.871660, 15.871607, 15.871553, 15.871499, 15.871444, 15.871390, 15.871337, 15.871282, 15.871228, 15.871174, 15.871119, 15.871065, 15.871012, 15.870958, 15.870903, 15.870849, 15.870795, 15.870740, 15.870686, 15.870633, 15.870577, 15.870524, 15.870470, 15.870416, 15.870361, 15.870307, 15.870253, 15.870198, 15.870144, 15.870091, 15.870037, 15.869982, 15.869928, 15.869874, 15.869819, 15.869765, 15.869711, 15.869656, 15.869602, 15.869548, 15.869495, 15.869439, 15.869386, 15.869332, 15.869277, 15.869223, 15.869169, 15.869114, 15.869060, 15.869006, 15.868952, 15.868897, 15.868843, 15.868789, 15.868734, 15.868679, 15.868618, 15.868556, 15.868489, 15.868421, 15.868351, 15.868280, 15.868208, 15.868134, 15.868063, 15.867991, 15.867921, 15.867852, 15.867785, 15.867721, 15.867659, 15.867601, 15.867549, 15.867499, 15.867455, 15.867416, 15.867383, 15.867357, 15.867338, 15.867327, 15.867321, 15.867327, 15.867338, 15.867359, 15.867386, 15.867419, 15.867459, 15.867505, 15.867555, 15.867610, 15.867671, 15.867734, 15.867801, 15.867869, 15.867941, 15.868012, 15.868087, 15.868161, 15.868236, 15.868310, 15.868384, 15.868457, 15.868527, 15.868595, 15.868661, 15.868722, 15.868780, 15.868837, 15.868892, 15.868948, 15.869005, 15.869061, 15.869116, 15.869173, 15.869229, 15.869284, 15.869341, 15.869397, 15.869452, 15.869509, 15.869565, 15.869620, 15.869677, 15.869733, 15.869788, 15.869845, 15.869901, 15.869956, 15.870012, 15.870069, 15.870124, 15.870180, 15.870237, 15.870292, 15.870348, 15.870405, 15.870461, 15.870516, 15.870572, 15.870629, 15.870684, 15.870740, 15.870796, 15.870851, 15.870908, 15.870964, 15.871019, 15.871076, 15.871132, 15.871187, 15.871243, 15.871300, 15.871355, 15.871411, 15.871467, 15.871522, 15.871579, 15.871635, 15.871691, 15.871746, 15.871802, 15.871859, 15.871914, 15.871970, 15.872026, 15.872081, 15.872138, 15.872194, 15.872249, 15.872305, 15.872361, 15.872416, 15.872473, 15.872529, 15.872584, 15.872640, 15.872696, 15.872751, 15.872807, 15.872864, 15.872919, 15.872975, 15.873031, 15.873087, 15.873142, 15.873198, 15.873255, 15.873310, 15.873366, 15.873422, 15.873477, 15.873533, 15.873589, 15.873644, 15.873700, 15.873757, 15.873811, 15.873868, 15.873924, 15.873979, 15.874035, 15.874091, 15.874146, 15.874202, 15.874258, 15.874313, 15.874369, 15.874425, 15.874481, 15.874536, 15.874592] + + logIstar = [7.943245, 8.269994, 8.517212, 8.814208, 9.151740, 9.478472, 9.559847, 9.664087, 9.735378, 9.852583, 9.692265, 9.498807, 9.097634, 8.388878, 7.870516, 7.012956, 6.484941, 5.825368, 5.346815, 5.548361, 5.706732, 5.712617, 5.709714, 5.696888, 5.530087, 5.826563, 6.643563, 7.004292, 7.044663, 7.190259, 7.335926, 7.516861, 7.831779, 8.188895, 8.450204, 8.801436, 8.818379, 8.787658, 8.601685, 8.258338, 7.943364, 7.425585, 7.062834, 6.658307, 6.339600, 6.526984, 6.679178, 6.988758, 7.367331, 7.746694, 8.260558, 8.676522, 9.235582, 9.607778, 9.841917, 10.081571, 10.216090, 10.350366, 10.289668, 10.248842, 10.039504, 9.846343, 9.510392, 9.190923, 8.662465, 7.743221, 7.128458, 5.967898, 5.373883, 5.097497, 4.836570, 5.203345, 5.544798, 5.443047, 5.181152, 5.508669, 6.144130, 6.413744, 6.610423, 6.748885, 6.729511, 6.789841, 6.941034, 7.093516, 7.307039, 7.541077, 7.644803, 7.769145, 7.760187, 7.708017, 7.656795, 7.664983, 7.483828, 6.887324, 6.551093, 6.457449, 6.346064, 6.486300, 6.612378, 6.778753, 6.909477, 7.360570, 8.150303, 8.549044, 8.897572, 9.239323, 9.538751, 9.876531, 10.260911, 10.613536, 10.621510, 10.661115, 10.392899, 10.065536, 9.920090, 9.933097, 9.561691, 8.807713, 8.263463, 7.252184, 6.669083, 5.877763, 5.331878, 5.356563, 5.328469, 5.631146, 6.027497, 6.250717, 6.453919, 6.718444, 7.071636, 7.348905, 7.531528, 7.798226, 8.197941, 8.578809, 8.722964, 8.901152, 8.904370, 8.889865, 8.881902, 8.958903, 8.721281, 8.211509, 7.810624, 7.164607, 6.733688, 6.268503, 5.905983, 5.900432, 5.846547, 6.245427, 6.786271, 7.088480, 7.474295, 7.650063, 7.636703, 7.830990, 8.231516, 8.584816, 8.886908, 9.225216, 9.472778, 9.765505, 9.928623, 10.153033, 10.048574, 9.892620, 9.538818, 8.896100, 8.437584, 7.819738, 7.362598, 6.505880, 5.914972, 6.264584, 6.555019, 6.589319, 6.552029, 6.809771, 7.187616, 7.513918, 8.017712, 8.224957, 8.084474, 8.079148, 8.180991, 8.274269, 8.413748, 8.559599, 8.756090, 9.017927, 9.032720, 9.047983, 8.826873, 8.366489, 8.011876, 7.500830, 7.140406, 6.812626, 6.538719, 6.552218, 6.540129, 6.659927, 6.728530, 7.179692, 7.989210, 8.399173, 8.781128, 9.122303, 9.396378, 9.698512, 9.990104, 10.276543, 10.357284, 10.465869, 10.253833, 10.018503, 9.738407, 9.484367, 9.087025, 8.526409, 8.041126, 7.147168, 6.626706, 6.209446, 5.867231, 5.697439, 5.536769, 5.421413, 5.238297, 5.470136, 5.863007, 6.183083, 6.603569, 6.906278, 7.092324, 7.326612, 7.576052, 7.823430, 7.922775, 8.041677, 8.063403, 8.073229, 8.099726, 8.168522, 8.099041, 8.011404, 7.753147, 6.945211, 6.524244, 6.557723, 6.497742, 6.256247, 5.988794, 6.268093, 6.583316, 7.106842, 8.053929, 8.508237, 8.938915, 9.311863, 9.619753, 9.931745, 10.182361, 10.420978, 10.390829, 10.389230, 10.079342, 9.741479, 9.444561, 9.237448, 8.777687, 7.976436, 7.451502, 6.742856, 6.271545, 5.782289, 5.403089, 5.341954, 5.243509, 5.522993, 5.897001, 6.047042, 6.100738, 6.361727, 6.849562, 7.112544, 7.185346, 7.309412, 7.423746, 7.532142, 7.510318, 7.480175, 7.726362, 8.061117, 8.127072, 8.206166, 8.029634, 7.592953, 7.304869, 7.005394, 6.750019, 6.461377, 6.226432, 6.287047, 6.306452, 6.783694, 7.450957, 7.861692, 8.441530, 8.739626, 8.921994, 9.168961, 9.428077, 9.711664, 10.032714, 10.349937, 10.483985, 10.647475, 10.574038, 10.522431, 10.192246, 9.756246, 9.342511, 8.872072, 8.414189, 7.606582, 7.084701, 6.149903, 5.517257, 5.839429, 6.098090, 6.268935, 6.475965, 6.560543, 6.598942, 6.693938, 6.802531, 6.934345, 7.078370, 7.267736, 7.569640, 7.872204, 8.083603, 8.331226, 8.527144, 8.773523, 8.836599, 8.894303, 8.808326, 8.641717, 8.397901, 7.849034, 7.482899, 7.050252, 6.714103, 6.900603, 7.050765, 7.322905, 7.637986, 8.024340, 8.614505, 8.933591, 9.244008, 9.427410, 9.401385, 9.457744, 9.585068, 9.699673, 9.785478, 9.884559, 9.769732, 9.655075, 9.423071, 9.210198, 8.786654, 8.061787, 7.560976, 6.855829, 6.390707, 5.904006, 5.526631, 5.712303, 5.867027, 5.768367, 5.523352, 5.909118, 6.745543, 6.859218 ] + + deltaS = [ 9916.490263 ,12014.263380 ,13019.275755 ,12296.373612 ,8870.995603 ,1797.354574 ,-6392.880771 ,-16150.825387 ,-27083.245106 ,-40130.421462 ,-50377.169958 ,-57787.717468 ,-60797.223427 ,-59274.041897 ,-55970.213230 ,-51154.650927 ,-45877.841034 ,-40278.553775 ,-34543.967175 ,-28849.633641 ,-23192.776605 ,-17531.130740 ,-11862.021829 ,-6182.456792 ,-450.481090 ,5201.184400 ,10450.773882 ,15373.018272 ,20255.699431 ,24964.431669 ,29470.745887 ,33678.079947 ,37209.808930 ,39664.432393 ,41046.735479 ,40462.982011 ,39765.070209 ,39270.815830 ,39888.077002 ,42087.276604 ,45332.012929 ,49719.128772 ,54622.190928 ,59919.718626 ,65436.341097 ,70842.911460 ,76143.747430 ,81162.358574 ,85688.102884 ,89488.917734 ,91740.108470 ,91998.787916 ,87875.986012 ,79123.877908 ,66435.611045 ,48639.250610 ,27380.282817 ,2166.538464 ,-21236.428084 ,-43490.803535 ,-60436.624080 ,-73378.401966 ,-80946.278268 ,-84831.969493 ,-84696.627286 ,-81085.365407 ,-76410.847049 ,-70874.415387 ,-65156.276464 ,-59379.086883 ,-53557.267619 ,-47784.164830 ,-42078.001172 ,-36340.061427 ,-30541.788202 ,-24805.281435 ,-19280.817165 ,-13893.690606 ,-8444.172221 ,-3098.160839 ,2270.908649 ,7594.679295 ,12780.079247 ,17801.722109 ,22543.091206 ,26897.369814 ,31051.285734 ,34933.809557 ,38842.402859 ,42875.230152 ,47024.395356 ,51161.516122 ,55657.298307 ,60958.155424 ,66545.635029 ,72202.930397 ,77934.761905 ,83588.207792 ,89160.874522 ,94606.115027 ,99935.754968 ,104701.404975 ,107581.670606 ,108768.440311 ,107905.700480 ,104062.148863 ,96620.281684 ,83588.443029 ,61415.088182 ,27124.031692 ,-7537.285321 ,-43900.451653 ,-70274.062783 ,-87573.481475 ,-101712.148408 ,-116135.719087 ,-124187.225446 ,-124725.278371 ,-122458.145590 ,-117719.918256 ,-112352.138605 ,-106546.806030 ,-100583.803012 ,-94618.253238 ,-88639.090897 ,-82725.009842 ,-76938.910669 ,-71248.957807 ,-65668.352795 ,-60272.761991 ,-55179.538428 ,-50456.021161 ,-46037.728058 ,-42183.912670 ,-39522.184006 ,-38541.255303 ,-38383.665728 ,-39423.998130 ,-40489.466130 ,-41450.406768 ,-42355.156592 ,-43837.562085 ,-43677.262972 ,-41067.896944 ,-37238.628465 ,-32230.392026 ,-26762.766062 ,-20975.163308 ,-15019.218554 ,-9053.105545 ,-3059.663132 ,2772.399618 ,8242.538397 ,13407.752291 ,18016.047539 ,22292.125752 ,26616.583347 ,30502.564253 ,33153.890890 ,34216.684448 ,33394.220786 ,29657.417791 ,23064.375405 ,12040.831532 ,-2084.921068 ,-21390.235970 ,-38176.615985 ,-51647.714482 ,-59242.564959 ,-60263.150854 ,-58599.245165 ,-54804.972560 ,-50092.112608 ,-44465.812552 ,-38533.096297 ,-32747.104307 ,-27130.082610 ,-21529.632955 ,-15894.611939 ,-10457.566933 ,-5429.042583 ,-903.757828 ,2481.947589 ,5173.789976 ,8358.768202 ,11565.584635 ,14431.147931 ,16951.619820 ,18888.807708 ,20120.884465 ,20222.141242 ,18423.168124 ,16498.668271 ,14442.624242 ,14070.038273 ,16211.370808 ,19639.815904 ,24280.360465 ,29475.380079 ,35030.793540 ,40812.325095 ,46593.082382 ,52390.906885 ,58109.310860 ,63780.896094 ,68984.456561 ,72559.442320 ,74645.487900 ,74695.219755 ,72098.143876 ,66609.929889 ,56864.971296 ,41589.295266 ,19057.032104 ,-5951.329863 ,-34608.796853 ,-56603.801584 ,-72678.838057 ,-83297.070856 ,-90127.593511 ,-92656.040614 ,-91394.995510 ,-88192.056842 ,-83148.833075 ,-77582.587173 ,-71750.440823 ,-65765.369857 ,-59716.101820 ,-53613.430067 ,-47473.832358 ,-41287.031890 ,-35139.919259 ,-29097.671507 ,-23178.836760 ,-17486.807388 ,-12046.775779 ,-6802.483422 ,-1867.556171 ,2644.380534 ,6615.829501 ,10332.557518 ,13706.737038 ,17017.991307 ,20303.136670 ,23507.386461 ,26482.194102 ,29698.585356 ,33196.305757 ,37385.914179 ,42872.996212 ,48725.617879 ,54564.488527 ,60453.841604 ,66495.146265 ,72668.620416 ,78723.644870 ,84593.136677 ,89974.936239 ,93439.798630 ,95101.207834 ,94028.126381 ,89507.925620 ,80989.846001 ,66944.274744 ,47016.422041 ,19932.783790 ,-6198.433172 ,-32320.379400 ,-49822.852084 ,-60517.553414 ,-66860.548269 ,-70849.714105 ,-71058.721556 ,-67691.947812 ,-63130.703822 ,-57687.607311 ,-51916.952488 ,-45932.054982 ,-39834.909941 ,-33714.535713 ,-27564.443333 ,-21465.186188 ,-15469.326408 ,-9522.358787 ,-3588.742161 ,2221.802073 ,7758.244339 ,13020.269708 ,18198.562827 ,23211.338588 ,28051.699645 ,32708.577247 ,37413.795242 ,42181.401920 ,46462.499633 ,49849.582315 ,53026.578940 ,55930.600705 ,59432.642178 ,64027.356857 ,69126.843653 ,74620.328837 ,80372.056070 ,86348.152766 ,92468.907239 ,98568.998246 ,104669.511588 ,110445.790143 ,115394.348973 ,119477.553152 ,121528.574511 ,121973.674087 ,121048.017786 ,118021.473181 ,112151.993711 ,102195.999157 ,85972.731130 ,61224.719621 ,31949.279603 ,-3726.022971 ,-36485.298619 ,-67336.469799 ,-87799.366129 ,-98865.713558 ,-104103.651120 ,-105068.402300 ,-103415.820781 ,-99261.356633 ,-94281.850081 ,-88568.701325 ,-82625.711921 ,-76766.776770 ,-70998.803524 ,-65303.404499 ,-59719.198305 ,-54182.230439 ,-48662.904657 ,-43206.731668 ,-37732.701095 ,-32375.478519 ,-27167.508567 ,-22197.211891 ,-17722.869502 ,-13925.135219 ,-10737.893027 ,-8455.327914 ,-7067.008358 ,-7086.991191 ,-7527.693561 ,-8378.025732 ,-8629.383998 ,-7854.586079 ,-5853.040657 ,-1973.225485 ,2699.850783 ,8006.098287 ,13651.734934 ,19139.318072 ,24476.645420 ,29463.480336 ,33899.078820 ,37364.528796 ,38380.214949 ,37326.585649 ,33428.470616 ,27441.000494 ,21761.126583 ,15368.408081 ,7224.234078 ,-2702.217396 ,-14109.682505 ,-27390.915614 ,-38569.562393 ,-47875.155339 ,-53969.121872 ,-57703.473001 ,-57993.198171 ,-54908.391840 ,-50568.410328 ,-45247.622563 ,-39563.224328 ,-33637.786521 ,-27585.345413 ,-21572.074797 ,-15597.363909 ,-9577.429076 ,-3475.770622 ,2520.378408 ,8046.881775 ,13482.345595 ] + + beta_set = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26] + + #from new_data_set import * + # declare model name - model = AbstractModel() + model = ConcreteModel() # declare constants bpy = 26 # biweeks per year @@ -114,28 +121,35 @@ def _disj(model, i): return [model.high_low[i,j] for j in model.y] model.disj = Disjunction(model.S_beta, rule=_disj) - - """ - # high beta disjuncts - def highbeta_L(m,i): - expr = m.logbeta[i] - m.logbeta_high + bigM*(1-m.y[i]) - return (0.0, expr, None) - model.highbeta_L = Constraint(model.S_beta, rule=highbeta_L) - - def highbeta_U(m,i): - expr = m.logbeta[i] - m.logbeta_high - return (None, expr, 0.0) - model.highbeta_U = Constraint(model.S_beta, rule=highbeta_U) - - # low beta disjuncts - def lowbeta_U(m,i): - expr = m.logbeta[i] - m.logbeta_low - bigM*(m.y[i]) - return (None, expr, 0.0) - model.lowbeta_U = Constraint(model.S_beta, rule=lowbeta_U) - - def lowbeta_L(m,i): - expr = m.logbeta[i] - m.logbeta_low - return (0.0, expr, None) - model.lowbeta_L = Constraint(model.S_beta, rule=lowbeta_L) - """ return model + + +""" +# high beta disjuncts +def highbeta_L(m,i): + expr = m.logbeta[i] - m.logbeta_high + bigM*(1-m.y[i]) + return (0.0, expr, None) +model.highbeta_L = Constraint(model.S_beta, rule=highbeta_L) + +def highbeta_U(m,i): + expr = m.logbeta[i] - m.logbeta_high + return (None, expr, 0.0) +model.highbeta_U = Constraint(model.S_beta, rule=highbeta_U) + +# low beta disjuncts +def lowbeta_U(m,i): + expr = m.logbeta[i] - m.logbeta_low - bigM*(m.y[i]) + return (None, expr, 0.0) +model.lowbeta_U = Constraint(model.S_beta, rule=lowbeta_U) + +def lowbeta_L(m,i): + expr = m.logbeta[i] - m.logbeta_low + return (0.0, expr, None) +model.lowbeta_L = Constraint(model.S_beta, rule=lowbeta_L) +""" + +if __name__ == "__main__": + m = build_model() + TransformationFactory('gdp.bigm').apply_to(m) + SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) + m.obj.display() diff --git a/examples/gdp/jobshop.py b/examples/gdp/jobshop.py index 04d94a0dd6e..35f7f235699 100644 --- a/examples/gdp/jobshop.py +++ b/examples/gdp/jobshop.py @@ -2,13 +2,13 @@ # # Pyomo: Python Optimization Modeling Objects # Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC -# Under the terms of Contract DE-NA0003525 with National Technology and -# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain # rights in this software. # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pyomo.core import * +from pyomo.environ import * from pyomo.gdp import * # @@ -30,7 +30,6 @@ # def build_model(): - model = AbstractModel() model.JOBS = Set(ordered=True) @@ -79,5 +78,15 @@ def _disj(model, I, K, J): # minimize makespan model.makespan = Objective(expr=model.ms) - return model + + +def build_small_concrete(): + return build_model().create_instance('jobshop-small.dat') + + +if __name__ == "__main__": + m = build_small_concrete() + TransformationFactory('gdp.bigm').apply_to(m) + SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) + m.makespan.display() diff --git a/examples/gdp/medTermPurchasing_Literal.py b/examples/gdp/medTermPurchasing_Literal.py index c44048d293b..13116209da0 100755 --- a/examples/gdp/medTermPurchasing_Literal.py +++ b/examples/gdp/medTermPurchasing_Literal.py @@ -1,605 +1,616 @@ -from pyomo.environ import * -from pyomo.gdp import * - -# Medium-term Purchasing Contracts problem from http://minlp.org/library/lib.php?lib=GDP -# This model maximizes profit in a short-term horizon in which various contracts -# are available for purchasing raw materials. The model decides inventory levels, -# amounts to purchase, amount sold, and flows through the process nodes while -# maximizing profit. The four different contracts available are: -# FIXED PRICE CONTRACT: buy as much as you want at constant price -# DISCOUNT CONTRACT: quantities below minimum amount cost RegPrice. Any additional quantity -# above min amount costs DiscoutPrice. -# BULK CONTRACT: If more than min amount is purchased, whole purchase is at discount price. -# FIXED DURATION CONTRACT: Depending on length of time contract is valid, there is a purchase -# price during that time and min quantity that must be purchased - - -# This version of the model is a literal transcription of what is in -# ShortTermContractCH.gms from the website. Some data is hardcoded into this model, -# most notably the process structure itself and the mass balance information. - -def build_model(): - model = AbstractModel() - - # Constants (data that was hard-coded in GAMS model) - AMOUNT_UB = 1000 - COST_UB = 1e4 - MAX_AMOUNT_FP = 1000 - MIN_AMOUNT_FD_1MONTH = 0 - - RandomConst_Line264 = 0.17 - RandomConst_Line265 = 0.83 - - ################### - # Sets - ################### - - # T - # t in GAMS - model.TimePeriods = Set(ordered=True) - - # Available length contracts - # p in GAMS - model.Contracts_Length = Set() - - # JP - # final(j) in GAMS - # Finished products - model.Products = Set() - - # JM - # rawmat(J) in GAMS - # Set of Raw Materials-- raw materials, intermediate products, and final products partition J - model.RawMaterials = Set() - - # C - # c in GAMS - model.Contracts = Set() - - # I - # i in GAMS - model.Processes = Set() - - # J - # j in GAMS - model.Streams = Set() - - - ################## - # Parameters - ################## - - # Q_it - # excap(i) in GAMS - model.Capacity = Param(model.Processes) - - # u_ijt - # cov(i) in GAMS - model.ProcessConstants = Param(model.Processes) - - # a_jt^U and d_jt^U - # spdm(j,t) in GAMS - model.SupplyAndDemandUBs = Param(model.Streams, model.TimePeriods, default=0) - - # d_jt^L - # lbdm(j, t) in GAMS - model.DemandLB = Param(model.Streams, model.TimePeriods, default=0) - - # delta_it - # delta(i, t) in GAMS - # operating cost of process i at time t - model.OperatingCosts = Param(model.Processes, model.TimePeriods) - - # prices of raw materials under FP contract and selling prices of products - # pf(j, t) in GAMS - # omega_jt and pf_jt - model.Prices = Param(model.Streams, model.TimePeriods, default=0) - - # Price for quantities less than min amount under discount contract - # pd1(j, t) in GAMS - model.RegPrice_Discount = Param(model.Streams, model.TimePeriods) - # Discounted price for the quantity purchased exceeding the min amount - # pd2(j,t0 in GAMS - model.DiscountPrice_Discount = Param(model.Streams, model.TimePeriods) - - # Price for quantities below min amount - # pb1(j,t) in GAMS - model.RegPrice_Bulk = Param(model.Streams, model.TimePeriods) - # Price for quantities aboce min amount - # pb2(j, t) in GAMS - model.DiscountPrice_Bulk = Param(model.Streams, model.TimePeriods) - - # prices with length contract - # pl(j, p, t) in GAMS - model.Prices_Length = Param(model.Streams, model.Contracts_Length, model.TimePeriods, default=0) - - # sigmad_jt - # sigmad(j, t) in GAMS - # Minimum quantity of chemical j that must be bought before recieving a Discount under discount contract - model.MinAmount_Discount = Param(model.Streams, model.TimePeriods, default=0) - - # min quantity to recieve discount under bulk contract - # sigmab(j, t) in GAMS - model.MinAmount_Bulk = Param(model.Streams, model.TimePeriods, default=0) - - # min quantity to recieve discount under length contract - # sigmal(j, p) in GAMS - model.MinAmount_Length = Param(model.Streams, model.Contracts_Length, default=0) - - # main products of process i - # These are 1 (true) if stream j is the main product of process i, false otherwise. - # jm(j, i) in GAMS - model.MainProducts = Param(model.Streams, model.Processes, default=0) - - # theta_jt - # psf(j, t) in GAMS - # Shortfall penalty of product j at time t - model.ShortfallPenalty = Param(model.Products, model.TimePeriods) - - # shortfall upper bound - # sfub(j, t) in GAMS - model.ShortfallUB = Param(model.Products, model.TimePeriods, default=0) - - # epsilon_jt - # cinv(j, t) in GAMS - # inventory cost of material j at time t - model.InventoryCost = Param(model.Streams, model.TimePeriods) - - # invub(j, t) in GAMS - # inventory upper bound - model.InventoryLevelUB = Param(model.Streams, model.TimePeriods, default=0) - - ## UPPER BOUNDS HARDCODED INTO GAMS MODEL - - # All of these upper bounds are hardcoded. So I am just leaving them that way. - # This means they all have to be the same as each other right now. - def getAmountUBs(model, j, t): - return AMOUNT_UB - - def getCostUBs(model, j, t): - return COST_UB - - model.AmountPurchasedUB_FP = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) - model.AmountPurchasedUB_Discount = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) - model.AmountPurchasedBelowMinUB_Discount = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) - model.AmountPurchasedAboveMinUB_Discount = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) - model.AmountPurchasedUB_FD = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) - model.AmountPurchasedUB_Bulk = Param(model.Streams, model.TimePeriods, - initialize=getAmountUBs) - - model.CostUB_FP = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) - model.CostUB_FD = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) - model.CostUB_Discount = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) - model.CostUB_Bulk = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) - - - #################### - #VARIABLES - #################### - - # prof in GAMS - # will be objective - model.Profit = Var() - - # f(j, t) in GAMS - # mass flow rates in tons per time interval t - model.FlowRate = Var(model.Streams, model.TimePeriods, within=NonNegativeReals) - - # V_jt - # inv(j, t) in GAMS - # inventory level of chemical j at time period t - def getInventoryBounds(model, i, j): - return (0, model.InventoryLevelUB[i,j]) - model.InventoryLevel = Var(model.Streams, model.TimePeriods, - bounds=getInventoryBounds) - - # SF_jt - # sf(j, t) in GAMS - # Shortfall of demand for chemical j at time period t - def getShortfallBounds(model, i, j): - return(0, model.ShortfallUB[i,j]) - model.Shortfall = Var(model.Products, model.TimePeriods, - bounds=getShortfallBounds) - - - # amounts purchased under different contracts - - # spf(j, t) in GAMS - # Amount of raw material j bought under fixed price contract at time period t - def get_FP_bounds(model, j, t): - return (0, model.AmountPurchasedUB_FP[j,t]) - model.AmountPurchased_FP = Var(model.Streams, model.TimePeriods, - bounds=get_FP_bounds) - - # spd(j, t) in GAMS - def get_Discount_Total_bounds(model, j, t): - return (0, model.AmountPurchasedUB_Discount[j,t]) - model.AmountPurchasedTotal_Discount = Var(model.Streams, model.TimePeriods, - bounds=get_Discount_Total_bounds) - - # Amount purchased below min amount for discount under discount contract - # spd1(j, t) in GAMS - def get_Discount_BelowMin_bounds(model, j, t): - return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) - model.AmountPurchasedBelowMin_Discount = Var(model.Streams, - model.TimePeriods, bounds=get_Discount_BelowMin_bounds) - - # spd2(j, t) in GAMS - # Amount purchased above min amount for discount under discount contract - def get_Discount_AboveMin_bounds(model, j, t): - return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) - model.AmountPurchasedAboveMin_Discount = Var(model.Streams, - model.TimePeriods, bounds=get_Discount_AboveMin_bounds) - - # Amount purchased under bulk contract - # spb(j, t) in GAMS - def get_bulk_bounds(model, j, t): - return (0, model.AmountPurchasedUB_Bulk[j,t]) - model.AmountPurchased_Bulk = Var(model.Streams, model.TimePeriods, - bounds=get_bulk_bounds) - - # spl(j, t) in GAMS - # Amount purchased under Fixed Duration contract - def get_FD_bounds(model, j, t): - return (0, model.AmountPurchasedUB_FD[j,t]) - model.AmountPurchased_FD = Var(model.Streams, model.TimePeriods, - bounds=get_FD_bounds) - - - # costs - - # costpl(j, t) in GAMS - # cost of variable length contract - def get_CostUBs_FD(model, j, t): - return (0, model.CostUB_FD[j,t]) - model.Cost_FD = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FD) - - # costpf(j, t) in GAMS - # cost of fixed duration contract - def get_CostUBs_FP(model, j, t): - return (0, model.CostUB_FP[j,t]) - model.Cost_FP = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FP) - - # costpd(j, t) in GAMS - # cost of discount contract - def get_CostUBs_Discount(model, j, t): - return (0, model.CostUB_Discount[j,t]) - model.Cost_Discount = Var(model.Streams, model.TimePeriods, - bounds=get_CostUBs_Discount) - - # costpb(j, t) in GAMS - # cost of bulk contract - def get_CostUBs_Bulk(model, j, t): - return (0, model.CostUB_Bulk[j,t]) - model.Cost_Bulk = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_Bulk) - - - # binary variables - - model.BuyFPContract = RangeSet(0,1) - model.BuyDiscountContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected')) - model.BuyBulkContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected')) - model.BuyFDContract = Set(initialize=('1Month', '2Month', '3Month', 'NotSelected')) - - - ################ - # CONSTRAINTS - ################ - - # Objective: maximize profit - def profit_rule(model): - salesIncome = sum(model.Prices[j,t] * model.FlowRate[j,t] - for j in model.Products for t in model.TimePeriods) - purchaseCost = sum(model.Cost_FD[j,t] - for j in model.RawMaterials for t in model.TimePeriods) + \ - sum(model.Cost_Discount[j,t] - for j in model.RawMaterials for t in model.TimePeriods) + \ - sum(model.Cost_Bulk[j,t] - for j in model.RawMaterials for t in model.TimePeriods) + \ - sum(model.Cost_FP[j,t] - for j in model.RawMaterials for t in model.TimePeriods) - productionCost = sum(model.OperatingCosts[i,t] * sum(model.FlowRate[j,t] - for j in model.Streams if model.MainProducts[j,i]) - for i in model.Processes for t in model.TimePeriods) - shortfallCost = sum(model.Shortfall[j,t] * model.ShortfallPenalty[j, t] - for j in model.Products for t in model.TimePeriods) - inventoryCost = sum(model.InventoryCost[j,t] * model.InventoryLevel[j,t] - for j in model.Products for t in model.TimePeriods) - return salesIncome - purchaseCost - productionCost - inventoryCost - shortfallCost - model.profit = Objective(rule=profit_rule, sense=maximize) - - # flow of raw materials is the total amount purchased (accross all contracts) - def raw_material_flow_rule(model, j, t): - return model.FlowRate[j,t] == model.AmountPurchased_FD[j,t] + \ - model.AmountPurchased_FP[j,t] + model.AmountPurchased_Bulk[j,t] + \ - model.AmountPurchasedTotal_Discount[j,t] - model.raw_material_flow = Constraint(model.RawMaterials, model.TimePeriods, - rule=raw_material_flow_rule) - - def discount_amount_total_rule(model, j, t): - return model.AmountPurchasedTotal_Discount[j,t] == \ - model.AmountPurchasedBelowMin_Discount[j,t] + \ - model.AmountPurchasedAboveMin_Discount[j,t] - model.discount_amount_total_rule = Constraint(model.RawMaterials, model.TimePeriods, - rule=discount_amount_total_rule) - - # mass balance equations for each node - # these are specific to the process network in this example. - def mass_balance_rule1(model, t): - return model.FlowRate[1, t] == model.FlowRate[2, t] + model.FlowRate[3, t] - model.mass_balance1 = Constraint(model.TimePeriods, rule=mass_balance_rule1) - - def mass_balance_rule2(model, t): - return model.FlowRate[5, t] == model.FlowRate[4, t] + model.FlowRate[8,t] - model.mass_balance2 = Constraint(model.TimePeriods, rule=mass_balance_rule2) - - def mass_balance_rule3(model, t): - return model.FlowRate[6, t] == model.FlowRate[7, t] - model.mass_balance3 = Constraint(model.TimePeriods, rule=mass_balance_rule3) - - def mass_balance_rule4(model, t): - return model.FlowRate[3, t] == 10*model.FlowRate[5, t] - model.mass_balance4 = Constraint(model.TimePeriods, rule=mass_balance_rule4) - - # process input/output constraints - # these are also totally specific to the process network - def process_balance_rule1(model, t): - return model.FlowRate[9, t] == model.ProcessConstants[1] * model.FlowRate[2, t] - model.process_balance1 = Constraint(model.TimePeriods, rule=process_balance_rule1) - - def process_balance_rule2(model, t): - return model.FlowRate[10, t] == model.ProcessConstants[2] * \ - (model.FlowRate[5, t] + model.FlowRate[3, t]) - model.process_balance2 = Constraint(model.TimePeriods, rule=process_balance_rule2) - - def process_balance_rule3(model, t): - return model.FlowRate[8, t] == RandomConst_Line264 * \ - model.ProcessConstants[3] * model.FlowRate[7, t] - model.process_balance3 = Constraint(model.TimePeriods, rule=process_balance_rule3) - - def process_balance_rule4(model, t): - return model.FlowRate[11, t] == RandomConst_Line265 * \ - model.ProcessConstants[3] * model.FlowRate[7, t] - model.process_balance4 = Constraint(model.TimePeriods, rule=process_balance_rule4) - - # process capacity contraints - # these are hardcoded based on the three processes and the process flow structure - def process_capacity_rule1(model, t): - return model.FlowRate[9, t] <= model.Capacity[1] - model.process_capacity1 = Constraint(model.TimePeriods, rule=process_capacity_rule1) - - def process_capacity_rule2(model, t): - return model.FlowRate[10, t] <= model.Capacity[2] - model.process_capacity2 = Constraint(model.TimePeriods, rule=process_capacity_rule2) - - def process_capacity_rule3(model, t): - return model.FlowRate[11, t] + model.FlowRate[8, t] <= model.Capacity[3] - model.process_capacity3 = Constraint(model.TimePeriods, rule=process_capacity_rule3) - - # Inventory balance of final products - # again, these are hardcoded. - - def inventory_balance1(model, t): - prev = 0 if t == min(model.TimePeriods) else model.InventoryLevel[12, t-1] - return prev + model.FlowRate[9, t] == model.FlowRate[12, t] + model.InventoryLevel[12,t] - model.inventory_balance1 = Constraint(model.TimePeriods, rule=inventory_balance1) - - def inventory_balance_rule2(model, t): - if t != 1: - return Constraint.Skip - return model.FlowRate[10, t] + model.FlowRate[11, t] == \ - model.InventoryLevel[13,t] + model.FlowRate[13, t] - model.inventory_balance2 = Constraint(model.TimePeriods, rule=inventory_balance_rule2) - - def inventory_balance_rule3(model, t): - if t <= 1: - return Constraint.Skip - return model.InventoryLevel[13, t-1] + model.FlowRate[10, t] + \ - model.FlowRate[11,t] == model.InventoryLevel[13, t] + model.FlowRate[13, t] - model.inventory_balance3 = Constraint(model.TimePeriods, rule=inventory_balance_rule3) - - # Max capacities of inventories - def inventory_capacity_rule(model, j, t): - return model.InventoryLevel[j,t] <= model.InventoryLevelUB[j,t] - model.inventory_capacity_rule = Constraint(model.Products, model.TimePeriods, rule=inventory_capacity_rule) - - # Shortfall calculation - def shortfall_rule(model, j, t): - return model.Shortfall[j, t] == model.SupplyAndDemandUBs[j, t] - model.FlowRate[j,t] - model.shortfall = Constraint(model.Products, model.TimePeriods, rule=shortfall_rule) - - # maximum shortfall allowed - def shortfall_max_rule(model, j, t): - return model.Shortfall[j, t] <= model.ShortfallUB[j, t] - model.shortfall_max = Constraint(model.Products, model.TimePeriods, rule=shortfall_max_rule) - - # maxiumum capacities of suppliers - def supplier_capacity_rule(model, j, t): - return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j, t] - model.supplier_capacity = Constraint(model.RawMaterials, model.TimePeriods, rule=supplier_capacity_rule) - - # demand upper bound - def demand_UB_rule(model, j, t): - return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j,t] - model.demand_UB = Constraint(model.Products, model.TimePeriods, rule=demand_UB_rule) - # demand lower bound - def demand_LB_rule(model, j, t): - return model.FlowRate[j, t] >= model.DemandLB[j,t] - model.demand_LB = Constraint(model.Products, model.TimePeriods, rule=demand_LB_rule) - - - # FIXED PRICE CONTRACT - - # Disjunction for Fixed Price contract buying options - def FP_contract_disjunct_rule(disjunct, j, t, buy): - model = disjunct.model() - if buy: - disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] <= MAX_AMOUNT_FP) - else: - disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] == 0) - model.FP_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyFPContract, rule=FP_contract_disjunct_rule) - - # Fixed price disjunction - def FP_contract_rule(model, j, t): - return [model.FP_contract_disjunct[j,t,buy] for buy in model.BuyFPContract] - model.FP_disjunction = Disjunction(model.RawMaterials, model.TimePeriods, - rule=FP_contract_rule) - - # cost constraint for fixed price contract (independent contraint) - def FP_contract_cost_rule(model, j, t): - return model.Cost_FP[j,t] == model.AmountPurchased_FP[j,t] * \ - model.Prices[j,t] - model.FP_contract_cost = Constraint(model.RawMaterials, model.TimePeriods, - rule=FP_contract_cost_rule) - - - # DISCOUNT CONTRACT - - # Disjunction for Discount contract - def discount_contract_disjunct_rule(disjunct, j, t, buy): - model = disjunct.model() - if buy == 'BelowMin': - disjunct.belowMin = Constraint( - expr=model.AmountPurchasedBelowMin_Discount[j,t] <= \ - model.MinAmount_Discount[j,t]) - disjunct.aboveMin = Constraint( - expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) - elif buy == 'AboveMin': - disjunct.belowMin = Constraint( - expr=model.AmountPurchasedBelowMin_Discount[j,t] == \ - model.MinAmount_Discount[j,t]) - disjunct.aboveMin = Constraint( - expr=model.AmountPurchasedAboveMin_Discount[j,t] >= 0) - elif buy == 'NotSelected': - disjunct.belowMin = Constraint( - expr=model.AmountPurchasedBelowMin_Discount[j,t] == 0) - disjunct.aboveMin = Constraint( - expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) - else: - raise RuntimeError("Unrecognized choice for discount contract: %s" % buy) - model.discount_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyDiscountContract, rule=discount_contract_disjunct_rule) - - # Discount contract disjunction - def discount_contract_rule(model, j, t): - return [model.discount_contract_disjunct[j,t,buy] \ - for buy in model.BuyDiscountContract] - model.discount_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=discount_contract_rule) - - # cost constraint for discount contract (independent constraint) - def discount_cost_rule(model, j, t): - return model.Cost_Discount[j,t] == model.RegPrice_Discount[j,t] * \ - model.AmountPurchasedBelowMin_Discount[j,t] + \ - model.DiscountPrice_Discount[j,t] * model.AmountPurchasedAboveMin_Discount[j,t] - model.discount_cost = Constraint(model.RawMaterials, model.TimePeriods, - rule=discount_cost_rule) - - - # BULK CONTRACT - - # Bulk contract buying options disjunct - def bulk_contract_disjunct_rule(disjunct, j, t, buy): - model = disjunct.model() - if buy == 'BelowMin': - disjunct.amount = Constraint( - expr=model.AmountPurchased_Bulk[j,t] <= model.MinAmount_Bulk[j,t]) - disjunct.price = Constraint( - expr=model.Cost_Bulk[j,t] == model.RegPrice_Bulk[j,t] * \ - model.AmountPurchased_Bulk[j,t]) - elif buy == 'AboveMin': - disjunct.amount = Constraint( - expr=model.AmountPurchased_Bulk[j,t] >= model.MinAmount_Bulk[j,t]) - disjunct.price = Constraint( - expr=model.Cost_Bulk[j,t] == model.DiscountPrice_Bulk[j,t] * \ - model.AmountPurchased_Bulk[j,t]) - elif buy == 'NotSelected': - disjunct.amount = Constraint(expr=model.AmountPurchased_Bulk[j,t] == 0) - disjunct.price = Constraint(expr=model.Cost_Bulk[j,t] == 0) - else: - raise RuntimeError("Unrecognized choice for bulk contract: %s" % buy) - model.bulk_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, - model.BuyBulkContract, rule=bulk_contract_disjunct_rule) - - # Bulk contract disjunction - def bulk_contract_rule(model, j, t): - return [model.bulk_contract_disjunct[j,t,buy] for buy in model.BuyBulkContract] - model.bulk_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=bulk_contract_rule) - - - # FIXED DURATION CONTRACT - - def FD_1mo_contract(disjunct, j, t): - model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - MIN_AMOUNT_FD_1MONTH) - disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,1,t] * model.AmountPurchased_FD[j,t]) - model.FD_1mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_1mo_contract) - - def FD_2mo_contract(disjunct, j, t): - model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - model.MinAmount_Length[j,2]) - disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j,t]) - # only enforce these if we aren't in the last time period - if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j, t+1] >= \ - model.MinAmount_Length[j,2]) - disjunct.price2 = Constraint(expr=model.Cost_FD[j,t+1] == \ - model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j, t+1]) - model.FD_2mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_2mo_contract) - - def FD_3mo_contract(disjunct, j, t): - model = disjunct.model() - # NOTE: I think there is a mistake in the GAMS file in line 327. - # they use the bulk minamount rather than the length one. - #I am doing the same here for validation purposes. - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ - model.MinAmount_Bulk[j,3]) - disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t]) - # check we aren't in one of the last two time periods - if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] >= \ - model.MinAmount_Length[j,3]) - disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+1]) - if t < model.TimePeriods[-2]: - disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] >= \ - model.MinAmount_Length[j,3]) - disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == \ - model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+2]) - model.FD_3mo_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_3mo_contract) - - def FD_no_contract(disjunct, j, t): - model = disjunct.model() - disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] == 0) - disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == 0) - if t < model.TimePeriods[-1]: - disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] == 0) - disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == 0) - if t < model.TimePeriods[-2]: - disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] == 0) - disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == 0) - model.FD_no_contract = Disjunct( - model.RawMaterials, model.TimePeriods, rule=FD_no_contract) - - def FD_contract(model, j, t): - return [ model.FD_1mo_contract[j,t], model.FD_2mo_contract[j,t], - model.FD_3mo_contract[j,t], model.FD_no_contract[j,t], ] - model.FD_contract = Disjunction(model.RawMaterials, model.TimePeriods, - rule=FD_contract) - - return model +from pyomo.environ import * +from pyomo.gdp import * + +# Medium-term Purchasing Contracts problem from http://minlp.org/library/lib.php?lib=GDP +# This model maximizes profit in a short-term horizon in which various contracts +# are available for purchasing raw materials. The model decides inventory levels, +# amounts to purchase, amount sold, and flows through the process nodes while +# maximizing profit. The four different contracts available are: +# FIXED PRICE CONTRACT: buy as much as you want at constant price +# DISCOUNT CONTRACT: quantities below minimum amount cost RegPrice. Any additional quantity +# above min amount costs DiscoutPrice. +# BULK CONTRACT: If more than min amount is purchased, whole purchase is at discount price. +# FIXED DURATION CONTRACT: Depending on length of time contract is valid, there is a purchase +# price during that time and min quantity that must be purchased + + +# This version of the model is a literal transcription of what is in +# ShortTermContractCH.gms from the website. Some data is hardcoded into this model, +# most notably the process structure itself and the mass balance information. + +def build_model(): + model = AbstractModel() + + # Constants (data that was hard-coded in GAMS model) + AMOUNT_UB = 1000 + COST_UB = 1e4 + MAX_AMOUNT_FP = 1000 + MIN_AMOUNT_FD_1MONTH = 0 + + RandomConst_Line264 = 0.17 + RandomConst_Line265 = 0.83 + + ################### + # Sets + ################### + + # T + # t in GAMS + model.TimePeriods = Set(ordered=True) + + # Available length contracts + # p in GAMS + model.Contracts_Length = Set() + + # JP + # final(j) in GAMS + # Finished products + model.Products = Set() + + # JM + # rawmat(J) in GAMS + # Set of Raw Materials-- raw materials, intermediate products, and final products partition J + model.RawMaterials = Set() + + # C + # c in GAMS + model.Contracts = Set() + + # I + # i in GAMS + model.Processes = Set() + + # J + # j in GAMS + model.Streams = Set() + + + ################## + # Parameters + ################## + + # Q_it + # excap(i) in GAMS + model.Capacity = Param(model.Processes) + + # u_ijt + # cov(i) in GAMS + model.ProcessConstants = Param(model.Processes) + + # a_jt^U and d_jt^U + # spdm(j,t) in GAMS + model.SupplyAndDemandUBs = Param(model.Streams, model.TimePeriods, default=0) + + # d_jt^L + # lbdm(j, t) in GAMS + model.DemandLB = Param(model.Streams, model.TimePeriods, default=0) + + # delta_it + # delta(i, t) in GAMS + # operating cost of process i at time t + model.OperatingCosts = Param(model.Processes, model.TimePeriods) + + # prices of raw materials under FP contract and selling prices of products + # pf(j, t) in GAMS + # omega_jt and pf_jt + model.Prices = Param(model.Streams, model.TimePeriods, default=0) + + # Price for quantities less than min amount under discount contract + # pd1(j, t) in GAMS + model.RegPrice_Discount = Param(model.Streams, model.TimePeriods) + # Discounted price for the quantity purchased exceeding the min amount + # pd2(j,t0 in GAMS + model.DiscountPrice_Discount = Param(model.Streams, model.TimePeriods) + + # Price for quantities below min amount + # pb1(j,t) in GAMS + model.RegPrice_Bulk = Param(model.Streams, model.TimePeriods) + # Price for quantities aboce min amount + # pb2(j, t) in GAMS + model.DiscountPrice_Bulk = Param(model.Streams, model.TimePeriods) + + # prices with length contract + # pl(j, p, t) in GAMS + model.Prices_Length = Param(model.Streams, model.Contracts_Length, model.TimePeriods, default=0) + + # sigmad_jt + # sigmad(j, t) in GAMS + # Minimum quantity of chemical j that must be bought before recieving a Discount under discount contract + model.MinAmount_Discount = Param(model.Streams, model.TimePeriods, default=0) + + # min quantity to recieve discount under bulk contract + # sigmab(j, t) in GAMS + model.MinAmount_Bulk = Param(model.Streams, model.TimePeriods, default=0) + + # min quantity to recieve discount under length contract + # sigmal(j, p) in GAMS + model.MinAmount_Length = Param(model.Streams, model.Contracts_Length, default=0) + + # main products of process i + # These are 1 (true) if stream j is the main product of process i, false otherwise. + # jm(j, i) in GAMS + model.MainProducts = Param(model.Streams, model.Processes, default=0) + + # theta_jt + # psf(j, t) in GAMS + # Shortfall penalty of product j at time t + model.ShortfallPenalty = Param(model.Products, model.TimePeriods) + + # shortfall upper bound + # sfub(j, t) in GAMS + model.ShortfallUB = Param(model.Products, model.TimePeriods, default=0) + + # epsilon_jt + # cinv(j, t) in GAMS + # inventory cost of material j at time t + model.InventoryCost = Param(model.Streams, model.TimePeriods) + + # invub(j, t) in GAMS + # inventory upper bound + model.InventoryLevelUB = Param(model.Streams, model.TimePeriods, default=0) + + ## UPPER BOUNDS HARDCODED INTO GAMS MODEL + + # All of these upper bounds are hardcoded. So I am just leaving them that way. + # This means they all have to be the same as each other right now. + def getAmountUBs(model, j, t): + return AMOUNT_UB + + def getCostUBs(model, j, t): + return COST_UB + + model.AmountPurchasedUB_FP = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedUB_Discount = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedBelowMinUB_Discount = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedAboveMinUB_Discount = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedUB_FD = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + model.AmountPurchasedUB_Bulk = Param(model.Streams, model.TimePeriods, + initialize=getAmountUBs) + + model.CostUB_FP = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) + model.CostUB_FD = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) + model.CostUB_Discount = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) + model.CostUB_Bulk = Param(model.Streams, model.TimePeriods, initialize=getCostUBs) + + + #################### + #VARIABLES + #################### + + # prof in GAMS + # will be objective + model.Profit = Var() + + # f(j, t) in GAMS + # mass flow rates in tons per time interval t + model.FlowRate = Var(model.Streams, model.TimePeriods, within=NonNegativeReals) + + # V_jt + # inv(j, t) in GAMS + # inventory level of chemical j at time period t + def getInventoryBounds(model, i, j): + return (0, model.InventoryLevelUB[i,j]) + model.InventoryLevel = Var(model.Streams, model.TimePeriods, + bounds=getInventoryBounds) + + # SF_jt + # sf(j, t) in GAMS + # Shortfall of demand for chemical j at time period t + def getShortfallBounds(model, i, j): + return(0, model.ShortfallUB[i,j]) + model.Shortfall = Var(model.Products, model.TimePeriods, + bounds=getShortfallBounds) + + + # amounts purchased under different contracts + + # spf(j, t) in GAMS + # Amount of raw material j bought under fixed price contract at time period t + def get_FP_bounds(model, j, t): + return (0, model.AmountPurchasedUB_FP[j,t]) + model.AmountPurchased_FP = Var(model.Streams, model.TimePeriods, + bounds=get_FP_bounds) + + # spd(j, t) in GAMS + def get_Discount_Total_bounds(model, j, t): + return (0, model.AmountPurchasedUB_Discount[j,t]) + model.AmountPurchasedTotal_Discount = Var(model.Streams, model.TimePeriods, + bounds=get_Discount_Total_bounds) + + # Amount purchased below min amount for discount under discount contract + # spd1(j, t) in GAMS + def get_Discount_BelowMin_bounds(model, j, t): + return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) + model.AmountPurchasedBelowMin_Discount = Var(model.Streams, + model.TimePeriods, bounds=get_Discount_BelowMin_bounds) + + # spd2(j, t) in GAMS + # Amount purchased above min amount for discount under discount contract + def get_Discount_AboveMin_bounds(model, j, t): + return (0, model.AmountPurchasedBelowMinUB_Discount[j,t]) + model.AmountPurchasedAboveMin_Discount = Var(model.Streams, + model.TimePeriods, bounds=get_Discount_AboveMin_bounds) + + # Amount purchased under bulk contract + # spb(j, t) in GAMS + def get_bulk_bounds(model, j, t): + return (0, model.AmountPurchasedUB_Bulk[j,t]) + model.AmountPurchased_Bulk = Var(model.Streams, model.TimePeriods, + bounds=get_bulk_bounds) + + # spl(j, t) in GAMS + # Amount purchased under Fixed Duration contract + def get_FD_bounds(model, j, t): + return (0, model.AmountPurchasedUB_FD[j,t]) + model.AmountPurchased_FD = Var(model.Streams, model.TimePeriods, + bounds=get_FD_bounds) + + + # costs + + # costpl(j, t) in GAMS + # cost of variable length contract + def get_CostUBs_FD(model, j, t): + return (0, model.CostUB_FD[j,t]) + model.Cost_FD = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FD) + + # costpf(j, t) in GAMS + # cost of fixed duration contract + def get_CostUBs_FP(model, j, t): + return (0, model.CostUB_FP[j,t]) + model.Cost_FP = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_FP) + + # costpd(j, t) in GAMS + # cost of discount contract + def get_CostUBs_Discount(model, j, t): + return (0, model.CostUB_Discount[j,t]) + model.Cost_Discount = Var(model.Streams, model.TimePeriods, + bounds=get_CostUBs_Discount) + + # costpb(j, t) in GAMS + # cost of bulk contract + def get_CostUBs_Bulk(model, j, t): + return (0, model.CostUB_Bulk[j,t]) + model.Cost_Bulk = Var(model.Streams, model.TimePeriods, bounds=get_CostUBs_Bulk) + + + # binary variables + + model.BuyFPContract = RangeSet(0,1) + model.BuyDiscountContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected')) + model.BuyBulkContract = Set(initialize=('BelowMin', 'AboveMin', 'NotSelected')) + model.BuyFDContract = Set(initialize=('1Month', '2Month', '3Month', 'NotSelected')) + + + ################ + # CONSTRAINTS + ################ + + # Objective: maximize profit + def profit_rule(model): + salesIncome = sum(model.Prices[j,t] * model.FlowRate[j,t] + for j in model.Products for t in model.TimePeriods) + purchaseCost = sum(model.Cost_FD[j,t] + for j in model.RawMaterials for t in model.TimePeriods) + \ + sum(model.Cost_Discount[j,t] + for j in model.RawMaterials for t in model.TimePeriods) + \ + sum(model.Cost_Bulk[j,t] + for j in model.RawMaterials for t in model.TimePeriods) + \ + sum(model.Cost_FP[j,t] + for j in model.RawMaterials for t in model.TimePeriods) + productionCost = sum(model.OperatingCosts[i,t] * sum(model.FlowRate[j,t] + for j in model.Streams if model.MainProducts[j,i]) + for i in model.Processes for t in model.TimePeriods) + shortfallCost = sum(model.Shortfall[j,t] * model.ShortfallPenalty[j, t] + for j in model.Products for t in model.TimePeriods) + inventoryCost = sum(model.InventoryCost[j,t] * model.InventoryLevel[j,t] + for j in model.Products for t in model.TimePeriods) + return salesIncome - purchaseCost - productionCost - inventoryCost - shortfallCost + model.profit = Objective(rule=profit_rule, sense=maximize) + + # flow of raw materials is the total amount purchased (accross all contracts) + def raw_material_flow_rule(model, j, t): + return model.FlowRate[j,t] == model.AmountPurchased_FD[j,t] + \ + model.AmountPurchased_FP[j,t] + model.AmountPurchased_Bulk[j,t] + \ + model.AmountPurchasedTotal_Discount[j,t] + model.raw_material_flow = Constraint(model.RawMaterials, model.TimePeriods, + rule=raw_material_flow_rule) + + def discount_amount_total_rule(model, j, t): + return model.AmountPurchasedTotal_Discount[j,t] == \ + model.AmountPurchasedBelowMin_Discount[j,t] + \ + model.AmountPurchasedAboveMin_Discount[j,t] + model.discount_amount_total_rule = Constraint(model.RawMaterials, model.TimePeriods, + rule=discount_amount_total_rule) + + # mass balance equations for each node + # these are specific to the process network in this example. + def mass_balance_rule1(model, t): + return model.FlowRate[1, t] == model.FlowRate[2, t] + model.FlowRate[3, t] + model.mass_balance1 = Constraint(model.TimePeriods, rule=mass_balance_rule1) + + def mass_balance_rule2(model, t): + return model.FlowRate[5, t] == model.FlowRate[4, t] + model.FlowRate[8,t] + model.mass_balance2 = Constraint(model.TimePeriods, rule=mass_balance_rule2) + + def mass_balance_rule3(model, t): + return model.FlowRate[6, t] == model.FlowRate[7, t] + model.mass_balance3 = Constraint(model.TimePeriods, rule=mass_balance_rule3) + + def mass_balance_rule4(model, t): + return model.FlowRate[3, t] == 10*model.FlowRate[5, t] + model.mass_balance4 = Constraint(model.TimePeriods, rule=mass_balance_rule4) + + # process input/output constraints + # these are also totally specific to the process network + def process_balance_rule1(model, t): + return model.FlowRate[9, t] == model.ProcessConstants[1] * model.FlowRate[2, t] + model.process_balance1 = Constraint(model.TimePeriods, rule=process_balance_rule1) + + def process_balance_rule2(model, t): + return model.FlowRate[10, t] == model.ProcessConstants[2] * \ + (model.FlowRate[5, t] + model.FlowRate[3, t]) + model.process_balance2 = Constraint(model.TimePeriods, rule=process_balance_rule2) + + def process_balance_rule3(model, t): + return model.FlowRate[8, t] == RandomConst_Line264 * \ + model.ProcessConstants[3] * model.FlowRate[7, t] + model.process_balance3 = Constraint(model.TimePeriods, rule=process_balance_rule3) + + def process_balance_rule4(model, t): + return model.FlowRate[11, t] == RandomConst_Line265 * \ + model.ProcessConstants[3] * model.FlowRate[7, t] + model.process_balance4 = Constraint(model.TimePeriods, rule=process_balance_rule4) + + # process capacity contraints + # these are hardcoded based on the three processes and the process flow structure + def process_capacity_rule1(model, t): + return model.FlowRate[9, t] <= model.Capacity[1] + model.process_capacity1 = Constraint(model.TimePeriods, rule=process_capacity_rule1) + + def process_capacity_rule2(model, t): + return model.FlowRate[10, t] <= model.Capacity[2] + model.process_capacity2 = Constraint(model.TimePeriods, rule=process_capacity_rule2) + + def process_capacity_rule3(model, t): + return model.FlowRate[11, t] + model.FlowRate[8, t] <= model.Capacity[3] + model.process_capacity3 = Constraint(model.TimePeriods, rule=process_capacity_rule3) + + # Inventory balance of final products + # again, these are hardcoded. + + def inventory_balance1(model, t): + prev = 0 if t == min(model.TimePeriods) else model.InventoryLevel[12, t-1] + return prev + model.FlowRate[9, t] == model.FlowRate[12, t] + model.InventoryLevel[12,t] + model.inventory_balance1 = Constraint(model.TimePeriods, rule=inventory_balance1) + + def inventory_balance_rule2(model, t): + if t != 1: + return Constraint.Skip + return model.FlowRate[10, t] + model.FlowRate[11, t] == \ + model.InventoryLevel[13,t] + model.FlowRate[13, t] + model.inventory_balance2 = Constraint(model.TimePeriods, rule=inventory_balance_rule2) + + def inventory_balance_rule3(model, t): + if t <= 1: + return Constraint.Skip + return model.InventoryLevel[13, t-1] + model.FlowRate[10, t] + \ + model.FlowRate[11,t] == model.InventoryLevel[13, t] + model.FlowRate[13, t] + model.inventory_balance3 = Constraint(model.TimePeriods, rule=inventory_balance_rule3) + + # Max capacities of inventories + def inventory_capacity_rule(model, j, t): + return model.InventoryLevel[j,t] <= model.InventoryLevelUB[j,t] + model.inventory_capacity_rule = Constraint(model.Products, model.TimePeriods, rule=inventory_capacity_rule) + + # Shortfall calculation + def shortfall_rule(model, j, t): + return model.Shortfall[j, t] == model.SupplyAndDemandUBs[j, t] - model.FlowRate[j,t] + model.shortfall = Constraint(model.Products, model.TimePeriods, rule=shortfall_rule) + + # maximum shortfall allowed + def shortfall_max_rule(model, j, t): + return model.Shortfall[j, t] <= model.ShortfallUB[j, t] + model.shortfall_max = Constraint(model.Products, model.TimePeriods, rule=shortfall_max_rule) + + # maxiumum capacities of suppliers + def supplier_capacity_rule(model, j, t): + return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j, t] + model.supplier_capacity = Constraint(model.RawMaterials, model.TimePeriods, rule=supplier_capacity_rule) + + # demand upper bound + def demand_UB_rule(model, j, t): + return model.FlowRate[j, t] <= model.SupplyAndDemandUBs[j,t] + model.demand_UB = Constraint(model.Products, model.TimePeriods, rule=demand_UB_rule) + # demand lower bound + def demand_LB_rule(model, j, t): + return model.FlowRate[j, t] >= model.DemandLB[j,t] + model.demand_LB = Constraint(model.Products, model.TimePeriods, rule=demand_LB_rule) + + + # FIXED PRICE CONTRACT + + # Disjunction for Fixed Price contract buying options + def FP_contract_disjunct_rule(disjunct, j, t, buy): + model = disjunct.model() + if buy: + disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] <= MAX_AMOUNT_FP) + else: + disjunct.c = Constraint(expr=model.AmountPurchased_FP[j,t] == 0) + model.FP_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, + model.BuyFPContract, rule=FP_contract_disjunct_rule) + + # Fixed price disjunction + def FP_contract_rule(model, j, t): + return [model.FP_contract_disjunct[j,t,buy] for buy in model.BuyFPContract] + model.FP_disjunction = Disjunction(model.RawMaterials, model.TimePeriods, + rule=FP_contract_rule) + + # cost constraint for fixed price contract (independent contraint) + def FP_contract_cost_rule(model, j, t): + return model.Cost_FP[j,t] == model.AmountPurchased_FP[j,t] * \ + model.Prices[j,t] + model.FP_contract_cost = Constraint(model.RawMaterials, model.TimePeriods, + rule=FP_contract_cost_rule) + + + # DISCOUNT CONTRACT + + # Disjunction for Discount contract + def discount_contract_disjunct_rule(disjunct, j, t, buy): + model = disjunct.model() + if buy == 'BelowMin': + disjunct.belowMin = Constraint( + expr=model.AmountPurchasedBelowMin_Discount[j,t] <= \ + model.MinAmount_Discount[j,t]) + disjunct.aboveMin = Constraint( + expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) + elif buy == 'AboveMin': + disjunct.belowMin = Constraint( + expr=model.AmountPurchasedBelowMin_Discount[j,t] == \ + model.MinAmount_Discount[j,t]) + disjunct.aboveMin = Constraint( + expr=model.AmountPurchasedAboveMin_Discount[j,t] >= 0) + elif buy == 'NotSelected': + disjunct.belowMin = Constraint( + expr=model.AmountPurchasedBelowMin_Discount[j,t] == 0) + disjunct.aboveMin = Constraint( + expr=model.AmountPurchasedAboveMin_Discount[j,t] == 0) + else: + raise RuntimeError("Unrecognized choice for discount contract: %s" % buy) + model.discount_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, + model.BuyDiscountContract, rule=discount_contract_disjunct_rule) + + # Discount contract disjunction + def discount_contract_rule(model, j, t): + return [model.discount_contract_disjunct[j,t,buy] \ + for buy in model.BuyDiscountContract] + model.discount_contract = Disjunction(model.RawMaterials, model.TimePeriods, + rule=discount_contract_rule) + + # cost constraint for discount contract (independent constraint) + def discount_cost_rule(model, j, t): + return model.Cost_Discount[j,t] == model.RegPrice_Discount[j,t] * \ + model.AmountPurchasedBelowMin_Discount[j,t] + \ + model.DiscountPrice_Discount[j,t] * model.AmountPurchasedAboveMin_Discount[j,t] + model.discount_cost = Constraint(model.RawMaterials, model.TimePeriods, + rule=discount_cost_rule) + + + # BULK CONTRACT + + # Bulk contract buying options disjunct + def bulk_contract_disjunct_rule(disjunct, j, t, buy): + model = disjunct.model() + if buy == 'BelowMin': + disjunct.amount = Constraint( + expr=model.AmountPurchased_Bulk[j,t] <= model.MinAmount_Bulk[j,t]) + disjunct.price = Constraint( + expr=model.Cost_Bulk[j,t] == model.RegPrice_Bulk[j,t] * \ + model.AmountPurchased_Bulk[j,t]) + elif buy == 'AboveMin': + disjunct.amount = Constraint( + expr=model.AmountPurchased_Bulk[j,t] >= model.MinAmount_Bulk[j,t]) + disjunct.price = Constraint( + expr=model.Cost_Bulk[j,t] == model.DiscountPrice_Bulk[j,t] * \ + model.AmountPurchased_Bulk[j,t]) + elif buy == 'NotSelected': + disjunct.amount = Constraint(expr=model.AmountPurchased_Bulk[j,t] == 0) + disjunct.price = Constraint(expr=model.Cost_Bulk[j,t] == 0) + else: + raise RuntimeError("Unrecognized choice for bulk contract: %s" % buy) + model.bulk_contract_disjunct = Disjunct(model.RawMaterials, model.TimePeriods, + model.BuyBulkContract, rule=bulk_contract_disjunct_rule) + + # Bulk contract disjunction + def bulk_contract_rule(model, j, t): + return [model.bulk_contract_disjunct[j,t,buy] for buy in model.BuyBulkContract] + model.bulk_contract = Disjunction(model.RawMaterials, model.TimePeriods, + rule=bulk_contract_rule) + + + # FIXED DURATION CONTRACT + + def FD_1mo_contract(disjunct, j, t): + model = disjunct.model() + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ + MIN_AMOUNT_FD_1MONTH) + disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ + model.Prices_Length[j,1,t] * model.AmountPurchased_FD[j,t]) + model.FD_1mo_contract = Disjunct( + model.RawMaterials, model.TimePeriods, rule=FD_1mo_contract) + + def FD_2mo_contract(disjunct, j, t): + model = disjunct.model() + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ + model.MinAmount_Length[j,2]) + disjunct.price1 = Constraint(expr=model.Cost_FD[j,t] == \ + model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j,t]) + # only enforce these if we aren't in the last time period + if t < model.TimePeriods[-1]: + disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j, t+1] >= \ + model.MinAmount_Length[j,2]) + disjunct.price2 = Constraint(expr=model.Cost_FD[j,t+1] == \ + model.Prices_Length[j,2,t] * model.AmountPurchased_FD[j, t+1]) + model.FD_2mo_contract = Disjunct( + model.RawMaterials, model.TimePeriods, rule=FD_2mo_contract) + + def FD_3mo_contract(disjunct, j, t): + model = disjunct.model() + # NOTE: I think there is a mistake in the GAMS file in line 327. + # they use the bulk minamount rather than the length one. + #I am doing the same here for validation purposes. + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] >= \ + model.MinAmount_Bulk[j,3]) + disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == \ + model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t]) + # check we aren't in one of the last two time periods + if t < model.TimePeriods[-1]: + disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] >= \ + model.MinAmount_Length[j,3]) + disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == \ + model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+1]) + if t < model.TimePeriods[-2]: + disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] >= \ + model.MinAmount_Length[j,3]) + disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == \ + model.Prices_Length[j,3,t] * model.AmountPurchased_FD[j,t+2]) + model.FD_3mo_contract = Disjunct( + model.RawMaterials, model.TimePeriods, rule=FD_3mo_contract) + + def FD_no_contract(disjunct, j, t): + model = disjunct.model() + disjunct.amount1 = Constraint(expr=model.AmountPurchased_FD[j,t] == 0) + disjunct.cost1 = Constraint(expr=model.Cost_FD[j,t] == 0) + if t < model.TimePeriods[-1]: + disjunct.amount2 = Constraint(expr=model.AmountPurchased_FD[j,t+1] == 0) + disjunct.cost2 = Constraint(expr=model.Cost_FD[j,t+1] == 0) + if t < model.TimePeriods[-2]: + disjunct.amount3 = Constraint(expr=model.AmountPurchased_FD[j,t+2] == 0) + disjunct.cost3 = Constraint(expr=model.Cost_FD[j,t+2] == 0) + model.FD_no_contract = Disjunct( + model.RawMaterials, model.TimePeriods, rule=FD_no_contract) + + def FD_contract(model, j, t): + return [ model.FD_1mo_contract[j,t], model.FD_2mo_contract[j,t], + model.FD_3mo_contract[j,t], model.FD_no_contract[j,t], ] + model.FD_contract = Disjunction(model.RawMaterials, model.TimePeriods, + rule=FD_contract) + + return model + + +def build_concrete(): + return build_model().create_instance('medTermPurchasing_Literal_Chull.dat') + + +if __name__ == "__main__": + m = build_concrete() + TransformationFactory('gdp.bigm').apply_to(m) + SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) + m.profit.display() diff --git a/examples/gdp/medTermPurchasing_Literal_Chull.dat b/examples/gdp/medTermPurchasing_Literal_Chull.dat index 68605cbe4a8..712bdcb77ac 100755 --- a/examples/gdp/medTermPurchasing_Literal_Chull.dat +++ b/examples/gdp/medTermPurchasing_Literal_Chull.dat @@ -1,535 +1,531 @@ -set TimePeriods := 1 2 3 4 5 6 ; -set Processes := 1 2 3 ; -set Streams := 1 2 3 4 5 6 7 8 9 10 11 12 13 ; -set Products := 12 13 ; -set RawMaterials := 1 4 6 ; -set Contracts_Length := 1 2 3 4 ; -set Contracts := 1 2 3 4 ; - -param Capacity := - 1 27 - 2 30 - 3 25 -; - -param ProcessConstants := - 1 0.9 - 2 0.85 - 3 0.8 -; - -param SupplyAndDemandUBs := - 1 1 100 - 1 2 100 - 1 3 100 - 1 4 100 - 1 5 100 - 1 6 100 - 4 1 30 - 4 2 30 - 4 3 30 - 4 4 30 - 4 5 30 - 4 6 30 - 6 1 100 - 6 2 100 - 6 3 100 - 6 4 100 - 6 5 100 - 6 6 100 - 12 1 20 - 12 2 25 - 12 3 22 - 12 4 30 - 12 5 28 - 12 6 26 - 13 1 51 - 13 2 50 - 13 3 53 - 13 4 60 - 13 5 59 - 13 6 50 -; - - - -# -# JDS: Note that you can specify data as a table (similar to GAMS... -# -#param DemandLB := -# 12 1 5 -# 12 2 5 -# 12 3 5 -# 12 4 5 -# 12 5 5 -# 12 6 5 -# 13 1 5 -# 13 2 5 -# 13 3 5 -# 13 4 5 -# 13 5 5 -# 13 6 5 -#; - -param DemandLB: - 1 2 3 4 5 6 := - 12 5 5 5 5 5 5 - 13 5 5 5 5 5 5 ; - -# TODO you are apparently here!! -param OperatingCosts := - 1 2 3 4 5 6 := - - -param OperatingCosts := - 1 1 0.6 - 1 2 0.7 - 1 3 0.6 - 1 4 0.6 - 1 5 0.7 - 1 6 0.7 - 2 1 0.5 - 2 2 0.5 - 2 3 0.5 - 2 4 0.4 - 2 5 0.4 - 2 6 0.5 - 3 1 0.6 - 3 2 0.6 - 3 3 0.5 - 3 4 0.6 - 3 5 0.6 - 3 6 0.5 -; - -param Prices := - 1 1 2.2 - 1 2 2.4 - 1 3 2.4 - 1 4 2.3 - 1 5 2.2 - 1 6 2.2 - 4 1 1.9 - 4 2 2.4 - 4 3 2.4 - 4 4 2.2 - 4 5 2.1 - 4 6 2.1 - 6 1 5.2 - 6 2 5.7 - 6 3 5.5 - 6 4 5.4 - 6 5 5.7 - 6 6 5.7 - 12 1 22.1 - 12 2 23.9 - 12 3 24.4 - 12 4 22.7 - 12 5 27.9 - 12 6 23.6 - 13 1 20.5 - 13 2 21.5 - 13 3 24.5 - 13 4 21.2 - 13 5 22.8 - 13 6 24.9 -; - -param RegPrice_Discount := - 1 1 2.25 - 1 2 2.25 - 1 3 2.25 - 1 4 2.25 - 1 5 2.25 - 1 6 2.25 - 4 1 2.35 - 4 2 2.35 - 4 3 2.35 - 4 4 2.35 - 4 5 2.35 - 4 6 2.35 - 6 1 5.5 - 6 2 5.5 - 6 3 5.5 - 6 4 5.5 - 6 5 5.5 - 6 6 5.5 -; - -param DiscountPrice_Discount := - 1 1 2.15 - 1 2 2.15 - 1 3 2.15 - 1 4 2.15 - 1 5 2.15 - 1 6 2.15 - 4 1 2.1 - 4 2 2.1 - 4 3 2.1 - 4 4 2.1 - 4 5 2.1 - 4 6 2.1 - 6 1 5.3 - 6 2 5.3 - 6 3 5.3 - 6 4 5.3 - 6 5 5.3 - 6 6 5.3 -; - -param RegPrice_Bulk := - 1 1 2.3 - 1 2 2.3 - 1 3 2.3 - 1 4 2.3 - 1 5 2.3 - 1 6 2.3 - 4 1 2.35 - 4 2 2.35 - 4 3 2.35 - 4 4 2.35 - 4 5 2.35 - 4 6 2.35 - 6 1 5.5 - 6 2 5.5 - 6 3 5.5 - 6 4 5.5 - 6 5 5.5 - 6 6 5.5 -; - -param DiscountPrice_Bulk := - 1 1 2.1 - 1 2 2.1 - 1 3 2.1 - 1 4 2.1 - 1 5 2.1 - 1 6 2.1 - 4 1 2.0 - 4 2 2.0 - 4 3 2.0 - 4 4 2.0 - 4 5 2.0 - 4 6 2.0 - 6 1 5.25 - 6 2 5.25 - 6 3 5.25 - 6 4 5.25 - 6 5 5.25 - 6 6 5.25 -; - -param Prices_Length := - 1 1 1 2.25 - 1 1 2 2.25 - 1 1 3 2.25 - 1 1 4 2.25 - 1 1 5 2.25 - 1 1 6 2.25 - 4 1 1 2.35 - 4 1 2 2.35 - 4 1 3 2.35 - 4 1 4 2.35 - 4 1 5 2.35 - 4 1 6 2.35 - 6 1 1 5.5 - 6 1 2 5.5 - 6 1 3 5.5 - 6 1 4 5.5 - 6 1 5 5.5 - 6 1 6 5.5 - 1 2 1 2.2 - 1 2 2 2.2 - 1 2 3 2.2 - 1 2 4 2.2 - 1 2 5 2.2 - 1 2 6 2.2 - 4 2 1 2.25 - 4 2 2 2.25 - 4 2 3 2.25 - 4 2 4 2.25 - 4 2 5 2.25 - 4 2 6 2.25 - 6 2 1 5.4 - 6 2 2 5.4 - 6 2 3 5.4 - 6 2 4 5.4 - 6 2 5 5.4 - 6 2 6 5.4 - 1 3 1 2.15 - 1 3 2 2.15 - 1 3 3 2.15 - 1 3 4 2.15 - 1 3 5 2.15 - 1 3 6 2.15 - 4 3 1 2.15 - 4 3 2 2.15 - 4 3 3 2.15 - 4 3 4 2.15 - 4 3 5 2.15 - 4 3 6 2.15 - 6 3 1 5.3 - 6 3 2 5.3 - 6 3 3 5.3 - 6 3 4 5.3 - 6 3 5 5.3 - 6 3 6 5.3 -; - -param MinAmount_Discount := - 1 1 63 - 1 2 63 - 1 3 63 - 1 4 63 - 1 5 63 - 1 6 63 - 4 1 4 - 4 2 4 - 4 3 4 - 4 4 4 - 4 5 4 - 4 6 4 - 6 1 22 - 6 2 22 - 6 3 22 - 6 4 22 - 6 5 22 - 6 6 22 -; - -param MinAmount_Bulk := - 1 1 64 - 1 2 64 - 1 3 64 - 1 4 64 - 1 5 64 - 1 6 64 - 4 1 5 - 4 2 5 - 4 3 5 - 4 4 5 - 4 5 5 - 4 6 5 - 6 1 24 - 6 2 24 - 6 3 24 - 6 4 24 - 6 5 24 - 6 6 24 -; - -param MinAmount_Length := - 1 1 0 - 1 2 62 - 1 3 66 - 4 1 0 - 4 2 3 - 4 3 4 - 6 1 0 - 6 2 11 - 6 3 24 -; - -param MainProducts := - 9 1 1 - 9 2 0 - 9 3 0 - 10 1 0 - 10 2 1 - 10 3 0 - 11 1 0 - 11 2 0 - 11 3 1 -; - -param ShortfallPenalty := - 12 1 25 - 12 2 25 - 12 3 25 - 12 4 25 - 12 5 25 - 12 6 25 - 13 1 25 - 13 2 25 - 13 3 25 - 13 4 25 - 13 5 25 - 13 6 25 -; - -param ShortfallUB := - 12 1 10 - 12 2 10 - 12 3 10 - 12 4 10 - 12 5 10 - 12 6 10 - 13 1 10 - 13 2 10 - 13 3 10 - 13 4 10 - 13 5 10 - 13 6 10 -; - -param InventoryCost := - 1 1 0 - 1 2 0 - 1 3 0 - 1 4 0 - 1 5 0 - 1 6 0 - 2 1 0 - 2 2 0 - 2 3 0 - 2 4 0 - 2 5 0 - 2 6 0 - 3 1 0 - 3 2 0 - 3 3 0 - 3 4 0 - 3 5 0 - 3 6 0 - 4 1 0 - 4 2 0 - 4 3 0 - 4 4 0 - 4 5 0 - 4 6 0 - 5 1 0 - 5 2 0 - 5 3 0 - 5 4 0 - 5 5 0 - 5 6 0 - 6 1 0 - 6 2 0 - 6 3 0 - 6 4 0 - 6 5 0 - 6 6 0 - 7 1 0 - 7 2 0 - 7 3 0 - 7 4 0 - 7 5 0 - 7 6 0 - 8 1 0 - 8 2 0 - 8 3 0 - 8 4 0 - 8 5 0 - 8 6 0 - 9 1 0 - 9 2 0 - 9 3 0 - 9 4 0 - 9 5 0 - 9 6 0 - 10 1 0 - 10 2 0 - 10 3 0 - 10 4 0 - 10 5 0 - 10 6 0 - 11 1 0 - 11 2 0 - 11 3 0 - 11 4 0 - 11 5 0 - 11 6 0 - 12 1 1 - 12 2 1 - 12 3 1 - 12 4 1 - 12 5 1 - 12 6 1 - 13 1 1 - 13 2 1 - 13 3 1 - 13 4 1 - 13 5 1 - 13 6 1 -; - -param InventoryLevelUB := - 1 1 0 - 1 2 0 - 1 3 0 - 1 4 0 - 1 5 0 - 1 6 0 - 2 1 0 - 2 2 0 - 2 3 0 - 2 4 0 - 2 5 0 - 2 6 0 - 3 1 0 - 3 2 0 - 3 3 0 - 3 4 0 - 3 5 0 - 3 6 0 - 4 1 0 - 4 2 0 - 4 3 0 - 4 4 0 - 4 5 0 - 4 6 0 - 5 1 0 - 5 2 0 - 5 3 0 - 5 4 0 - 5 5 0 - 5 6 0 - 6 1 0 - 6 2 0 - 6 3 0 - 6 4 0 - 6 5 0 - 6 6 0 - 7 1 0 - 7 2 0 - 7 3 0 - 7 4 0 - 7 5 0 - 7 6 0 - 8 1 0 - 8 2 0 - 8 3 0 - 8 4 0 - 8 5 0 - 8 6 0 - 9 1 0 - 9 2 0 - 9 3 0 - 9 4 0 - 9 5 0 - 9 6 0 - 10 1 0 - 10 2 0 - 10 3 0 - 10 4 0 - 10 5 0 - 10 6 0 - 11 1 0 - 11 2 0 - 11 3 0 - 11 4 0 - 11 5 0 - 11 6 0 - 12 1 30 - 12 2 30 - 12 3 30 - 12 4 30 - 12 5 30 - 12 6 30 - 13 1 30 - 13 2 30 - 13 3 30 - 13 4 30 - 13 5 30 - 13 6 30 -; +set TimePeriods := 1 2 3 4 5 6 ; +set Processes := 1 2 3 ; +set Streams := 1 2 3 4 5 6 7 8 9 10 11 12 13 ; +set Products := 12 13 ; +set RawMaterials := 1 4 6 ; +set Contracts_Length := 1 2 3 4 ; +set Contracts := 1 2 3 4 ; + +param Capacity := + 1 27 + 2 30 + 3 25 +; + +param ProcessConstants := + 1 0.9 + 2 0.85 + 3 0.8 +; + +param SupplyAndDemandUBs := + 1 1 100 + 1 2 100 + 1 3 100 + 1 4 100 + 1 5 100 + 1 6 100 + 4 1 30 + 4 2 30 + 4 3 30 + 4 4 30 + 4 5 30 + 4 6 30 + 6 1 100 + 6 2 100 + 6 3 100 + 6 4 100 + 6 5 100 + 6 6 100 + 12 1 20 + 12 2 25 + 12 3 22 + 12 4 30 + 12 5 28 + 12 6 26 + 13 1 51 + 13 2 50 + 13 3 53 + 13 4 60 + 13 5 59 + 13 6 50 +; + + + +# +# JDS: Note that you can specify data as a table (similar to GAMS... +# +#param DemandLB := +# 12 1 5 +# 12 2 5 +# 12 3 5 +# 12 4 5 +# 12 5 5 +# 12 6 5 +# 13 1 5 +# 13 2 5 +# 13 3 5 +# 13 4 5 +# 13 5 5 +# 13 6 5 +#; + +param DemandLB: + 1 2 3 4 5 6 := + 12 5 5 5 5 5 5 + 13 5 5 5 5 5 5 ; + + +param OperatingCosts := + 1 1 0.6 + 1 2 0.7 + 1 3 0.6 + 1 4 0.6 + 1 5 0.7 + 1 6 0.7 + 2 1 0.5 + 2 2 0.5 + 2 3 0.5 + 2 4 0.4 + 2 5 0.4 + 2 6 0.5 + 3 1 0.6 + 3 2 0.6 + 3 3 0.5 + 3 4 0.6 + 3 5 0.6 + 3 6 0.5 +; + +param Prices := + 1 1 2.2 + 1 2 2.4 + 1 3 2.4 + 1 4 2.3 + 1 5 2.2 + 1 6 2.2 + 4 1 1.9 + 4 2 2.4 + 4 3 2.4 + 4 4 2.2 + 4 5 2.1 + 4 6 2.1 + 6 1 5.2 + 6 2 5.7 + 6 3 5.5 + 6 4 5.4 + 6 5 5.7 + 6 6 5.7 + 12 1 22.1 + 12 2 23.9 + 12 3 24.4 + 12 4 22.7 + 12 5 27.9 + 12 6 23.6 + 13 1 20.5 + 13 2 21.5 + 13 3 24.5 + 13 4 21.2 + 13 5 22.8 + 13 6 24.9 +; + +param RegPrice_Discount := + 1 1 2.25 + 1 2 2.25 + 1 3 2.25 + 1 4 2.25 + 1 5 2.25 + 1 6 2.25 + 4 1 2.35 + 4 2 2.35 + 4 3 2.35 + 4 4 2.35 + 4 5 2.35 + 4 6 2.35 + 6 1 5.5 + 6 2 5.5 + 6 3 5.5 + 6 4 5.5 + 6 5 5.5 + 6 6 5.5 +; + +param DiscountPrice_Discount := + 1 1 2.15 + 1 2 2.15 + 1 3 2.15 + 1 4 2.15 + 1 5 2.15 + 1 6 2.15 + 4 1 2.1 + 4 2 2.1 + 4 3 2.1 + 4 4 2.1 + 4 5 2.1 + 4 6 2.1 + 6 1 5.3 + 6 2 5.3 + 6 3 5.3 + 6 4 5.3 + 6 5 5.3 + 6 6 5.3 +; + +param RegPrice_Bulk := + 1 1 2.3 + 1 2 2.3 + 1 3 2.3 + 1 4 2.3 + 1 5 2.3 + 1 6 2.3 + 4 1 2.35 + 4 2 2.35 + 4 3 2.35 + 4 4 2.35 + 4 5 2.35 + 4 6 2.35 + 6 1 5.5 + 6 2 5.5 + 6 3 5.5 + 6 4 5.5 + 6 5 5.5 + 6 6 5.5 +; + +param DiscountPrice_Bulk := + 1 1 2.1 + 1 2 2.1 + 1 3 2.1 + 1 4 2.1 + 1 5 2.1 + 1 6 2.1 + 4 1 2.0 + 4 2 2.0 + 4 3 2.0 + 4 4 2.0 + 4 5 2.0 + 4 6 2.0 + 6 1 5.25 + 6 2 5.25 + 6 3 5.25 + 6 4 5.25 + 6 5 5.25 + 6 6 5.25 +; + +param Prices_Length := + 1 1 1 2.25 + 1 1 2 2.25 + 1 1 3 2.25 + 1 1 4 2.25 + 1 1 5 2.25 + 1 1 6 2.25 + 4 1 1 2.35 + 4 1 2 2.35 + 4 1 3 2.35 + 4 1 4 2.35 + 4 1 5 2.35 + 4 1 6 2.35 + 6 1 1 5.5 + 6 1 2 5.5 + 6 1 3 5.5 + 6 1 4 5.5 + 6 1 5 5.5 + 6 1 6 5.5 + 1 2 1 2.2 + 1 2 2 2.2 + 1 2 3 2.2 + 1 2 4 2.2 + 1 2 5 2.2 + 1 2 6 2.2 + 4 2 1 2.25 + 4 2 2 2.25 + 4 2 3 2.25 + 4 2 4 2.25 + 4 2 5 2.25 + 4 2 6 2.25 + 6 2 1 5.4 + 6 2 2 5.4 + 6 2 3 5.4 + 6 2 4 5.4 + 6 2 5 5.4 + 6 2 6 5.4 + 1 3 1 2.15 + 1 3 2 2.15 + 1 3 3 2.15 + 1 3 4 2.15 + 1 3 5 2.15 + 1 3 6 2.15 + 4 3 1 2.15 + 4 3 2 2.15 + 4 3 3 2.15 + 4 3 4 2.15 + 4 3 5 2.15 + 4 3 6 2.15 + 6 3 1 5.3 + 6 3 2 5.3 + 6 3 3 5.3 + 6 3 4 5.3 + 6 3 5 5.3 + 6 3 6 5.3 +; + +param MinAmount_Discount := + 1 1 63 + 1 2 63 + 1 3 63 + 1 4 63 + 1 5 63 + 1 6 63 + 4 1 4 + 4 2 4 + 4 3 4 + 4 4 4 + 4 5 4 + 4 6 4 + 6 1 22 + 6 2 22 + 6 3 22 + 6 4 22 + 6 5 22 + 6 6 22 +; + +param MinAmount_Bulk := + 1 1 64 + 1 2 64 + 1 3 64 + 1 4 64 + 1 5 64 + 1 6 64 + 4 1 5 + 4 2 5 + 4 3 5 + 4 4 5 + 4 5 5 + 4 6 5 + 6 1 24 + 6 2 24 + 6 3 24 + 6 4 24 + 6 5 24 + 6 6 24 +; + +param MinAmount_Length := + 1 1 0 + 1 2 62 + 1 3 66 + 4 1 0 + 4 2 3 + 4 3 4 + 6 1 0 + 6 2 11 + 6 3 24 +; + +param MainProducts := + 9 1 1 + 9 2 0 + 9 3 0 + 10 1 0 + 10 2 1 + 10 3 0 + 11 1 0 + 11 2 0 + 11 3 1 +; + +param ShortfallPenalty := + 12 1 25 + 12 2 25 + 12 3 25 + 12 4 25 + 12 5 25 + 12 6 25 + 13 1 25 + 13 2 25 + 13 3 25 + 13 4 25 + 13 5 25 + 13 6 25 +; + +param ShortfallUB := + 12 1 10 + 12 2 10 + 12 3 10 + 12 4 10 + 12 5 10 + 12 6 10 + 13 1 10 + 13 2 10 + 13 3 10 + 13 4 10 + 13 5 10 + 13 6 10 +; + +param InventoryCost := + 1 1 0 + 1 2 0 + 1 3 0 + 1 4 0 + 1 5 0 + 1 6 0 + 2 1 0 + 2 2 0 + 2 3 0 + 2 4 0 + 2 5 0 + 2 6 0 + 3 1 0 + 3 2 0 + 3 3 0 + 3 4 0 + 3 5 0 + 3 6 0 + 4 1 0 + 4 2 0 + 4 3 0 + 4 4 0 + 4 5 0 + 4 6 0 + 5 1 0 + 5 2 0 + 5 3 0 + 5 4 0 + 5 5 0 + 5 6 0 + 6 1 0 + 6 2 0 + 6 3 0 + 6 4 0 + 6 5 0 + 6 6 0 + 7 1 0 + 7 2 0 + 7 3 0 + 7 4 0 + 7 5 0 + 7 6 0 + 8 1 0 + 8 2 0 + 8 3 0 + 8 4 0 + 8 5 0 + 8 6 0 + 9 1 0 + 9 2 0 + 9 3 0 + 9 4 0 + 9 5 0 + 9 6 0 + 10 1 0 + 10 2 0 + 10 3 0 + 10 4 0 + 10 5 0 + 10 6 0 + 11 1 0 + 11 2 0 + 11 3 0 + 11 4 0 + 11 5 0 + 11 6 0 + 12 1 1 + 12 2 1 + 12 3 1 + 12 4 1 + 12 5 1 + 12 6 1 + 13 1 1 + 13 2 1 + 13 3 1 + 13 4 1 + 13 5 1 + 13 6 1 +; + +param InventoryLevelUB := + 1 1 0 + 1 2 0 + 1 3 0 + 1 4 0 + 1 5 0 + 1 6 0 + 2 1 0 + 2 2 0 + 2 3 0 + 2 4 0 + 2 5 0 + 2 6 0 + 3 1 0 + 3 2 0 + 3 3 0 + 3 4 0 + 3 5 0 + 3 6 0 + 4 1 0 + 4 2 0 + 4 3 0 + 4 4 0 + 4 5 0 + 4 6 0 + 5 1 0 + 5 2 0 + 5 3 0 + 5 4 0 + 5 5 0 + 5 6 0 + 6 1 0 + 6 2 0 + 6 3 0 + 6 4 0 + 6 5 0 + 6 6 0 + 7 1 0 + 7 2 0 + 7 3 0 + 7 4 0 + 7 5 0 + 7 6 0 + 8 1 0 + 8 2 0 + 8 3 0 + 8 4 0 + 8 5 0 + 8 6 0 + 9 1 0 + 9 2 0 + 9 3 0 + 9 4 0 + 9 5 0 + 9 6 0 + 10 1 0 + 10 2 0 + 10 3 0 + 10 4 0 + 10 5 0 + 10 6 0 + 11 1 0 + 11 2 0 + 11 3 0 + 11 4 0 + 11 5 0 + 11 6 0 + 12 1 30 + 12 2 30 + 12 3 30 + 12 4 30 + 12 5 30 + 12 6 30 + 13 1 30 + 13 2 30 + 13 3 30 + 13 4 30 + 13 5 30 + 13 6 30 +; From cb3a4073ea5e5200f8244d4bb893f7873eef77ba Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 11 Feb 2020 18:17:59 -0500 Subject: [PATCH 07/38] Changing jobshop integration tests to be scripted rather than use pyomo command --- pyomo/gdp/tests/test_gdp.py | 79 +++++++++++++------------------------ 1 file changed, 27 insertions(+), 52 deletions(-) diff --git a/pyomo/gdp/tests/test_gdp.py b/pyomo/gdp/tests/test_gdp.py index 1fd85675eec..650f1ffaf08 100644 --- a/pyomo/gdp/tests/test_gdp.py +++ b/pyomo/gdp/tests/test_gdp.py @@ -17,6 +17,8 @@ from os.path import abspath, dirname, normpath, join currdir = dirname(abspath(__file__)) exdir = normpath(join(currdir,'..','..','..','examples', 'gdp')) +sys.path.append(exdir) +from jobshop import build_model try: import new @@ -31,8 +33,6 @@ from six import iteritems -#DEBUG -from nose.tools import set_trace try: import yaml @@ -79,47 +79,28 @@ class CommonTests: #__metaclass__ = Labeler solve=True - + def pyomo(self, *args, **kwds): + m_jobshop = build_model() + # This is awful, but it's the convention of the old method, so it will + # work for now + datafile = args[0] + m = m_jobshop.create_instance(join(exdir, datafile)) + + if 'preprocess' in kwds: + transformation = kwds['preprocess'] + + TransformationFactory('gdp.%s' % transformation).apply_to(m) + m.write('%s_result.lp' % self.problem, + io_options={'symbolic_solver_labels': True}) + if self.solve: - args=['solve']+list(args) + solver = 'glpk' if 'solver' in kwds: - args.append('--solver='+kwds['solver']) - else: - args.append('--solver=glpk') - args.append('--save-results=result.yml') - else: - args=['convert']+list(args) - if 'preprocess' in kwds: - pp = kwds['preprocess'] - if pp == 'bigm': - args.append('--transform=gdp.bigm') - # ESJ: HACK for now: also apply the reclassify - # transformation in this case - args.append('--transform=gdp.reclassify') - elif pp == 'chull': - args.append('--transform=gdp.chull') - # ESJ: HACK for now: also apply the reclassify - # transformation in this case - args.append('--transform=gdp.reclassify') - elif pp == 'cuttingplane': - args.append('--transform=gdp.cuttingplane') - args.append('-c') - args.append('--symbolic-solver-labels') - os.chdir(currdir) - - print('***') - #if pproc is not None: - # pproc.activate() - # print("Activating " + kwds['preprocess']) - #else: - # print("ERROR: no transformation activated: " + pp) - print(' '.join(args)) - output = main.main(args) - #if pproc is not None: - # pproc = None - print('***') - return output + solver = kwds['solver'] + results = SolverFactory(solver).solve(m) + m.solutions.store_to(results) + results.write(filename='result.yml') def check(self, problem, solver): pass @@ -145,8 +126,7 @@ def updateDocStrings(self): def test_bigm_jobshop_small(self): self.problem='test_bigm_jobshop_small' # Run the small jobshop example using the BigM transformation - self.pyomo( join(exdir,'jobshop.py'), join(exdir,'jobshop-small.dat'), - preprocess='bigm' ) + self.pyomo('jobshop-small.dat', preprocess='bigm') # ESJ: TODO: Right now the indicator variables have names they won't # have when they don't have to be reclassified. So I think this LP file # will need to change again. @@ -155,8 +135,7 @@ def test_bigm_jobshop_small(self): def test_bigm_jobshop_large(self): self.problem='test_bigm_jobshop_large' # Run the large jobshop example using the BigM transformation - self.pyomo( join(exdir,'jobshop.py'), join(exdir,'jobshop.dat'), - preprocess='bigm') + self.pyomo('jobshop.dat', preprocess='bigm') # ESJ: TODO: this LP file also will need to change with the # indicator variable change. self.check( 'jobshop_large', 'bigm' ) @@ -172,31 +151,27 @@ def test_bigm_jobshop_large(self): def test_chull_jobshop_small(self): self.problem='test_chull_jobshop_small' # Run the small jobshop example using the CHull transformation - self.pyomo( join(exdir,'jobshop.py'), join(exdir,'jobshop-small.dat'), - preprocess='chull') + self.pyomo('jobshop-small.dat', preprocess='chull') self.check( 'jobshop_small', 'chull' ) def test_chull_jobshop_large(self): self.problem='test_chull_jobshop_large' # Run the large jobshop example using the CHull transformation - self.pyomo( join(exdir,'jobshop.py'), join(exdir,'jobshop.dat'), - preprocess='chull') + self.pyomo('jobshop.dat', preprocess='chull') self.check( 'jobshop_large', 'chull' ) @unittest.skip("cutting plane LP file tests are too fragile") @unittest.skipIf('gurobi' not in solvers, 'Gurobi solver not available') def test_cuttingplane_jobshop_small(self): self.problem='test_cuttingplane_jobshop_small' - self.pyomo( join(exdir,'jobshop.py'), join(exdir,'jobshop-small.dat'), - preprocess='cuttingplane') + self.pyomo('jobshop-small.dat', preprocess='cuttingplane') self.check( 'jobshop_small', 'cuttingplane' ) @unittest.skip("cutting plane LP file tests are too fragile") @unittest.skipIf('gurobi' not in solvers, 'Gurobi solver not available') def test_cuttingplane_jobshop_large(self): self.problem='test_cuttingplane_jobshop_large' - self.pyomo( join(exdir,'jobshop.py'), join(exdir,'jobshop.dat'), - preprocess='cuttingplane') + self.pyomo('jobshop.dat', preprocess='cuttingplane') self.check( 'jobshop_large', 'cuttingplane' ) From 847cc0d8dc15641e831cf5019d9f03c2ee7be0bf Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 12 Feb 2020 08:00:01 -0500 Subject: [PATCH 08/38] Writing LP and yaml files to the correct directory this time (I think) --- pyomo/gdp/tests/test_gdp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/gdp/tests/test_gdp.py b/pyomo/gdp/tests/test_gdp.py index 650f1ffaf08..27af9e82e4e 100644 --- a/pyomo/gdp/tests/test_gdp.py +++ b/pyomo/gdp/tests/test_gdp.py @@ -91,7 +91,7 @@ def pyomo(self, *args, **kwds): transformation = kwds['preprocess'] TransformationFactory('gdp.%s' % transformation).apply_to(m) - m.write('%s_result.lp' % self.problem, + m.write(join(currdir, '%s_result.lp' % self.problem), io_options={'symbolic_solver_labels': True}) if self.solve: @@ -100,7 +100,7 @@ def pyomo(self, *args, **kwds): solver = kwds['solver'] results = SolverFactory(solver).solve(m) m.solutions.store_to(results) - results.write(filename='result.yml') + results.write(filename=join(currdir, 'result.yml')) def check(self, problem, solver): pass From 45f012951eae133218dbf3e36d77be3137c19930 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 12 Feb 2020 13:47:48 -0700 Subject: [PATCH 09/38] Attempting a master push test --- .github/workflows/master_push_test.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/workflows/master_push_test.yml diff --git a/.github/workflows/master_push_test.yml b/.github/workflows/master_push_test.yml new file mode 100644 index 00000000000..307bc686d49 --- /dev/null +++ b/.github/workflows/master_push_test.yml @@ -0,0 +1 @@ +name: continuous-integration/github/push/linux on: push jobs: pyomo-linux-master-test: name: py${{ matrix.python-version }} runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: python-version: [3.7] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install extras..." echo "" pip install numpy scipy ipython openpyxl sympy pyodbc pyyaml networkx xlrd matplotlib dill pandas seaborn pymysql pyro4 pint pathos echo "" echo "Install GAMS..." echo "" wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe chmod +x linux_x64_64_sfx.exe ./linux_x64_64_sfx.exe -q -d gams cd gams/gams29.1_linux_x64_64_sfx/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." export PATH=$PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ pip install nose KEY_JOB=1 test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file From 92e3e27a4cf65608238122c7fdd0b5f73b943521 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 12 Feb 2020 15:01:23 -0700 Subject: [PATCH 10/38] Master test renamed and merged with master --- .github/workflows/push_master_test.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/workflows/push_master_test.yml diff --git a/.github/workflows/push_master_test.yml b/.github/workflows/push_master_test.yml new file mode 100644 index 00000000000..307bc686d49 --- /dev/null +++ b/.github/workflows/push_master_test.yml @@ -0,0 +1 @@ +name: continuous-integration/github/push/linux on: push jobs: pyomo-linux-master-test: name: py${{ matrix.python-version }} runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: python-version: [3.7] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install extras..." echo "" pip install numpy scipy ipython openpyxl sympy pyodbc pyyaml networkx xlrd matplotlib dill pandas seaborn pymysql pyro4 pint pathos echo "" echo "Install GAMS..." echo "" wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe chmod +x linux_x64_64_sfx.exe ./linux_x64_64_sfx.exe -q -d gams cd gams/gams29.1_linux_x64_64_sfx/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." export PATH=$PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ pip install nose KEY_JOB=1 test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file From 512ca2b958c304ecf77f0bea9e0427e0d24fcba0 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 12 Feb 2020 15:07:27 -0700 Subject: [PATCH 11/38] Remove first draft --- .github/workflows/master_push_test.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/workflows/master_push_test.yml diff --git a/.github/workflows/master_push_test.yml b/.github/workflows/master_push_test.yml deleted file mode 100644 index 307bc686d49..00000000000 --- a/.github/workflows/master_push_test.yml +++ /dev/null @@ -1 +0,0 @@ -name: continuous-integration/github/push/linux on: push jobs: pyomo-linux-master-test: name: py${{ matrix.python-version }} runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: python-version: [3.7] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install extras..." echo "" pip install numpy scipy ipython openpyxl sympy pyodbc pyyaml networkx xlrd matplotlib dill pandas seaborn pymysql pyro4 pint pathos echo "" echo "Install GAMS..." echo "" wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe chmod +x linux_x64_64_sfx.exe ./linux_x64_64_sfx.exe -q -d gams cd gams/gams29.1_linux_x64_64_sfx/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." export PATH=$PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ pip install nose KEY_JOB=1 test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file From 34a44f158a0912b02c6e14f6accfd4509a214f52 Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Wed, 12 Feb 2020 16:04:47 -0700 Subject: [PATCH 12/38] Workflow was not rendering as human-readable --- .github/workflows/push_master_test.yml | 68 +++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/.github/workflows/push_master_test.yml b/.github/workflows/push_master_test.yml index 307bc686d49..bc41e73f3ea 100644 --- a/.github/workflows/push_master_test.yml +++ b/.github/workflows/push_master_test.yml @@ -1 +1,67 @@ -name: continuous-integration/github/push/linux on: push jobs: pyomo-linux-master-test: name: py${{ matrix.python-version }} runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: python-version: [3.7] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install extras..." echo "" pip install numpy scipy ipython openpyxl sympy pyodbc pyyaml networkx xlrd matplotlib dill pandas seaborn pymysql pyro4 pint pathos echo "" echo "Install GAMS..." echo "" wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe chmod +x linux_x64_64_sfx.exe ./linux_x64_64_sfx.exe -q -d gams cd gams/gams29.1_linux_x64_64_sfx/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." export PATH=$PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ pip install nose KEY_JOB=1 test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file +name: continuous-integration/github/push/linux + +on: push + +jobs: + pyomo-linux-master-test: + name: py${{ matrix.python-version }} + runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + python-version: [3.7] + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install Pyomo dependencies + run: | + echo "Upgrade pip..." + python -m pip install --upgrade pip + echo "" + echo "Install extras..." + echo "" + pip install numpy scipy ipython openpyxl sympy pyodbc pyyaml networkx xlrd matplotlib dill pandas seaborn pymysql pyro4 pint pathos + echo "" + echo "Install GAMS..." + echo "" + wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe + chmod +x linux_x64_64_sfx.exe + ./linux_x64_64_sfx.exe -q -d gams + cd gams/gams29.1_linux_x64_64_sfx/apifiles/Python/ + py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') + gams_ver=api + for ver in api_*; do + if test ${ver:4} -le $py_ver; then + gams_ver=$ver + fi + done + cd $gams_ver + python setup.py -q install -noCheck + - name: Install Pyomo and extensions + run: | + echo "Clone Pyomo-model-libraries..." + git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git + echo "" + echo "Install PyUtilib..." + echo "" + pip install --quiet git+https://github.com/PyUtilib/pyutilib + echo "" + echo "Install Pyomo..." + echo "" + python setup.py develop + echo "" + echo "Download and install extensions..." + echo "" + pyomo download-extensions + pyomo build-extensions + - name: Run nightly tests with test.pyomo + run: | + echo "Run test.pyomo..." + export PATH=$PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ + pip install nose + KEY_JOB=1 + test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries From c0db66cc7f8423620a8ab3fb779a1d25f965d478 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Thu, 13 Feb 2020 09:04:03 -0700 Subject: [PATCH 13/38] Removed erroneous tests --- .github/workflows/master_push_test.yml | 1 - .github/workflows/push_master_test.yml | 1 - 2 files changed, 2 deletions(-) delete mode 100644 .github/workflows/master_push_test.yml delete mode 100644 .github/workflows/push_master_test.yml diff --git a/.github/workflows/master_push_test.yml b/.github/workflows/master_push_test.yml deleted file mode 100644 index 307bc686d49..00000000000 --- a/.github/workflows/master_push_test.yml +++ /dev/null @@ -1 +0,0 @@ -name: continuous-integration/github/push/linux on: push jobs: pyomo-linux-master-test: name: py${{ matrix.python-version }} runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: python-version: [3.7] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install extras..." echo "" pip install numpy scipy ipython openpyxl sympy pyodbc pyyaml networkx xlrd matplotlib dill pandas seaborn pymysql pyro4 pint pathos echo "" echo "Install GAMS..." echo "" wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe chmod +x linux_x64_64_sfx.exe ./linux_x64_64_sfx.exe -q -d gams cd gams/gams29.1_linux_x64_64_sfx/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." export PATH=$PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ pip install nose KEY_JOB=1 test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file diff --git a/.github/workflows/push_master_test.yml b/.github/workflows/push_master_test.yml deleted file mode 100644 index 307bc686d49..00000000000 --- a/.github/workflows/push_master_test.yml +++ /dev/null @@ -1 +0,0 @@ -name: continuous-integration/github/push/linux on: push jobs: pyomo-linux-master-test: name: py${{ matrix.python-version }} runs-on: ubuntu-18.04 strategy: fail-fast: false matrix: python-version: [3.7] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install extras..." echo "" pip install numpy scipy ipython openpyxl sympy pyodbc pyyaml networkx xlrd matplotlib dill pandas seaborn pymysql pyro4 pint pathos echo "" echo "Install GAMS..." echo "" wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe chmod +x linux_x64_64_sfx.exe ./linux_x64_64_sfx.exe -q -d gams cd gams/gams29.1_linux_x64_64_sfx/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." export PATH=$PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)/gams/gams29.1_linux_x64_64_sfx/ pip install nose KEY_JOB=1 test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file From 11e379fd37cf6a8ee4d4655d372fb73e6ff45e05 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Tue, 18 Feb 2020 15:01:20 -0500 Subject: [PATCH 14/38] Switching to import_file to get the jobshop model --- pyomo/gdp/tests/test_gdp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyomo/gdp/tests/test_gdp.py b/pyomo/gdp/tests/test_gdp.py index 27af9e82e4e..2a91fd4e3ae 100644 --- a/pyomo/gdp/tests/test_gdp.py +++ b/pyomo/gdp/tests/test_gdp.py @@ -15,10 +15,9 @@ import os import sys from os.path import abspath, dirname, normpath, join +from pyutilib.misc import import_file currdir = dirname(abspath(__file__)) exdir = normpath(join(currdir,'..','..','..','examples', 'gdp')) -sys.path.append(exdir) -from jobshop import build_model try: import new @@ -81,7 +80,8 @@ class CommonTests: solve=True def pyomo(self, *args, **kwds): - m_jobshop = build_model() + exfile = import_file(join(exdir, 'jobshop.py')) + m_jobshop = exfile.build_model() # This is awful, but it's the convention of the old method, so it will # work for now datafile = args[0] From 326456b84abad5c94742255fad51511fe151f7c3 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Wed, 19 Feb 2020 15:27:59 -0700 Subject: [PATCH 15/38] Remove CBC from our installation documentation --- doc/OnlineDocs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/OnlineDocs/installation.rst b/doc/OnlineDocs/installation.rst index ad9f5ccec09..e4e82b31585 100644 --- a/doc/OnlineDocs/installation.rst +++ b/doc/OnlineDocs/installation.rst @@ -23,7 +23,7 @@ optimization solvers can be installed with conda as well: :: - conda install -c conda-forge ipopt coincbc glpk + conda install -c conda-forge ipopt glpk Using PIP From 155ca5afc0fa23a12182283813e252082c9666e1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 24 Feb 2020 10:20:53 -0700 Subject: [PATCH 16/38] Add flatten_dae_variables() utility --- pyomo/dae/flatten.py | 130 ++++++++++++++++++++++++++++++++ pyomo/dae/tests/test_flatten.py | 116 ++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 pyomo/dae/flatten.py create mode 100644 pyomo/dae/tests/test_flatten.py diff --git a/pyomo/dae/flatten.py b/pyomo/dae/flatten.py new file mode 100644 index 00000000000..8125a23c33c --- /dev/null +++ b/pyomo/dae/flatten.py @@ -0,0 +1,130 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ +from pyomo.core.base import Block, Var, Reference +from pyomo.core.base.block import SubclassOf +from pyomo.core.base.sets import _SetProduct +from pyomo.core.base.indexed_component_slice import _IndexedComponent_slice + +def identify_member_sets(index): + if index is None: + return [] + queue = [index] + ans = [] + while queue: + s = queue.pop(0) + if not isinstance(s, _SetProduct): + ans.append(s) + else: + queue.extend(s.set_tuple) + return ans + + +def generate_time_only_slices(obj, time): + o_sets = identify_member_sets(obj.index_set()) + # Given a potentially complex set, determine the index of the TIME + # set, as well as all other "fixed" indices. We will even support a + # single Set with dimen==None (using ellipsis in the slice). + ellipsis_idx = None + time_idx = None + regular_idx = [] + idx = 0 + for s in o_sets: + if s is time: + time_idx = idx + idx += 1 + elif s.dimen is not None: + for sub_idx in range(s.dimen): + regular_idx.append(idx) + idx += s.dimen + elif ellipsis_idx is None: + ellipsis_idx = idx + idx += 1 + else: + raise RuntimeError( + "We can only handle a single Set with dimen=None") + # To support Sets with dimen==None (using ellipsis), we need to have + # all fixed/time indices be positive if they appear before the + # ellipsis and negative (counting from the end of the list) if they + # are after the ellipsis. + if ellipsis_idx: + if time_idx > ellipsis_idx: + time_idx = time_idx - idx + regular_idx = [ i - idx if i > ellipsis_idx else i + for i in fixed_idx ] + # We now form a temporary slice that slices over all the regular + # indices for a fixed value of the time index. + tmp_sliced = {i: slice(None) for i in regular_idx} + tmp_fixed = {time_idx: time.first()} + tmp_ellipsis = ellipsis_idx + _slice = _IndexedComponent_slice( + obj, tmp_fixed, tmp_sliced, tmp_ellipsis + ) + # For each combination of regular indices, we can generate a single + # slice over the time index + time_sliced = {time_idx: time.first()} + for key in _slice.wildcard_keys(): + if type(key) is not tuple: + key = (key,) + time_fixed = dict( + (i, val) if i ", _.name + ref_data = { + self._hashRef(Reference(m.a[:])), + self._hashRef(Reference(m.b[:,1])), + self._hashRef(Reference(m.b[:,2])), + self._hashRef(Reference(m.c[3,:])), + self._hashRef(Reference(m.c[4,:])), + } + self.assertEqual(len(time), len(ref_data)) + for ref in time: + self.assertIn(self._hashRef(ref), ref_data) + + def test_1level_model(self): + m = ConcreteModel() + m.T = ContinuousSet(bounds=(0,1)) + @m.Block([1,2],m.T) + def B(b, i, t): + b.x = Var(list(range(2*i, 2*i+2))) + + regular, time = flatten_dae_variables(m, m.T) + self.assertEqual(len(regular), 0) + # Output for debugging + #for v in time: + # v.pprint() + # for _ in v.values(): + # print" -> ", _.name + ref_data = { + self._hashRef(Reference(m.B[1,:].x[2])), + self._hashRef(Reference(m.B[1,:].x[3])), + self._hashRef(Reference(m.B[2,:].x[4])), + self._hashRef(Reference(m.B[2,:].x[5])), + } + self.assertEqual(len(time), len(ref_data)) + for ref in time: + self.assertIn(self._hashRef(ref), ref_data) + + + def test_2level_model(self): + m = ConcreteModel() + m.T = ContinuousSet(bounds=(0,1)) + @m.Block([1,2],m.T) + def B(b, i, t): + @b.Block(list(range(2*i, 2*i+2))) + def bb(bb, j): + bb.y = Var([10,11]) + b.x = Var(list(range(2*i, 2*i+2))) + + regular, time = flatten_dae_variables(m, m.T) + self.assertEqual(len(regular), 0) + # Output for debugging + #for v in time: + # v.pprint() + # for _ in v.values(): + # print" -> ", _.name + ref_data = { + self._hashRef(Reference(m.B[1,:].x[2])), + self._hashRef(Reference(m.B[1,:].x[3])), + self._hashRef(Reference(m.B[2,:].x[4])), + self._hashRef(Reference(m.B[2,:].x[5])), + self._hashRef(Reference(m.B[1,:].bb[2].y[10])), + self._hashRef(Reference(m.B[1,:].bb[2].y[11])), + self._hashRef(Reference(m.B[1,:].bb[3].y[10])), + self._hashRef(Reference(m.B[1,:].bb[3].y[11])), + self._hashRef(Reference(m.B[2,:].bb[4].y[10])), + self._hashRef(Reference(m.B[2,:].bb[4].y[11])), + self._hashRef(Reference(m.B[2,:].bb[5].y[10])), + self._hashRef(Reference(m.B[2,:].bb[5].y[11])), + } + self.assertEqual(len(time), len(ref_data)) + for ref in time: + self.assertIn(self._hashRef(ref), ref_data) + + # TODO: Add tests for Sets with dimen==None + + +if __name__ == "__main__": + unittest.main() From 21b55d40e792807c14b78eaf9fa7a587c0a37a41 Mon Sep 17 00:00:00 2001 From: Bethany Nicholson Date: Tue, 25 Feb 2020 10:52:23 -0700 Subject: [PATCH 17/38] Adding a couple docstrings to the functions for DAE model "flattening" --- pyomo/dae/flatten.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pyomo/dae/flatten.py b/pyomo/dae/flatten.py index 8125a23c33c..16c73cd52f2 100644 --- a/pyomo/dae/flatten.py +++ b/pyomo/dae/flatten.py @@ -13,6 +13,12 @@ from pyomo.core.base.indexed_component_slice import _IndexedComponent_slice def identify_member_sets(index): + """ + Identify all of the individual subsets in an indexing set. When the + Set rewrite is finished this function should no longer be needed, + the `subsets` method will provide this functionality. + """ + if index is None: return [] queue = [index] @@ -81,13 +87,13 @@ def generate_time_only_slices(obj, time): def generate_time_indexed_block_slices(block, time): # TODO: We should probably do a sanity check that time does not - # appeat in any sub-block / var indices. + # appear in any sub-block / var indices. queue = list( generate_time_only_slices(block, time) ) while queue: _slice = queue.pop(0) # Pick a random block from this slice (i.e. TIME == TIME.first()) # - # TODO: we should probably sometime check that the OTHER blocks + # TODO: we should probably check that the OTHER blocks # in the time set have the same variables. b = next(iter(_slice)) # Any sub-blocks must be put on the queue to descend into and @@ -104,6 +110,27 @@ def generate_time_indexed_block_slices(block, time): def flatten_dae_variables(model, time): + """ + This function takes in a (hierarchical, block-structured) Pyomo + model and a `ContinuousSet` and returns two lists of "flattened" + variables. The first is a list of all `_VarData` that are not + indexed by the `ContinuousSet` and the second is a list of + `Reference` components such that each reference is indexed only by + the specified `ContinuousSet`. This function is convenient for + identifying variables that are implicitly indexed by the + `ContinuousSet`, for example, a singleton `Var` living on a `Block` + that is indexed by the `ContinuousSet`. + + Parameters + ---------- + model : Concrete Pyomo model + + time : ``pyomo.dae.ContinuousSet`` + + Returns + ------- + Two lists + """ assert time.model() is model.model() block_queue = [model] From d2b828befc85399cdb5e2d1c79f94dd7b43713d3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 25 Feb 2020 11:04:41 -0700 Subject: [PATCH 18/38] Cleaning up the test to remove duplicate sets --- pyomo/network/tests/test_arc.py | 199 ++++++++++++++++---------------- 1 file changed, 101 insertions(+), 98 deletions(-) diff --git a/pyomo/network/tests/test_arc.py b/pyomo/network/tests/test_arc.py index 19375af5217..600cbbc1f8b 100644 --- a/pyomo/network/tests/test_arc.py +++ b/pyomo/network/tests/test_arc.py @@ -1145,13 +1145,11 @@ def test_extensive_no_splitfrac_expansion(self): m.load2 = Block() def source_block(b): - b.t = Set(initialize=[1, 2, 3]) - b.p_out = Var(b.t) + b.p_out = Var(b.model().time) b.outlet = Port(initialize={'p': (b.p_out, Port.Extensive, {'include_splitfrac':False})}) def load_block(b): - b.t = Set(initialize=[1, 2, 3]) - b.p_in = Var(b.t) + b.p_in = Var(b.model().time) b.inlet = Port(initialize={'p': (b.p_in, Port.Extensive, {'include_splitfrac':False})}) source_block(m.source) @@ -1163,102 +1161,107 @@ def load_block(b): TransformationFactory("network.expand_arcs").apply_to(m) + ref = """ +1 Set Declarations + time : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3) + [1, 2, 3] + +5 Block Declarations + cs1_expanded : Size=1, Index=None, Active=True + 1 Var Declarations + p : Size=3, Index=time + Key : Lower : Value : Upper : Fixed : Stale : Domain + 1 : None : None : None : False : True : Reals + 2 : None : None : None : False : True : Reals + 3 : None : None : None : False : True : Reals + + 1 Declarations: p + cs2_expanded : Size=1, Index=None, Active=True + 1 Var Declarations + p : Size=3, Index=time + Key : Lower : Value : Upper : Fixed : Stale : Domain + 1 : None : None : None : False : True : Reals + 2 : None : None : None : False : True : Reals + 3 : None : None : None : False : True : Reals + + 1 Declarations: p + load1 : Size=1, Index=None, Active=True + 1 Var Declarations + p_in : Size=3, Index=time + Key : Lower : Value : Upper : Fixed : Stale : Domain + 1 : None : None : None : False : True : Reals + 2 : None : None : None : False : True : Reals + 3 : None : None : None : False : True : Reals + + 1 Constraint Declarations + inlet_p_insum : Size=3, Index=time, Active=True + Key : Lower : Body : Upper : Active + 1 : 0.0 : cs1_expanded.p[1] - load1.p_in[1] : 0.0 : True + 2 : 0.0 : cs1_expanded.p[2] - load1.p_in[2] : 0.0 : True + 3 : 0.0 : cs1_expanded.p[3] - load1.p_in[3] : 0.0 : True + + 1 Port Declarations + inlet : Size=1, Index=None + Key : Name : Size : Variable + None : p : 3 : load1.p_in + + 3 Declarations: p_in inlet inlet_p_insum + load2 : Size=1, Index=None, Active=True + 1 Var Declarations + p_in : Size=3, Index=time + Key : Lower : Value : Upper : Fixed : Stale : Domain + 1 : None : None : None : False : True : Reals + 2 : None : None : None : False : True : Reals + 3 : None : None : None : False : True : Reals + + 1 Constraint Declarations + inlet_p_insum : Size=3, Index=time, Active=True + Key : Lower : Body : Upper : Active + 1 : 0.0 : cs2_expanded.p[1] - load2.p_in[1] : 0.0 : True + 2 : 0.0 : cs2_expanded.p[2] - load2.p_in[2] : 0.0 : True + 3 : 0.0 : cs2_expanded.p[3] - load2.p_in[3] : 0.0 : True + + 1 Port Declarations + inlet : Size=1, Index=None + Key : Name : Size : Variable + None : p : 3 : load2.p_in + + 3 Declarations: p_in inlet inlet_p_insum + source : Size=1, Index=None, Active=True + 1 Var Declarations + p_out : Size=3, Index=time + Key : Lower : Value : Upper : Fixed : Stale : Domain + 1 : None : None : None : False : True : Reals + 2 : None : None : None : False : True : Reals + 3 : None : None : None : False : True : Reals + + 1 Constraint Declarations + outlet_p_outsum : Size=3, Index=time, Active=True + Key : Lower : Body : Upper : Active + 1 : 0.0 : cs1_expanded.p[1] + cs2_expanded.p[1] - source.p_out[1] : 0.0 : True + 2 : 0.0 : cs1_expanded.p[2] + cs2_expanded.p[2] - source.p_out[2] : 0.0 : True + 3 : 0.0 : cs1_expanded.p[3] + cs2_expanded.p[3] - source.p_out[3] : 0.0 : True + + 1 Port Declarations + outlet : Size=1, Index=None + Key : Name : Size : Variable + None : p : 3 : source.p_out + + 3 Declarations: p_out outlet outlet_p_outsum + +2 Arc Declarations + cs1 : Size=1, Index=None, Active=False + Key : Ports : Directed : Active + None : (source.outlet, load1.inlet) : True : False + cs2 : Size=1, Index=None, Active=False + Key : Ports : Directed : Active + None : (source.outlet, load2.inlet) : True : False + +8 Declarations: time source load1 load2 cs1 cs2 cs1_expanded cs2_expanded +""" os = StringIO() m.pprint(ostream=os) - self.assertEqual(os.getvalue(), - '1 Set Declarations\n' - ' time : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3)\n' - ' [1, 2, 3]\n\n5 Block Declarations\n' - ' cs1_expanded : Size=1, Index=None, Active=True\n' - ' 1 Var Declarations\n' - ' p : Size=3, Index=source.t\n' - ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' - ' 1 : None : None : None : False : True : Reals\n' - ' 2 : None : None : None : False : True : Reals\n' - ' 3 : None : None : None : False : True : Reals\n\n' - ' 1 Declarations: p\n' - ' cs2_expanded : Size=1, Index=None, Active=True\n' - ' 1 Var Declarations\n' - ' p : Size=3, Index=source.t\n' - ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' - ' 1 : None : None : None : False : True : Reals\n' - ' 2 : None : None : None : False : True : Reals\n' - ' 3 : None : None : None : False : True : Reals\n\n' - ' 1 Declarations: p\n' - ' load1 : Size=1, Index=None, Active=True\n' - ' 1 Set Declarations\n' - ' t : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3)\n' - ' [1, 2, 3]\n\n' - ' 1 Var Declarations\n p_in : Size=3, Index=load1.t\n' - ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' - ' 1 : None : None : None : False : True : Reals\n' - ' 2 : None : None : None : False : True : Reals\n' - ' 3 : None : None : None : False : True : Reals\n\n' - ' 1 Constraint Declarations\n' - ' inlet_p_insum : Size=3, Index=source.t, Active=True\n' - ' Key : Lower : Body : Upper : Active\n' - ' 1 : 0.0 : cs1_expanded.p[1] - load1.p_in[1] : 0.0 : True\n' - ' 2 : 0.0 : cs1_expanded.p[2] - load1.p_in[2] : 0.0 : True\n' - ' 3 : 0.0 : cs1_expanded.p[3] - load1.p_in[3] : 0.0 : True\n\n' - ' 1 Port Declarations\n' - ' inlet : Size=1, Index=None\n' - ' Key : Name : Size : Variable\n' - ' None : p : 3 : load1.p_in\n\n' - ' 4 Declarations: t p_in inlet inlet_p_insum\n' - ' load2 : Size=1, Index=None, Active=True\n' - ' 1 Set Declarations\n' - ' t : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3)\n' - ' [1, 2, 3]\n\n' - ' 1 Var Declarations\n' - ' p_in : Size=3, Index=load2.t\n' - ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' - ' 1 : None : None : None : False : True : Reals\n' - ' 2 : None : None : None : False : True : Reals\n' - ' 3 : None : None : None : False : True : Reals\n\n' - ' 1 Constraint Declarations\n' - ' inlet_p_insum : Size=3, Index=source.t, Active=True\n' - ' Key : Lower : Body : Upper : Active\n' - ' 1 : 0.0 : cs2_expanded.p[1] - load2.p_in[1] : 0.0 : True\n' - ' 2 : 0.0 : cs2_expanded.p[2] - load2.p_in[2] : 0.0 : True\n' - ' 3 : 0.0 : cs2_expanded.p[3] - load2.p_in[3] : 0.0 : True\n\n' - ' 1 Port Declarations\n' - ' inlet : Size=1, Index=None\n' - ' Key : Name : Size : Variable\n' - ' None : p : 3 : load2.p_in\n\n' - ' 4 Declarations: t p_in inlet inlet_p_insum\n' - ' source : Size=1, Index=None, Active=True\n' - ' 1 Set Declarations\n' - ' t : Dim=0, Dimen=1, Size=3, Domain=None, Ordered=False, Bounds=(1, 3)\n' - ' [1, 2, 3]\n\n' - ' 1 Var Declarations\n' - ' p_out : Size=3, Index=source.t\n' - ' Key : Lower : Value : Upper : Fixed : Stale : Domain\n' - ' 1 : None : None : None : False : True : Reals\n' - ' 2 : None : None : None : False : True : Reals\n' - ' 3 : None : None : None : False : True : Reals\n\n' - ' 1 Constraint Declarations\n' - ' outlet_p_outsum : Size=3, Index=source.t, Active=True\n' - ' Key : Lower : Body' - ' : Upper : Active\n' - ' 1 : 0.0 : cs1_expanded.p[1]' - ' + cs2_expanded.p[1] - source.p_out[1] : 0.0 : True\n' - ' 2 : 0.0 : cs1_expanded.p[2]' - ' + cs2_expanded.p[2] - source.p_out[2] : 0.0 : True\n' - ' 3 : 0.0 : cs1_expanded.p[3]' - ' + cs2_expanded.p[3] - source.p_out[3] : 0.0 : True\n\n' - ' 1 Port Declarations\n' - ' outlet : Size=1, Index=None\n' - ' Key : Name : Size : Variable\n' - ' None : p : 3 : source.p_out\n\n' - ' 4 Declarations: t p_out outlet outlet_p_outsum\n\n' - '2 Arc Declarations\n' - ' cs1 : Size=1, Index=None, Active=False\n' - ' Key : Ports : Directed : Active\n' - ' None : (source.outlet, load1.inlet) : True : False\n' - ' cs2 : Size=1, Index=None, Active=False\n' - ' Key : Ports : Directed : Active\n' - ' None : (source.outlet, load2.inlet) : True : False\n\n' - '8 Declarations: time source load1 load2 cs1 cs2 cs1_expanded cs2_expanded\n') + self.assertEqual(os.getvalue().strip(), ref.strip()) def test_extensive_expansion(self): m = ConcreteModel() From 5309ac99d252b189d8f2a2402ed06ba732de8111 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Tue, 25 Feb 2020 15:38:33 -0500 Subject: [PATCH 19/38] *.dat associations need to use this_file_dir. For now, just return AbstractModel. --- examples/gdp/batchProcessing.py | 4 ++-- examples/gdp/jobshop.py | 6 +----- examples/gdp/medTermPurchasing_Literal.py | 6 +----- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/gdp/batchProcessing.py b/examples/gdp/batchProcessing.py index d1363aeda2f..7ac536b2eb9 100644 --- a/examples/gdp/batchProcessing.py +++ b/examples/gdp/batchProcessing.py @@ -221,11 +221,11 @@ def units_in_phase_xor_rule(model, j): return sum(model.inPhase[j,k] for k in model.PARALLELUNITS) == 1 model.units_in_phase_xor = Constraint(model.STAGES, rule=units_in_phase_xor_rule) - return model.create_instance('batchProcessing1.dat') + return model if __name__ == "__main__": - m = build_model() + m = build_model().create_instance('batchProcessing1.dat') TransformationFactory('gdp.bigm').apply_to(m) SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) m.min_cost.display() diff --git a/examples/gdp/jobshop.py b/examples/gdp/jobshop.py index 35f7f235699..3c2dec08bd3 100644 --- a/examples/gdp/jobshop.py +++ b/examples/gdp/jobshop.py @@ -81,12 +81,8 @@ def _disj(model, I, K, J): return model -def build_small_concrete(): - return build_model().create_instance('jobshop-small.dat') - - if __name__ == "__main__": - m = build_small_concrete() + m = build_model().create_instance('jobshop-small.dat') TransformationFactory('gdp.bigm').apply_to(m) SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) m.makespan.display() diff --git a/examples/gdp/medTermPurchasing_Literal.py b/examples/gdp/medTermPurchasing_Literal.py index 13116209da0..e6d3d2a6b03 100755 --- a/examples/gdp/medTermPurchasing_Literal.py +++ b/examples/gdp/medTermPurchasing_Literal.py @@ -605,12 +605,8 @@ def FD_contract(model, j, t): return model -def build_concrete(): - return build_model().create_instance('medTermPurchasing_Literal_Chull.dat') - - if __name__ == "__main__": - m = build_concrete() + m = build_model().create_instance('medTermPurchasing_Literal_Chull.dat') TransformationFactory('gdp.bigm').apply_to(m) SolverFactory('gams').solve(m, solver='baron', tee=True, add_options=['option optcr=1e-6;']) m.profit.display() From cb4cbcaaf347b88d56f78cb658854b2e543e068a Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 26 Feb 2020 10:01:34 -0700 Subject: [PATCH 20/38] Updating Readthedocs Contribution guide with instructions for Github Actions master workflow --- doc/OnlineDocs/contribution_guide.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/OnlineDocs/contribution_guide.rst b/doc/OnlineDocs/contribution_guide.rst index 8e196f4e126..e717558bfac 100644 --- a/doc/OnlineDocs/contribution_guide.rst +++ b/doc/OnlineDocs/contribution_guide.rst @@ -44,6 +44,13 @@ at least 70% coverage of the lines modified in the PR and prefer coverage closer to 90%. We also require that all tests pass before a PR will be merged. +The Pyomo master branch (as of `this commit `) provides a Github Action +workflow that will test any changes pushed to a branch using Ubuntu with +Python 3.7. For existing forks, fetch and merge your fork (and branches) with +Pyomo's master. For new forks, you will need to enable Github Actions +in the 'Actions' tab on your fork. Then the test will begin to run +automatically with each push to your fork. + At any point in the development cycle, a "work in progress" pull request may be opened by including '[WIP]' at the beginning of the PR title. This allows your code changes to be tested by Pyomo's automatic From 05f59fca95689db304b1341d754aeda95121d910 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 26 Feb 2020 10:10:43 -0700 Subject: [PATCH 21/38] Update the PR template to link to the guide --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 23de77b98af..f6da4169dc5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,7 +9,7 @@ ### Legal Acknowledgement -By contributing to this software project, I agree to the following terms and conditions for my contribution: +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. From 5745b7c03e8f80e49e02c464132e5f1e12c4d7e5 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 26 Feb 2020 10:30:54 -0700 Subject: [PATCH 22/38] Combine mac and ubuntu runs into a single workflow --- .github/workflows/unix_python_matrix_test.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/workflows/unix_python_matrix_test.yml diff --git a/.github/workflows/unix_python_matrix_test.yml b/.github/workflows/unix_python_matrix_test.yml new file mode 100644 index 00000000000..12c72b31af8 --- /dev/null +++ b/.github/workflows/unix_python_matrix_test.yml @@ -0,0 +1 @@ +name: continuous-integration/github/pr/${{ matrix.os }} on: push: branches: - combine_mac_linux_wf jobs: pyomo-mac-tests: name: py${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-latest, ubuntu-18.04] python-version: [3.5, 3.6, 3.7, 3.8] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | if [ ${{ matrix.os }} -eq 'macos-latest' ]; do echo "Install pre-dependencies for pyodbc..." brew update brew install bash gcc brew link --overwrite gcc brew install pkg-config brew install unixodbc brew install freetds fi echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install Pyomo dependencies..." echo "" pip install cython numpy scipy ipython openpyxl sympy pyyaml pyodbc networkx xlrd pandas matplotlib dill seaborn pymysql pyro4 pint pathos echo "" echo "Install CPLEX Community Edition..." echo "" pip install cplex || echo "CPLEX Community Edition is not available for ${{ matrix.python-version }}" echo "" echo "Install GAMS..." echo "" wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/macosx/osx_x64_64_sfx.exe -O gams_installer.exe chmod +x gams_installer.exe ./gams_installer.exe -q -d gams cd gams/*/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." GAMS_DIR=`ls -d1 $(pwd)/gams/*/ | head -1` export PATH=$PATH:$GAMS_DIR export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GAMS_DIR export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$GAMS_DIR pip install nose test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file From 7866131db6999da42750894631fcec5da2f0bb6d Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 26 Feb 2020 11:00:57 -0700 Subject: [PATCH 23/38] Made all syntax and OS-specific changes --- .github/workflows/unix_python_matrix_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unix_python_matrix_test.yml b/.github/workflows/unix_python_matrix_test.yml index 12c72b31af8..7c7ef53d2d8 100644 --- a/.github/workflows/unix_python_matrix_test.yml +++ b/.github/workflows/unix_python_matrix_test.yml @@ -1 +1 @@ -name: continuous-integration/github/pr/${{ matrix.os }} on: push: branches: - combine_mac_linux_wf jobs: pyomo-mac-tests: name: py${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-latest, ubuntu-18.04] python-version: [3.5, 3.6, 3.7, 3.8] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | if [ ${{ matrix.os }} -eq 'macos-latest' ]; do echo "Install pre-dependencies for pyodbc..." brew update brew install bash gcc brew link --overwrite gcc brew install pkg-config brew install unixodbc brew install freetds fi echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install Pyomo dependencies..." echo "" pip install cython numpy scipy ipython openpyxl sympy pyyaml pyodbc networkx xlrd pandas matplotlib dill seaborn pymysql pyro4 pint pathos echo "" echo "Install CPLEX Community Edition..." echo "" pip install cplex || echo "CPLEX Community Edition is not available for ${{ matrix.python-version }}" echo "" echo "Install GAMS..." echo "" wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/macosx/osx_x64_64_sfx.exe -O gams_installer.exe chmod +x gams_installer.exe ./gams_installer.exe -q -d gams cd gams/*/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." GAMS_DIR=`ls -d1 $(pwd)/gams/*/ | head -1` export PATH=$PATH:$GAMS_DIR export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GAMS_DIR export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$GAMS_DIR pip install nose test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file +name: continuous-integration/github/pr on: pull-request: branches: - master jobs: pyomo-mac-tests: name: ${{ matrix.os }}/py${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-latest, ubuntu-18.04] python-version: [3.5, 3.6, 3.7, 3.8] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | if hash brew; then echo "Install pre-dependencies for pyodbc..." brew update brew install bash gcc brew link --overwrite gcc brew install pkg-config brew install unixodbc brew install freetds fi echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install Pyomo dependencies..." echo "" pip install cython numpy scipy ipython openpyxl sympy pyyaml pyodbc networkx xlrd pandas matplotlib dill seaborn pymysql pyro4 pint pathos echo "" echo "Install CPLEX Community Edition..." echo "" pip install cplex || echo "CPLEX Community Edition is not available for ${{ matrix.python-version }}" echo "" echo "Install GAMS..." echo "" if hash brew; then wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/macosx/osx_x64_64_sfx.exe -O gams_installer.exe else wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe -O gams_installer.exe fi chmod +x gams_installer.exe ./gams_installer.exe -q -d gams cd gams/*/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." GAMS_DIR=`ls -d1 $(pwd)/gams/*/ | head -1` export PATH=$PATH:$GAMS_DIR export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GAMS_DIR export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$GAMS_DIR pip install nose test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file From e53574dc17f2ea49785093e7305f256c5b5794fa Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Wed, 26 Feb 2020 11:03:43 -0700 Subject: [PATCH 24/38] Rendering workflow human-readable --- .github/workflows/unix_python_matrix_test.yml | 91 ++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unix_python_matrix_test.yml b/.github/workflows/unix_python_matrix_test.yml index 7c7ef53d2d8..9163e6255de 100644 --- a/.github/workflows/unix_python_matrix_test.yml +++ b/.github/workflows/unix_python_matrix_test.yml @@ -1 +1,90 @@ -name: continuous-integration/github/pr on: pull-request: branches: - master jobs: pyomo-mac-tests: name: ${{ matrix.os }}/py${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-latest, ubuntu-18.04] python-version: [3.5, 3.6, 3.7, 3.8] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install Pyomo dependencies run: | if hash brew; then echo "Install pre-dependencies for pyodbc..." brew update brew install bash gcc brew link --overwrite gcc brew install pkg-config brew install unixodbc brew install freetds fi echo "Upgrade pip..." python -m pip install --upgrade pip echo "" echo "Install Pyomo dependencies..." echo "" pip install cython numpy scipy ipython openpyxl sympy pyyaml pyodbc networkx xlrd pandas matplotlib dill seaborn pymysql pyro4 pint pathos echo "" echo "Install CPLEX Community Edition..." echo "" pip install cplex || echo "CPLEX Community Edition is not available for ${{ matrix.python-version }}" echo "" echo "Install GAMS..." echo "" if hash brew; then wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/macosx/osx_x64_64_sfx.exe -O gams_installer.exe else wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe -O gams_installer.exe fi chmod +x gams_installer.exe ./gams_installer.exe -q -d gams cd gams/*/apifiles/Python/ py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') gams_ver=api for ver in api_*; do if test ${ver:4} -le $py_ver; then gams_ver=$ver fi done cd $gams_ver python setup.py -q install -noCheck - name: Install Pyomo and extensions run: | echo "Clone Pyomo-model-libraries..." git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git echo "" echo "Install PyUtilib..." echo "" pip install --quiet git+https://github.com/PyUtilib/pyutilib echo "" echo "Install Pyomo..." echo "" python setup.py develop echo "" echo "Download and install extensions..." echo "" pyomo download-extensions pyomo build-extensions - name: Run nightly tests with test.pyomo run: | echo "Run test.pyomo..." GAMS_DIR=`ls -d1 $(pwd)/gams/*/ | head -1` export PATH=$PATH:$GAMS_DIR export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GAMS_DIR export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$GAMS_DIR pip install nose test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries \ No newline at end of file +name: continuous-integration/github/pr + +on: + pull-request: + branches: + - master + +jobs: + pyomo-mac-tests: + name: ${{ matrix.os }}/py${{ matrix.python-version }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-18.04] + python-version: [3.5, 3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install Pyomo dependencies + run: | + if hash brew; then + echo "Install pre-dependencies for pyodbc..." + brew update + brew install bash gcc + brew link --overwrite gcc + brew install pkg-config + brew install unixodbc + brew install freetds + fi + echo "Upgrade pip..." + python -m pip install --upgrade pip + echo "" + echo "Install Pyomo dependencies..." + echo "" + pip install cython numpy scipy ipython openpyxl sympy pyyaml pyodbc networkx xlrd pandas matplotlib dill seaborn pymysql pyro4 pint pathos + echo "" + echo "Install CPLEX Community Edition..." + echo "" + pip install cplex || echo "CPLEX Community Edition is not available for ${{ matrix.python-version }}" + echo "" + echo "Install GAMS..." + echo "" + if hash brew; then + wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/macosx/osx_x64_64_sfx.exe -O gams_installer.exe + else + wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe -O gams_installer.exe + fi + chmod +x gams_installer.exe + ./gams_installer.exe -q -d gams + cd gams/*/apifiles/Python/ + py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') + gams_ver=api + for ver in api_*; do + if test ${ver:4} -le $py_ver; then + gams_ver=$ver + fi + done + cd $gams_ver + python setup.py -q install -noCheck + - name: Install Pyomo and extensions + run: | + echo "Clone Pyomo-model-libraries..." + git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git + echo "" + echo "Install PyUtilib..." + echo "" + pip install --quiet git+https://github.com/PyUtilib/pyutilib + echo "" + echo "Install Pyomo..." + echo "" + python setup.py develop + echo "" + echo "Download and install extensions..." + echo "" + pyomo download-extensions + pyomo build-extensions + - name: Run nightly tests with test.pyomo + run: | + echo "Run test.pyomo..." + GAMS_DIR=`ls -d1 $(pwd)/gams/*/ | head -1` + export PATH=$PATH:$GAMS_DIR + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GAMS_DIR + export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$GAMS_DIR + pip install nose + test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries From 8f731c287091a83dd947c5f2e931ceb51bfd761e Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 26 Feb 2020 11:05:22 -0700 Subject: [PATCH 25/38] Removing ubuntu/mac tests from workflows --- .github/workflows/mac_python_matrix_test.yml | 82 ------------------- .../workflows/ubuntu_python_matrix_test.yml | 75 ----------------- .github/workflows/unix_python_matrix_test.yml | 2 +- 3 files changed, 1 insertion(+), 158 deletions(-) delete mode 100644 .github/workflows/mac_python_matrix_test.yml delete mode 100644 .github/workflows/ubuntu_python_matrix_test.yml diff --git a/.github/workflows/mac_python_matrix_test.yml b/.github/workflows/mac_python_matrix_test.yml deleted file mode 100644 index 785b7bb9e95..00000000000 --- a/.github/workflows/mac_python_matrix_test.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: continuous-integration/github/pr/osx - -on: - pull_request: - branches: - - master - -jobs: - pyomo-mac-tests: - name: py${{ matrix.python-version }} - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - python-version: [3.5, 3.6, 3.7, 3.8] - - steps: - - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install Pyomo dependencies - run: | - echo "Install pre-dependencies for pyodbc..." - brew update # Install pre-dependencies for pyodbc - brew install bash gcc - brew link --overwrite gcc - brew install pkg-config - brew install unixodbc - brew install freetds # Now install Python modules - echo "Upgrade pip..." - python -m pip install --upgrade pip - echo "" - echo "Install Pyomo dependencies..." - echo "" - pip install cython numpy scipy ipython openpyxl sympy pyyaml pyodbc networkx xlrd pandas matplotlib dill seaborn pymysql pyro4 pint pathos - echo "" - echo "Install CPLEX Community Edition..." - echo "" - pip install cplex || echo "CPLEX Community Edition is not available for ${{ matrix.python-version }}" - echo "" - echo "Install GAMS..." - echo "" - wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/macosx/osx_x64_64_sfx.exe -O gams_installer.exe - chmod +x gams_installer.exe - ./gams_installer.exe -q -d gams - cd gams/*/apifiles/Python/ - py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') - gams_ver=api - for ver in api_*; do - if test ${ver:4} -le $py_ver; then - gams_ver=$ver - fi - done - cd $gams_ver - python setup.py -q install -noCheck - - name: Install Pyomo and extensions - run: | - echo "Clone Pyomo-model-libraries..." - git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git - echo "" - echo "Install PyUtilib..." - echo "" - pip install --quiet git+https://github.com/PyUtilib/pyutilib - echo "" - echo "Install Pyomo..." - echo "" - python setup.py develop - echo "" - echo "Download and install extensions..." - echo "" - pyomo download-extensions - pyomo build-extensions - - name: Run nightly tests with test.pyomo - run: | - echo "Run test.pyomo..." - GAMS_DIR=`ls -d1 $(pwd)/gams/*/ | head -1` - export PATH=$PATH:$GAMS_DIR - export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$GAMS_DIR - pip install nose - test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries diff --git a/.github/workflows/ubuntu_python_matrix_test.yml b/.github/workflows/ubuntu_python_matrix_test.yml deleted file mode 100644 index 57f38bb1d49..00000000000 --- a/.github/workflows/ubuntu_python_matrix_test.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: continuous-integration/github/pr/linux - -on: - pull_request: - branches: - - master - -jobs: - pyomo-linux-tests: - name: py${{ matrix.python-version }} - runs-on: ubuntu-18.04 - strategy: - fail-fast: false - matrix: - python-version: [3.5, 3.6, 3.7, 3.8] - - steps: - - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install Pyomo dependencies - run: | - echo "Upgrade pip..." - python -m pip install --upgrade pip - echo "" - echo "Install Pyomo dependencies..." - echo "" - pip install cython numpy scipy ipython openpyxl sympy pyyaml pyodbc networkx xlrd pandas matplotlib dill seaborn pymysql pyro4 pint pathos - echo "" - echo "Install CPLEX Community Edition..." - echo "" - pip install cplex || echo "CPLEX Community Edition is not available for ${{ matrix.python-version }}" - echo "" - echo "Install GAMS..." - echo "" - wget -q https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0/linux/linux_x64_64_sfx.exe -O gams_installer.exe - chmod +x gams_installer.exe - ./gams_installer.exe -q -d gams - cd gams/*/apifiles/Python/ - py_ver=$(python -c 'import sys;print("%s%s" % sys.version_info[:2])') - gams_ver=api - for ver in api_*; do - if test ${ver:4} -le $py_ver; then - gams_ver=$ver - fi - done - cd $gams_ver - python setup.py -q install -noCheck - - name: Install Pyomo and extensions - run: | - echo "Clone Pyomo-model-libraries..." - git clone --quiet https://github.com/Pyomo/pyomo-model-libraries.git - echo "" - echo "Install PyUtilib..." - echo "" - pip install --quiet git+https://github.com/PyUtilib/pyutilib - echo "" - echo "Install Pyomo..." - echo "" - python setup.py develop - echo "" - echo "Download and install extensions..." - echo "" - pyomo download-extensions - pyomo build-extensions - - name: Run nightly tests with test.pyomo - run: | - echo "Run test.pyomo..." - GAMS_DIR=`ls -d1 $(pwd)/gams/*/ | head -1` - export PATH=$PATH:$GAMS_DIR - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GAMS_DIR - pip install nose - test.pyomo -v --cat="nightly" pyomo `pwd`/pyomo-model-libraries diff --git a/.github/workflows/unix_python_matrix_test.yml b/.github/workflows/unix_python_matrix_test.yml index 9163e6255de..dc8932df0dd 100644 --- a/.github/workflows/unix_python_matrix_test.yml +++ b/.github/workflows/unix_python_matrix_test.yml @@ -1,7 +1,7 @@ name: continuous-integration/github/pr on: - pull-request: + pull_request: branches: - master From c751c62ab116a672c2a34151b37a2bbea32f2e57 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 26 Feb 2020 11:37:57 -0700 Subject: [PATCH 26/38] Updating include_splitfrac documentation --- pyomo/network/port.py | 51 ++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/pyomo/network/port.py b/pyomo/network/port.py index 4f12fce20f8..7faa76800e3 100644 --- a/pyomo/network/port.py +++ b/pyomo/network/port.py @@ -460,27 +460,37 @@ def Equality(port, name, index_set): @staticmethod def Extensive(port, name, index_set, include_splitfrac=None, write_var_sum=True): - """ - Arc Expansion procedure for extensive variable properties + """Arc Expansion procedure for extensive variable properties This procedure is the rule to use when variable quantities should - be split for outlets and combined for inlets. - - This will first go through every destination of the port and create - a new variable on the arc's expanded block of the same index as the - current variable being processed. It will also create a splitfrac - variable on the expanded block as well. Then it will generate - constraints for the new variable that relates it to the port member - variable by the split fraction. Following this, an indexed constraint - is written that states that the sum of all the new variables equals - the parent. However, if `write_var_sum=False` is passed, instead of - this last indexed constraint, a single constraint will be written - that states the sum of the split fractions equals 1. - - Then, this procedure will go through every source of the port and - create a new variable (unless it already exists), and then write - a constraint that states the sum of all the incoming new variables - must equal the parent variable. + be conserved; that is, split for outlets and combined for inlets. + + This will first go through every destination of the port (i.e., + arcs whose source is this Port) and create a new variable on the + arc's expanded block of the same index as the current variable + being processed to store the amount of the variable that flows + over the arc. For ports that have multiple outgoing arcs, this + procedure will create a single splitfrac variable on the arc's + expanded block as well. Then it will generate constraints for + the new variable that relate it to the port member variable + using the split fraction, ensuring that all extensive variables + in the Port are split using the same ratio. The generation of + the split fraction variable and constraint can be suppressed by + setting the `include_splitfrac` argument to `False`. + + Once all arc-specific variables are created, this + procedure will create the "balancing constraint" that ensures + that the sum of all the new variables equals the original port + member variable. This constraint can be suppressed by setting + the `write_var_sum` argument to `False`; in which case, a single + constraint will be written that states the sum of the split + fractions equals 1. + + Finally, this procedure will go through every source for this + port and create a new arc variable (unless it already exists), + before generating the balancing constraint that ensures the sum + of all the incoming new arc variables equals the original port + variable. Model simplifications: @@ -496,11 +506,12 @@ def Extensive(port, name, index_set, include_splitfrac=None, If the port only contains a single Extensive variable, the splitfrac variables and the splitting constraints will be skipped since they will be unnecessary. However, they - can be still be included by passing include_splitfrac=True. + can be still be included by passing `include_splitfrac=True`. .. note:: If split fractions are skipped, the `write_var_sum=False` option is not allowed. + """ port_parent = port.parent_block() out_vars = Port._Split(port, name, index_set, From 6667973eec90ed50d5d0af5885a9c533b2d7c1d8 Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Wed, 26 Feb 2020 12:23:37 -0700 Subject: [PATCH 27/38] Changing name of jobs to match new TARGET value --- .github/workflows/unix_python_matrix_test.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unix_python_matrix_test.yml b/.github/workflows/unix_python_matrix_test.yml index dc8932df0dd..a6cfc8cd5a2 100644 --- a/.github/workflows/unix_python_matrix_test.yml +++ b/.github/workflows/unix_python_matrix_test.yml @@ -7,12 +7,17 @@ on: jobs: pyomo-mac-tests: - name: ${{ matrix.os }}/py${{ matrix.python-version }} + name: ${{ matrix.TARGET }}/py${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-latest, ubuntu-18.04] + include: + - os: macos-latest + TARGET: osx + - os: ubuntu-18.04 + TARGET: linux python-version: [3.5, 3.6, 3.7, 3.8] steps: From cf744de3e951a4d86a631522d2e43c442052888c Mon Sep 17 00:00:00 2001 From: "David L. Woodruff" Date: Fri, 28 Feb 2020 10:57:19 -0800 Subject: [PATCH 28/38] Added a test for the csvwriter to rapper tests --- pyomo/pysp/tests/rapper/rapper_tester.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pyomo/pysp/tests/rapper/rapper_tester.py b/pyomo/pysp/tests/rapper/rapper_tester.py index 4910ab6cb30..33208bc3c75 100644 --- a/pyomo/pysp/tests/rapper/rapper_tester.py +++ b/pyomo/pysp/tests/rapper/rapper_tester.py @@ -1,5 +1,5 @@ # Provide some test for rapper; most are smoke because PySP is tested elsewhere -# Author: David L. Woodruff (circa March 2017 and Sept 2018) +# Author: David L. Woodruff (circa March 2017; Sept 2018; Feb 2020) import pyutilib.th as unittest import tempfile @@ -10,11 +10,12 @@ import pyomo.environ as pyo import pyomo.pysp.util.rapper as rapper from pyomo.pysp.scenariotree.tree_structure_model import CreateAbstractScenarioTreeModel +import pyomo.pysp.plugins.csvsolutionwriter as csvw import pyomo as pyomoroot __author__ = 'David L. Woodruff ' __date__ = 'August 14, 2017' -__version__ = 1.5 +__version__ = 1.6 solvername = "ipopt" # could use almost any solver solver_available = pyo.SolverFactory(solvername).available(False) @@ -110,6 +111,19 @@ def test_ef_solve_with_gap(self): tree_model = self.farmer_concrete_tree) res, gap = stsolver.solve_ef(solvername, tee=True, need_gap=True) + @unittest.skipIf(not solver_available, + "%s solver is not available" % (solvername,)) + def test_ef_solve_with_csvwriter(self): + """ solve the ef and report gap""" + stsolver = rapper.StochSolver("ReferenceModel.py", + fsfct = "pysp_instance_creation_callback", + tree_model = self.farmer_concrete_tree) + res, gap = stsolver.solve_ef(solvername, tee=True, need_gap=True) + csvw.write_csv_soln(stsolver.scenario_tree, "testcref") + with open("testcref.csv", 'r') as f: + line = f.readline() + assert(line.split(",")[0] == "FirstStage") + def test_ef_cvar_construct(self): """ construct the ef with cvar """ stsolver = rapper.StochSolver("ReferenceModel.py", From dc1158ec410056a04c2e0942d7d3de7c56179a57 Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Fri, 28 Feb 2020 18:15:20 -0500 Subject: [PATCH 29/38] testing bugfixes --- pyomo/contrib/gdpopt/GDPopt.py | 4 +++- pyomo/contrib/gdpopt/tests/test_gdpopt.py | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pyomo/contrib/gdpopt/GDPopt.py b/pyomo/contrib/gdpopt/GDPopt.py index 4ac581020ad..c0ab2320d8b 100644 --- a/pyomo/contrib/gdpopt/GDPopt.py +++ b/pyomo/contrib/gdpopt/GDPopt.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- """Main driver module for GDPopt solver. +20.2.28 changes: +- bugfixes on tests 20.1.22 changes: - improved subsolver time limit support for GAMS interface - add maxTimeLimit exit condition for GDPopt-LBB @@ -48,7 +50,7 @@ setup_solver_environment) from pyomo.opt.base import SolverFactory -__version__ = (20, 1, 22) # Note: date-based version number +__version__ = (20, 2, 28) # Note: date-based version number @SolverFactory.register( diff --git a/pyomo/contrib/gdpopt/tests/test_gdpopt.py b/pyomo/contrib/gdpopt/tests/test_gdpopt.py index 37df9d224a0..4b223612c3d 100644 --- a/pyomo/contrib/gdpopt/tests/test_gdpopt.py +++ b/pyomo/contrib/gdpopt/tests/test_gdpopt.py @@ -11,10 +11,10 @@ from pyomo.contrib.gdpopt.GDPopt import GDPoptSolver from pyomo.contrib.gdpopt.data_class import GDPoptSolveData from pyomo.contrib.gdpopt.mip_solve import solve_linear_GDP -from pyomo.contrib.gdpopt.util import is_feasible +from pyomo.contrib.gdpopt.util import is_feasible, time_code from pyomo.environ import ConcreteModel, Objective, SolverFactory, Var, value, Integers, Block, Constraint, maximize from pyomo.gdp import Disjunct, Disjunction -from pyutilib.misc import import_file +from pyutilib.misc import import_file, Container from pyomo.contrib.mcpp.pyomo_mcpp import mcpp_available from pyomo.opt import TerminationCondition @@ -51,7 +51,10 @@ def test_solve_linear_GDP_unbounded(self): m.GDPopt_utils.disjunct_list = [m.d._autodisjuncts[0], m.d._autodisjuncts[1]] output = StringIO() with LoggingIntercept(output, 'pyomo.contrib.gdpopt', logging.WARNING): - solve_linear_GDP(m, GDPoptSolveData(), GDPoptSolver.CONFIG(dict(mip_solver=mip_solver))) + solver_data = GDPoptSolveData() + solver_data.timing = Container() + with time_code(solver_data.timing, 'main', is_main_timer=True): + solve_linear_GDP(m, solver_data, GDPoptSolver.CONFIG(dict(mip_solver=mip_solver))) self.assertIn("Linear GDP was unbounded. Resolving with arbitrary bound values", output.getvalue().strip()) From a8b90220d5b686632c047cf7c696b995b3f5313a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Mon, 2 Mar 2020 09:52:05 -0700 Subject: [PATCH 30/38] rapper tearDown: clean up temporary dir / path from setUp() --- pyomo/pysp/tests/rapper/rapper_tester.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyomo/pysp/tests/rapper/rapper_tester.py b/pyomo/pysp/tests/rapper/rapper_tester.py index 33208bc3c75..4f5fa3f0ea8 100644 --- a/pyomo/pysp/tests/rapper/rapper_tester.py +++ b/pyomo/pysp/tests/rapper/rapper_tester.py @@ -63,6 +63,8 @@ def tearDown(self): if "ReferenceModel" in sys.modules: del sys.modules["ReferenceModel"] + sys.path.remove(self.tdir) + shutil.rmtree(self.tdir, ignore_errors=True) os.chdir(self.savecwd) def test_fct_contruct(self): From f4dd2362478a6d145086fe2e01e472616f40664b Mon Sep 17 00:00:00 2001 From: Qi Chen Date: Mon, 2 Mar 2020 19:20:02 -0500 Subject: [PATCH 31/38] Add missing import --- pyomo/repn/tests/test_util.py | 25 +++++++++++++++++++++++++ pyomo/repn/util.py | 7 +++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 pyomo/repn/tests/test_util.py diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py new file mode 100644 index 00000000000..06bec33387f --- /dev/null +++ b/pyomo/repn/tests/test_util.py @@ -0,0 +1,25 @@ +import logging + +import pyutilib.th as unittest +from six import StringIO + +from pyomo.common.log import LoggingIntercept +from pyomo.repn.util import ftoa + + +class TestRepnUtils(unittest.TestCase): + def test_ftoa(self): + warning_output = StringIO() + with LoggingIntercept(warning_output, 'pyomo.core', logging.WARNING): + x1 = 1.123456789012345678 + x1str = ftoa(x1) + self.assertEqual(x1str, '1.1234567890123457') + # self.assertIn("to string resulted in loss of precision", warning_output.getvalue()) + # Not sure how to construct a case that hits that part of the code, but it should be done. + x2 = 1.0 + 1E-15 + x2str = ftoa(x2) + self.assertEqual(x2str, str(x2)) + + +if __name__ == "__main__": + unittest.main() diff --git a/pyomo/repn/util.py b/pyomo/repn/util.py index 4b969644b84..a00814452cc 100644 --- a/pyomo/repn/util.py +++ b/pyomo/repn/util.py @@ -11,12 +11,14 @@ from pyomo.core.base import Var, Param, Expression, Objective, Block, \ Constraint, Suffix from pyomo.core.expr.numvalue import native_numeric_types, is_fixed, value +import logging + +logger = logging.getLogger('pyomo.core') valid_expr_ctypes_minlp = {Var, Param, Expression, Objective} valid_active_ctypes_minlp = {Block, Constraint, Objective, Suffix} - -#Copied from cpxlp.py: +# Copied from cpxlp.py: # Keven Hunter made a nice point about using %.16g in his attachment # to ticket #4319. I am adjusting this to %.17g as this mocks the # behavior of using %r (i.e., float('%r'%) == ) with @@ -30,6 +32,7 @@ # the number's sign. _ftoa_precision_str = '%.17g' + def ftoa(val): if val is None: return val From 2af2856d8f13ef27d057fb4f17930fe759938428 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 3 Mar 2020 11:29:22 -0700 Subject: [PATCH 32/38] Updating tests to use sys.executable to launch python subprocesses Switch tests over to use sys.executable to specify the python interpreter to use when spawning python subprocesses for testing. This enables testing with pypy, where the interpreter name is not "python". --- pyomo/contrib/parmest/tests/test_parmest.py | 7 ++++--- .../core/tests/examples/test_kernel_examples.py | 3 ++- pyomo/pysp/tests/convert/test_ddsip.py | 5 +++-- pyomo/pysp/tests/convert/test_schuripopt.py | 3 ++- pyomo/pysp/tests/convert/test_smps.py | 5 +++-- pyomo/pysp/tests/examples/test_examples.py | 17 +++++++++-------- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/pyomo/contrib/parmest/tests/test_parmest.py b/pyomo/contrib/parmest/tests/test_parmest.py index d13f37b8db6..72f4d57f90d 100644 --- a/pyomo/contrib/parmest/tests/test_parmest.py +++ b/pyomo/contrib/parmest/tests/test_parmest.py @@ -17,6 +17,7 @@ import shutil import glob import subprocess +import sys from itertools import product import pyomo.contrib.parmest.parmest as parmest @@ -152,10 +153,10 @@ def test_rb_main(self): "rooney_biegler" + os.sep + "rooney_biegler.py" rbpath = os.path.abspath(rbpath) # paranoia strikes deep... if sys.version_info >= (3,5): - ret = subprocess.run(["python", rbpath]) + ret = subprocess.run([sys.executable, rbpath]) retcode = ret.returncode else: - retcode = subprocess.call(["python", rbpath]) + retcode = subprocess.call([sys.executable, rbpath]) assert(retcode == 0) @unittest.skip("Presently having trouble with mpiexec on appveyor") @@ -168,7 +169,7 @@ def test_parallel_parmest(self): rbpath = parmestpath + os.sep + "examples" + os.sep + \ "rooney_biegler" + os.sep + "rooney_biegler_parmest.py" rbpath = os.path.abspath(rbpath) # paranoia strikes deep... - rlist = ["mpiexec", "--allow-run-as-root", "-n", "2", "python", rbpath] + rlist = ["mpiexec", "--allow-run-as-root", "-n", "2", sys.executable, rbpath] if sys.version_info >= (3,5): ret = subprocess.run(rlist) retcode = ret.returncode diff --git a/pyomo/core/tests/examples/test_kernel_examples.py b/pyomo/core/tests/examples/test_kernel_examples.py index fb08fde960c..1d8c1e54c3d 100644 --- a/pyomo/core/tests/examples/test_kernel_examples.py +++ b/pyomo/core/tests/examples/test_kernel_examples.py @@ -14,6 +14,7 @@ import os import glob +import sys from os.path import basename, dirname, abspath, join import pyutilib.subprocess @@ -75,7 +76,7 @@ def testmethod(self): if (not testing_solvers['ipopt','nl']) or \ (not testing_solvers['mosek','python']): self.skipTest("Ipopt or Mosek is not available") - rc, log = pyutilib.subprocess.run(['python',example]) + rc, log = pyutilib.subprocess.run([sys.executable,example]) self.assertEqual(rc, 0, msg=log) return testmethod diff --git a/pyomo/pysp/tests/convert/test_ddsip.py b/pyomo/pysp/tests/convert/test_ddsip.py index cd87bdbfdb6..4377419f011 100644 --- a/pyomo/pysp/tests/convert/test_ddsip.py +++ b/pyomo/pysp/tests/convert/test_ddsip.py @@ -15,6 +15,7 @@ import filecmp import shutil import subprocess +import sys import pyutilib.subprocess import pyutilib.th as unittest from pyutilib.pyro import using_pyro3, using_pyro4 @@ -70,7 +71,7 @@ def _get_cmd(self, shutil.rmtree(options['--output-directory'], ignore_errors=True) - cmd = ['python','-m','pyomo.pysp.convert.ddsip'] + cmd = [sys.executable,'-m','pyomo.pysp.convert.ddsip'] for name, val in options.items(): cmd.append(name) if val is not None: @@ -214,7 +215,7 @@ def _setup(self, options): shutil.rmtree(options['--output-directory'], ignore_errors=True) def _get_cmd(self): - cmd = ['python','-m','pyomo.pysp.convert.ddsip'] + cmd = [sys.executable,'-m','pyomo.pysp.convert.ddsip'] for name, val in self.options.items(): cmd.append(name) if val is not None: diff --git a/pyomo/pysp/tests/convert/test_schuripopt.py b/pyomo/pysp/tests/convert/test_schuripopt.py index 07d2ed5b857..f42ebef5a3c 100644 --- a/pyomo/pysp/tests/convert/test_schuripopt.py +++ b/pyomo/pysp/tests/convert/test_schuripopt.py @@ -15,6 +15,7 @@ import filecmp import shutil import subprocess +import sys import pyutilib.th as unittest from pyutilib.pyro import using_pyro3, using_pyro4 from pyomo.pysp.util.misc import (_get_test_nameserver, @@ -83,7 +84,7 @@ def _setup(self, options): shutil.rmtree(options['--output-directory'], ignore_errors=True) def _get_cmd(self): - cmd = ['python','-m','pyomo.pysp.convert.schuripopt'] + cmd = [sys.executable,'-m','pyomo.pysp.convert.schuripopt'] for name, val in self.options.items(): cmd.append(name) if val is not None: diff --git a/pyomo/pysp/tests/convert/test_smps.py b/pyomo/pysp/tests/convert/test_smps.py index ebf4db96270..6ca6b65299a 100644 --- a/pyomo/pysp/tests/convert/test_smps.py +++ b/pyomo/pysp/tests/convert/test_smps.py @@ -16,6 +16,7 @@ import filecmp import shutil import subprocess +import sys import pyutilib.subprocess import pyutilib.services import pyutilib.th as unittest @@ -74,7 +75,7 @@ def _get_cmd(self, shutil.rmtree(options['--output-directory'], ignore_errors=True) - cmd = ['python','-m','pyomo.pysp.convert.smps'] + cmd = [sys.executable,'-m','pyomo.pysp.convert.smps'] for name, val in options.items(): cmd.append(name) if val is not None: @@ -247,7 +248,7 @@ def _setup(self, options): shutil.rmtree(options['--output-directory'], ignore_errors=True) def _get_cmd(self): - cmd = ['python','-m','pyomo.pysp.convert.smps'] + cmd = [sys.executable,'-m','pyomo.pysp.convert.smps'] for name, val in self.options.items(): cmd.append(name) if val is not None: diff --git a/pyomo/pysp/tests/examples/test_examples.py b/pyomo/pysp/tests/examples/test_examples.py index eea975b5af3..6a67c4c99e2 100644 --- a/pyomo/pysp/tests/examples/test_examples.py +++ b/pyomo/pysp/tests/examples/test_examples.py @@ -16,6 +16,7 @@ import difflib import filecmp import shutil +import sys from pyutilib.pyro import using_pyro3, using_pyro4 import pyutilib.services @@ -84,14 +85,14 @@ def _cleanup(self): @unittest.skipIf(not 'cplex' in solvers, 'cplex not available') def test_ef_duals(self): - cmd = ['python', join(examples_dir, 'ef_duals.py')] + cmd = [sys.executable, join(examples_dir, 'ef_duals.py')] self._run_cmd(cmd) self._cleanup() @unittest.skipIf(not 'cplex' in solvers, 'cplex not available') def test_benders_scripting(self): - cmd = ['python', join(examples_dir, 'benders_scripting.py')] + cmd = [sys.executable, join(examples_dir, 'benders_scripting.py')] self._run_cmd(cmd) self._cleanup() @@ -102,7 +103,7 @@ def test_compile_scenario_tree(self): tmpdir = os.path.join(thisdir, class_name+"_"+test_name) shutil.rmtree(tmpdir, ignore_errors=True) self.assertEqual(os.path.exists(tmpdir), False) - cmd = ['python', join(examples_dir, 'apps', 'compile_scenario_tree.py')] + cmd = [sys.executable, join(examples_dir, 'apps', 'compile_scenario_tree.py')] cmd.extend(["-m", join(pysp_examples_dir, "networkx_scenariotree", "ReferenceModel.py")]) @@ -122,7 +123,7 @@ def test_generate_distributed_NL(self): tmpdir = os.path.join(thisdir, class_name+"_"+test_name) shutil.rmtree(tmpdir, ignore_errors=True) self.assertEqual(os.path.exists(tmpdir), False) - cmd = ['python', join(examples_dir, 'apps', 'generate_distributed_NL.py')] + cmd = [sys.executable, join(examples_dir, 'apps', 'generate_distributed_NL.py')] cmd.extend(["-m", join(pysp_examples_dir, "networkx_scenariotree", "ReferenceModel.py")]) @@ -145,7 +146,7 @@ def test_scenario_tree_image(self): except OSError: pass self.assertEqual(os.path.exists(tmpfname), False) - cmd = ['python', join(examples_dir, 'apps', 'scenario_tree_image.py')] + cmd = [sys.executable, join(examples_dir, 'apps', 'scenario_tree_image.py')] cmd.extend(["-m", join(pysp_examples_dir, "networkx_scenariotree", "ReferenceModel.py")]) @@ -256,7 +257,7 @@ def test_solve_distributed(self): ["--pyro-port="+str(ns_port)], stdout=f, stderr=subprocess.STDOUT)) - cmd = ['python', join(examples_dir, 'solve_distributed.py'), str(ns_port)] + cmd = [sys.executable, join(examples_dir, 'solve_distributed.py'), str(ns_port)] time.sleep(2) [_poll(proc) for proc in scenariotreeserver_processes] self._run_cmd(cmd) @@ -278,7 +279,7 @@ def test_compile_scenario_tree(self): tmpdir = os.path.join(thisdir, class_name+"_"+test_name) shutil.rmtree(tmpdir, ignore_errors=True) self.assertEqual(os.path.exists(tmpdir), False) - cmd = ['python', join(examples_dir, 'apps', 'compile_scenario_tree.py')] + cmd = [sys.executable, join(examples_dir, 'apps', 'compile_scenario_tree.py')] cmd.extend(["-m", join(pysp_examples_dir, "networkx_scenariotree", "ReferenceModel.py")]) @@ -298,7 +299,7 @@ def test_generate_distributed_NL(self): tmpdir = os.path.join(thisdir, class_name+"_"+test_name) shutil.rmtree(tmpdir, ignore_errors=True) self.assertEqual(os.path.exists(tmpdir), False) - cmd = ['python', join(examples_dir, 'apps', 'generate_distributed_NL.py')] + cmd = [sys.executable, join(examples_dir, 'apps', 'generate_distributed_NL.py')] cmd.extend(["-m", join(pysp_examples_dir, "networkx_scenariotree", "ReferenceModel.py")]) From 57262e0f83a4c667ea280856c4b61103cff8edb2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 3 Mar 2020 14:56:43 -0700 Subject: [PATCH 33/38] Updating the ftoa tests to trigger loss of precision warning --- pyomo/repn/tests/test_util.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 06bec33387f..8523c0530d3 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -6,20 +6,31 @@ from pyomo.common.log import LoggingIntercept from pyomo.repn.util import ftoa +try: + import numpy as np + numpy_available = True +except: + numpy_available = False class TestRepnUtils(unittest.TestCase): def test_ftoa(self): - warning_output = StringIO() - with LoggingIntercept(warning_output, 'pyomo.core', logging.WARNING): - x1 = 1.123456789012345678 - x1str = ftoa(x1) - self.assertEqual(x1str, '1.1234567890123457') - # self.assertIn("to string resulted in loss of precision", warning_output.getvalue()) - # Not sure how to construct a case that hits that part of the code, but it should be done. - x2 = 1.0 + 1E-15 - x2str = ftoa(x2) - self.assertEqual(x2str, str(x2)) + # Test that trailing zeros are removed + f = 1.0 + a = ftoa(f) + self.assertEqual(a, '1') + @unittest.skipIf(not numpy_available, "NumPy is not available") + def test_ftoa_precision(self): + log = StringIO() + with LoggingIntercept(log, 'pyomo.core', logging.WARNING): + f = np.longdouble('1.1234567890123456789') + a = ftoa(f) + self.assertNotEqual(f, float(a)) + self.assertEqual(a, '1.1234567890123457') + self.assertRegexpMatches( + log.getvalue(), + '.*Converting 1.1234567890123456789 to string ' + 'resulted in loss of precision') if __name__ == "__main__": unittest.main() From c73782035bf682c3697036bd23ae6fe09f83c557 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 3 Mar 2020 15:17:23 -0700 Subject: [PATCH 34/38] ftoa tests: handle platforms where np.longdouble == float --- pyomo/repn/tests/test_util.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pyomo/repn/tests/test_util.py b/pyomo/repn/tests/test_util.py index 8523c0530d3..55304c5b8f3 100644 --- a/pyomo/repn/tests/test_util.py +++ b/pyomo/repn/tests/test_util.py @@ -25,12 +25,16 @@ def test_ftoa_precision(self): with LoggingIntercept(log, 'pyomo.core', logging.WARNING): f = np.longdouble('1.1234567890123456789') a = ftoa(f) - self.assertNotEqual(f, float(a)) self.assertEqual(a, '1.1234567890123457') - self.assertRegexpMatches( - log.getvalue(), - '.*Converting 1.1234567890123456789 to string ' - 'resulted in loss of precision') + # Depending on the platform, np.longdouble may or may not have + # higher precision than float: + if f == float(f): + test = self.assertNotRegexpMatches + else: + test = self.assertRegexpMatches + test( log.getvalue(), + '.*Converting 1.1234567890123456789 to string ' + 'resulted in loss of precision' ) if __name__ == "__main__": unittest.main() From 542e9ccacc44ca6e0bd9f52f15ce8ceffd311a00 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 3 Mar 2020 15:39:38 -0700 Subject: [PATCH 35/38] Standardizing github actions naming --- .github/workflows/parallel_tests.yml | 9 +++++++-- .../{push_master_test.yml => push_branch_test.yml} | 14 +++++++++----- .github/workflows/unix_python_matrix_test.yml | 2 +- .github/workflows/win_python_matrix_test.yml | 4 ++-- 4 files changed, 19 insertions(+), 10 deletions(-) rename .github/workflows/{push_master_test.yml => push_branch_test.yml} (88%) diff --git a/.github/workflows/parallel_tests.yml b/.github/workflows/parallel_tests.yml index 5e567c92ae7..67e750a892d 100644 --- a/.github/workflows/parallel_tests.yml +++ b/.github/workflows/parallel_tests.yml @@ -1,4 +1,4 @@ -name: parallel_tests +name: continuous-integration/github/pr on: pull_request: @@ -7,11 +7,16 @@ on: jobs: build: - runs-on: ubuntu-latest + name: parallel/${{ matrix.TARGET }}/py${{ matrix.python-version }} + runs-on: ${{ matrix.os }} strategy: max-parallel: 1 matrix: + os: [ubuntu-latest] python-version: [3.7] + include: + - os: ubuntu-latest + TARGET: linux steps: - uses: actions/checkout@v1 - name: setup conda diff --git a/.github/workflows/push_master_test.yml b/.github/workflows/push_branch_test.yml similarity index 88% rename from .github/workflows/push_master_test.yml rename to .github/workflows/push_branch_test.yml index 84ef802a133..93e9610e638 100644 --- a/.github/workflows/push_master_test.yml +++ b/.github/workflows/push_branch_test.yml @@ -1,15 +1,19 @@ -name: continuous-integration/github/push/linux +name: continuous-integration/github/push on: push jobs: - pyomo-linux-master-test: - name: py${{ matrix.python-version }} - runs-on: ubuntu-18.04 + pyomo-linux-branch-test: + name: ${{ matrix.TARGET }}/py${{ matrix.python-version }} + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - python-version: [3.7] + os: [ubuntu-18.04] + include: + - os: ubuntu-18.04 + TARGET: linux + python-version: [3.7] steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/unix_python_matrix_test.yml b/.github/workflows/unix_python_matrix_test.yml index a6cfc8cd5a2..d9fb9aae71b 100644 --- a/.github/workflows/unix_python_matrix_test.yml +++ b/.github/workflows/unix_python_matrix_test.yml @@ -6,7 +6,7 @@ on: - master jobs: - pyomo-mac-tests: + pyomo-unix-tests: name: ${{ matrix.TARGET }}/py${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: diff --git a/.github/workflows/win_python_matrix_test.yml b/.github/workflows/win_python_matrix_test.yml index b865b5d27c4..4b4d117ccee 100644 --- a/.github/workflows/win_python_matrix_test.yml +++ b/.github/workflows/win_python_matrix_test.yml @@ -1,4 +1,4 @@ -name: continuous-integration/github/pr/win +name: continuous-integration/github/pr on: pull_request: @@ -7,7 +7,7 @@ on: jobs: pyomo-tests: - name: py${{ matrix.python-version }} + name: win/py${{ matrix.python-version }} runs-on: windows-latest strategy: fail-fast: false # This flag causes all of the matrix to continue to run, even if one matrix option fails From 8e3fcbcbdeee451076f034470cf811f6ff9eb0a9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 3 Mar 2020 15:58:41 -0700 Subject: [PATCH 36/38] Renaming 'parallel' to 'mpi' --- .github/workflows/{parallel_tests.yml => mpi_matrix_test.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{parallel_tests.yml => mpi_matrix_test.yml} (93%) diff --git a/.github/workflows/parallel_tests.yml b/.github/workflows/mpi_matrix_test.yml similarity index 93% rename from .github/workflows/parallel_tests.yml rename to .github/workflows/mpi_matrix_test.yml index 67e750a892d..5f7fb9f0660 100644 --- a/.github/workflows/parallel_tests.yml +++ b/.github/workflows/mpi_matrix_test.yml @@ -7,7 +7,7 @@ on: jobs: build: - name: parallel/${{ matrix.TARGET }}/py${{ matrix.python-version }} + name: mpi/${{ matrix.TARGET }}/py${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: max-parallel: 1 From f45c9216336f908035b7e77dbfe6fccc2072721f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Tue, 3 Mar 2020 16:09:22 -0700 Subject: [PATCH 37/38] Updating checkout to @v2 to resolve rebuild issues --- .github/workflows/mpi_matrix_test.yml | 2 +- .github/workflows/push_branch_test.yml | 2 +- .github/workflows/unix_python_matrix_test.yml | 2 +- .github/workflows/win_python_matrix_test.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mpi_matrix_test.yml b/.github/workflows/mpi_matrix_test.yml index 5f7fb9f0660..4451b357dea 100644 --- a/.github/workflows/mpi_matrix_test.yml +++ b/.github/workflows/mpi_matrix_test.yml @@ -18,7 +18,7 @@ jobs: - os: ubuntu-latest TARGET: linux steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: setup conda uses: s-weigand/setup-conda@v1 with: diff --git a/.github/workflows/push_branch_test.yml b/.github/workflows/push_branch_test.yml index 93e9610e638..317c1c178b5 100644 --- a/.github/workflows/push_branch_test.yml +++ b/.github/workflows/push_branch_test.yml @@ -16,7 +16,7 @@ jobs: python-version: [3.7] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: diff --git a/.github/workflows/unix_python_matrix_test.yml b/.github/workflows/unix_python_matrix_test.yml index d9fb9aae71b..475ab8526d8 100644 --- a/.github/workflows/unix_python_matrix_test.yml +++ b/.github/workflows/unix_python_matrix_test.yml @@ -21,7 +21,7 @@ jobs: python-version: [3.5, 3.6, 3.7, 3.8] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 with: diff --git a/.github/workflows/win_python_matrix_test.yml b/.github/workflows/win_python_matrix_test.yml index 4b4d117ccee..96d7e1d4bee 100644 --- a/.github/workflows/win_python_matrix_test.yml +++ b/.github/workflows/win_python_matrix_test.yml @@ -14,7 +14,7 @@ jobs: matrix: python-version: [2.7, 3.5, 3.6, 3.7, 3.8] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} with Miniconda uses: goanpeca/setup-miniconda@v1 # Using an action created by user goanpeca to set up different Python Miniconda environments with: From 79d920a0e7d08d66fcf8e33db18b905e9677b8b7 Mon Sep 17 00:00:00 2001 From: David L Woodruff Date: Tue, 3 Mar 2020 15:28:25 -0800 Subject: [PATCH 38/38] =?UTF-8?q?Repair=20damage=20from=20a=20bug=20in=20t?= =?UTF-8?q?ree=5Fstructure=20that=20manifests=20itself=20only=E2=80=A6=20(?= =?UTF-8?q?#1321)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Repair damage from a bug in tree_structure that manifests itself only in csvwriter --- pyomo/pysp/plugins/csvsolutionwriter.py | 8 +++++- pyomo/pysp/scenariotree/tree_structure.py | 3 ++- pyomo/pysp/tests/rapper/rapper_tester.py | 31 +++++++++++++++++++++-- pyomo/pysp/util/rapper.py | 7 +---- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/pyomo/pysp/plugins/csvsolutionwriter.py b/pyomo/pysp/plugins/csvsolutionwriter.py index 4ce5bb3e21b..d64763f8fa8 100644 --- a/pyomo/pysp/plugins/csvsolutionwriter.py +++ b/pyomo/pysp/plugins/csvsolutionwriter.py @@ -66,9 +66,15 @@ def write_csv_soln(scenario_tree, output_file_prefix): cost_filename = output_file_prefix + "_StageCostDetail.csv" with open(cost_filename, "w") as f: for stage in scenario_tree.stages: - cost_name, cost_index = stage._cost_variable + # DLW March 2020 to pasting over a bug in handling + # of NetworkX by tree_structure.py + # (stage costs may be None but are OK at the node level) + scost = stage._cost_variable # might be None for tree_node in sorted(stage.nodes, key=lambda x: x.name): + if scost is None: + scost = tree_node._cost_variable + cost_name, cost_index = scost # moved into loop 3/2020 hack for scenario in sorted(tree_node.scenarios, key=lambda x: x.name): stage_cost = scenario._stage_costs[stage.name] diff --git a/pyomo/pysp/scenariotree/tree_structure.py b/pyomo/pysp/scenariotree/tree_structure.py index ccd7cd322ae..a6e05b3126a 100644 --- a/pyomo/pysp/scenariotree/tree_structure.py +++ b/pyomo/pysp/scenariotree/tree_structure.py @@ -1389,7 +1389,8 @@ def _construct_stages(self, new_stage._derived_variable_templates[variable_name].append(match_template) # de-reference is required to access the parameter value - + # TBD March 2020: make it so the stages always know their cost names. + # dlw March 2020: when coming from NetworkX, we don't know these yet!! cost_variable_string = stage_cost_variable_names[stage_name].value if cost_variable_string is not None: if isVariableNameIndexed(cost_variable_string): diff --git a/pyomo/pysp/tests/rapper/rapper_tester.py b/pyomo/pysp/tests/rapper/rapper_tester.py index 4f5fa3f0ea8..c428b0d3f34 100644 --- a/pyomo/pysp/tests/rapper/rapper_tester.py +++ b/pyomo/pysp/tests/rapper/rapper_tester.py @@ -12,10 +12,15 @@ from pyomo.pysp.scenariotree.tree_structure_model import CreateAbstractScenarioTreeModel import pyomo.pysp.plugins.csvsolutionwriter as csvw import pyomo as pyomoroot +try: + import networkx + havenetx = True +except: + havenetx = False __author__ = 'David L. Woodruff ' __date__ = 'August 14, 2017' -__version__ = 1.6 +__version__ = 1.7 solvername = "ipopt" # could use almost any solver solver_available = pyo.SolverFactory(solvername).available(False) @@ -53,7 +58,14 @@ def setUp(self): shutil.copyfile(farmpath + os.sep +"scenariodata" + os.sep + "ScenarioStructure.dat", self.tdir + os.sep + "ScenarioStructure.dat") self.farmer_concrete_tree = \ - abstract_tree.create_instance("ScenarioStructure.dat") + abstract_tree.create_instance("ScenarioStructure.dat") + # added networkx example March 2020 + self.farmer_netx_file = farmpath + os.sep + \ + "concreteNetX" + os.sep + "ReferenceModel.py" + + shutil.copyfile(self.farmer_netx_file, + self.tdir + os.sep + "NetXReferenceModel.py") + def tearDown(self): # from GH: This step is key, as Python keys off the name of the module, not the location. @@ -154,5 +166,20 @@ def test_ph_solve(self): pass assert(nodename == 'RootNode') + @unittest.skipIf(not solver_available or not havenetx, + "solver or NetworkX not available") + def test_NetX_ef_csvwriter(self): + """ solve the ef and report gap""" + import NetXReferenceModel as ref + tree_model = ref.pysp_scenario_tree_model_callback() + stsolver = rapper.StochSolver("NetXReferenceModel.py", + fsfct="pysp_instance_creation_callback", + tree_model=tree_model) + res, gap = stsolver.solve_ef(solvername, tee=True, need_gap=True) + csvw.write_csv_soln(stsolver.scenario_tree, "testcref") + with open("testcref_StageCostDetail.csv", 'r') as f: + line = f.readline() + assert(line.split(",")[0] == "Stage1") + if __name__ == '__main__': unittest.main() diff --git a/pyomo/pysp/util/rapper.py b/pyomo/pysp/util/rapper.py index 7db75ea2d41..27fb2ba5571 100644 --- a/pyomo/pysp/util/rapper.py +++ b/pyomo/pysp/util/rapper.py @@ -132,12 +132,7 @@ def __init__(self, fsfile, tree_maker = getattr(m, treecbname) tree = tree_maker() - if isinstance(tree, Pyo.ConcreteModel): - tree_model = tree - else: - raise RuntimeError("The tree returned by",treecbname, - "must be a ConcreteModel") - + scenario_instance_factory = ScenarioTreeInstanceFactory(scen_function, tree_model) else: