Source code for ormm.mathprog.scheduling

"""
"""

import pyomo.environ as pyo


[docs]def scheduling(prob_class, linear=False, **kwargs): """ Calls factory methods for different scheduling problems. The `prob_class` parameter allows the user to choose from different types of problem classes, which in turn have different model structures. The valid choices are: - :py:obj:`employee`: A simple employee scheduling problem to minimize the number of workers employed to meet period requirements. Currently assumes that a worker works their periods in a row (determined by `ShiftLenth` parameter). - :py:obj:`rental`: A type of scheduling problem where there are different plans (with different durations & costs), and the goal is to minimize the total cost of the plans purchased while meeting the period requirements (covering constraints). - :py:obj`agg_planning`: A planning problem where the decision variables are how much to produce during each period, to minimize production and holding costs while satisfying demand. More details on these model classes can be found in the Notes section here, as well as the corresponding section of the :ref:`math_prog`. Parameters ---------- prob_class : :py:obj:`str`, optional Choice of "employee", "rental", or "agg_planning" to return different scheduling models. linear : :py:obj:`bool`, optional Determines whether decision variables will be Reals (True) or Integer (False). **kwargs Passed into Pyomo Abstract Model's `create_instance` to return Pyomo Concrete Model instead. Raises ------ TypeError Raised if invalid argument value is given for `prob_class`. Notes ----- The employee model minimizes workers hired with covering constraints: .. math:: \\text{Min} \\sum_{p \\in P} X_p \\text{s.t.} \\sum_{p - (L - 1)}^p X_p \\geq R_p \\quad \\forall p \\in P X_p \\geq 0\\text{, int} \\quad \\forall p \\in P The rental model minimizes cost of plans purchased with covering constraints: .. math:: \\text{Min} \\sum_{a \\in A} C_a \\sum_{p \\in P \\, \\mid \\, (p,a) \\in J} X_{(p,a)} \\text{s.t.} \\sum_{j \\in J \\, \\mid \\, f} X_j \\geq R_p \\quad \\forall p \\in P X_j \\geq 0\\text{, int} \\quad \\forall j \\in J The aggregate planning model minimizes production & holding costs while meeting demand over a number of periods: .. math:: \\text{Min} \\sum_{p \\in P} C_pX_p &+ hY_p \\text{s.t.} \\enspace Y_{p-1} + X_p - Y_p &= D_p &\\forall p \\in P Y_p &\\leq m &\\forall p \\in P Y_{\\min(P)-1} &= I_I Y_{\\max(P)} &= I_F X_p, \\, Y_p &\\geq 0\\text{, int} &\\forall p \\in P """ if prob_class.lower() == "rental": return _rental(**kwargs) elif prob_class.lower() == "employee": return _employee(**kwargs) elif prob_class.lower() == "agg_planning": return _aggregate_planning(**kwargs) else: raise TypeError(( "Invalid argument value {prob_class}: " "must be 'rental', 'employee', or 'job shop'.\n"))
def _rental(linear=False, **kwargs): """ Factory method for the Rental scheduling problem. Parameters ---------- linear : :py:obj:`bool`, optional Determines whether decision variables will be Reals (True) or Integer (False). **kwargs : optional if any given, returns pyomo concrete model instead, with these passed into pyomo's `create_instance`. """ def _obj_expression(model): """Objective Expression: Minimizing Number of Workers""" my_expr = 0 for (period, plan) in model.PlanToPeriod: my_expr += model.PlanCosts[plan] * model.NumRent[(period, plan)] return my_expr def _period_reqs_constraint_rule(model, p): """Constraints for having enough workers per period""" num_periods = len(model.Periods) my_sum = 0 sum_terms = [] for (period, plan) in model.PlanToPeriod: # Get index of current period ind = model.Periods.ord(p) # Get effective periods based on PlanLength periods_in_plan = [ model.Periods[( (ind - 1 - pl) % num_periods) + 1] for pl in range(model.PlanLengths[plan])] periods_in_plan = [per for per in periods_in_plan if (per, plan) in model.PlanToPeriod] # Sum up how many rented in effective periods # new_terms makes sure that not adding same term again new_terms = [(p_in_plan, plan) for p_in_plan in periods_in_plan if (p_in_plan, plan) not in sum_terms] my_sum += sum([model.NumRent[term] for term in new_terms]) sum_terms.extend(new_terms) return my_sum >= model.PeriodReqs[p] # Create the abstract model & dual suffix model = pyo.AbstractModel() model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT) # Define sets/params that are always used model.Periods = pyo.Set(ordered=True) model.Plans = pyo.Set() model.PlanToPeriod = pyo.Set(dimen=2) model.PeriodReqs = pyo.Param(model.Periods) model.PlanCosts = pyo.Param(model.Plans) model.PlanLengths = pyo.Param(model.Plans) # Define decision variables model.NumRent = pyo.Var( model.PlanToPeriod, within=pyo.NonNegativeReals if linear else pyo.NonNegativeIntegers) # Define objective & constraints model.OBJ = pyo.Objective(rule=_obj_expression, sense=pyo.minimize) model.PeriodReqsConstraint = pyo.Constraint( model.Periods, rule=_period_reqs_constraint_rule) # Check if returning concrete or abstract model if kwargs: return model.create_instance(**kwargs) else: return model def _employee(linear=False, **kwargs): """ Factory method for the Employee scheduling problem. Parameters ---------- linear : :py:obj:`bool`, optional Determines whether decision variables will be Reals (True) or Integer (False). **kwargs : optional if any given, returns pyomo concrete model instead, with these passed into pyomo's `create_instance`. Notes ----- Simple model: minimize # of workers employed to meet shift requirements """ def _obj_expression(model): """Objective Expression: Minimizing Number of Workers""" return pyo.summation(model.NumWorkers) def _period_reqs_constraint_rule(model, p): """Constraints for having enough workers per period""" # Get index of current period ind = model.Periods.ord(p) num_periods = len(model.Periods) # Get effective periods based on ShiftLength - loops back effective_periods = [ model.Periods[((ind - 1 - shift) % num_periods) + 1] for shift in range(model.ShiftLength.value)] # Sum up how many workers are working this period my_sum = sum([model.NumWorkers[period] for period in effective_periods]) return my_sum >= model.PeriodReqs[p] # Create the abstract model & dual suffix model = pyo.AbstractModel() model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT) # Define sets/params that are always used model.Periods = pyo.Set(ordered=True) model.ShiftLength = pyo.Param() # num periods a worker works in a row model.PeriodReqs = pyo.Param(model.Periods) # Define decision variables model.NumWorkers = pyo.Var( model.Periods, within=pyo.NonNegativeReals if linear else pyo.NonNegativeIntegers) # Define objective & constraints model.OBJ = pyo.Objective(rule=_obj_expression, sense=pyo.minimize) model.PeriodReqsConstraint = pyo.Constraint( model.Periods, rule=_period_reqs_constraint_rule) # Check if returning concrete or abstract model if kwargs: return model.create_instance(**kwargs) else: return model def _aggregate_planning(linear=False, **kwargs): """ Factory method returning Pyomo Abstract/Concrete Model for the Aggregate Planning Problem Parameters ---------- **kwargs Passed into Pyomo Abstract Model's `create_instance` to return Pyomo Concrete Model instead. Notes ----- """ def _obj_expression(model): """Objective Expression: """ return (pyo.summation(model.Cost, model.Produce) + model.HoldingCost * pyo.summation(model.InvLevel)) def _conserve_flow_constraint_rule(model, p): """Constraints for """ ind = model.Periods.ord(p) if ind == 1: return (model.InitialInv + model.Produce[p] - model.InvLevel[p] == model.Demand[p]) else: last_p = model.Periods[ind - 1] return (model.InvLevel[last_p] + model.Produce[p] - model.InvLevel[p] == model.Demand[p]) def _max_storage_constraint_rule(model, p): return (0, model.InvLevel[p], model.MaxStorage) def _final_inv_constraint_rule(model): last_period = model.Periods[-1] return model.InvLevel[last_period] == model.FinalInv # Create the abstract model & dual suffix model = pyo.AbstractModel() model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT) # Define sets/params that are always used model.Periods = pyo.Set(ordered=True) model.Cost = pyo.Param(model.Periods) model.Demand = pyo.Param(model.Periods) model.HoldingCost = pyo.Param() model.MaxStorage = pyo.Param(within=pyo.Any, default=None) model.InitialInv = pyo.Param(default=0) model.FinalInv = pyo.Param(default=0) # Define decision variables model.Produce = pyo.Var( model.Periods, within=pyo.NonNegativeReals if linear else pyo.NonNegativeIntegers) model.InvLevel = pyo.Var( model.Periods, within=pyo.NonNegativeReals if linear else pyo.NonNegativeIntegers) # Define objective & constraints model.OBJ = pyo.Objective(rule=_obj_expression, sense=pyo.minimize) model.ConserveFlowConstraint = pyo.Constraint( model.Periods, rule=_conserve_flow_constraint_rule) model.MaxStorageConstraint = pyo.Constraint( model.Periods, rule=_max_storage_constraint_rule) model.FinalInvConstraint = pyo.Constraint( rule=_final_inv_constraint_rule) # Check if returning concrete or abstract model if kwargs: return model.create_instance(**kwargs) else: return model