def test_gradient(space, method, padding): """Discretized spatial gradient operator.""" places = 2 if space.dtype == np.float32 else 4 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 almost_equal(lhs, rhs, places=places) # 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_forward_backward_with_lin_ops(): """Test for the forward-backward solver with linear operatros. The test is done by minimizing ||x - b||_2^2 + ||alpha * x||_2^2. The general problem is of the form ``min_x f(x) + sum_i g_i(L_i x) + h(x)`` and here we take f = 0, g = ||.||_2^2, L = alpha * IndentityOperator, and h = ||. - b||_2^2. """ space = odl.rn(10) alpha = 0.1 b = noise_element(space) lin_ops = [alpha * odl.IdentityOperator(space)] g = [odl.solvers.L2NormSquared(space)] f = odl.solvers.ZeroFunctional(space) # Gradient of two-norm square h = odl.solvers.L2NormSquared(space).translated(b) x = noise_element(space) # Explicit solution: x_hat = (I^T * I + (alpha*I)^T * (alpha*I))^-1 * (I*b) x_global_min = b / (1 + alpha ** 2) forward_backward_pd(x, f, g, lin_ops, h, tau=0.5, sigma=[1.0], niter=20) assert all_almost_equal(x, x_global_min, places=LOW_ACCURACY)
def test_separable_sum(space): """Test for the separable sum.""" l1 = odl.solvers.L1Norm(space) l2 = odl.solvers.L2Norm(space) x = noise_element(space) y = noise_element(space) # Initialization and calling func = odl.solvers.SeparableSum(l1, l2) assert func([x, y]) == pytest.approx(l1(x) + l2(y)) power_func = odl.solvers.SeparableSum(l1, 5) assert power_func([x, x, x, x, x]) == pytest.approx(5 * l1(x)) # Gradient grad = func.gradient([x, y]) assert grad[0] == l1.gradient(x) assert grad[1] == l2.gradient(y) # Proximal sigma = 1.0 prox = func.proximal(sigma)([x, y]) assert prox[0] == l1.proximal(sigma)(x) assert prox[1] == l2.proximal(sigma)(y) # Convex conjugate assert func.convex_conj([x, y]) == l1.convex_conj(x) + l2.convex_conj(y)
def test_bregman_functional_l2_squared(space, sigma): """Test Bregman distance using l2 norm squared as underlying functional.""" sigma = float(sigma) l2_sq = odl.solvers.L2NormSquared(space) point = noise_element(space) subgrad = l2_sq.gradient(point) bregman_dist = odl.solvers.BregmanDistance(l2_sq, point, subgrad) expected_func = odl.solvers.L2NormSquared(space).translated(point) x = noise_element(space) # Function evaluation assert all_almost_equal(bregman_dist(x), expected_func(x)) # Gradient evaluation assert all_almost_equal(bregman_dist.gradient(x), expected_func.gradient(x)) # Convex conjugate cc_bregman_dist = bregman_dist.convex_conj cc_expected_func = expected_func.convex_conj assert all_almost_equal(cc_bregman_dist(x), cc_expected_func(x)) # Proximal operator prox_bregman_dist = bregman_dist.proximal(sigma) prox_expected_func = expected_func.proximal(sigma) assert all_almost_equal(prox_bregman_dist(x), prox_expected_func(x))
def test_proximal_quadratic_perturbation(nonneg_scalar, sigma): """Test for the proximal of quadratic perturbation.""" sigma = float(sigma) space = odl.uniform_discr(0, 1, 10) lam = 1.2 prox_factory = proximal_l2_squared(space, lam=lam) # parameter for the quadratic perturbation, needs to be non-negative a = nonneg_scalar # Test without linear term if a != 0: with pytest.raises(ValueError): # negative values not allowed proximal_quadratic_perturbation(prox_factory, -a) prox = proximal_quadratic_perturbation(prox_factory, a)(sigma) x = noise_element(space) expected_result = x / (2 * sigma * (lam + a) + 1) assert all_almost_equal(prox(x), expected_result, places=PLACES) # Test with linear term u = noise_element(space) prox = proximal_quadratic_perturbation(prox_factory, a, u)(sigma) expected_result = (x - sigma * u) / (2 * sigma * (lam + a) + 1) assert all_almost_equal(prox(x), expected_result, places=PLACES)
def test_mat_op_inverse(fn): # Sparse case sparse_mat = _sparse_matrix(fn) op_sparse = MatrixOperator(sparse_mat, fn, fn) op_sparse_inv = op_sparse.inverse assert op_sparse_inv.domain == op_sparse.range assert op_sparse_inv.range == op_sparse.domain assert all_almost_equal(op_sparse_inv.matrix, np.linalg.inv(op_sparse.matrix.todense())) assert all_almost_equal(op_sparse_inv.inverse.matrix, op_sparse.matrix.todense()) # Test application x = noise_element(fn) assert all_almost_equal(x, op_sparse.inverse(op_sparse(x))) # Dense case dense_mat = _dense_matrix(fn) op_dense = MatrixOperator(dense_mat, fn, fn) op_dense_inv = op_dense.inverse assert op_dense_inv.domain == op_dense.range assert op_dense_inv.range == op_dense.domain assert all_almost_equal(op_dense_inv.matrix, np.linalg.inv(op_dense.matrix)) assert all_almost_equal(op_dense_inv.inverse.matrix, op_dense.matrix) # Test application x = noise_element(fn) assert all_almost_equal(x, op_dense.inverse(op_dense(x)))
def test_cconj_defintion(functional): """Test the defintion of the convex conjugate: f^*(y) = sup_x {<x, y> - f(x)} Hence we expect for all x in the domain of the proximal <x, y> - f(x) <= f^*(y) """ f_cconj = functional.convex_conj if isinstance(functional.convex_conj, FunctionalDefaultConvexConjugate): pytest.skip('functional has no convex conjugate') return for _ in range(100): y = noise_element(functional.domain) f_cconj_y = f_cconj(y) x = noise_element(functional.domain) lhs = x.inner(y) - functional(x) if not lhs <= f_cconj_y + EPS: print(repr(functional), repr(f_cconj), x, y, lhs, f_cconj_y) assert lhs <= f_cconj_y + EPS
def test_arithmetic(): """Test that all standard arithmetic works.""" space = odl.rn(3) # Create elements needed for later functional = odl.solvers.L2Norm(space).translated([1, 2, 3]) functional2 = odl.solvers.L2NormSquared(space) operator = odl.IdentityOperator(space) - space.element([4, 5, 6]) x = noise_element(functional.domain) y = noise_element(functional.domain) scalar = np.pi # Simple tests here, more in depth comes later assert functional(x) == functional(x) assert functional(x) != functional2(x) assert (scalar * functional)(x) == scalar * functional(x) assert (scalar * (scalar * functional))(x) == scalar**2 * functional(x) assert (functional * scalar)(x) == functional(scalar * x) assert ((functional * scalar) * scalar)(x) == functional(scalar**2 * x) assert (functional + functional2)(x) == functional(x) + functional2(x) assert (functional - functional2)(x) == functional(x) - functional2(x) assert (functional * operator)(x) == functional(operator(x)) assert all_almost_equal((y * functional)(x), y * functional(x)) assert all_almost_equal((y * (y * functional))(x), (y * y) * functional(x)) assert all_almost_equal((functional * y)(x), functional(y * x)) assert all_almost_equal(((functional * y) * y)(x), functional((y * y) * x))
def test_convex_conj_defintion(functional): """Test the defintion of the convex conjugate: f^*(y) = sup_x {<x, y> - f(x)} Hence we expect for all x in the domain of the proximal <x, y> - f(x) <= f^*(y) """ if isinstance(functional, FunctionalDefaultConvexConjugate): pytest.skip('functional has no call') return f_convex_conj = functional.convex_conj if isinstance(f_convex_conj, FunctionalDefaultConvexConjugate): pytest.skip('functional has no convex conjugate') return for _ in range(100): y = noise_element(functional.domain) f_convex_conj_y = f_convex_conj(y) x = noise_element(functional.domain) lhs = x.inner(y) - functional(x) if not lhs <= f_convex_conj_y + EPS: print(repr(functional), repr(f_convex_conj), x, y, lhs, f_convex_conj_y) assert lhs <= f_convex_conj_y + EPS
def test_resizing_op_mixed_uni_nonuni(): """Check if resizing along uniform axes in mixed discretizations works.""" nonuni_part = odl.nonuniform_partition([0, 1, 4]) uni_part = odl.uniform_partition(-1, 1, 4) part = uni_part.append(nonuni_part, uni_part, nonuni_part) fspace = odl.FunctionSpace(odl.IntervalProd(part.min_pt, part.max_pt)) tspace = odl.rn(part.shape) space = odl.DiscreteLp(fspace, part, tspace) # Keep non-uniform axes fixed res_op = odl.ResizingOperator(space, ran_shp=(6, 3, 6, 3)) assert res_op.axes == (0, 2) assert res_op.offset == (1, 0, 1, 0) # Evaluation test with a simpler case part = uni_part.append(nonuni_part) fspace = odl.FunctionSpace(odl.IntervalProd(part.min_pt, part.max_pt)) tspace = odl.rn(part.shape) space = odl.DiscreteLp(fspace, part, tspace) res_op = odl.ResizingOperator(space, ran_shp=(6, 3)) result = res_op(space.one()) true_result = [[0, 0, 0], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [0, 0, 0]] assert np.array_equal(result, true_result) # Test adjoint elem = noise_element(space) res_elem = noise_element(res_op.range) inner1 = res_op(elem).inner(res_elem) inner2 = elem.inner(res_op.adjoint(res_elem)) assert almost_equal(inner1, inner2)
def test_functional_composition(space): """Test composition from the right with an operator.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 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 almost_equal(func_op_comp(x), func(op(x)), places=places) # Test gradient and derivative with composition from the right assert all_almost_equal(func_op_comp.gradient(x), (op.adjoint * func.gradient * op)(x), places=places) p = noise_element(space) assert all_almost_equal(func_op_comp.derivative(x)(p), (op.adjoint * func.gradient * op)(x).inner(p), places=places)
def test_inner(fn): xd = noise_element(fn) yd = noise_element(fn) correct_inner = np.vdot(yd, xd) assert almost_equal(fn.inner(xd, yd), correct_inner) assert almost_equal(xd.inner(yd), correct_inner)
def test_forward_backward_with_lin_ops(): """Test for the forward-backward solver with linear operatros. The test is done by minimizing ||x - b||_2^2 + ||alpha * x||_2^2. The general problem is of the form ``min_x f(x) + sum_i g_i(L_i x) + h(x)`` and here we take f = 0, g = ||.||_2^2, L = alpha * IndentityOperator, and h = ||. - b||_2^2. """ space = odl.rn(10) alpha = 0.1 b = noise_element(space) lin_ops = [alpha * odl.IdentityOperator(space)] g = [odl.solvers.L2NormSquared(space)] f = odl.solvers.ZeroFunctional(space) # Gradient of two-norm square h = odl.solvers.L2NormSquared(space).translated(b) x = noise_element(space) # Explicit solution: x_hat = (I^T * I + (alpha*I)^T * (alpha*I))^-1 * (I*b) x_global_min = b / (1 + alpha**2) forward_backward_pd(x, f, g, lin_ops, h, tau=0.5, sigma=[1.0], niter=20) assert all_almost_equal(x, x_global_min, places=LOW_ACCURACY)
def test_operator_pointwise_product(): """Check call and adjoint of operator pointwise multiplication.""" if dom_eq_ran: mat1 = np.random.rand(3, 3) mat2 = np.random.rand(3, 3) else: mat1 = np.random.rand(4, 3) mat2 = np.random.rand(4, 3) op1 = MultiplyAndSquareOp(mat1) op2 = MultiplyAndSquareOp(mat2) x = noise_element(op1.domain) prod_op = odl.OperatorPointwiseProduct(op1, op2) # Evaluate expected = op1(x) * op2(x) check_call(prod_op, x, expected) # Derivative y = noise_element(op1.domain) expected = (op1.derivative(x)(y) * op2(x) + op2.derivative(x)(y) * op1(x)) prod_deriv_op = prod_op.derivative(x) assert prod_deriv_op.is_linear check_call(prod_deriv_op, y, expected) # Derivative Adjoint z = noise_element(op1.range) expected = (op1.derivative(x).adjoint(z * op2(x)) + op2.derivative(x).adjoint(z * op1(x))) prod_deriv_adj_op = prod_deriv_op.adjoint assert prod_deriv_adj_op.is_linear check_call(prod_deriv_adj_op, z, expected)
def newpart(request, space): element_form = request.param.strip() if element_form == 'space': tmp = noise_element(space) newreal = space.element(tmp.real) elif element_form == 'real_space': newreal = noise_element(space).real elif element_form == 'numpy_array': tmp = noise_element(space) newreal = [tmp[0].real.asarray(), tmp[1].real.asarray()] elif element_form == 'array': if space.is_power_space: newreal = [[0, 1, 2], [3, 4, 5]] else: newreal = [[0, 1, 2], [3, 4]] elif element_form == 'scalar': newreal = np.random.randn() elif element_form == '1d_array': if not space.is_power_space: pytest.skip('arrays matching only one dimension can only be used ' 'for power spaces') newreal = [0, 1, 2] else: raise ValueError('undefined form of element') return newreal
def test_proximal_defintion(functional, stepsize): """Test the defintion of the proximal: prox[f](x) = argmin_y {f(y) + 1/2 ||x-y||^2} Hence we expect for all x in the domain of the proximal x* = prox[f](x) f(x*) + 1/2 ||x-x*||^2 <= f(y) + 1/2 ||x-y||^2 """ proximal = functional.proximal(stepsize) assert proximal.domain == functional.domain assert proximal.range == functional.domain for _ in range(100): x = noise_element(proximal.domain) * 10 prox_x = proximal(x) f_prox_x = proximal_objective(stepsize * functional, x, prox_x) y = noise_element(proximal.domain) f_y = proximal_objective(stepsize * functional, x, y) if not f_prox_x <= f_y + EPS: print(repr(functional), x, y, prox_x, f_prox_x, f_y) assert f_prox_x <= f_y + EPS
def test_arithmetic(): """Test that all standard arithmetic works.""" # Create elements needed for later operator = MultiplyAndSquareOp(np.random.rand(4, 3)) operator2 = MultiplyAndSquareOp(np.random.rand(4, 3)) operator3 = MultiplyAndSquareOp(np.random.rand(3, 3)) operator4 = MultiplyAndSquareOp(np.random.rand(4, 4)) x = noise_element(operator.domain) y = noise_element(operator.domain) z = noise_element(operator.range) scalar = np.pi # Simple tests here, more in depth comes later check_call(+operator, x, operator(x)) check_call(-operator, x, -operator(x)) check_call(scalar * operator, x, scalar * operator(x)) check_call(scalar * (scalar * operator), x, scalar**2 * operator(x)) check_call(operator * scalar, x, operator(scalar * x)) check_call((operator * scalar) * scalar, x, operator(scalar**2 * x)) check_call(operator + operator2, x, operator(x) + operator2(x)) check_call(operator - operator2, x, operator(x) - operator2(x)) check_call(operator * operator3, x, operator(operator3(x))) check_call(operator4 * operator, x, operator4(operator(x))) check_call(z * operator, x, z * operator(x)) check_call(z * (z * operator), x, (z * z) * operator(x)) check_call(operator * y, x, operator(x * y)) check_call((operator * y) * y, x, operator((y * y) * x)) check_call(operator + z, x, operator(x) + z) check_call(operator - z, x, operator(x) - z) check_call(z + operator, x, z + operator(x)) check_call(z - operator, x, z - operator(x)) check_call(operator + scalar, x, operator(x) + scalar) check_call(operator - scalar, x, operator(x) - scalar) check_call(scalar + operator, x, scalar + operator(x)) check_call(scalar - operator, x, scalar - operator(x))
def test_operator_pointwise_product(): """Test OperatorPointwiseProduct.""" Aop = MultiplyAndSquareOp(np.random.rand(4, 3)) Bop = MultiplyAndSquareOp(np.random.rand(4, 3)) x = noise_element(Aop.domain) prod = odl.OperatorPointwiseProduct(Aop, Bop) # Evaluate expected = Aop(x) * Bop(x) check_call(prod, x, expected) # Derivative y = noise_element(Aop.domain) expected = (Aop.derivative(x)(y) * Bop(x) + Bop.derivative(x)(y) * Aop(x)) derivative = prod.derivative(x) assert derivative.is_linear check_call(derivative, y, expected) # Adjoint z = noise_element(Aop.range) expected = (Aop.derivative(x).adjoint(z * Bop(x)) + Bop.derivative(x).adjoint(z * Aop(x))) adjoint = derivative.adjoint assert adjoint.is_linear check_call(adjoint, z, expected)
def test_matrix_op_adjoint(matrix): """Test if the adjoint of matrix operators is correct.""" dense_matrix = matrix sparse_matrix = scipy.sparse.coo_matrix(dense_matrix) tol = 2 * matrix.size * np.finfo(matrix.dtype).resolution # Default 1d case dmat_op = MatrixOperator(dense_matrix) smat_op = MatrixOperator(sparse_matrix) x = noise_element(dmat_op.domain) y = noise_element(dmat_op.range) inner_ran = dmat_op(x).inner(y) inner_dom = x.inner(dmat_op.adjoint(y)) assert inner_ran == pytest.approx(inner_dom, rel=tol, abs=tol) inner_ran = smat_op(x).inner(y) inner_dom = x.inner(smat_op.adjoint(y)) assert inner_ran == pytest.approx(inner_dom, rel=tol, abs=tol) # Multi-dimensional case domain = odl.tensor_space((2, 2, 4), matrix.dtype) mat_op = MatrixOperator(dense_matrix, domain, axis=2) x = noise_element(mat_op.domain) y = noise_element(mat_op.range) inner_ran = mat_op(x).inner(y) inner_dom = x.inner(mat_op.adjoint(y)) assert inner_ran == pytest.approx(inner_dom, rel=tol, abs=tol)
def functional(request, linear_offset, quadratic_offset, dual): """Return functional whose proximal should be tested.""" name = request.param.strip() space = odl.uniform_discr(0, 1, 2) if name == 'l1': func = odl.solvers.L1Norm(space) elif name == 'l2': func = odl.solvers.L2Norm(space) elif name == 'l2^2': func = odl.solvers.L2NormSquared(space) elif name == 'kl': func = odl.solvers.KullbackLeibler(space) elif name == 'kl_cross_ent': func = odl.solvers.KullbackLeiblerCrossEntropy(space) elif name == 'const': func = odl.solvers.ConstantFunctional(space, constant=2) elif name.startswith('groupl1'): exponent = float(name.split('-')[1]) space = odl.ProductSpace(space, 2) func = odl.solvers.GroupL1Norm(space, exponent=exponent) elif name.startswith('nuclearnorm'): outer_exp = float(name.split('-')[1]) singular_vector_exp = float(name.split('-')[2]) space = odl.ProductSpace(odl.ProductSpace(space, 2), 3) func = odl.solvers.NuclearNorm(space, outer_exp=outer_exp, singular_vector_exp=singular_vector_exp) elif name == 'quadratic': func = odl.solvers.QuadraticForm(operator=odl.IdentityOperator(space), vector=space.one(), constant=0.623) elif name == 'linear': func = odl.solvers.QuadraticForm(vector=space.one(), constant=0.623) else: assert False if quadratic_offset: if linear_offset: g = noise_element(space) if name.startswith('kl'): g = np.abs(g) else: g = None quadratic_coeff = 1.32 func = odl.solvers.FunctionalQuadraticPerturb( func, quadratic_coeff=quadratic_coeff, linear_term=g) elif linear_offset: g = noise_element(space) if name.startswith('kl'): g = np.abs(g) func = func.translated(g) if dual: func = func.convex_conj return func
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_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 almost_equal(inner1, inner2, places=dtype_places(dtype))
def test_bregman_functional_no_gradient(space): """Test Bregman distance for functional without gradient. Test that the Bregman distance functional fails if the underlying functional does not have a gradient and no subgradient operator is given. Also test giving the subgradient operator separately. """ ind_func = odl.solvers.IndicatorNonnegativity(space) point = noise_element(space) # Indicator function has no gradient, hence one cannot create a bregman # distance functional with pytest.raises(NotImplementedError): odl.solvers.BregmanDistance(ind_func, point) # If a subgradient operator is given separately, it is possible to create # an instance of the functional subgrad_op = odl.IdentityOperator(space) bregman_dist = odl.solvers.BregmanDistance(ind_func, point, subgrad_op) # In this case we should be able to call the gradient of the bregman # distance, which would give us a subgradient x = np.abs(noise_element(space)) expected_result = subgrad_op(x) - subgrad_op(point) assert all_almost_equal(bregman_dist.gradient(x), expected_result)
def test_functional_quadratic_perturb(space, linear_term, quadratic_coeff): """Test for the functional f(.) + a | . |^2 + <y, .>.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 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 all_almost_equal(functional(x), (orig_func(x) + quadratic_coeff * x.inner(x) + x.inner(linear_term)), places=places) # Test for the gradient assert all_almost_equal(functional.gradient(x), (orig_func.gradient(x) + 2.0 * quadratic_coeff * x + linear_term), places=places) # 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, places=places) # Test convex conjugate functional if quadratic_coeff == 0: expected = orig_func.convex_conj.translated(linear_term)(x) assert almost_equal(functional.convex_conj(x), expected, places=places) # 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), places=places)
def test_equality(metric_space): """Verify that equality testing works.""" x = noise_element(metric_space) y = noise_element(metric_space) assert x == x assert y == y assert x != y
def test_gradient(space, method, padding): """Discretized spatial gradient operator.""" places = 2 if space.dtype == np.float32 else 4 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 almost_equal(lhs, rhs, places=places) # higher dimensional arrays lin_size = 3 for ndim in [1, 3, 6]: # DiscreteLpElement space = odl.uniform_discr([0.] * ndim, [1.] * ndim, [lin_size] * ndim) dom_vec = odl.phantom.cuboid(space, [0.2] * ndim, [0.8] * ndim) # gradient grad = Gradient(space, method=method, pad_mode=pad_mode, pad_const=pad_const) grad(dom_vec)
def functional(request, space): name = request.param.strip() if name == 'l1': func = odl.solvers.functional.L1Norm(space) elif name == 'l2': func = odl.solvers.functional.L2Norm(space) elif name == 'l2^2': func = odl.solvers.functional.L2NormSquared(space) elif name == 'constant': func = odl.solvers.functional.ConstantFunctional(space, 2) elif name == 'zero': func = odl.solvers.functional.ZeroFunctional(space) elif name == 'ind_unit_ball_1': func = odl.solvers.functional.IndicatorLpUnitBall(space, 1) elif name == 'ind_unit_ball_2': func = odl.solvers.functional.IndicatorLpUnitBall(space, 2) elif name == 'ind_unit_ball_pi': func = odl.solvers.functional.IndicatorLpUnitBall(space, np.pi) elif name == 'ind_unit_ball_inf': func = odl.solvers.functional.IndicatorLpUnitBall(space, np.inf) elif name == 'product': left = odl.solvers.functional.L2Norm(space) right = odl.solvers.functional.ConstantFunctional(space, 2) func = odl.solvers.functional.FunctionalProduct(left, right) elif name == 'quotient': dividend = odl.solvers.functional.L2Norm(space) divisor = odl.solvers.functional.ConstantFunctional(space, 2) func = odl.solvers.functional.FunctionalQuotient(dividend, divisor) elif name == 'kl': func = odl.solvers.functional.KullbackLeibler(space) elif name == 'kl_cc': func = odl.solvers.KullbackLeibler(space).convex_conj elif name == 'kl_cross_ent': func = odl.solvers.functional.KullbackLeiblerCrossEntropy(space) elif name == 'kl_cc_cross_ent': func = odl.solvers.KullbackLeiblerCrossEntropy(space).convex_conj elif name == 'huber': func = odl.solvers.Huber(space, gamma=0.1) elif name == 'groupl1': if isinstance(space, odl.ProductSpace): pytest.skip("The `GroupL1Norm` is not supported on `ProductSpace`") space = odl.ProductSpace(space, 3) func = odl.solvers.GroupL1Norm(space) elif name == 'bregman_l2squared': point = noise_element(space) l2_squared = odl.solvers.L2NormSquared(space) subgrad = l2_squared.gradient(point) func = odl.solvers.BregmanDistance(l2_squared, point, subgrad) elif name == 'bregman_l1': point = noise_element(space) l1 = odl.solvers.L1Norm(space) subgrad = l1.gradient(point) func = odl.solvers.BregmanDistance(l1, point, subgrad) else: assert False return func
def test_proximal_defintion(functional, stepsize): """Test the defintion of the proximal: prox[f](x) = argmin_y {f(y) + 1/2 ||x-y||^2} Hence we expect for all x in the domain of the proximal x* = prox[f](x) f(x*) + 1/2 ||x-x*||^2 <= f(y) + 1/2 ||x-y||^2 """ if isinstance(functional, FunctionalDefaultConvexConjugate): pytest.skip('functional has no call method') return # No implementation of the proximal for convex conj of # FunctionalQuadraticPerturb unless the quadratic term is 0. if (isinstance(functional, odl.solvers.FunctionalQuadraticPerturb) and functional.quadratic_coeff != 0): pytest.skip('functional has no proximal') return # No implementation of the proximal for quardartic form if isinstance(functional, odl.solvers.QuadraticForm): pytest.skip('functional has no proximal') return # No implementation of the proximal for translations of quardartic form if (isinstance(functional, odl.solvers.FunctionalTranslation) and isinstance(functional.functional, odl.solvers.QuadraticForm)): pytest.skip('functional has no proximal') return # No implementation of the proximal for convex conj of quardartic form, # except if the quadratic part is 0. if (isinstance(functional, odl.solvers.FunctionalQuadraticPerturb) and isinstance(functional.functional, odl.solvers.QuadraticForm) and functional.functional.operator is not None): pytest.skip('functional has no proximal') return proximal = functional.proximal(stepsize) assert proximal.domain == functional.domain assert proximal.range == functional.domain for _ in range(100): x = noise_element(proximal.domain) * 10 prox_x = proximal(x) f_prox_x = proximal_objective(stepsize * functional, x, prox_x) y = noise_element(proximal.domain) f_y = proximal_objective(stepsize * functional, x, y) if not f_prox_x <= f_y + EPS: print(repr(functional), x, y, prox_x, f_prox_x, f_y) assert f_prox_x <= f_y + EPS
def test_translation_of_functional(space): """Test for the translation of a functional: (f(. - y))^*.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 # 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, places=places) # 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, places=places) # 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, places=places) # Test for conjugate functional # The helper function below is tested explicitly further down in this file expected_result = odl.solvers.FunctionalLinearPerturb( test_functional.convex_conj, translation)(x) assert all_almost_equal(translated_functional.convex_conj(x), expected_result, places=places) # 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, places=places) # Test for optimized implementation, when translating a translated # functional second_translation = noise_element(space) double_translated_functional = translated_functional.translated( second_translation) # Evaluation assert almost_equal(double_translated_functional(x), test_functional(x - translation - second_translation), places=places)
def test_translation_of_functional(space): """Test for the translation of a functional: (f(. - y))^*.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 # 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, places=places) # 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, places=places) # 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, places=places) # 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, places=places) # 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, places=places) # Test for optimized implementation, when translating a translated # functional second_translation = noise_element(space) double_translated_functional = translated_functional.translated( second_translation) # Evaluation assert almost_equal(double_translated_functional(x), test_functional(x - translation - second_translation), places=places)
def test_laplacian(space, padding): """Discretized spatial laplacian operator.""" # Invalid space with pytest.raises(TypeError): Laplacian(range=odl.rn(1)) 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 almost_equal(lhs, rhs, places=4)
def test_multiplication_with_vector(space): """Test for multiplying a functional with a vector, both left and right.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 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 almost_equal(func_times_y(x), expected_result, places=places) # 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, places=places) # 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 almost_equal(cc_func_times_y(x), expected_result, places=places) # 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, places=places) # 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, places=places)
def test_assign(fn): x = noise_element(fn) y = noise_element(fn) y.assign(x) assert y == x assert y is not x # test alignment x *= 2 assert y != x
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 almost_equal(lhs, rhs, places=4) # Higher dimensional arrays for ndim in range(1, 6): # DiscreteLpElement lin_size = 3 space = odl.uniform_discr([0.] * ndim, [1.] * ndim, [lin_size] * ndim)
def test_transpose(fn): x = noise_element(fn) y = noise_element(fn) # Assert linear operator assert isinstance(x.T, Operator) assert x.T.is_linear # Check result assert almost_equal(x.T(y), y.inner(x)) assert all_equal(x.T.adjoint(1.0), x) # x.T.T returns self assert x.T.T == x
def test_transpose(fn): x = noise_element(fn) y = noise_element(fn) # Assert linear operator assert isinstance(x.T, odl.Operator) assert x.T.is_linear # Check result assert almost_equal(x.T(y), x.inner(y)) assert all_almost_equal(x.T.adjoint(1.0), x) # x.T.T returns self assert x.T.T == x
def test_laplacian(space, padding): """Discretized spatial laplacian operator.""" # Invalid space with pytest.raises(TypeError): Laplacian(range=odl.rn(1)) 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 almost_equal(lhs, rhs, places=4)
def test_proximal_translation(sigma): """Test for the proximal of a translation: ``prox[F(. - g)]``.""" sigma = float(sigma) space = odl.uniform_discr(0, 1, 10) lam = 1.2 prox_factory = proximal_l2_squared(space, lam=lam) translation = noise_element(space) prox = proximal_translation(prox_factory, translation)(sigma) x = noise_element(space) expected_result = ((x + 2 * sigma * lam * translation) / (1 + 2 * sigma * lam)) assert all_almost_equal(prox(x), expected_result, places=PLACES)
def test_forward_backward_basic(): """Test for the forward-backward solver by minimizing ||x||_2^2. The general problem is of the form ``min_x f(x) + sum_i g_i(L_i x) + h(x)`` and here we take f(x) = g(x) = 0, h(x) = ||x||_2^2 and L is the zero-operator. """ space = odl.rn(10) lin_ops = [odl.ZeroOperator(space)] g = [odl.solvers.ZeroFunctional(space)] f = odl.solvers.ZeroFunctional(space) h = odl.solvers.L2NormSquared(space) x = noise_element(space) x_global_min = space.zero() forward_backward_pd(x, f, g, lin_ops, h, tau=0.5, sigma=[1.0], niter=10) assert all_almost_equal(x, x_global_min, places=HIGH_ACCURACY)
def test_quadratic_form(space): """Test the quadratic form functional.""" operator = odl.IdentityOperator(space) vector = space.one() constant = 0.363 func = odl.solvers.QuadraticForm(operator, vector, constant) x = noise_element(space) # Checking that values is stored correctly assert func.operator == operator assert func.vector == vector assert func.constant == constant # Evaluation of the functional expected_result = x.inner(operator(x)) + vector.inner(x) + constant assert almost_equal(func(x), expected_result) # Also test with some values as none func_no_offset = odl.solvers.QuadraticForm(operator, constant=constant) expected_result = x.inner(operator(x)) + constant assert almost_equal(func_no_offset(x), expected_result) func_no_operator = odl.solvers.QuadraticForm(vector=vector, constant=constant) expected_result = vector.inner(x) + constant assert almost_equal(func_no_operator(x), expected_result) # The gradient expected_gradient = 2 * operator(x) + vector assert all_almost_equal(func.gradient(x), expected_gradient) # The convex conjugate assert isinstance(func.convex_conj, odl.solvers.QuadraticForm)
def test_wavelet_transform(wave_impl, shape_setup, floating_dtype): # Verify that the operator works as expected wavelet, pad_mode, nlevels, shape, _ = shape_setup ndim = len(shape) space = odl.uniform_discr([-1] * ndim, [1] * ndim, shape, dtype=floating_dtype) image = noise_element(space) # TODO: check more error scenarios if wave_impl == 'pywt' and pad_mode == 'constant': with pytest.raises(ValueError): wave_trafo = odl.trafos.WaveletTransform( space, wavelet, nlevels, pad_mode, pad_const=1, impl=wave_impl) wave_trafo = odl.trafos.WaveletTransform( space, wavelet, nlevels, pad_mode, impl=wave_impl) assert wave_trafo.domain.dtype == floating_dtype assert wave_trafo.range.dtype == floating_dtype wave_trafo_inv = wave_trafo.inverse assert wave_trafo_inv.domain.dtype == floating_dtype assert wave_trafo_inv.range.dtype == floating_dtype assert wave_trafo_inv.nlevels == wave_trafo.nlevels assert wave_trafo_inv.wavelet == wave_trafo.wavelet assert wave_trafo_inv.pad_mode == wave_trafo.pad_mode assert wave_trafo_inv.pad_const == wave_trafo.pad_const assert wave_trafo_inv.pywt_pad_mode == wave_trafo.pywt_pad_mode coeffs = wave_trafo(image) reco_image = wave_trafo.inverse(coeffs) assert all_almost_equal(image.real, reco_image.real) assert all_almost_equal(image, reco_image)
def test_functional_linear_perturb(space): """Test for the functional f(.) + <y, .>.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 # The translation; an element in the domain linear_term = noise_element(space) # Creating the functional ||x||_2^2 and add the linear perturbation orig_func = odl.solvers.L2NormSquared(space) functional = odl.solvers.FunctionalLinearPerturb(orig_func, linear_term) # Create an element in the space, in which to evaluate x = noise_element(space) # Test for evaluation of the functional assert all_almost_equal(functional(x), x.norm()**2 + x.inner(linear_term), places=places) # Test for the gradient assert all_almost_equal(functional.gradient(x), 2.0 * x + linear_term, places=places) # Test for derivative in direction p p = noise_element(space) assert all_almost_equal(functional.derivative(x)(p), p.inner(2 * x + linear_term), places=places) # Test for the proximal operator sigma = 1.2 # Explicit computation gives (x - sigma * translation)/(2 * sigma + 1) expected_result = (x - sigma * linear_term) / (2.0 * sigma + 1.0) assert all_almost_equal(functional.proximal(sigma)(x), expected_result, places=places) # Test convex conjugate functional assert almost_equal(functional.convex_conj(x), orig_func.convex_conj.translated(linear_term)(x), places=places) # Test proximal of the convex conjugate assert all_almost_equal( functional.convex_conj.proximal(sigma)(x), orig_func.convex_conj.translated(linear_term).proximal(sigma)(x), places=places)
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 places = 3 if space.dtype == np.float32 else 5 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()), places=places) # Nothing more to do, rest is part of ConstantFunctional test return # Test functional evaluation assert almost_equal(rmul_func(x), func(scalar * x), places=places) # Test gradient of right scalar multiplication assert all_almost_equal(rmul_func.gradient(x), scalar * func.gradient(scalar * x), places=places) # 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), places=places) # Test convex conjugate conjugate assert all_almost_equal(rmul_func.convex_conj(x), func.convex_conj(x / scalar), places=places) # 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), places=places) # Verify that for linear functionals, left multiplication is used. func = odl.solvers.ZeroFunctional(space) assert isinstance(func * scalar, odl.solvers.FunctionalLeftScalarMult)
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 places = 3 if space.dtype == np.float32 else 5 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 almost_equal(lmul_func(x), scalar * func(x), places=places) # Test gradient of left scalar multiplication assert all_almost_equal(lmul_func.gradient(x), scalar * func.gradient(x), places=places) # Test derivative of left scalar multiplication p = noise_element(space) assert all_almost_equal(lmul_func.derivative(x)(p), scalar * (func.derivative(x))(p), places=places) # 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), places=places) # 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_L2_norm(space, sigma): """Test the L2-norm.""" func = odl.solvers.L2Norm(space) x = noise_element(space) x_norm = x.norm() # Test functional evaluation expected_result = np.sqrt((x ** 2).inner(space.one())) assert pytest.approx(func(x), expected_result) # Test gradient if x_norm > 0: expected_result = x / x.norm() assert all_almost_equal(func.gradient(x), expected_result) # Verify that the gradient at zero is zero assert all_almost_equal(func.gradient(func.domain.zero()), space.zero()) # Test proximal operator - expecting # x * (1 - sigma/||x||) if ||x|| > sigma, 0 else norm_less_than_sigma = 0.99 * sigma * x / x_norm assert all_almost_equal(func.proximal(sigma)(norm_less_than_sigma), space.zero()) norm_larger_than_sigma = 1.01 * sigma * x / x_norm expected_result = (norm_larger_than_sigma * (1.0 - sigma / norm_larger_than_sigma.norm())) assert all_almost_equal(func.proximal(sigma)(norm_larger_than_sigma), expected_result) # Test convex conjugate func_cc = func.convex_conj # Test evaluation of the convex conjugate - expecting # 0 if ||x|| < 1, infty else norm_larger_than_one = 1.01 * x / x_norm assert func_cc(norm_larger_than_one) == np.inf norm_less_than_one = 0.99 * x / x_norm assert func_cc(norm_less_than_one) == 0 # Gradient of the convex conjugate (not implemeted) with pytest.raises(NotImplementedError): func_cc.gradient # Test the proximal of the convex conjugate - expecting # x if ||x||_2 < 1, x/||x|| else if x_norm < 1: expected_result = x else: expected_result = x / x_norm assert all_almost_equal(func_cc.proximal(sigma)(x), expected_result) # Verify that the biconjugate is the functional itself func_cc_cc = func_cc.convex_conj assert pytest.approx(func_cc_cc(x), func(x))
def test_part_deriv(space, method, padding): """Discretized partial derivative.""" with pytest.raises(TypeError): PartialDerivative(odl.rn(1)) if isinstance(padding, tuple): pad_mode, pad_const = padding else: pad_mode, pad_const = padding, 0 # discretized space dom_vec = noise_element(space) dom_vec_arr = dom_vec.asarray() # operator 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.asarray(), 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 almost_equal(lhs, rhs, places=4)
def test_forward_backward_input_handling(): """Test to see that input is handled correctly.""" space1 = odl.uniform_discr(0, 1, 10) lin_ops = [odl.ZeroOperator(space1), odl.ZeroOperator(space1)] g = [odl.solvers.ZeroFunctional(space1), odl.solvers.ZeroFunctional(space1)] f = odl.solvers.ZeroFunctional(space1) h = odl.solvers.ZeroFunctional(space1) # Check that the algorithm runs. With the above operators, the algorithm # returns the input. x0 = noise_element(space1) x = x0.copy() niter = 3 forward_backward_pd(x, f, g, lin_ops, h, tau=1.0, sigma=[1.0, 1.0], niter=niter) assert x == x0 # Testing that sizes needs to agree: # Too few sigma_i:s with pytest.raises(ValueError): forward_backward_pd(x, f, g, lin_ops, h, tau=1.0, sigma=[1.0], niter=niter) # Too many operators g_too_many = [odl.solvers.ZeroFunctional(space1), odl.solvers.ZeroFunctional(space1), odl.solvers.ZeroFunctional(space1)] with pytest.raises(ValueError): forward_backward_pd(x, f, g_too_many, lin_ops, h, tau=1.0, sigma=[1.0, 1.0], niter=niter) # Test for correct space space2 = odl.uniform_discr(1, 2, 10) x = noise_element(space2) with pytest.raises(ValueError): forward_backward_pd(x, f, g, lin_ops, h, tau=1.0, sigma=[1.0, 1.0], niter=niter)
def test_functional_plus_scalar(space): """Test for sum of functioanl and scalar.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 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 almost_equal(func_scalar_sum(x), func(x) + scalar, places=places) # Test for derivative and gradient assert all_almost_equal(func_scalar_sum.gradient(x), func.gradient(x), places=places) assert almost_equal(func_scalar_sum.derivative(x)(p), func.gradient(x).inner(p), places=places) # Test proximal operator sigma = 1.2 assert all_almost_equal(func_scalar_sum.proximal(sigma)(x), func.proximal(sigma)(x), places=places) # Test convex conjugate functional assert almost_equal(func_scalar_sum.convex_conj(x), func.convex_conj(x) - scalar, places=places) assert all_almost_equal(func_scalar_sum.convex_conj.gradient(x), func.convex_conj.gradient(x), places=places)
def test_functional_sum(space): """Test for the sum of two functionals.""" # Less strict checking for single precision places = 3 if space.dtype == np.float32 else 5 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 almost_equal(func_sum(x), func1(x) + func2(x), places=places) # Test gradient and derivative assert all_almost_equal(func_sum.gradient(x), func1.gradient(x) + func2.gradient(x), places=places) assert almost_equal( func_sum.derivative(x)(p), func1.gradient(x).inner(p) + func2.gradient(x).inner(p), places=places) # 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_moreau_envelope_l2_sq(space, sigma): """Test for the Moreau envelope with l2 norm squared.""" # Result is ||x||_2^2 / (1 + 2 sigma) # Gradient is x * 2 / (1 + 2 * sigma) l2_sq = odl.solvers.L2NormSquared(space) smoothed_l2_sq = odl.solvers.MoreauEnvelope(l2_sq, sigma=sigma) x = noise_element(space) assert all_almost_equal(smoothed_l2_sq.gradient(x), x * 2 / (1 + 2 * sigma))
def test_copy(fn): import copy x = noise_element(fn) y = copy.copy(x) assert x == y assert y is not x z = copy.deepcopy(x) assert x == z assert z is not x
def test_derivative(functional): """Test for the derivative of a functional. The test checks that the directional derivative in a point is the same as the inner product of the gradient and the direction, if the gradient is defined. """ if isinstance(functional, odl.solvers.functional.IndicatorLpUnitBall): # IndicatorFunction has no derivative with pytest.raises(NotImplementedError): functional.derivative(functional.domain.zero()) return x = noise_element(functional.domain) y = 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.ufunc.absolute() y = y.ufunc.absolute() if isinstance(functional, KullbackLeiblerConvexConj): # The functional is not defined for values >= 1 x = x - x.ufunc.max() + 0.99 y = y - y.ufunc.max() + 0.99 # Compute a "small" step size according to dtype of space step = float(np.sqrt(np.finfo(functional.domain.dtype).eps)) # Numerical test of gradient, only low accuracy can be guaranteed. assert all_almost_equal((functional(x + step * y) - functional(x)) / step, y.inner(functional.gradient(x)), places=1) # Check that derivative and gradient is consistent assert all_almost_equal(functional.derivative(x)(y), y.inner(functional.gradient(x)))