0

亲爱的,我在下面的代码中遇到了性能问题。我知道预测 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))
4

2 回答 2

2

我相信你可以制定一个接近最优的解决方案。

首先是profit_gold >> profit_silver,而bad_debt_silver ~= bad_debt_gold。所以你可以假设你只会分配黄金。

因此,问题现在变成了分配最大目标数,同时保持在 250000 限制之下。为此,请按 customer_pd 对客户进行排序,然后按 1 选择 1 直到达到限制。

于 2019-09-17T19:45:35.893 回答
1

如果你真的想用求解器求解,这里有一个清理后的版本

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 的赋值。

于 2019-09-17T20:04:27.843 回答