Skip to content

Commit b26f035

Browse files
committed
updates to some canons
1 parent 21d65ea commit b26f035

5 files changed

Lines changed: 82 additions & 60 deletions

File tree

cvxpy/reductions/dnlp2smooth/canonicalizers/div_canon.py

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,64 @@
1717
import numpy as np
1818

1919
from cvxpy.atoms.affine.binary_operators import multiply
20+
from cvxpy.atoms.elementwise.power import power
2021
from cvxpy.expressions.variable import Variable
22+
from cvxpy.utilities.warn import warn
2123

2224
MIN_INIT = 1e-3
2325

24-
# We canonicalize div(f(x), g(x)) as z * y = f(x), y = g(x), y >= 0.
25-
# In other words, it assumes that the denominator is nonnegative.
26+
# We canonicalize div(f(x), g(x)) as f(x) * power(z, -1), z = g(x), z >= 0.
2627
def div_canon(expr, args):
2728

28-
# raise an error if the denominator is not nonnegative
2929
if not args[1].is_nonneg():
30-
raise ValueError("The denominator of a division must be nonnegative. "
31-
"Did you forget to specify bounds?")
30+
warn(
31+
"CVXPY (DNLP) could not verify that the denominator of a division "
32+
"appearing in your problem is nonnegative. The solver will proceed "
33+
"under the assumption that the denominator is nonnegative. If this "
34+
"assumption is incorrect, the solution may be invalid. "
35+
)
3236

33-
dim = args[0].shape
34-
sgn_z = args[0].sign
35-
36-
if sgn_z == 'NONNEGATIVE':
37-
z = Variable(dim, nonneg=True)
38-
elif sgn_z == 'NONPOSITIVE':
39-
z = Variable(dim, nonpos=True)
40-
else:
41-
z = Variable(dim)
42-
43-
y = Variable(args[1].shape, nonneg=True)
37+
z = Variable(args[1].shape, nonneg=True)
4438

45-
if args[0].value is not None and args[1].value is not None:
46-
y.value = np.maximum(args[1].value, MIN_INIT)
47-
val = args[0].value / y.value
48-
49-
# dimension hack
50-
if dim == () and val.shape == (1,):
51-
z.value = val[0]
52-
else:
53-
z.value = val
39+
if args[1].value is not None:
40+
z.value = np.maximum(args[1].value, MIN_INIT)
5441

55-
return z, [multiply(z, y) == args[0], y == args[1]]
42+
# TODO (dance858): should. multiply and power be other canons?
43+
return multiply(args[0], power(z, -1)), [z == args[1]]
44+
45+
46+
# ----------------------------------------------------------------------------------
47+
# Old version
48+
# ----------------------------------------------------------------------------------
49+
# We canonicalize div(f(x), g(x)) as z * y = f(x), y = g(x), y >= 0.
50+
# In other words, it assumes that the denominator is nonnegative.
51+
#def div_canon_old(expr, args):
52+
#
53+
# # raise an error if the denominator is not nonnegative
54+
# if not args[1].is_nonneg():
55+
# raise ValueError("The denominator of a division must be nonnegative. "
56+
# "Did you forget to specify bounds?")
57+
#
58+
# dim = args[0].shape
59+
# sgn_z = args[0].sign
60+
#
61+
# if sgn_z == 'NONNEGATIVE':
62+
# z = Variable(dim, nonneg=True)
63+
# elif sgn_z == 'NONPOSITIVE':
64+
# z = Variable(dim, nonpos=True)
65+
# else:
66+
# z = Variable(dim)
67+
#
68+
# y = Variable(args[1].shape, nonneg=True)
69+
#
70+
# if args[0].value is not None and args[1].value is not None:
71+
# y.value = np.maximum(args[1].value, MIN_INIT)
72+
# val = args[0].value / y.value
73+
#
74+
# # dimension hack
75+
# if dim == () and val.shape == (1,):
76+
# z.value = val[0]
77+
# else:
78+
# z.value = val
79+
#
80+
# return z, [multiply(z, y) == args[0], y == args[1]]

