CP-SAT中的性能问题

时间:2019-09-17 14:23:09

标签: python or-tools

亲爱的,我在下面的代码上遇到性能问题。我知道预测435个变量具有挑战性,但是如果我发现以下用于运行大量变量的模型的方法,对我来说将是非常棒的,否则,如果您可以提出其他替代方法,我将不胜感激。这里的代码:

from __future__ import division
from __future__ import print_function

from ortools.sat.python import cp_model
from ortools.sat.python.cp_model import IntVar
import time
import datetime


def cp_sat_program():

    # We have 2 products Gold, Silver each of them has a profit and a bad debt value.
    # We have a dataset with a list of customers each one with a PD value.
    # We want to maximize the profit of the portfolio of customers keeping the expected loss under a threshold
    # and the number of reject less than 55%

    time_start = time.time()
    date_start = datetime.datetime.fromtimestamp(time_start).strftime('%Y-%m-%d %H:%M:%S')

    # Creates the constant variables.
    scaling = 1000  # i'm scaling since the algorithm works with integer only
    profit_gold = int(0.09 * scaling)
    profit_silver = int(0.023 * scaling)
    profit_reject = int(0 * scaling)
    customers_pd = [0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24,
                    25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94,
                    0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24, 0.01,
                    25.26, 0.01, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94,
                    0.01, 0.24, 25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01,
                    25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94,
                    0.24, 0.01,0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24,
                    25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94,
                    0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24
                   ]

    num_customers = len(customers_pd)
    all_customers_ids = range(num_customers)

    customers_pd_scaled = {}
    # I'm scaling the PD of the customers because the system works with integers
    for i in all_customers_ids:
        customers_pd_scaled[i] = int(customers_pd[i] * scaling)
    # Bad Debt for each product:
    bad_debt_gold = int(profit_gold*0.63*scaling)
    bad_debt_silver = int(profit_silver*0.62*scaling)
    bad_debt_reject = int(0 * scaling)

    # Creates the model.
    model = cp_model.CpModel()
    # Creates the model variables
    suggested_products = {}
    for p in all_customers_ids:
        suggested_products[p] = [model.NewBoolVar('c' + str(p) + 'Gold'), model.NewBoolVar('c' + str(p) + 'Silver'),
                                 model.NewBoolVar('c' + str(p) + 'Reject')]

    scaled_r = model.NewIntVar(0, scaling * 1000, 'rejects')
    denom = model.NewIntVar(1, 3 * 1000, 'total Requests')
    division = model.NewIntVar(0, scaling * 1000, 'reject/total Requests')
    num_products = range(len(suggested_products[0]))

    # Creates the constraints.
    for i in all_customers_ids:
        model.Add(sum(suggested_products[i][b] for b in num_products) == 1)
    num_rejects = sum(suggested_products[i][2] for i in all_customers_ids)
    model.Add(scaled_r == (num_rejects * scaling))  # i'm scaling since the algorithm works with integer only
    model.Add(denom == num_customers)
    model.AddDivisionEquality(division, scaled_r, denom)  # calculate the division
    model.Add(division <= int(0.55 * scaling))  # percentage of rejects less than 55%

    # expected loss less or equal than 250.000:
    model.Add(sum(suggested_products[i][0] * customers_pd_scaled[i] * bad_debt_gold for i in all_customers_ids) +
              sum(suggested_products[i][1] * customers_pd_scaled[i] * bad_debt_silver for i in all_customers_ids) +
              sum(suggested_products[i][2] * customers_pd_scaled[i] * bad_debt_reject for i in all_customers_ids)
              <= int(250000 * scaling))

    # goal
    model.Maximize(sum(suggested_products[i][0] * profit_gold for i in all_customers_ids) +
                   sum(suggested_products[i][1] * profit_silver for i in all_customers_ids) +
                   sum(suggested_products[i][2] * profit_reject for i in all_customers_ids)
                   )
    # Creates a solver and solves the model.
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8
    status = solver.Solve(model)
    goal = solver.ObjectiveValue()
    if status != cp_model.INFEASIBLE:
        for i in all_customers_ids:
            print('CUSTOMER NUMBER ' + str(i) + ': Gold =' + str(solver.Value(suggested_products[i][0]))
                  + '  Silver =' + str(solver.Value(suggested_products[i][1]))
                  + '  Reject =' + str(solver.Value(suggested_products[i][2])))

        print('Goal = %i' % goal)
        print("status = %s" % solver.StatusName(status))
    else:
        print("status = %s" % solver.StatusName(status))


    time_end = time.time()
    date_end = datetime.datetime.fromtimestamp(time_end).strftime('%Y-%m-%d %H:%M:%S')
    print('Date Start: ' + date_start)
    print('Date End: ' + date_end)

cp_sat_program()

如果以下约束的极限值为330000,则非常快;如果极限为250000,则一小时后将永远不会响应:

