Skip to content

Commit

Permalink
tests, removed some unused functions, coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
rileyjmurray committed Oct 6, 2019
1 parent 96f130d commit 51703f5
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 59 deletions.
4 changes: 1 addition & 3 deletions sageopt/coniclifts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,8 @@ def presolve_trivial_age_cones(true_or_false=True):
The default value for ``true_or_false`` in this function's signature represents
sageopt's default behavior for this setting.
"""
import sageopt.coniclifts.constraints.set_membership.ordinary_sage_cone as sc
import sageopt.coniclifts.constraints.set_membership.sage_cones as sc
sc._ELIMINATE_TRIVIAL_AGE_CONES_ = true_or_false
import sageopt.coniclifts.constraints.set_membership.conditional_sage_cone as csc
csc._ELIMINATE_TRIVIAL_AGE_CONES_ = true_or_false
pass


Expand Down
15 changes: 3 additions & 12 deletions sageopt/coniclifts/operators/affine.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,6 @@ def multi_dot(arrays):
return cast_res(res)


def vdot(a, b):
res = np.vdot(a, b)
return cast_res(res)


def inner(a, b):
res = np.inner(a, b)
return cast_res(res)
Expand All @@ -60,14 +55,10 @@ def outer(a, b):
return cast_res(res)


def matmul(a, b):
res = np.matmul(a, b)
return cast_res(res)


def tensordot(a, b, axes=1):
def tensordot(a, b, axes=2):
"""
WARNING: default value for axes is changed from 2, to 1.
The default value ``axes=2`` is used for consistency with numpy.
Usage in coniclifts is mostly with ``axes=1``.
"""
res = np.tensordot(a, b, axes)
return cast_res(res)
Expand Down
27 changes: 6 additions & 21 deletions sageopt/relaxations/sage_polys.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,14 @@ def poly_relaxation(f, X=None, form='dual', **kwargs):
prob = poly_dual(f, poly_ell, sigrep_ell, X)
elif form[0] == 'p':
prob = poly_primal(f, poly_ell, sigrep_ell, X)
else:
else: # pragma: no cover
raise RuntimeError('Unrecognized form: ' + form + '.')
return prob


def poly_dual(f, poly_ell=0, sigrep_ell=0, X=None):
if poly_ell == 0:
sr, cons = f.sig_rep
if len(cons) > 0:
msg = '\n\nThe provided Polynomial has nonconstant coefficients.\n'
msg += 'The most likely cause is that a mistake has been made in setting up '
msg += 'the data for this function.\n Raising a RuntimeError.\n'
raise RuntimeError(msg)
sr, _ = f.sig_rep
prob = sage_sigs.sig_dual(sr, sigrep_ell, X=X)
cl.clear_variable_indices()
return prob
Expand All @@ -130,19 +125,14 @@ def poly_dual(f, poly_ell=0, sigrep_ell=0, X=None):
prob = cl.Problem(cl.MIN, obj, constraints)
cl.clear_variable_indices()
return prob
else:
else: # pragma: no cover
raise NotImplementedError()


def poly_primal(f, poly_ell=0, sigrep_ell=0, X=None):
if poly_ell == 0:
sr, cons = f.sig_rep
if len(cons) > 0:
msg = '\n\nThe provided Polynomial has nonconstant coefficients.\n'
msg += 'The most likely cause is that a mistake has been made in setting up '
msg += 'the data for this function.\n Raising a RuntimeError.\n'
raise RuntimeError(msg)
prob = sage_sigs.sig_primal(sr, sigrep_ell, X=X, additional_cons=cons)
sr, _ = f.sig_rep
prob = sage_sigs.sig_primal(sr, sigrep_ell, X=X)
cl.clear_variable_indices()
return prob
else:
Expand Down Expand Up @@ -295,7 +285,7 @@ def poly_constrained_relaxation(f, gts, eqs, X=None, form='dual', **kwargs):
prob = poly_constrained_dual(f, gts, eqs, p, q, ell, X)
elif form[0] == 'p':
prob = poly_constrained_primal(f, gts, eqs, p, q, ell, X)
else:
else: # pragma: no cover
raise RuntimeError('Unrecognized form: ' + form + '.')
return prob

Expand Down Expand Up @@ -590,8 +580,3 @@ def hierarchy_e_k(polys, k):
s = s ** k
return s.alpha


def log_domain_converter(f):
# noinspection PyPep8
fhat = lambda x: f(np.exp(x))
return fhat
6 changes: 2 additions & 4 deletions sageopt/relaxations/sage_sigs.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def sig_relaxation(f, X=None, form='dual', **kwargs):
if form.lower()[0] == 'd':
prob = sig_dual(f, ell, X, mod_supp)
elif form.lower()[0] == 'p':
prob = sig_primal(f, ell, X, mod_supp, None)
prob = sig_primal(f, ell, X, mod_supp)
else:
raise RuntimeError('Unrecognized form: ' + form + '.')
return prob
Expand Down Expand Up @@ -108,7 +108,7 @@ def sig_dual(f, ell=0, X=None, modulator_support=None):
return prob


def sig_primal(f, ell=0, X=None, modulator_support=None, additional_cons=None):
def sig_primal(f, ell=0, X=None, modulator_support=None):
f.remove_terms_with_zero_as_coefficient()
if modulator_support is None:
modulator_support = f.alpha
Expand All @@ -119,8 +119,6 @@ def sig_primal(f, ell=0, X=None, modulator_support=None, additional_cons=None):
con = primal_sage_cone(s_mod, name=str(s_mod), X=X)
constraints = [con]
obj = gamma.as_expr()
if additional_cons is not None:
constraints += additional_cons
prob = cl.Problem(cl.MAX, obj, constraints)
cl.clear_variable_indices()
return prob
Expand Down
113 changes: 113 additions & 0 deletions sageopt/tests/test_coniclifts/test_affine_operators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""
Copyright 2019 Riley John Murray
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import unittest
import numpy as np
from sageopt.coniclifts.base import Variable, Expression
from sageopt.coniclifts.operators import affine as aff


class TestAffineOperators(unittest.TestCase):

def test_dot(self):
x = Variable(shape=(4,))
a = np.array([1, 2, 3, 4])
expr0 = aff.dot(x, a)
expr1 = aff.dot(a, x)
x0 = np.random.rand(4).round(decimals=4)
expect = np.dot(a, x0)
x.set_scalar_variables(x0)
actual0 = expr0.value
actual1 = expr1.value
assert actual0 == expect
assert actual1 == expect
assert Expression.are_equivalent(expr0, expr1)

def test_multi_dot(self):
A = np.random.rand(5, 3).round(decimals=3)
X = Variable(shape=(3, 3), var_properties=['symmetric'])
X0 = np.random.rand(3, 3).round(decimals=3)
X0 += X0.T
X.set_scalar_variables(X0)
B = np.random.rand(3, 3).round(decimals=3)
C = np.random.rand(3, 7).round(decimals=3)

chain1 = [A, X, B, C]
expr1 = aff.multi_dot(chain1)
expect1 = np.linalg.multi_dot([A, X0, B, C])
actual1 = expr1.value
assert np.allclose(expect1, actual1)

chain2 = [A, B, X, C]
expr2 = aff.multi_dot(chain2)
expect2 = np.linalg.multi_dot([A, B, X0, C])
actual2 = expr2.value
assert np.allclose(expect2, actual2)

def test_inner(self):
# test with scalar inputs
x = Variable()
a = 2.0
expr0 = aff.inner(a, x)
expr1 = aff.inner(x, a)
assert Expression.are_equivalent(expr0, expr1)
# test with multidimensional arrays
a = np.arange(24).reshape((2, 3, 4))
x = Variable(shape=(4,))
x.set_scalar_variables(np.arange(4))
expr = aff.inner(a, x)
expect = np.inner(a, np.arange(4))
actual = expr.value
assert np.allclose(expect, actual)

def test_outer(self):
x = Variable(shape=(3,))
x0 = np.random.randn(3).round(decimals=3)
x.set_scalar_variables(x0)
a = np.array([1, 2, 3, 4])
expr0 = aff.outer(a, x)
assert np.allclose(expr0.value, np.outer(a, x0))
expr1 = aff.outer(x, a)
assert np.allclose(expr1.value, np.outer(x0, a))
b = np.array([9, 8])
expr2 = aff.outer(b, x)
assert np.allclose(expr2.value, np.outer(b, x0))
expr3 = aff.outer(x, b)
assert np.allclose(expr3.value, np.outer(x0, b))

def test_kron(self):
I = np.eye(2)
X = Variable(shape=(2, 2))
expr0 = aff.kron(I, X)
expr1 = aff.kron(X, I)
X0 = np.random.randn(2, 2).round(decimals=3)
X.set_scalar_variables(X0)
assert np.allclose(expr0.value, np.kron(I, X0))
assert np.allclose(expr1.value, np.kron(X0, I))

def test_trace_and_diag(self):
x = Variable(shape=(5,))
A = np.random.randn(5, 5).round(decimals=3)
for i in range(5):
A[i, i] = 0
temp = A + aff.diag(x)
expr0 = aff.trace(temp)
expr1 = aff.sum(x)
assert Expression.are_equivalent(expr0, expr1)



if __name__ == '__main__':
unittest.main()
12 changes: 6 additions & 6 deletions sageopt/tests/test_coniclifts/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ def test_factorization(self):
assert verify_entries(expr, x)

T1 = np.random.randn(6, 3).round(decimals=4)
expr1 = affine.tensordot(T1, x)
expr1 = affine.tensordot(T1, x, axes=1)
(A1, X1, B1) = expr1.factor()
X1 = Expression(X1)
assert verify_entries(affine.tensordot(A1, X1) + B1, expr1)
assert verify_entries(affine.tensordot(A1, X1, axes=1) + B1, expr1)

T2 = np.random.randn(2, 6).round(decimals=4)
expr2 = affine.tensordot(T2, expr1)
expr2 = affine.tensordot(T2, expr1, axes=1)
(A2, X2, B2) = expr2.factor()
X2 = Expression(X2)
assert verify_entries(affine.tensordot(A2, X2) + B2, expr2)
assert verify_entries(affine.tensordot(A2, X2, axes=1) + B2, expr2)

Tall = affine.tensordot(T2, A1)
res3 = affine.tensordot(Tall, X1)
Tall = affine.tensordot(T2, A1, axes=1)
res3 = affine.tensordot(Tall, X1, axes=1)
assert verify_entries(res3, expr2)

# noinspection PyUnusedLocal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,26 @@
"""
import unittest
import numpy as np
from scipy.special import rel_entr
from sageopt.coniclifts.base import Variable
from sageopt.coniclifts.operators import affine
from sageopt.coniclifts.operators.relent import relent
from sageopt.coniclifts.operators.norms import vector2norm
from sageopt.coniclifts.compilers import compile_constrained_system
from sageopt.coniclifts.cones import Cone


