`cp.bmat` considered as non-symmetric/non-Hermitian.
Describe the bug
cp.bmat cause matrix of constants as to be considered as non-symmetric/non-Hermitian. Replacing cp.bmat with np.array solve the problem. This is not an urgent issue, because cp.bmat ussually not used to contant matrix building block.
To Reproduce
import numpy as np
import cvxpy as cp
# Create two scalar optimization variables.
x = cp.Variable((2, 1))
# Define expressions for the constraints.
A = np.array([
[1, 1], # First inequality: x + y <= 2
[-1, 2], # Second inequality: -x + 2y <= -1
[-1, 0], # Third inequality: -x <= 0
])
b = cp.bmat([
[2],
[-1],
[0]
])
# Create the original constraints using the expressions.
constraints = [
A @ x <= b
]
# For the quadratic objective 2 * (x - y)^2
P = cp.bmat([
[2, -2],
[-2, 2]
])
obj = cp.Minimize(cp.quad_form(x, P))
# Solve the original problem.
prob = cp.Problem(obj, constraints)
sol = prob.solve()
# Display the original solution and objective.
print("Original Problem:")
print(f"Optimal solution: x = {x.value.tolist()}")
print(f"Optimal cost: {prob.value:.4f}")
Expected behavior
Works just like when cp.bmat replaced by np.array. Properly detect if it matrix of constant and properly check symetry and hermitian property.
Output
ValueError: Quadratic form matrices must be symmetric/Hermitian.
Version
- OS: mac m1
- CVXPY Version: 1.6.4
Additional context
This is an interesting find!
This happens because cp.bmat is a wrapper around cp.vstack composed with cp.hstack, whose is_symmetric and is_hermitian methods default to the implementations in cp.Expression. Those implementations are not designed with constant expressions primarily in mind, so they apply very (excessively?) strict rules for inferring if an expression is symmetric/Hermitian.
There is a basic fix we can apply to any cvxpy atom that's really a wrapper around other atoms. That fix would check if the nominal return value, expr, is constant and contains no Parameter objects. When that's the case, we can return Constant(expr.value) instead of expr.
Making things concrete for bmat ...
from cvxpy.atoms.affine.vstack import vstack
from cvxpy.atoms.affine.hstack import hstack
from cvxpy.expressions.constants.constant import Constant
def bmat(block_lists):
row_blocks = [hstack(blocks) for blocks in block_lists]
expr = vstack(row_blocks)
if expr.is_constant() and len(expr.parameters()) == 0:
expr = Constant(expr.value)
return expr
That approach is useful because we'll inherit all the property checking of the Constant class. This approach is less useful for top-level atoms that define separate types.
Ping @SteveDiamond and @PTNobel. What are your thoughts on trying to include this logic in the Expression class? Basically, if an Expression is constant and contains no Parameters, then we can infer all "disciplined-xyz-programming" properties by inspecting self.value, just like is done in Constant.
Ping @SteveDiamond and @PTNobel once more. Here's my proposal:
If an Expression is constant and contains no Parameters, then we can infer all "disciplined-xyz-programming" properties by inspecting self.value, just like is done in Constant.
Ping @SteveDiamond and @PTNobel once more. Here's my proposal:
If an Expression is constant and contains no Parameters, then we can infer all "disciplined-xyz-programming" properties by inspecting self.value, just like is done in Constant.
Seems fine to me...
Ping @SteveDiamond and @PTNobel once more. Here's my proposal:
If an Expression is constant and contains no Parameters, then we can infer all "disciplined-xyz-programming" properties by inspecting self.value, just like is done in Constant.
Also fine with me.