def test_functional_composition(space): """Test composition from the right with an operator.""" # Less strict checking for single precision ndigits = dtype_ndigits(space.dtype) rtol = dtype_tol(space.dtype) func = odl.solvers.L2NormSquared(space) # Verify that an error is raised if an invalid operator is used # (e.g. wrong range) scalar = 2.1 wrong_space = odl.uniform_discr(1, 2, 10) op_wrong = odl.operator.ScalingOperator(wrong_space, scalar) with pytest.raises(OpTypeError): func * op_wrong # Test composition with operator from the right op = odl.operator.ScalingOperator(space, scalar) func_op_comp = func * op assert isinstance(func_op_comp, odl.solvers.Functional) x = noise_element(space) assert func_op_comp(x) == pytest.approx(func(op(x)), rel=rtol) # Test gradient and derivative with composition from the right assert all_almost_equal(func_op_comp.gradient(x), (op.adjoint * func.gradient * op)(x), ndigits) p = noise_element(space) assert all_almost_equal( func_op_comp.derivative(x)(p), (op.adjoint * func.gradient * op)(x).inner(p), ndigits)
def test_translation_of_functional(space): """Test for the translation of a functional: (f(. - y))^*.""" # Less strict checking for single precision ndigits = dtype_ndigits(space.dtype) rtol = dtype_tol(space.dtype) # The translation; an element in the domain translation = noise_element(space) test_functional = odl.solvers.L2NormSquared(space) translated_functional = test_functional.translated(translation) x = noise_element(space) # Test for evaluation of the functional expected_result = test_functional(x - translation) assert all_almost_equal(translated_functional(x), expected_result, ndigits) # Test for the gradient expected_result = test_functional.gradient(x - translation) translated_gradient = translated_functional.gradient assert all_almost_equal(translated_gradient(x), expected_result, ndigits) # Test for proximal sigma = 1.2 # The helper function below is tested explicitly in proximal_utils_test expected_result = odl.solvers.proximal_translation( test_functional.proximal, translation)(sigma)(x) assert all_almost_equal( translated_functional.proximal(sigma)(x), expected_result, ndigits) # Test for conjugate functional # The helper function below is tested explicitly further down in this file expected_result = odl.solvers.FunctionalQuadraticPerturb( test_functional.convex_conj, linear_term=translation)(x) assert all_almost_equal(translated_functional.convex_conj(x), expected_result, ndigits) # Test for derivative in direction p p = noise_element(space) # Explicit computation in point x, in direction p: <x/2 + translation, p> expected_result = p.inner(test_functional.gradient(x - translation)) assert all_almost_equal( translated_functional.derivative(x)(p), expected_result, ndigits) # Test for optimized implementation, when translating a translated # functional second_translation = noise_element(space) double_translated_functional = translated_functional.translated( second_translation) # Evaluation assert (double_translated_functional(x) == pytest.approx( test_functional(x - translation - second_translation), rel=rtol))
def test_functional_quadratic_perturb(space, linear_term, quadratic_coeff): """Test for the functional f(.) + a | . |^2 + <y, .>.""" # Less strict checking for single precision ndigits = dtype_ndigits(space.dtype) rtol = dtype_tol(space.dtype) orig_func = odl.solvers.L2NormSquared(space) if linear_term: linear_term_arg = None linear_term = space.zero() else: linear_term_arg = linear_term = noise_element(space) # Creating the functional ||x||_2^2 and add the quadratic perturbation functional = odl.solvers.FunctionalQuadraticPerturb( orig_func, quadratic_coeff=quadratic_coeff, linear_term=linear_term_arg) # Create an element in the space, in which to evaluate x = noise_element(space) # Test for evaluation of the functional assert (functional(x) == pytest.approx( orig_func(x) + quadratic_coeff * x.inner(x) + x.inner(linear_term), rel=rtol)) # Test for the gradient assert all_almost_equal( functional.gradient(x), orig_func.gradient(x) + 2.0 * quadratic_coeff * x + linear_term, ndigits) # Test for the proximal operator if it exists sigma = 1.2 # Explicit computation gives c = 1 / np.sqrt(2 * sigma * quadratic_coeff + 1) prox = orig_func.proximal(sigma * c**2) expected_result = prox((x - sigma * linear_term) * c**2) assert all_almost_equal( functional.proximal(sigma)(x), expected_result, ndigits) # Test convex conjugate functional if quadratic_coeff == 0: expected = orig_func.convex_conj.translated(linear_term)(x) assert functional.convex_conj(x) == pytest.approx(expected, rel=rtol) # Test proximal of the convex conjugate cconj_prox = odl.solvers.proximal_convex_conj(functional.proximal) assert all_almost_equal( functional.convex_conj.proximal(sigma)(x), cconj_prox(sigma)(x), ndigits)
def test_multiplication_with_vector(space): """Test for multiplying a functional with a vector, both left and right.""" # Less strict checking for single precision ndigits = dtype_ndigits(space.dtype) rtol = dtype_tol(space.dtype) x = noise_element(space) y = noise_element(space) func = odl.solvers.L2NormSquared(space) wrong_space = odl.uniform_discr(1, 2, 10) y_other_space = noise_element(wrong_space) # Multiplication from the right. Make sure it is a # FunctionalRightVectorMult func_times_y = func * y assert isinstance(func_times_y, odl.solvers.FunctionalRightVectorMult) expected_result = func(y * x) assert func_times_y(x) == pytest.approx(expected_result, rel=rtol) # Test for the gradient. # Explicit calculations: 2*y*y*x expected_result = 2.0 * y * y * x assert all_almost_equal(func_times_y.gradient(x), expected_result, ndigits) # Test for convex_conj cc_func_times_y = func_times_y.convex_conj # Explicit calculations: 1/4 * ||x/y||_2^2 expected_result = 1.0 / 4.0 * (x / y).norm()**2 assert cc_func_times_y(x) == pytest.approx(expected_result, rel=rtol) # Make sure that right muliplication is not allowed with vector from # another space with pytest.raises(TypeError): func * y_other_space # Multiplication from the left. Make sure it is a FunctionalLeftVectorMult y_times_func = y * func assert isinstance(y_times_func, odl.FunctionalLeftVectorMult) expected_result = y * func(x) assert all_almost_equal(y_times_func(x), expected_result, ndigits) # Now, multiplication with vector from another space is ok (since it is the # same as scaling that vector with the scalar returned by the functional). y_other_times_func = y_other_space * func assert isinstance(y_other_times_func, odl.FunctionalLeftVectorMult) expected_result = y_other_space * func(x) assert all_almost_equal(y_other_times_func(x), expected_result, ndigits)
def test_left_scalar_mult(space, scalar): """Test for right and left multiplication of a functional with a scalar.""" # Less strict checking for single precision ndigits = dtype_ndigits(space.dtype) rtol = dtype_tol(space.dtype) x = noise_element(space) func = odl.solvers.functional.L2Norm(space) lmul_func = scalar * func if scalar == 0: assert isinstance(scalar * func, odl.solvers.ZeroFunctional) return # Test functional evaluation assert lmul_func(x) == pytest.approx(scalar * func(x), rel=rtol) # Test gradient of left scalar multiplication assert all_almost_equal(lmul_func.gradient(x), scalar * func.gradient(x), ndigits) # Test derivative of left scalar multiplication p = noise_element(space) assert all_almost_equal( lmul_func.derivative(x)(p), scalar * (func.derivative(x))(p), ndigits) # Test convex conjugate. This requires positive scaling to work pos_scalar = abs(scalar) neg_scalar = -pos_scalar with pytest.raises(ValueError): (neg_scalar * func).convex_conj assert all_almost_equal((pos_scalar * func).convex_conj(x), pos_scalar * func.convex_conj(x / pos_scalar), ndigits) # Test proximal operator. This requires scaling to be positive. sigma = 1.2 with pytest.raises(ValueError): (neg_scalar * func).proximal(sigma) assert all_almost_equal((pos_scalar * func).proximal(sigma)(x), func.proximal(sigma * pos_scalar)(x))
def test_right_scalar_mult(space, scalar): """Test for right and left multiplication of a functional with a scalar.""" # Less strict checking for single precision ndigits = dtype_ndigits(space.dtype) rtol = dtype_tol(space.dtype) x = noise_element(space) func = odl.solvers.functional.L2NormSquared(space) rmul_func = func * scalar if scalar == 0: # expecting the constant functional x -> func(0) assert isinstance(rmul_func, odl.solvers.ConstantFunctional) assert all_almost_equal(rmul_func(x), func(space.zero()), ndigits) # Nothing more to do, rest is part of ConstantFunctional test return # Test functional evaluation assert rmul_func(x) == pytest.approx(func(scalar * x), rel=rtol) # Test gradient of right scalar multiplication assert all_almost_equal(rmul_func.gradient(x), scalar * func.gradient(scalar * x), ndigits) # Test derivative of right scalar multiplication p = noise_element(space) assert all_almost_equal( rmul_func.derivative(x)(p), scalar * func.derivative(scalar * x)(p), ndigits) # Test convex conjugate conjugate assert all_almost_equal(rmul_func.convex_conj(x), func.convex_conj(x / scalar), ndigits) # Test proximal operator sigma = 1.2 assert all_almost_equal( rmul_func.proximal(sigma)(x), (1.0 / scalar) * func.proximal(sigma * scalar**2)(x * scalar), ndigits) # Verify that for linear functionals, left multiplication is used. func = odl.solvers.ZeroFunctional(space) assert isinstance(func * scalar, odl.solvers.FunctionalLeftScalarMult)
def test_functional_plus_scalar(space): """Test for sum of functioanl and scalar.""" # Less strict checking for single precision ndigits = dtype_ndigits(space.dtype) rtol = dtype_tol(space.dtype) func = odl.solvers.L2NormSquared(space) scalar = -1.3 # Test for scalar not in the field (field of unifor_discr is RealNumbers) complex_scalar = 1j with pytest.raises(TypeError): func + complex_scalar func_scalar_sum = func + scalar x = noise_element(space) p = noise_element(space) # Test for evaluation assert func_scalar_sum(x) == pytest.approx(func(x) + scalar, rel=rtol) # Test for derivative and gradient assert all_almost_equal(func_scalar_sum.gradient(x), func.gradient(x), ndigits) assert (func_scalar_sum.derivative(x)(p) == pytest.approx( func.gradient(x).inner(p), rel=rtol)) # Test proximal operator sigma = 1.2 assert all_almost_equal( func_scalar_sum.proximal(sigma)(x), func.proximal(sigma)(x), ndigits) # Test convex conjugate assert (func_scalar_sum.convex_conj(x) == pytest.approx( func.convex_conj(x) - scalar, rel=rtol)) assert all_almost_equal(func_scalar_sum.convex_conj.gradient(x), func.convex_conj.gradient(x), ndigits)
def test_functional_sum(space): """Test for the sum of two functionals.""" # Less strict checking for single precision ndigits = dtype_ndigits(space.dtype) rtol = dtype_tol(space.dtype) func1 = odl.solvers.L2NormSquared(space) func2 = odl.solvers.L2Norm(space) # Verify that an error is raised if one operand is "wrong" op = odl.operator.IdentityOperator(space) with pytest.raises(OpTypeError): func1 + op wrong_space = odl.uniform_discr(1, 2, 10) func_wrong_domain = odl.solvers.L2Norm(wrong_space) with pytest.raises(OpTypeError): func1 + func_wrong_domain func_sum = func1 + func2 x = noise_element(space) p = noise_element(space) # Test functional evaluation assert func_sum(x) == pytest.approx(func1(x) + func2(x), rel=rtol) # Test gradient and derivative assert all_almost_equal(func_sum.gradient(x), func1.gradient(x) + func2.gradient(x), ndigits) assert (func_sum.derivative(x)(p) == pytest.approx( func1.gradient(x).inner(p) + func2.gradient(x).inner(p), rel=rtol)) # Verify that proximal raises with pytest.raises(NotImplementedError): func_sum.proximal # Test the convex conjugate raises with pytest.raises(NotImplementedError): func_sum.convex_conj(x)