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
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) 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
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
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())
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
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
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) 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, 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
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