model.Add(sum(suggested_products[i][0] * customers_pd_scaled[i] * bad_debt_gold for i in all_customers_ids) +
          sum(suggested_products[i][1] * customers_pd_scaled[i] * bad_debt_silver for i in all_customers_ids) +
          sum(suggested_products[i][2] * customers_pd_scaled[i] * bad_debt_reject for i in all_customers_ids)
          <= int(330000 * scaling))

2 个答案:

答案 0 :(得分:1)

我相信您可以制定出接近最佳的解决方案。

第一个获利金>>获利银,而bad_debt_silver〜= bad_debt_gold。因此,您可以假设您只会分配黄金。

因此,现在的问题是分配最大目标数,同时保持在250000以下的限制。为此,请按customer_pd对客户进行排序,然后依次选择1比1,直到达到限制为止。

答案 1 :(得分:0)

如果您真的想使用求解器进行求解,这里是一个清理版本

from __future__ import division
from __future__ import print_function

from ortools.sat.python import cp_model
from ortools.sat.python.cp_model import IntVar
import time
import datetime


def cp_sat_program():

    # We have 2 products Gold, Silver each of them has a profit and a bad debt value.
    # We have a dataset with a list of customers each one with a PD value.
    # We want to maximize the profit of the portfolio of customers keeping the expected loss under a threshold
    # and the number of reject less than 55%

    time_start = time.time()
    date_start = datetime.datetime.fromtimestamp(time_start).strftime('%Y-%m-%d %H:%M:%S')

    # Creates the constant variables.
    scaling = 1000  # i'm scaling since the algorithm works with integer only

    profit_gold = int(0.09 * scaling)
    profit_silver = int(0.023 * scaling)
    profit_reject = int(0 * scaling)

    customers_pd = [0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24,
                    25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94,
                    0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24, 0.01,
                    25.26, 0.01, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94,
                    0.01, 0.24, 25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01,
                    25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94,
                    0.24, 0.01,0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24,
                    25.26, 5.94, 0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94,
                    0.24, 0.01, 25.26, 0.01, 25.26, 5.94, 0.01, 0.24, 0.01, 25.26, 5.94, 0.01, 25.26, 5.94, 0.24
                   ]

    num_customers = len(customers_pd)
    all_customers_ids = range(num_customers)

    customers_pd_scaled = [int(pd * scaling) for pd in customers_pd]

    # Bad Debt for each product:
    bad_debt_gold = int(profit_gold*0.63*scaling)
    bad_debt_silver = int(profit_silver*0.62*scaling)
    bad_debt_reject = int(0 * scaling)

    # Creates the model.
    model = cp_model.CpModel()

    # Creates the model variables
    suggested_products = {}
    for p in all_customers_ids:
        suggested_products[p] = (model.NewBoolVar('c' + str(p) + 'Gold'), model.NewBoolVar('c' + str(p) + 'Silver'),
                                 model.NewBoolVar('c' + str(p) + 'Reject'))

    num_products = range(len(suggested_products[0]))

    # Creates the constraints.
    for i in all_customers_ids:
        model.Add(sum(suggested_products[i][b] for b in num_products) == 1)
        model.Add(suggested_products[i][1] == 0)

    # At most 55% of rejects
    model.Add(sum(suggested_products[i][2] for i in all_customers_ids) <= int(num_customers * 0.55))

    # expected loss less or equal than 250.000:
    model.Add(sum(suggested_products[i][0] * customers_pd_scaled[i] * bad_debt_gold for i in all_customers_ids) +
              sum(suggested_products[i][1] * customers_pd_scaled[i] * bad_debt_silver for i in all_customers_ids)
              <= int(250000 * scaling))

    # goal
    model.Maximize(sum(suggested_products[i][0] * profit_gold for i in all_customers_ids) +
                   sum(suggested_products[i][1] * profit_silver for i in all_customers_ids))
    # Creates a solver and solves the model.
    solver = cp_model.CpSolver()
    solver.parameters.log_search_progress = True

    status = solver.Solve(model)
    goal = solver.ObjectiveValue()
    if status != cp_model.INFEASIBLE:
        for i in all_customers_ids:
            print('CUSTOMER NUMBER ' + str(i) + ': Gold =' + str(solver.Value(suggested_products[i][0]))
                  + '  Silver =' + str(solver.Value(suggested_products[i][1]))
                  + '  Reject =' + str(solver.Value(suggested_products[i][2])))

        print('Goal = %i' % goal)
        print("status = %s" % solver.StatusName(status))
    else:
        print("status = %s" % solver.StatusName(status))


    time_end = time.time()
    date_end = datetime.datetime.fromtimestamp(time_end).strftime('%Y-%m-%d %H:%M:%S')
    print('Date Start: ' + date_start)
    print('Date End: ' + date_end)

cp_sat_program()

它立即返回一个值为6030的赋值。