def solve(dat): assert input_schema.good_tic_dat_object(dat) mdl = gu.Model("diet", env=gurobi_env()) nutrition = {c:mdl.addVar(lb=n["Min Nutrition"], ub=n["Max Nutrition"], name=c) for c,n in dat.categories.items()} # Create decision variables for the foods to buy buy = {f:mdl.addVar(name=f) for f in dat.foods} # Nutrition constraints for c in dat.categories: mdl.addConstr(gu.quicksum(dat.nutrition_quantities[f,c]["Quantity"] * buy[f] for f in dat.foods) == nutrition[c], name = c) mdl.setObjective(gu.quicksum(buy[f] * c["Cost"] for f,c in dat.foods.items()), sense=gu.GRB.MINIMIZE) mdl.optimize() if mdl.status == gu.GRB.OPTIMAL: sln = solution_schema.TicDat() for f,x in buy.items(): if x.x > 0: sln.buy_food[f] = x.x for c,x in nutrition.items(): sln.consume_nutrition[c] = x.x sln.parameters['Total Cost'] = sum(dat.foods[f]["Cost"] * r["Quantity"] for f,r in sln.buy_food.items()) return sln
def solve(dat): assert input_schema.good_tic_dat_object(dat) assert not input_schema.find_foreign_key_failures(dat) assert not input_schema.find_data_type_failures(dat) assert not input_schema.find_data_row_failures(dat) if len(dat.roster) < len(dat.positions): print( "\n****\nModel is infeasible due to shortage of players!\n****\n") mdl = gu.Model("simpler little_league", env=gurobi_env()) lineup = {(i, p, pl): mdl.addVar(vtype=gu.GRB.BINARY, name="lineup_%s_%s_%s" % (i, p, pl)) for i, p, pl in product(dat.innings, dat.positions, dat.roster)} l_slicer = Slicer(lineup) # 1 player assigned to each position per inning for i, p in product(dat.innings, dat.positions): mdl.addConstr(gu.quicksum(lineup[k] for k in l_slicer.slice(i, p, '*')) == 1, name="all_positions_filled_per_inning_%s_%s" % (i, p)) # each player can play at most one position per inning for i, pl in product(dat.innings, dat.roster): mdl.addConstr(gu.quicksum(lineup[k] for k in l_slicer.slice(i, '*', pl)) <= 1, name="at_most_one_position_per_inning_%s_%s" % (i, pl)) grade_slice = Slicer([(pl, r["Grade"]) for pl, r in dat.roster.items()]) position_slice = Slicer([(p, r["Position Group"]) for p, r in dat.positions.items()]) innings_slice = Slicer([(i, r["Inning Group"]) for i, r in dat.innings.items()]) # Position_Constraints satisfied for (pg, ig, g), r in dat.position_constraints.items(): total_players = gu.quicksum( lineup[i[0], p[0], pl[0]] for i, p, pl in product( innings_slice.slice('*', ig), position_slice.slice('*', pg), grade_slice.slice('*', g))) mdl.addConstr(total_players >= r["Min Players"], name="min_players_%s_%s_%s" % (pg, ig, g)) mdl.addConstr(total_players <= r["Max Players"], name="max_players_%s_%s_%s" % (pg, ig, g)) mdl.setObjective( gu.quicksum(dat.positions[p]["Position Importance"] * dat.player_ratings[pl, pg]["Rating"] * v for (i, p, pl), v in lineup.items() for pg in [dat.positions[p]["Position Group"]] if (pl, pg) in dat.player_ratings), sense=gu.GRB.MAXIMIZE) mdl.optimize() if mdl.status == gu.GRB.OPTIMAL: sln = solution_schema.TicDat() for (i, p, pl), v in lineup.items(): if abs(v.x - 1) < 0.0001: sln.lineup[i, p] = pl return sln
def solve(dat): assert input_schema.good_tic_dat_object(dat) assert not input_schema.find_foreign_key_failures(dat) assert not input_schema.find_data_type_failures(dat) normal_price = {pdct:0 for pdct in dat.products} for pdct, price in dat.forecast_sales: normal_price[pdct] = max(normal_price[pdct], price) def revenue(pdct, price): return dat.forecast_sales[pdct, price]["Sales"] * price def investment(pdct, price): return max(0, dat.forecast_sales[pdct, price]["Sales"] * normal_price[pdct] - revenue(pdct, normal_price[pdct])) mdl = gu.Model("soda promotion", env=gurobi_env()) pdct_price = mdl.addVars(dat.forecast_sales, vtype=gu.GRB.BINARY,name='pdct_price') mdl.addConstrs((pdct_price.sum(pdct,'*') == 1 for pdct in dat.products), name = "pick_one_price") total_qty = mdl.addVar(name="total_qty") total_revenue = mdl.addVar(name="total_revenue") total_investment = mdl.addVar(name="total_investment", ub=dat.parameters["Maximum Total Investment"]["Value"] if "Maximum Total Investment" in dat.parameters else float("inf")) mdl.addConstr(total_qty == pdct_price.prod({_:dat.forecast_sales[_]["Sales"] for _ in pdct_price})) mdl.addConstr(total_revenue == pdct_price.prod({_:revenue(*_) for _ in pdct_price})) mdl.addConstr(total_investment == pdct_price.prod({_:investment(*_) for _ in pdct_price})) pf_slice = Slicer((dat.products[pdct]["Family"], pdct, price) for pdct, price in pdct_price) for pdct_family, r in dat.max_promotions.items(): mdl.addConstr(gu.quicksum(pdct_price[_pdct, _price] for _pdct_family, _pdct, _price in pf_slice.slice(pdct_family, '*', '*') if _price != normal_price[_pdct]) <= r["Max Promotions"], name = "max_promotions_%s"%pdct_family) mdl.setObjective(total_qty, sense=gu.GRB.MAXIMIZE) mdl.optimize() if mdl.status == gu.GRB.OPTIMAL: sln = solution_schema.TicDat() for (pdct, price), var in pdct_price.items(): if abs(var.X -1) < 0.0001: # i.e. almost one sln.product_pricing[pdct] = [price, dat.products[pdct]["Family"], "Normal Price" if price == normal_price[pdct] else "Discounted"] sln.parameters["Total Quantity Sold"] = total_qty.X sln.parameters["Total Revenue"] = total_revenue.X sln.parameters["Total Investment"] = total_investment.X number_meaningful_discounts = 0 for (pdct, price), r in dat.forecast_sales.items(): if (price < normal_price[pdct] and r["Sales"] > dat.forecast_sales[pdct,normal_price[pdct]]["Sales"]): number_meaningful_discounts += 1 sln.parameters["Number of Meaningful Discounts"] = number_meaningful_discounts return sln
def solve(dat): """ core solving routine :param dat: a good ticdat for the input_schema :return: a good ticdat for the solution_schema, or None """ assert input_schema.good_tic_dat_object(dat) assert not input_schema.find_foreign_key_failures(dat) assert not input_schema.find_data_type_failures(dat) mdl = gu.Model("netflow", env=gurobi_env()) flow = {(h, i, j): mdl.addVar(name='flow_%s_%s_%s' % (h, i, j)) for h, i, j in dat.cost if (i, j) in dat.arcs} flowslice = Slicer(flow) # Arc Capacity constraints for i, j in dat.arcs: mdl.addConstr( gu.quicksum(flow[_h, _i, _j] * dat.commodities[_h]["Volume"] for _h, _i, _j in flowslice.slice('*', i, j)) <= dat.arcs[i, j]["Capacity"], name='cap_%s_%s' % (i, j)) # Flow conservation constraints. Constraints are generated only for relevant pairs. # So we generate a conservation of flow constraint if there is negative or positive inflow # quantity, or at least one inbound flow variable, or at least one outbound flow variable. for h,j in set((h,i) for (h,i), v in dat.inflow.items() if abs(v["Quantity"]) > 0)\ .union({(h,i) for h,i,j in flow}, {(h,j) for h,i,j in flow}): mdl.addConstr(gu.quicksum(flow[h_i_j] for h_i_j in flowslice.slice(h, '*', j)) + dat.inflow.get( (h, j), {"Quantity": 0})["Quantity"] == gu.quicksum( flow[h_j_i] for h_j_i in flowslice.slice(h, j, '*')), name='node_%s_%s' % (h, j)) mdl.setObjective(gu.quicksum(flow * dat.cost[h, i, j]["Cost"] for (h, i, j), flow in flow.items()), sense=gu.GRB.MINIMIZE) mdl.optimize() if mdl.status == gu.GRB.OPTIMAL: rtn = solution_schema.TicDat() for (h, i, j), var in flow.items(): if var.x > 0: rtn.flow[h, i, j] = var.x rtn.parameters["Total Cost"] = sum( dat.cost[h, i, j]["Cost"] * r["Quantity"] for (h, i, j), r in rtn.flow.items()) return rtn
def solve(dat): """ core solving routine :param dat: a good ticdat for the input_schema :return: a good ticdat for the solution_schema, or None """ assert input_schema.good_tic_dat_object(dat) assert not input_schema.find_foreign_key_failures(dat) assert not input_schema.find_data_type_failures(dat) m = gu.Model("assignment", env=gurobi_env()) # Assignment variables: x[w,s] == 1 if worker w is assigned to shift s. x = m.addVars(dat.availability, vtype=gu.GRB.BINARY, name="x") # Slack variables for each shift constraint slacks = m.addVars(dat.shifts, name="Slack") totSlack = m.addVar(name="totSlack") # Variables to count the total shifts worked by each worker totShifts = m.addVars(dat.workers, name="TotShifts") # Constraint: assign exactly shiftRequirements[s] workers to each shift s, # plus the slack m.addConstrs((slacks[s] + x.sum('*', s) == dat.shifts[s]["Requirement"] for s in dat.shifts), "_") m.addConstr(totSlack == slacks.sum(), "totSlack") m.addConstrs((totShifts[w] == x.sum(w) for w in dat.workers), "totShifts") # Objective: minimize the total slack m.setObjective(totSlack) def _solve(): m.optimize() if m.status in [gu.GRB.Status.INF_OR_UNBD, gu.GRB.Status.INFEASIBLE, gu.GRB.Status.UNBOUNDED]: print('The model cannot be solved because it is infeasible or unbounded') elif m.status != gu.GRB.Status.OPTIMAL: print('Optimization was stopped with status %d' % m.status) else: return True if _solve(): # Constrain the slack by setting its upper and lower bounds totSlack.ub = totSlack.lb = totSlack.x totalPayments = m.addVar(name="totalPayments") m.addConstr(totalPayments == gu.quicksum(dat.workers[w]["Payment"]*x[w,s] for w,s in dat.availability)) m.setObjective(totalPayments) if _solve(): totalPayments.ub = totalPayments.lb = totalPayments.x # Variable to count the average number of shifts worked avgShifts = m.addVar(name="avgShifts") # Variables to count the difference from average for each worker; # note that these variables can take negative values. diffShifts = m.addVars(dat.workers, lb=-gu.GRB.INFINITY, name="Diff") # Constraint: compute the average number of shifts worked m.addConstr(len(dat.workers) * avgShifts == totShifts.sum(), "avgShifts") # Constraint: compute the difference from the average number of shifts m.addConstrs((diffShifts[w] == totShifts[w] - avgShifts for w in dat.workers), "Diff") # Objective: minimize the sum of the square of the difference from the # average number of shifts worked m.setObjective(gu.quicksum(diffShifts[w]*diffShifts[w] for w in dat.workers)) if _solve(): sln = solution_schema.TicDat() for (w,s),x_var in x.items(): if abs(x_var.x - 1) < 1e-5: sln.assignments[w,s] = {} for s,x_var in slacks.items(): if x_var.x > 0: sln.slacks[s] = x_var.x for w,x_var in totShifts.items(): if x_var.x > 0: sln.total_shifts[w] = x_var.x sln.parameters["Total Slack"] = totSlack.x sln.parameters["Total Payments"] = totalPayments.x sln.parameters["Variance of Total Shifts"] = float(m.objVal) / len(dat.workers) return sln
def solve(dat): assert input_schema.good_tic_dat_object(dat) assert not input_schema.find_foreign_key_failures(dat) assert not input_schema.find_data_type_failures(dat) assert not input_schema.find_data_row_failures(dat) expected_draft_position = {} # for our purposes, its fine to assume all those drafted by someone else are drafted # prior to any players drafted by me for player_name in sorted( dat.players, key=lambda _p: { "Un-drafted": dat.players[_p]["Average Draft Position"], "Drafted By Me": -1, "Drafted By Someone Else": -2 }[dat.players[_p]["Draft Status"]]): expected_draft_position[player_name] = len(expected_draft_position) + 1 assert max(expected_draft_position.values()) == len( set(expected_draft_position.values())) == len(dat.players) assert min(expected_draft_position.values()) == 1 already_drafted_by_me = { player_name for player_name, row in dat.players.items() if row["Draft Status"] == "Drafted By Me" } can_be_drafted_by_me = { player_name for player_name, row in dat.players.items() if row["Draft Status"] != "Drafted By Someone Else" } m = gu.Model('fantop', env=gurobi_env()) my_starters = { player_name: m.addVar(vtype=gu.GRB.BINARY, name="starter_%s" % player_name) for player_name in can_be_drafted_by_me } my_reserves = { player_name: m.addVar(vtype=gu.GRB.BINARY, name="reserve_%s" % player_name) for player_name in can_be_drafted_by_me } for player_name in can_be_drafted_by_me: if player_name in already_drafted_by_me: m.addConstr(my_starters[player_name] + my_reserves[player_name] == 1, name="already_drafted_%s" % player_name) else: m.addConstr( my_starters[player_name] + my_reserves[player_name] <= 1, name="cant_draft_twice_%s" % player_name) for i, draft_position in enumerate(sorted(dat.my_draft_positions)): m.addConstr(gu.quicksum( my_starters[player_name] + my_reserves[player_name] for player_name in can_be_drafted_by_me if expected_draft_position[player_name] < draft_position) <= i, name="at_most_%s_can_be_ahead_of_%s" % (i, draft_position)) my_draft_size = gu.quicksum(my_starters[player_name] + my_reserves[player_name] for player_name in can_be_drafted_by_me) m.addConstr(my_draft_size >= len(already_drafted_by_me) + 1, name="need_to_extend_by_at_least_one") m.addConstr(my_draft_size <= len(dat.my_draft_positions), name="cant_exceed_draft_total") for position, row in dat.roster_requirements.items(): players = { player_name for player_name in can_be_drafted_by_me if dat.players[player_name]["Position"] == position } starters = gu.quicksum(my_starters[player_name] for player_name in players) reserves = gu.quicksum(my_reserves[player_name] for player_name in players) m.addConstr(starters >= row["Min Num Starters"], name="min_starters_%s" % position) m.addConstr(starters <= row["Max Num Starters"], name="max_starters_%s" % position) m.addConstr(reserves >= row["Min Num Reserve"], name="min_reserve_%s" % position) m.addConstr(reserves <= row["Max Num Reserve"], name="max_reserve_%s" % position) if "Maximum Number of Flex Starters" in dat.parameters: players = { player_name for player_name in can_be_drafted_by_me if dat.roster_requirements[dat.players[player_name]["Position"]] ["Flex Status"] == "Flex Eligible" } m.addConstr(gu.quicksum(my_starters[player_name] for player_name in players) <= dat.parameters["Maximum Number of Flex Starters"]["Value"], name="max_flex") starter_weight = dat.parameters["Starter Weight"][ "Value"] if "Starter Weight" in dat.parameters else 1 reserve_weight = dat.parameters["Reserve Weight"][ "Value"] if "Reserve Weight" in dat.parameters else 1 m.setObjective(gu.quicksum(dat.players[player_name]["Expected Points"] * (my_starters[player_name] * starter_weight + my_reserves[player_name] * reserve_weight) for player_name in can_be_drafted_by_me), sense=gu.GRB.MAXIMIZE) m.optimize() if m.status != gu.GRB.OPTIMAL: print("No draft at all is possible!") return sln = solution_schema.TicDat() def almostone(x): return abs(x.x - 1) < 0.0001 picked = sorted([ player_name for player_name in can_be_drafted_by_me if almostone(my_starters[player_name]) or almostone(my_reserves[player_name]) ], key=lambda _p: expected_draft_position[_p]) assert len(picked) <= len(dat.my_draft_positions) if len(picked) < len(dat.my_draft_positions): print( "Your model is over-constrained, and thus only a partial draft was possible" ) draft_yield = 0 for player_name, draft_position in zip(picked, sorted(dat.my_draft_positions)): draft_yield += dat.players[player_name]["Expected Points"] * \ (starter_weight if almostone(my_starters[player_name]) else reserve_weight) assert draft_position <= expected_draft_position[player_name] sln.my_draft[player_name]["Draft Position"] = draft_position sln.my_draft[player_name]["Position"] = dat.players[player_name][ "Position"] sln.my_draft[player_name][ "Planned Or Actual"] = "Actual" if player_name in already_drafted_by_me else "Planned" sln.my_draft[player_name]["Starter Or Reserve"] = \ "Starter" if almostone(my_starters[player_name]) else "Reserve" sln.parameters["Total Yield"] = draft_yield sln.parameters["Draft Performed"] = "Complete" if len(sln.my_draft) == len(dat.my_draft_positions) \ else "Partial" return sln
def solve(dat, out, err, progress): assert isinstance(progress, Progress) assert isinstance(out, LogFile) and isinstance(err, LogFile) assert input_schema.good_tic_dat_object(dat) assert not input_schema.find_foreign_key_failures(dat) assert not input_schema.find_data_type_failures(dat) out.write("COG output log\n%s\n\n" % time_stamp()) err.write("COG error log\n%s\n\n" % time_stamp()) def get_distance(x, y): if (x, y) in dat.distance: return dat.distance[x, y]["Distance"] if (y, x) in dat.distance: return dat.distance[y, x]["Distance"] return float("inf") def can_assign(x, y): return dat.sites[y]["Center Status"] == "Can Be Center" \ and get_distance(x,y)<float("inf") unassignables = [ n for n in dat.sites if not any(can_assign(n, y) for y in dat.sites) and dat.sites[n]["Demand"] > 0 ] if unassignables: # Infeasibility detected. Generate an error table and return None err.write("The following sites have demand, but can't be " + "assigned to anything.\n") err.log_table("Un-assignable Demand Points", [["Site"]] + [[_] for _ in unassignables]) return useless = [ n for n in dat.sites if not any(can_assign(y, n) for y in dat.sites) and dat.sites[n]["Demand"] == 0 ] if useless: # Log in the error table as a warning, but can still try optimization. err.write( "The following sites have no demand, and can't serve as the " + "center point for any assignments.\n") err.log_table("Useless Sites", [["Site"]] + [[_] for _ in useless]) progress.numerical_progress("Feasibility Analysis", 100) m = gu.Model("cog", env=gurobi_env()) assign_vars = { (n, assigned_to): m.addVar(vtype=gu.GRB.BINARY, name="%s_%s" % (n, assigned_to), obj=get_distance(n, assigned_to) * dat.sites[n]["Demand"]) for n in dat.sites for assigned_to in dat.sites if can_assign(n, assigned_to) } open_vars = { n: m.addVar(vtype=gu.GRB.BINARY, name="open_%s" % n) for n in dat.sites if dat.sites[n]["Center Status"] == "Can Be Center" } if not open_vars: err.write("Nothing can be a center!\n") # Infeasibility detected. return m.update() progress.numerical_progress("Core Model Creation", 50) # using ticdat.Slicer instead of tuplelist simply as a matter of taste/vanity assign_slicer = Slicer(assign_vars) for n, r in dat.sites.items(): if r["Demand"] > 0: m.addConstr(gu.quicksum( assign_vars[n, assign_to] for _, assign_to in assign_slicer.slice(n, "*")) == 1, name="must_assign_%s" % n) crippledfordemo = "Formulation" in dat.parameters and \ dat.parameters["Formulation"]["Value"] == "Weak" for assigned_to, r in dat.sites.items(): if r["Center Status"] == "Can Be Center": _assign_vars = [ assign_vars[n, assigned_to] for n, _ in assign_slicer.slice("*", assigned_to) ] if crippledfordemo: m.addConstr(gu.quicksum(_assign_vars) <= len(_assign_vars) * open_vars[assigned_to], name="weak_force_open%s" % assigned_to) else: for var in _assign_vars: m.addConstr(var <= open_vars[assigned_to], name="strong_force_open_%s" % assigned_to) number_of_centroids = dat.parameters["Number of Centroids"]["Value"] \ if "Number of Centroids" in dat.parameters else 1 if number_of_centroids <= 0: err.write("Need to specify a positive number of centroids\n" ) # Infeasibility detected. return m.addConstr(gu.quicksum( v for v in open_vars.values()) == number_of_centroids, name="numCentroids") if "MIP Gap" in dat.parameters: m.Params.MIPGap = dat.parameters["MIP Gap"]["Value"] m.update() progress.numerical_progress("Core Model Creation", 100) m.optimize(progress.gurobi_call_back_factory("COG Optimization", m)) progress.numerical_progress("Core Optimization", 100) if not hasattr(m, "status"): print "missing status - likely premature termination" return for failStr, grbkey in (("inf_or_unbd", gu.GRB.INF_OR_UNBD), ("infeasible", gu.GRB.INFEASIBLE), ("unbounded", gu.GRB.UNBOUNDED)): if m.status == grbkey: print "Optimization failed due to model status of %s" % failStr return if m.status == gu.GRB.INTERRUPTED: err.write("Solve process interrupted by user feedback\n") if not all(hasattr(var, "x") for var in open_vars.values()): err.write("No solution was found\n") return elif m.status != gu.GRB.OPTIMAL: err.write("unexpected status %s\n" % m.status) return sln = solution_schema.TicDat() sln.parameters["Lower Bound"] = getattr(m, "objBound", m.objVal) sln.parameters["Upper Bound"] = m.objVal out.write('Upper Bound: %g\n' % sln.parameters["Upper Bound"]["Value"]) out.write('Lower Bound: %g\n' % sln.parameters["Lower Bound"]["Value"]) def almostone(x): return abs(x - 1) < 0.0001 for (n, assigned_to), var in assign_vars.items(): if almostone(var.x): sln.assignments[n, assigned_to] = {} for n, var in open_vars.items(): if almostone(var.x): sln.openings[n] = {} out.write('Number Centroids: %s\n' % len(sln.openings)) progress.numerical_progress("Full Cog Solve", 100) return sln
def solve(dat): assert input_schema.good_tic_dat_object(dat) assert not input_schema.find_foreign_key_failures(dat) assert not input_schema.find_data_type_failures(dat) assert not input_schema.find_data_row_failures(dat) # use default parameters, unless they are overridden by user-supplied parameters full_parameters = dict( default_parameters, **{k: v["Value"] for k, v in dat.parameters.items()}) bench = full_parameters["Bench Roster Label"] verify(bench in dat.positions, "%s needs to be one of the positions" % bench) verify( len(dat.roster) >= len(dat.positions) - 1, "Model is infeasible due to shortage of players!") mdl = gu.Model("little_league", env=gurobi_env()) lineup = {(i, p, pl): mdl.addVar(vtype=gu.GRB.BINARY, name="lineup_%s_%s_%s" % (i, p, pl)) for i, p, pl in product(dat.innings, dat.positions, dat.roster)} l_slicer = Slicer(lineup) # 1 player assigned to each active position per inning for i, p in product(dat.innings, dat.positions): if p != bench: mdl.addConstr( gu.quicksum(lineup[k] for k in l_slicer.slice(i, p, '*')) == 1, name="all_positions_filled_per_inning_%s_%s" % (i, p)) # each player must be assigned to one position (including the bench) per inning for i, pl in product(dat.innings, dat.roster): mdl.addConstr(gu.quicksum(lineup[k] for k in l_slicer.slice(i, '*', pl)) == 1, name="at_most_one_position_per_inning_%s_%s" % (i, pl)) grade_slice = Slicer([(pl, r["Grade"]) for pl, r in dat.roster.items()]) position_slice = Slicer([(p, r["Position Group"]) for p, r in dat.positions.items()]) innings_slice = Slicer([(i, r["Inning Group"]) for i, r in dat.innings.items()]) # Position_Constraints satisfied for (pg, ig, g), r in dat.position_constraints.items(): total_players = gu.quicksum( lineup[i[0], p[0], pl[0]] for i, p, pl in product( innings_slice.slice('*', ig), position_slice.slice('*', pg), grade_slice.slice('*', g))) mdl.addConstr(total_players >= r["Min Players"], name="min_players_%s_%s_%s" % (pg, ig, g)) mdl.addConstr(total_players <= r["Max Players"], name="max_players_%s_%s_%s" % (pg, ig, g)) # Enforce consecutive innings constraints sorted_innings = list(sorted(dat.innings)) for p, r in dat.positions.items(): if r["Consecutive Innings Only"] == "True": for pl in dat.roster: for pos1, pos2 in combinations(range(len(sorted_innings)), 2): if pos2 < pos1: pos1, pos2 = pos2, pos1 if pos2 - pos1 > 1: pass # need Derek's determination of what it means # Balanced Playing time = a min playing time for each player if full_parameters["Balanced Playing Time"] == "True": for pl, r in dat.roster.items(): mdl.addConstr(gu.quicksum( lineup[k] for k in l_slicer.slice('*', '*', pl)) >= floor( (r["Departure Inning"] - r["Arrival Inning"] + 1) / float(len(dat.positions) * len(dat.innings))), name="balanced_pt_%s" % pl) if full_parameters["Limit Outfield Play"] == "True": of = full_parameters["Outfield Group Label"] for pl in dat.roster: mdl.addConstr( gu.quicksum(lineup[i, p[0], pl] for p in position_slice.slice('*', of) for i in dat.innings) <= gu.quicksum(lineup[i, p, pl] for i, p in product(dat.innings, dat.positions)) * 0.5 + 0.5, name="limit_OF_play_%s" % pl) max_bench = full_parameters["Max Consecutive Bench"] for pos, i in enumerate(sorted_innings): if max_bench + 1 + pos <= len(sorted_innings): for pl in dat.roster: mdl.addConstr(gu.quicksum( lineup[k] for i_ in sorted_innings[pos:pos + max_bench + 1] for k in l_slicer.slice(i_, '*', pl)) >= 1, name="max_consecutive_bench_%s_%s" % (i, pl)) mdl.setObjective( gu.quicksum(dat.positions[p]["Position Importance"] * dat.player_ratings[pl, pg]["Rating"] * v for (i, p, pl), v in lineup.items() for pg in [dat.positions[p]["Position Group"]] if (pl, pg) in dat.player_ratings), sense=gu.GRB.MAXIMIZE) mdl.optimize() if mdl.status == gu.GRB.OPTIMAL: sln = solution_schema.TicDat() for (i, p, pl), v in lineup.items(): if abs(v.x - 1) < 0.0001: sln.lineup[i, p] = pl return sln