class TestOperators(unittest.TestCase):
class TestNonlinearOperators(unittest.TestCase):

def test_relent(self):
def test_relent_1(self):
# compilation and evaluation
x = Variable(shape=(2,), name='x')
y = Variable(shape=(2,), name='y')
re = relent(2 * x, np.exp(1) * y)
con = [re <= 10,
3 <= x,
x <= 5]
# compilation
A, b, K, _, _, _ = compile_constrained_system(con)
A_expect = np.array([[0., 0., 0., 0., -1., -1.], # linear inequality on epigraph for relent constr
[1., 0., 0., 0., 0., 0.], # bound constraints on x
Expand All @@ -47,6 +51,34 @@ def test_relent(self):
assert np.all(A == A_expect)
assert np.all(b == np.array([10., -3., -3., 5., 5., 0., 0., 0., 0., 0., 0.]))
assert K == [Cone('+', 1), Cone('+', 2), Cone('+', 2), Cone('e', 3), Cone('e', 3)]
# value propagation
x0 = np.array([1,2])
x.set_scalar_variables(x0)
y0 = np.array([3,4])
y.set_scalar_variables(y0)
actual = re.value
expect = np.sum(rel_entr(2 * x0, np.exp(1) * y0))
assert abs(actual - expect) < 1e-7

def test_relent_2(self):
# compilation with the elementwise option
x = Variable(shape=(2,), name='x')
y = Variable(shape=(2,), name='y')
re = affine.sum(relent(2 * x, np.exp(1) * y, elementwise=True))
con = [re <= 1]
A, b, K, _, _, _ = compile_constrained_system(con)
A_expect = np.array([[0., 0., 0., 0., -1., -1.], # linear inequality on epigraph for relent constr
[0., 0., 0., 0., -1., 0.], # first exponential cone
[0., 0., 2.72, 0., 0., 0.], #
[2., 0., 0., 0., 0., 0.], #
[0., 0., 0., 0., 0., -1.], # second exponential cone
[0., 0., 0., 2.72, 0., 0.], #
[0., 2., 0., 0., 0., 0.]]) #
A = np.round(A.toarray(), decimals=2)
assert np.all(A == A_expect)
assert np.all(b == np.array([1., 0., 0., 0., 0., 0., 0.]))
assert K == [Cone('+', 1), Cone('e', 3), Cone('e', 3)]


def test_vector2norm_1(self):
x = Variable(shape=(3,), name='x')
Expand Down
24 changes: 18 additions & 6 deletions sageopt/tests/test_coniclifts/test_system/test_toys_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,9 @@ def test_geometric_program_1(self):
cons = [expr <= 1]
obj = - x[0] - 2 * x[1]
prob = Problem(cl.MIN, obj, cons)
solver = 'ECOS'
res = prob.solve(solver=solver, verbose=False)
assert res[0] == 'solved'
assert abs(res[1] - 10.4075826) < 1e-6
status, val = prob.solve(solver='ECOS', verbose=False)
assert status == 'solved'
assert abs(val - 10.4075826) < 1e-6
x_star = x.value
expect = np.array([-4.93083, -2.73838])
assert np.allclose(x_star, expect, atol=1e-4)
Expand All @@ -60,12 +59,25 @@ def test_simple_sage_1(self):
[0.5, 0],
[0, 0.5]])
gamma = cl.Variable(shape=(), name='gamma')
c = np.array([0 - gamma, 3, 2, 1, -4, -2])
c = cl.Expression([0 - gamma, 3, 2, 1, -4, -2])
expected_val = -1.8333331773244161

# with presolve
cl.presolve_trivial_age_cones(True)
con = cl.PrimalSageCone(c, alpha, None, 'test_con_name')
obj = gamma
prob = Problem(cl.MAX, obj, [con])
status, val = prob.solve(solver='ECOS', verbose=False)
assert abs(val - expected_val) < 1e-6
v = con.violation()
assert v < 1e-6

# without presolve
cl.presolve_trivial_age_cones(False)
con = cl.PrimalSageCone(c, alpha, None, 'test_con_name')
obj = gamma
prob = Problem(cl.MAX, obj, [con])
status, val = prob.solve(solver='ECOS', verbose=False)
expected_val = -1.8333331773244161
assert abs(val - expected_val) < 1e-6
v = con.violation()
assert v < 1e-6
Expand Down

0 comments on commit 51703f5

Please sign in to comment.