def test_norm(tspace): weighting_const = tspace.weighting.const xarr, x = noise_elements(tspace) correct_norm = np.linalg.norm(xarr) * np.sqrt(weighting_const) assert (tspace.norm(x) == pytest.approx(correct_norm, rel=dtype_tol(tspace.dtype))) assert (x.norm() == pytest.approx(correct_norm, rel=dtype_tol(tspace.dtype)))
def test_dist(tspace): weighting_const = tspace.weighting.const [xarr, yarr], [x, y] = noise_elements(tspace, 2) correct_dist = np.linalg.norm(xarr - yarr) * np.sqrt(weighting_const) assert (tspace.dist(x, y) == pytest.approx(correct_dist, rel=dtype_tol(tspace.dtype))) assert (x.dist(y) == pytest.approx(correct_dist, rel=dtype_tol(tspace.dtype)))
def test_inner(tspace): weighting_const = tspace.weighting.const [xarr, yarr], [x, y] = noise_elements(tspace, 2) correct_inner = np.vdot(yarr, xarr) * weighting_const assert (tspace.inner(x, y) == pytest.approx(correct_inner, rel=dtype_tol(tspace.dtype))) assert (x.inner(y) == pytest.approx(correct_inner, rel=dtype_tol(tspace.dtype)))
def test_resize_array_adj(resize_setup, odl_floating_dtype): dtype = odl_floating_dtype pad_mode, pad_const, newshp, offset, array, _ = resize_setup if pad_const != 0: # Not well-defined return array = np.array(array, dtype=dtype) if is_real_dtype(dtype): other_arr = np.random.uniform(-10, 10, size=newshp) else: other_arr = (np.random.uniform(-10, 10, size=newshp) + 1j * np.random.uniform(-10, 10, size=newshp)) resized = resize_array(array, newshp, offset, pad_mode, pad_const, direction='forward') resized_adj = resize_array(other_arr, array.shape, offset, pad_mode, pad_const, direction='adjoint') assert (np.vdot(resized.ravel(), other_arr.ravel()) == pytest.approx(np.vdot( array.ravel(), resized_adj.ravel()), rel=dtype_tol(dtype)))
def test_resizing_op_adjoint(padding, odl_tspace_impl): impl = odl_tspace_impl pad_mode, pad_const = padding dtypes = [dt for dt in tensor_space_impl(impl).available_dtypes() if is_real_floating_dtype(dt)] for dtype in dtypes: space = odl.uniform_discr([0, -1], [1, 1], (4, 5), dtype=dtype, impl=impl) res_space = odl.uniform_discr([0, -1.4], [1.5, 1.4], (6, 7), dtype=dtype, impl=impl) res_op = odl.ResizingOperator(space, res_space, pad_mode=pad_mode, pad_const=pad_const) if pad_const != 0.0: with pytest.raises(NotImplementedError): res_op.adjoint return elem = noise_element(space) res_elem = noise_element(res_space) inner1 = res_op(elem).inner(res_elem) inner2 = elem.inner(res_op.adjoint(res_elem)) assert inner1 == pytest.approx( inner2, rel=space.size * dtype_tol(dtype) )
def test_bregman(functional): """Test for the Bregman distance of a functional.""" rtol = dtype_tol(functional.domain.dtype) if isinstance(functional, odl.solvers.functional.IndicatorLpUnitBall): # IndicatorFunction has no gradient with pytest.raises(NotImplementedError): functional.gradient(functional.domain.zero()) return y = noise_element(functional.domain) x = noise_element(functional.domain) if (isinstance(functional, odl.solvers.KullbackLeibler) or isinstance( functional, odl.solvers.KullbackLeiblerCrossEntropy)): # The functional is not defined for values <= 0 x = x.ufuncs.absolute() y = y.ufuncs.absolute() if isinstance(functional, KullbackLeiblerConvexConj): # The functional is not defined for values >= 1 x = x - x.ufuncs.max() + 0.99 y = y - y.ufuncs.max() + 0.99 grad = functional.gradient(y) quadratic_func = odl.solvers.QuadraticForm(vector=-grad, constant=-functional(y) + grad.inner(y)) expected_func = functional + quadratic_func assert (functional.bregman(y, grad)(x) == pytest.approx(expected_func(x), rel=rtol))
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_gradient(space, method, padding): """Discretized spatial gradient operator.""" with pytest.raises(TypeError): Gradient(odl.rn(1), method=method) if isinstance(padding, tuple): pad_mode, pad_const = padding else: pad_mode, pad_const = padding, 0 # DiscreteLp Vector dom_vec = noise_element(space) dom_vec_arr = dom_vec.asarray() # gradient grad = Gradient(space, method=method, pad_mode=pad_mode, pad_const=pad_const) grad_vec = grad(dom_vec) assert len(grad_vec) == space.ndim # computation of gradient components with helper function for axis, dx in enumerate(space.cell_sides): diff = finite_diff(dom_vec_arr, axis=axis, dx=dx, method=method, pad_mode=pad_mode, pad_const=pad_const) assert all_almost_equal(grad_vec[axis].asarray(), diff) # Test adjoint operator derivative = grad.derivative() ran_vec = noise_element(derivative.range) deriv_grad_vec = derivative(dom_vec) adj_grad_vec = derivative.adjoint(ran_vec) lhs = ran_vec.inner(deriv_grad_vec) rhs = dom_vec.inner(adj_grad_vec) # Check not to use trivial data assert lhs != 0 assert rhs != 0 assert lhs == pytest.approx(rhs, rel=dtype_tol(space.dtype)) # Higher-dimensional arrays lin_size = 3 for ndim in [1, 3, 6]: space = odl.uniform_discr([0.] * ndim, [1.] * ndim, [lin_size] * ndim) dom_vec = odl.phantom.cuboid(space, [0.2] * ndim, [0.8] * ndim) grad = Gradient(space, method=method, pad_mode=pad_mode, pad_const=pad_const) grad(dom_vec)
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_laplacian(space, padding): """Discretized spatial laplacian operator.""" if isinstance(padding, tuple): pad_mode, pad_const = padding else: pad_mode, pad_const = padding, 0 if pad_mode in ('order1', 'order2'): return # these pad modes not supported for laplacian # Operator instance lap = Laplacian(space, pad_mode=pad_mode, pad_const=pad_const) # Apply operator dom_vec = noise_element(space) div_dom_vec = lap(dom_vec) # computation of divergence with helper function expected_result = np.zeros(space.shape) for axis, dx in enumerate(space.cell_sides): diff_f = finite_diff(dom_vec.asarray(), axis=axis, dx=dx**2, method='forward', pad_mode=pad_mode, pad_const=pad_const) diff_b = finite_diff(dom_vec.asarray(), axis=axis, dx=dx**2, method='backward', pad_mode=pad_mode, pad_const=pad_const) expected_result += diff_f - diff_b assert all_almost_equal(expected_result, div_dom_vec.asarray()) # Adjoint operator derivative = lap.derivative() deriv_lap_dom_vec = derivative(dom_vec) ran_vec = noise_element(lap.range) adj_lap_ran_vec = derivative.adjoint(ran_vec) # Adjoint condition lhs = ran_vec.inner(deriv_lap_dom_vec) rhs = dom_vec.inner(adj_lap_ran_vec) # Check not to use trivial data assert lhs != 0 assert rhs != 0 assert lhs == pytest.approx(rhs, rel=dtype_tol(space.dtype))
def test_divergence(space, method, padding): """Discretized spatial divergence operator.""" # Invalid space with pytest.raises(TypeError): Divergence(range=odl.rn(1), method=method) if isinstance(padding, tuple): pad_mode, pad_const = padding else: pad_mode, pad_const = padding, 0 # Operator instance div = Divergence(range=space, method=method, pad_mode=pad_mode, pad_const=pad_const) # Apply operator dom_vec = noise_element(div.domain) div_dom_vec = div(dom_vec) # computation of divergence with helper function expected_result = np.zeros(space.shape) for axis, dx in enumerate(space.cell_sides): expected_result += finite_diff(dom_vec[axis], axis=axis, dx=dx, method=method, pad_mode=pad_mode, pad_const=pad_const) assert all_almost_equal(expected_result, div_dom_vec.asarray()) # Adjoint operator derivative = div.derivative() deriv_div_dom_vec = derivative(dom_vec) ran_vec = noise_element(div.range) adj_div_ran_vec = derivative.adjoint(ran_vec) # Adjoint condition lhs = ran_vec.inner(deriv_div_dom_vec) rhs = dom_vec.inner(adj_div_ran_vec) # Check not to use trivial data assert lhs != 0 assert rhs != 0 assert lhs == pytest.approx(rhs, rel=dtype_tol(space.dtype))
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_part_deriv(space, method, padding): """Discretized partial derivative.""" if isinstance(padding, tuple): pad_mode, pad_const = padding else: pad_mode, pad_const = padding, 0 dom_vec = noise_element(space) dom_vec_arr = dom_vec.asarray() for axis in range(space.ndim): partial = PartialDerivative(space, axis=axis, method=method, pad_mode=pad_mode, pad_const=pad_const) # Compare to helper function dx = space.cell_sides[axis] diff = finite_diff(dom_vec_arr, axis=axis, dx=dx, method=method, pad_mode=pad_mode, pad_const=pad_const) partial_vec = partial(dom_vec) assert all_almost_equal(partial_vec, diff) # Test adjoint operator derivative = partial.derivative() ran_vec = noise_element(space) deriv_vec = derivative(dom_vec) adj_vec = derivative.adjoint(ran_vec) lhs = ran_vec.inner(deriv_vec) rhs = dom_vec.inner(adj_vec) # Check not to use trivial data assert lhs != 0 assert rhs != 0 assert lhs == pytest.approx(rhs, rel=dtype_tol(space.dtype))
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)
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)