示例#1
0
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
示例#2
0
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
示例#4
0
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
示例#5
0
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
示例#6
0
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
示例#7
0
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
示例#8
0
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