예제 #1
0
def solve_from_modeling_dat(dat):
    """
    auxiliary solving routine
    :param dat: a good pan_dat for the modeling_schema
    :return: a good pan_dat for the solution_schema, or None
    """
    assert modeling_schema.good_pan_dat_object(dat), "bad dat check"
    assert not modeling_schema.find_duplicates(dat), "duplicate record check"
    assert not modeling_schema.find_foreign_key_failures(
        dat), "foreign key check"
    assert not modeling_schema.find_data_type_failures(
        dat), "data type value check"

    # Create optimization model
    mdl = gp.Model('netflow')

    # itertuples is the most performant way to iterate over the rows of a DataFrame
    flow = {(h, i, j): mdl.addVar(name=f'flow_{h}_{i}_{j}', obj=cost)
            for h, i, j, cost in dat.cost.itertuples(index=False)}

    flowslice = Slicer(flow)
    volume = {
        k: volume
        for k, volume in dat.commodities.itertuples(index=False)
    }

    # Arc Capacity constraints
    for i, j, capacity in dat.arcs.itertuples(index=False):
        mdl.addConstr(gp.quicksum(flow[_h, _i, _j] * volume[_h]
                                  for _h, _i, _j in flowslice.slice('*', i, j))
                      <= capacity,
                      name=f'cap_{i}_{j}')

    inflow = {(h, j): qty
              for h, j, qty in dat.inflow.itertuples(index=False)
              if abs(qty) > 0}
    # 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(inflow).union({(h, i)
                                   for h, i, j in flow},
                                  {(h, j)
                                   for h, i, j in flow}):
        mdl.addConstr(gp.quicksum(flow[h_i_j]
                                  for h_i_j in flowslice.slice(h, '*', j)) +
                      inflow.get((h, j), 0) == gp.quicksum(
                          flow[h_j_i] for h_j_i in flowslice.slice(h, j, '*')),
                      name=f'node_{h}_{j}')

    # Compute optimal solution
    mdl.optimize()

    if mdl.status == gp.GRB.status.OPTIMAL:
        # PanDatFactory also makes it easy to create DataFrame objects from rows of data
        rtn = solution_schema.PanDat(flow=[[h, i, j, var.x]
                                           for (h, i, j), var in flow.items()
                                           if var.x > 0],
                                     parameters=[["Total Cost", mdl.objVal]])
        return rtn
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
예제 #3
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
예제 #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)

    model = pulp.LpProblem(name="netflow", sense=pulp.LpMinimize)

    flow = {(h, i, j): pulp.LpVariable(name=f"flow_{h}_{i}_{j}", cat=pulp.LpContinuous, lowBound=0)
            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:
        model.addConstraint(pulp.LpConstraint(
                            e=pulp.lpSum(flow[_h, _i, _j] * dat.commodities[_h]["Volume"]
                                         for _h, _i, _j in flowslice.slice('*', i, j)),
                            rhs=dat.arcs[i,j]["Capacity"],
                            name=f'cap_{i}_{j}', sense=pulp.LpConstraintLE))

    # 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}):
        model.addConstraint(pulp.LpConstraint(
            e=pulp.lpSum(flow[h_i_j] for h_i_j in flowslice.slice(h,'*',j)) +
              dat.inflow.get((h, j), {"Quantity": 0})["Quantity"] -
              pulp.lpSum(flow[h_j_i] for h_j_i in flowslice.slice(h, j, '*')),
            rhs=0, # pulp isn't quite as nice as gurobipy, the rhs cannot have expressions
            sense=pulp.LpConstraintEQ,
            name=f'node_{h}_{j}'))

    model.setObjective(pulp.lpSum(flow * dat.cost[h, i, j]["Cost"]
                                 for (h, i, j), flow in flow.items()))
    model.solve()

    if pulp.LpStatus[model.status] == 'Optimal':
        rtn = solution_schema.TicDat()
        for (h, i, j), var in flow.items():
            if var.varValue > 0:
                rtn.flow[h, i, j] = var.varValue
        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
파일: netflow.py 프로젝트: nandi6uc/ticdat
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 = Model("netflow")

    flow = {(h, i, j): mdl.continuous_var(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.add_constraint(mdl.sum(flow[h, i, j]
                                   for h, i, j in flowslice.slice('*', i_, j_))
                           <= dat.arcs[i_, j_]["Capacity"],
                           ctname='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(k for k,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.add_constraint(
            mdl.sum(flow[h_, i_, j_]
                    for h_, i_, j_ in flowslice.slice(h, '*', j)) +
            dat.inflow.get((h, j), {"Quantity": 0})["Quantity"] == mdl.sum(
                flow[h_, j_, i_] for h_, j_, i_ in flowslice.slice(h, j, '*')),
            ctname='node_%s_%s' % (h, j))

    mdl.minimize(
        mdl.sum(flow * dat.cost[h, i, j]["Cost"]
                for (h, i, j), flow in flow.items()))

    # Compute optimal solution
    if mdl.solve():
        rtn = solution_schema.TicDat()
        for (h, i, j), var in flow.items():
            if mdl.solution.get_value(var) > 0:
                rtn.flow[h, i, j] = mdl.solution.get_value(var)
        rtn.parameters["Total Cost"] = sum(
            dat.cost[h, i, j]["Cost"] * r["Quantity"]
            for (h, i, j), r in rtn.flow.items())
        return rtn
예제 #6
0
def netflowSolver(modelType):
    tdf = TicDatFactory(**netflowSchema())
    addNetflowForeignKeys(tdf)
    addNetflowDataTypes(tdf)

    dat = tdf.copy_tic_dat(netflowData())
    assert not tdf.find_data_type_failures(
        dat) and not tdf.find_foreign_key_failures(dat)

    mdl = Model(modelType, "netflow")

    flow = {}
    for h, i, j in dat.cost:
        if (i, j) in dat.arcs:
            flow[h, i, j] = mdl.add_var(name='flow_%s_%s_%s' % (h, i, j))

    flowslice = Slicer(flow)

    for i_, j_ in dat.arcs:
        mdl.add_constraint(mdl.sum(flow[h, i, j]
                                   for h, i, j in flowslice.slice('*', i_, j_))
                           <= dat.arcs[i_, j_]["capacity"],
                           name='cap_%s_%s' % (i_, j_))

    for h_, j_ in set(k for k, 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.add_constraint(
            mdl.sum(flow[h, i, j]
                    for h, i, j in flowslice.slice(h_, '*', j_)) +
            dat.inflow.get((h_, j_), {"quantity": 0})["quantity"] == mdl.sum(
                flow[h, i, j] for h, i, j in flowslice.slice(h_, j_, '*')),
            name='node_%s_%s' % (h_, j_))

    mdl.set_objective(
        mdl.sum(flow * dat.cost[h, i, j]["cost"]
                for (h, i, j), flow in flow.items()))
    if mdl.optimize():
        solutionFactory = TicDatFactory(
            flow=[["commodity", "source", "destination"], ["quantity"]])
        if mdl.optimize():
            rtn = solutionFactory.TicDat()
            for (h, i, j), var in flow.items():
                if mdl.get_solution_value(var) > 0:
                    rtn.flow[h, i, j] = mdl.get_solution_value(var)
            return rtn, sum(dat.cost[h, i, j]["cost"] * r["quantity"]
                            for (h, i, j), r in rtn.flow.items())
예제 #7
0
파일: netflow.py 프로젝트: nandi6uc/ticdat
def solve(dat):
    assert input_schema.good_tic_dat_object(dat)

    mdl = gu.Model("netflow")

    flow = {(h, i, j): mdl.addVar(name='flow_%s_%s_%s' % (h, i, j)) for h, i, j in dat.cost}

    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 in dat.commodities:
        for j in dat.nodes:
            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
예제 #8
0
def solve(dat, out, err, progress):
    assert isinstance(progress, Progress)
    assert isinstance(out, LogFile) and isinstance(err, LogFile)
    assert dataFactory.good_tic_dat_object(dat)
    assert not dataFactory.find_foreign_key_failures(dat)
    assert not dataFactory.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 = Model("cog")

    assign_vars = {(n, assigned_to):
                   m.binary_var(name="%s_%s" % (n, assigned_to))
                   for n in dat.sites for assigned_to in dat.sites
                   if can_assign(n, assigned_to)}
    open_vars = {
        n: m.binary_var(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

    progress.numerical_progress("Core Model Creation", 50)

    assign_slicer = Slicer(assign_vars)

    for n, r in dat.sites.items():
        if r["demand"] > 0:
            m.add_constraint(m.sum(
                assign_vars[n, assign_to]
                for _, assign_to in assign_slicer.slice(n, "*")) == 1,
                             ctname="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.add_constraint(m.sum(_assign_vars) <=
                                 len(_assign_vars) * open_vars[assigned_to],
                                 ctname="weak_force_open%s" % assigned_to)
            else:
                for var in _assign_vars:
                    m.add_constraint(var <= open_vars[assigned_to],
                                     ctname="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.add_constraint(m.sum(v
                           for v in open_vars.values()) == number_of_centroids,
                     ctname="numCentroids")

    if "mipGap" in dat.parameters:
        m.parameters.mip.tolerances.mipgap = dat.parameters["mipGap"]["value"]

    progress.numerical_progress("Core Model Creation", 100)

    m.minimize(
        m.sum(var * get_distance(n, assigned_to) * dat.sites[n]["demand"]
              for (n, assigned_to), var in assign_vars.items()))

    progress.add_cplex_listener("COG Optimization", m)

    if m.solve():

        progress.numerical_progress("Core Optimization", 100)
        cplex_soln = m.solution
        sln = solutionFactory.TicDat()
        # see code trick http://ibm.co/2aQwKYG
        if m.solve_details.status == 'optimal':
            sln.parameters["Lower Bound"] = cplex_soln.get_objective_value()
        else:
            sln.parameters["Lower Bound"] = m.solve_details.get_best_bound()
        sln.parameters["Upper Bound"] = cplex_soln.get_objective_value()
        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(cplex_soln.get_value(var)):
                sln.assignments[n, assigned_to] = {}
        for n, var in open_vars.items():
            if almostone(cplex_soln.get_value(var)):
                sln.openings[n] = {}
        out.write('Number Centroids: %s\n' % len(sln.openings))
        progress.numerical_progress("Full Cog Solve", 100)
        return sln
예제 #9
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
예제 #10
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
예제 #11
0
def solve(dat, progress):
    assert isinstance(progress, Progress)
    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)

    full_parameters = input_schema.create_full_parameters_dict(dat)

    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")

    m = Model(model_name="cog", model_type=full_parameters["Core Model Type"])

    assign_vars = {(n, assigned_to): m.add_var(type="binary",
                                               name="%s_%s" % (n, assigned_to))
                   for n in dat.sites for assigned_to in dat.sites
                   if can_assign(n, assigned_to)}
    open_vars = {
        n: m.add_var(type="binary", name="open_%s" % n)
        for n in dat.sites if dat.sites[n]["Center Status"] == "Can Be Center"
    }
    assert open_vars, "nothing can be center"

    assign_slicer = Slicer(assign_vars)

    for n, r in dat.sites.items():
        if r["Demand"] > 0:
            m.add_constraint(m.sum(
                assign_vars[n, assign_to]
                for _, assign_to in assign_slicer.slice(n, "*")) == 1,
                             name="must_assign_%s" % n)

    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)
            ]
            # this is the weak formulation
            m.add_constraint(m.sum(_assign_vars) <=
                             len(_assign_vars) * open_vars[assigned_to],
                             name="weak_force_open%s" % assigned_to)

    number_of_centroids = full_parameters["Number of Centroids"]

    m.add_constraint(m.sum(v
                           for v in open_vars.values()) == number_of_centroids,
                     name="numCentroids")

    m.set_parameters(MIP_Gap=full_parameters["MIP Gap"])

    m.set_objective(m.sum(var * get_distance(n, assigned_to) *
                          dat.sites[n]["Demand"]
                          for (n, assigned_to), var in assign_vars.items()),
                    sense="minimize")
    if full_parameters["Core Model Type"] == "cplex":
        progress.add_cplex_listener("COG Optimization", m.core_model)

    worked = m.optimize(*(
        [progress.gurobi_call_back_factory("COG Optimization", m.core_model)]
        if full_parameters["Core Model Type"] == "gurobi" else []))

    assert worked, "testing model set up only for success"

    sln = solution_schema.TicDat()
    if full_parameters["Core Model Type"] == "gurobi":
        sln.parameters["Lower Bound"] = getattr(m.core_model, "objBound",
                                                m.core_model.objVal)
        sln.parameters["Upper Bound"] = m.core_model.objVal
    else:
        lb = m.core_model.solve_details.best_bound
        sln.parameters["Lower Bound"] = worked.get_objective_value() if isnan(
            lb) else lb
        sln.parameters["Upper Bound"] = worked.get_objective_value()

    def almostone(x):
        return abs(x - 1) < 0.0001

    for (n, assigned_to), var in assign_vars.items():
        if almostone(m.get_solution_value(var)):
            sln.assignments[n, assigned_to] = {}
    for n, var in open_vars.items():
        if almostone(m.get_solution_value(var)):
            sln.openings[n] = {}
    return sln
예제 #12
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