简体   繁体   English

将约束添加到优化问题时,Mosek求解器失败(10000变量,使用Python / cvxpy)

[英]Mosek solver failing when constraints added to optimisation problem (10000 variable, using Python/cvxpy)

In short 简而言之

The optimisation problem below is declared infeasible when run with Mosek, but is solvable (easily and accurately) using the open-source solver ECOS. 当与Mosek一起运行时,下面的优化问题被宣布为不可行,但使用开源求解器ECOS可轻松(准确地)解决。

I'm wondering: why is such an advanced commercial solver like Mosek failing to solve this problem ? 我想知道: 为什么像Mosek这样的高级商业解决方案无法解决此问题

import cvxpy as cvx
import numpy as np


print('cvxpy version:')
print(cvx.__version__)
print('')

np.random.seed(0)

SOLVER = 'ECOS_BB'  # Works fine, sticks to constraint thresholds very precisely
# SOLVER = 'MOSEK'  # Fails when many "sumproduct" constraints are added

def get_objective_function_and_weights(n, means, std_devs):
    weights = cvx.Variable(n)

    # Markowitz-style objective "expectation minus variance" objective function
    objective = cvx.Maximize(
        means * weights
        - cvx.sum_entries(cvx.mul_elemwise(std_devs, weights) ** 2)
    )

    return objective, weights

def calculate_objective_value(weights, means, std_devs):
    expec = weights.T @ means
    cov = np.power(np.diag(std_devs), 2)
    var = weights.T @ cov @ weights
    return float(expec - var)

def get_total_discrepancy(weights, A, b):
    # We want A @ weights <= b
    # Discrepancy is sum of any elements where this is not the case

    values = A @ weights
    assert values.shape == b.shape

    discrepancy_idx = values > b
    discrepancies = values[discrepancy_idx] - b[discrepancy_idx]

    return discrepancies.sum()

def get_weights_gt_0_discrepancy(weights):
    discrepancy_idx = weights < 0
    discrepancies = np.abs(weights[discrepancy_idx])

    return discrepancies.sum()

def get_sum_weights_le_1_discrepancy(weights):
    discrepancy = max(0, weights.sum() - 1)

    return discrepancy

def main():
    n = 10000

    means = np.random.normal(size=n)
    std_devs = np.random.chisquare(df=5, size=n)

    print()
    print(' === BASIC PROBLEM (only slightly constrained) ===')
    print()

    objective, weights = get_objective_function_and_weights(n, means, std_devs)
    constraints = [
        weights >= 0,
        cvx.sum_entries(weights) == 1,
    ]

    # Set up problem and solve
    basic_prob = cvx.Problem(objective, constraints)
    basic_prob.solve(solver=SOLVER, verbose=True)
    basic_weights = np.asarray(weights.value)

    print('Optimal weights')
    print(basic_weights.round(6))
    print('Objective function value:')
    basic_obj_value = calculate_objective_value(basic_weights, means, std_devs)
    print(basic_obj_value)
    print('Discrepancy: all weights > 0')
    print(get_weights_gt_0_discrepancy(basic_weights))
    print('Discrepancy: sum(weights) <= 1')
    print(get_sum_weights_le_1_discrepancy(basic_weights))
    print()
    print()


    print()
    print(' === CONSTRAINED PROBLEM (many added "sumproduct" constraints) ===')
    print()

    objective, weights = get_objective_function_and_weights(n, means, std_devs)

    # We will place all our sumproduct constraints into a single large matrix `A`
    # We want `A` to be a fairly sparse matrix with only a few 1s, mostly 0s
    m = 100  # number of "sumproduct" style constraints
    p = 5  # on average, number of 1s per row in `A`
    A = 1 * (np.random.uniform(size=(m, n)) < p/n)

    # We look at the observed values of A @ weights from the basic
    # problem, and base our constraint on that
    observed_values = (A @ basic_weights).reshape((-1, 1))
    b = observed_values * np.random.uniform(low=0.90, high=1.00, size=(m, 1))

    print('number of 1s in A')
    print(A.sum())

    new_constraints = [
        weights >= 0,
        cvx.sum_entries(weights) == 1,
        A * weights <= b,
    ]

    # Set up problem and solve
    prob = cvx.Problem(objective, new_constraints)
    prob.solve(solver=SOLVER, verbose=True)
    final_weights = np.asarray(weights.value)

    print('Optimal weights')
    print(final_weights.round(6))
    print('Objective function value:')
    constrained_obj_value = calculate_objective_value(final_weights, means, std_devs)
    print(constrained_obj_value)
    print('Difference vs basic')
    print(constrained_obj_value - basic_obj_value)

    # Now calculate the discrepancy - the amount by which the returned
    # optimal weights go beyond the required threshold
    print('Discrepancy: all weights > 0')
    print(get_weights_gt_0_discrepancy(final_weights))
    print('Discrepancy: sum(weights) <= 1')
    print(get_sum_weights_le_1_discrepancy(final_weights))
    print('Discrepancy: sumproduct threshold:')
    print(get_total_discrepancy(final_weights, A, b))


main()

_ _

More details 更多细节

I'm testing out some optimizers, and have been looking at Mosek. 我正在测试一些优化器,并且一直在研究Mosek。 I've downloaded a trial licence and am using Mosek v8.1, and cvxpy 0.4.10. 我已经下载了试用许可证,并且正在使用Mosek v8.1和cvxpy 0.4.10。