cvxpy/reductions/dnlp2smooth/canonicalizers/geo_mean_canon.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@
2525
MIN_INIT = 1e-3
2626

2727
def geo_mean_canon(expr, args):
28-
"""
29-
Canonicalization for the geometric mean function.
30-
"""
3128
t = Variable(expr.shape, nonneg=True)
3229

3330
if args[0].value is not None:

cvxpy/reductions/dnlp2smooth/canonicalizers/multiply_canon.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,10 @@
1818
from cvxpy.expressions.variable import Variable
1919

2020

21-
# If a user insert x * x where x is a variable it gets canonicalized to
22-
# square(x) before this function is called.
21+
# TODO: (dance858) do we need to copy things? Do we need to canonicalize at all?
22+
# Can we have a common canonicalizer for binary operators with full domain?
2323
def multiply_canon(expr, args):
24-
t1 = args[0]
25-
t2 = args[1]
26-
constraints = []
27-
28-
# if either is constant, no canonicalization needed
29-
#if t1.is_constant() or t2.is_constant():
30-
# return expr.copy([t1, t2]), []
31-
#
32-
#if not isinstance(t1, Variable):
33-
# t1 = Variable(t1.shape)
34-
# constraints += [t1 == args[0]]
35-
# t1.value = args[0].value
36-
#
37-
#if not isinstance(t2, Variable):
38-
# t2 = Variable(t2.shape)
39-
# constraints += [t2 == args[1]]
40-
# t2.value = args[1].value
41-
42-
return expr.copy([t1, t2]), constraints
24+
return expr.copy(args), []
4325

4426
def matmul_canon(expr, args):
4527
t1 = args[0]
@@ -61,3 +43,27 @@ def matmul_canon(expr, args):
6143
t2.value = args[1].value
6244

6345
return expr.copy([t1, t2]), constraints
46+
47+
# ----------------------------------------------------------------------------------
48+
# Old versions
49+
# ----------------------------------------------------------------------------------
50+
#def multiply_canon_old(expr, args):
51+
# t1 = args[0]
52+
# t2 = args[1]
53+
# constraints = []
54+
#
55+
# # if either is constant, no canonicalization needed
56+
# if t1.is_constant() or t2.is_constant():
57+
# return expr.copy([t1, t2]), []
58+
#
59+
# if not isinstance(t1, Variable):
60+
# t1 = Variable(t1.shape)
61+
# constraints += [t1 == args[0]]
62+
# t1.value = args[0].value
63+
#
64+
# if not isinstance(t2, Variable):
65+
# t2 = Variable(t2.shape)
66+
# constraints += [t2 == args[1]]
67+
# t2.value = args[1].value
68+
#
69+
# return expr.copy([t1, t2]), constraints

cvxpy/reductions/dnlp2smooth/canonicalizers/pnorm_canon.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ def pnorm_canon(expr, args):
2525
shape = expr.shape
2626
t = Variable(shape, nonneg=True)
2727

28-
# expression will always have a value here in DNLP
29-
t.value = expr.value
28+
# in DNLP, we expect the expression to always have a value here,
29+
# but we add this check to get an informative error message later.
30+
if expr.value is not None:
31+
t.value = expr.value
3032

3133
# we canonicalize 2-norm as follows:
3234
# ||x||_2 <= t <=> quad_over_lin(x, t) <= t

cvxpy/reductions/dnlp2smooth/canonicalizers/power_canon.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,6 @@ def power_canon(expr, args):
3333
return x, []
3434
elif isinstance(p, int) and p > 1:
3535
return expr.copy([x]), []
36-
if isinstance(x, Variable):
37-
return expr.copy(args), []
38-
39-
t = Variable(shape)
40-
if x.value is not None:
41-
t.value = x.value
42-
43-
return expr.copy([t]), [t == x]
4436
elif p > 0:
4537
t = Variable(shape, nonneg=True)
4638

0 commit comments

Comments
 (0)