I'm finding that Mosek doesn't seem to stick to constraints very accurately, or fails in cases with many constraints. 我发现Mosek似乎并不太准确地遵守约束,或者在有很多约束的情况下失败。 That's what I'd like help with - why is Mosek imprecise on these constraints, and why does it fail for clearly solvable problems ? 这就是我想要的帮助- 为什么Mosek在这些约束条件上不精确,以及为什么它因可解决的问题而失败

In the below script I solve a simple problem with just two constraints (all variables positive, summing to 1), then re-solve it with several added constraints, which I call "sumproduct" constraints. 在下面的脚本中,我用两个约束(所有变量均为正,总和为1)解决了一个简单的问题,然后用几个添加的约束(称为“ sumproduct”约束)重新解决了这个问题。

These added contraints are all of the form "the weights for some subset of variables must sum to less than b_i" for some constant b_i. 对于某些常数b_i,这些添加的约束全部为“变量的某些子集的权重之和必须小于b_i”的形式。 I pack these constraints into a matrix equation A @ weights <= b . 我将这些约束打包到矩阵方程A @ weights <= b

When I run the script at the bottom of this post using the in-built solver ECOS, it solves the basic problem easily, giving an optimal value of 2.63...: 当我使用内置求解器ECOS在本文底部运行脚本时,它可以轻松解决基本问题,给出的最佳值为2.63 ...:

Objective function value:
2.6338492447784283
Discrepancy: all weights > 0
4.735618828548444e-13
Discrepancy: sum(weights) <= 1
1.3322676295501878e-15

You can see that I'm also calculating what I call the discrepancy for each constraint. 您可以看到,我还在计算每个约束的差异 This is the amount by which the optimiser is "going over" the constraint in the returned weights. 这是优化器“遍历”返回权重中的约束的量。 So ECOS here is slightly "breaking the rules" defined by the constraints, but not by much. 因此,此处的ECOS稍微有点“突破了规则”,但这些约束并未定义太多。

I then ask ECOS to solve a much more constrained problem, with 100 added "sumproduct" constraints. 然后,我要求ECOS用100个附加的“ sumproduct”约束来解决更受约束的问题。 These sumproduct constraints are of the form A @ weights <= b , and A has 486 ones, the rest zeros. 这些和积约束的形式为A @ weights <= b ,并且A有486个,其余为零。

number of 1s in A
486

Then I re-solve the problem, and see a revised set of optimal weights. 然后,我重新解决了该问题,并看到了一组修改后的最佳权重。 The optimal value is a little less than it was before (because of the added constraints), and ECOS is still "obeying" all the constraints to within a very good accuracy: 最佳值比以前少了一点(由于增加了约束),并且ECOS仍“服从”所有约束以达到非常好的精度:

Objective function value:
2.6338405110044203
Difference vs basic
-8.733774008007344e-06
Discrepancy: all weights > 0
5.963041247103521e-12
Discrepancy: sum(weights) <= 1
9.103828801926284e-15
Discrepancy: sumproduct threshold:
0.0

If I run the same script with Mosek, I find that on the basic problem, Mosek solves it, but is already quite far out on one of the constraints: 如果我使用Mosek运行相同的脚本,我发现在基本问题上,Mosek可以解决该问题,但在其中一个约束条件上已经相距甚远:

Objective function value:
2.633643747862593
Discrepancy: all weights > 0
7.039232392536552e-06
Discrepancy: sum(weights) <= 1
0

That means that we have several weights less than zero summing to -7e-6, which isn't accurate enough for my liking. 这意味着我们有几个权重小于零,总和为-7e-6,这对我来说还不够准确。

Then when it comes to solving the more constrained problem, Mosek is failing completely and declaring it PRIMAL_INFEASIBLE . 然后,当要解决更受约束的问题时,Mosek完全失败并声明为PRIMAL_INFEASIBLE

Could anyone offer any ideas as to why Mosek is failing like this? 关于Mosek为何如此失败,有人可以提出任何想法吗? I've seen it being extremely inaccurate on constraints in other cases too. 在其他情况下,我也看到它在约束方面极其不准确。 I tried to increase the precision using the parameter intpnt_co_tol_pfeas , but whenever I do this the solver starts failing often. 我尝试使用参数intpnt_co_tol_pfeas来提高精度,但是每当执行此操作时,求解器就会开始经常失败。

Thanks in advance for any help with this. 在此先感谢您的任何帮助。 Here's my example code. 这是我的示例代码。 Running with solver='ECOS' works, running with solver='MOSEK' fails. 使用solver='ECOS'运行时,使用solver='MOSEK'失败。

There can be tons of reasons such that the two codes are using different tolerances. 可能有很多原因导致两个代码使用不同的公差。 For instance is the problem 例如是问题

1/x <= 0.0, x>=0 1 / x <= 0.0,x> = 0

feasible? 可行? Only yes if you allow for x being infinite. 如果您允许x为无限,则仅是。 In other worfds your problem can be nasty. 在其他情况下,您的问题可能令人讨厌。

In general I would recommend reading 一般来说,我建议阅读

https://docs.mosek.com/9.0/pythonapi/debugging-log.html https://docs.mosek.com/9.0/pythonapi/debugging-log.html

and in particular about the solution summary. 特别是关于解决方案摘要。 If that does not help, then post your question with solution summary in the Mosek google group. 如果这样做没有帮助,请在Mosek google组中发布您的问题以及解决方案摘要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM