Skip to content

Commit f22e762

Browse files
committed
ENH/DOC/TST: add FunctionalRightVectorMult and in-dept doc for functionals.
FunctionalRightVectorMult was implemeted before via OperatorRightVectorMult. However, this is an operator and not a functional, why FunctionalRightVectorMult has been explicitly added. The commit also add tests for FunctionalRightVectorMult. The commit also contains a (start for) an in-dept doc for functionals.
1 parent 85cdd65 commit f22e762

File tree

4 files changed

+273
-8
lines changed

4 files changed

+273
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
.. _functional_in_depth:
2+
3+
#####################
4+
Functional
5+
#####################
6+
7+
A *functional* is an operator ``f`` that maps from some vector space ``X`` to the field of scalars ``F`` associated with the vector space:
8+
9+
.. math::
10+
11+
f : X \to F.
12+
13+
In the ODL solver package, functionals are implemented in the `Functional` class, a subclass to `Operator`.
14+
15+
From a mathematical presepective, the above is a valide definition of a functional. However, since the purpose of these functionals are primarely to be used for solving optimization problems, the following assumptions are made:
16+
17+
* the vector space ``X`` is a Hilbert space.
18+
* the field of scalars ``F`` are the real numbers.
19+
20+
It is possible to use the class without fulfilling these assumtptions, however in this case some of the mathematical results might not hold.
21+
22+
23+
Implementation of functionals
24+
=============================
25+
26+
To define your own functional, you start by writing::
27+
28+
class MyFunctional(odl.solvers.Functional):
29+
...
30+
31+
Since `Functional` is a subclass of `Operator`, it has the *abstract method* ``domain`` which need to be explicitly overridden by any subclass.
32+
33+
``domain``: `Set`
34+
The domain of this functional, i.e., the set of elements to which this functional can be applied.
35+
36+
Moreover, there are several optional parameters associated with a functional. These are
37+
38+
``linear`` : `bool`, optional
39+
If `True`, the functional is considered as linear. In this case, ``domain`` and ``range`` have to be instances of `LinearSpace`, or `Field`.
40+
Default: ``False``
41+
smooth : `bool`, optional
42+
If `True`, assume that the functional is continuously differentiable.
43+
Default: ``False``
44+
concave : `bool`, optional
45+
If `True`, assume that the functional is concave.
46+
Default: ``False``
47+
convex : `bool`, optional
48+
If `True`, assume that the functional is convex.
49+
Default: ``False``
50+
grad_lipschitz : 'float', optional
51+
The Lipschitz constant of the gradient.
52+
Default: infinity
53+
54+
A functional also has a number of optional methods and properties associated with it. The default value of these are all to raise a `NotImplemetedError`. The properties are:
55+
56+
* ``gradient``. This returns the gradient operator of the functional, i.e., the operator that corresponds to the mapping
57+
58+
.. math::
59+
60+
x \\to \\nabla f(x)
61+
62+
where :math:`\\nabla f(x)` is the element used to evaluate derivatives in a direction :math:`d` by :math:`\\langle \\nabla f(x), d \\rangle`.
63+
* ``conjugate_functional``. This is the convex conjugate functional, also known as the Legendre transform or Fenchel conjugate. It is defined as
64+
65+
.. math::
66+
67+
f^*(x^*) = \sup_{x \in X} \{ \langle x^*,x \rangle - f(x) \}.
68+
69+
For general linear spaces :math:`X`, :math:`x^* \in X^*` the (continuous/normed) dual space of :math:`X`, i.e., the space of all continuous linear functionals defined on :math:`X`. Then :math:`\langle x^*,x \rangle` is the "dual pairing", i.e., the evaluation of the linear functional :math:`x^*` in the point :math:`x`. However, Hilbert spaces are self-dual, meaning :math:`X^* = X`, and :math:`\langle x^*,x \rangle` is the inner product.
70+
71+
The optional method is
72+
73+
* ``proximal(sigma)``. This returns the proximal operator of the functional, where ``sigma`` is a nonnegative step-size like parameter. The proximal operator is defined as
74+
75+
.. math::
76+
77+
\text{prox}_{\sigma f}(x) = \text{arg min}_{y \in X} \{ f(y) + \frac{1}{2\sigma} \|y - x\|_2^2 \}
78+
79+
The `Functional` class also contains two default implementations of two help functions:
80+
81+
* ``derivative(point)``. Given an implementation of the gradient, this method return the (directional) derivative operator in ``point``. This is the linear operator
82+
83+
.. math::
84+
x \to \langle x, \nabla f(point) \rangle,
85+
86+
where :math:`\nabla f(point)` is the gradient of the functional in the point :math:`point`.
87+
* ``translate(shift)``. Give a functional :math:`f(.)`, this method creates the functional :math:`f(. - shift)`
88+
89+
90+
Functional arithmetics
91+
======================
92+
It is common in applications to perform arithmetic operations with functionals, for example adding two functionals :math:`f` and :math:`g`
93+
94+
.. math::
95+
[f+g](x) = f(x) + g(x),
96+
97+
or multiplication of a functional by a scalar
98+
99+
.. math::
100+
[\alpha f](x) = \alpha f (x).
101+
102+
Another example is translating a functional with a vecotr :math:`y`
103+
104+
.. math::
105+
f(x - y),
106+
107+
or given an `Operator` :math:`A` whose range is the same as the domain as the functional we also have composition
108+
109+
.. math::
110+
[f * A](x) = f(A(x)).
111+
112+
In some of these cases, properties and methods such as ``gradient``, ``convex_conjugate`` and ``proximal`` can be calculated automatically given a default implementation of the corresponding property in :math:`f`.
113+
114+
All available functional arithmetic, including which properties and methods that automatically can be calculated, is shown below. ``f``, ``g`` represent `Functional`'s, and ``A`` an `Operator` whose range is the same as the domain as the functional. `` a`` is a scalar in the field of the domain of ``f`` and ``g``, and ``y`` is a vector in the domain of ``f`` and ``g``.
115+
116+
+------------------+-----------------+-------------------------------------------------+
117+
| Code | Meaning | Class |
118+
+==================+=================+=================================================+
119+
| ``(f + g)(x)`` | ``f(x) + g(x)`` | `FunctionalSum` |
120+
| | | - Retains `gradient`. |
121+
+------------------+-----------------+-------------------------------------------------+
122+
| ``(f + a)(x) | ``f(x) + a`` | `FunctionalScalarSum` |
123+
| | | - Retains all properties. |
124+
+------------------+-----------------+-------------------------------------------------+
125+
| ``(f * A)(x)`` | ``f(A(x))`` | `FunctionalComp` |
126+
| | | - Retains gradient |
127+
+------------------+-----------------+-------------------------------------------------+
128+
| ``(a * f)(x)`` | ``a * f(x)`` | `FunctionalLeftScalarMult` |
129+
| | | - Retains all properties, if ``a`` is positive. |
130+
+------------------+-----------------+-------------------------------------------------+
131+
| ``(f * a)(x)`` | ``f(a * x)`` | `FunctionalRightScalarMult` |
132+
| | | - Retains all properties |
133+
+------------------+-----------------+-------------------------------------------------+
134+
| ``(v * f)(x)`` | ``v * f(x)`` | `FunctionalLeftVectorMult` |
135+
| | | - Note that this is not a functional anymore. |
136+
+------------------+-----------------+-------------------------------------------------+
137+
| ``(f * v)(x)`` | ``f(v * x)`` | `FunctionalRightVectorMult` |
138+
| | | - Retains gradient and convex conjugate. |
139+
+------------------+-----------------+-------------------------------------------------+
140+
| ``f.translate(y) | ``f(. - y)`` | `TranslatedFunctional` |
141+
| | | - Retains all properties. |
142+
+------------------+-----------------+-------------------------------------------------+
143+
144+
145+
146+
147+
148+
149+

doc/source/guide/in_depth/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ This is a more in depth guide to the different parts of ODL.
1212
vectorization_guide
1313
numpy_guide
1414
chambolle_pock_guide
15+
functional_guide

odl/solvers/functional/functional.py

+106-4
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@
3535

3636

3737
# TODO: Add missing functionals here
38-
__all__ = ('Functional', 'ConvexConjugateArgScaling',
39-
'FunctionalLeftScalarMult',
40-
'ConvexConjugateFuncScaling', 'ConvexConjugateLinearPerturb',
41-
'ConvexConjugateTranslation', 'TranslatedFunctional')
38+
__all__ = ('Functional', 'FunctionalLeftScalarMult',
39+
'FunctionalRightScalarMult', 'FunctionalComp',
40+
'FunctionalRightVectorMult', 'FunctionalSum', 'FunctionalScalarSum',
41+
'TranslatedFunctional', 'ConvexConjugateTranslation',
42+
'ConvexConjugateFuncScaling', 'ConvexConjugateArgScaling',
43+
'ConvexConjugateLinearPerturb')
4244

4345

4446
class Functional(Operator):
@@ -278,6 +280,8 @@ def __mul__(self, other):
278280
return FunctionalLeftScalarMult(self, other)
279281
else:
280282
return FunctionalRightScalarMult(self, other)
283+
elif isinstance(other, LinearSpaceVector) and other in self.domain:
284+
return FunctionalRightVectorMult(self, other)
281285
else:
282286
return super().__mul__(other)
283287

@@ -728,6 +732,104 @@ def _call(self, x):
728732
return CompositGradient()
729733

730734

735+
class FunctionalRightVectorMult(Functional, OperatorRightVectorMult):
736+
737+
"""Expression type for the functional right vector multiplication.
738+
739+
Given a functional ``func`` and a vector ``y`` in the domain of ``func``,
740+
this corresponds to the functional
741+
742+
``(func * y)(x) = func(y*x)``.
743+
"""
744+
745+
def __init__(self, func, vector):
746+
"""Initialize a new instance.
747+
748+
Parameters
749+
----------
750+
func : `Functional`
751+
The domain of ``func`` must be a ``vector.space``.
752+
vector : `LinearSpaceVector` in ``func.domain``
753+
The vector to multiply by.
754+
"""
755+
if not isinstance(func, Functional):
756+
raise TypeError('functional {!r} is not a Functional instance.'
757+
''.format(func))
758+
759+
OperatorRightVectorMult.__init__(self, operator=func, vector=vector)
760+
761+
# TODO: can some of the parameters convex, etc. be decided?
762+
Functional.__init__(self, domain=func.domain)
763+
764+
@property
765+
def gradient(self):
766+
"""Gradient operator of the functional.
767+
768+
Notes
769+
-----
770+
The operator that corresponds to the mapping
771+
772+
.. math::
773+
774+
x \\to \\nabla f(x)
775+
776+
where :math:`\\nabla f(x)` is the element used to evaluate
777+
derivatives in a direction :math:`d` by
778+
:math:`\\langle \\nabla f(x), d \\rangle`.
779+
"""
780+
return self.vector * self.operator.gradient * self.vector
781+
782+
# TODO: can this be computed?
783+
def proximal(self, sigma=1.0):
784+
"""Return the proximal operator of the functional.
785+
786+
Parameters
787+
----------
788+
sigma : positive float, optional
789+
Regularization parameter of the proximal operator.
790+
791+
Returns
792+
-------
793+
out : `Operator`
794+
Domain and range equal to domain of functional.
795+
796+
Notes
797+
-----
798+
The nonsmooth solvers that make use of proximal operators in order to
799+
solve a given optimization problem, see for example
800+
`forward_backward_pd`, take a `proximal factory` as input. Note that
801+
``Functional.proximal`` is in fact a `proximal factory`.
802+
"""
803+
raise NotImplementedError('there is no known expression for this')
804+
805+
@property
806+
def conjugate_functional(self):
807+
"""Convex conjugate functional of the functional.
808+
809+
Notes
810+
-----
811+
The convex conjugate functional of a convex functional :math:`f(x)`,
812+
defined on a Hilber space, is defined as the functional
813+
814+
.. math::
815+
816+
f^*(x^*) = \\sup_{x} \{ \\langle x^*,x \\rangle - f(x) \}.
817+
818+
The concept is also known as the Legendre transformation.
819+
820+
References
821+
----------
822+
Wikipedia article on `Convex conjugate
823+
<https://en.wikipedia.org/wiki/Convex_conjugate>`_.
824+
825+
Wikipedia article on `Legendre transformation
826+
https://en.wikipedia.org/wiki/Legendre_transformation
827+
828+
For literature references see, e.g., [Lue1969]_, [Roc1970]_.
829+
"""
830+
return self.operator.conjugate_functional * (1.0 / self.vector)
831+
832+
731833
class FunctionalSum(Functional, OperatorSum):
732834

733835
"""Expression type for the sum of functionals.

test/solvers/functional/functional_test.py

+17-4
Original file line numberDiff line numberDiff line change
@@ -330,17 +330,30 @@ def test_multiplication_with_vector():
330330

331331
x = example_element(space)
332332
y = example_element(space)
333-
func = odl.solvers.L1Norm(space)
333+
func = odl.solvers.L2NormSquare(space)
334334

335335
wrong_space = odl.uniform_discr(1, 2, 10)
336336
y_other_space = example_element(wrong_space)
337337

338-
# Multiplication from the right. Make sure it is a OperatorRightVectorMult
338+
# Multiplication from the right. Make sure it is a
339+
# FunctionalRightVectorMult
339340
func_times_y = func * y
340-
assert isinstance(func_times_y, odl.OperatorRightVectorMult)
341+
assert isinstance(func_times_y, odl.solvers.FunctionalRightVectorMult)
341342

342343
expected_result = func(y * x)
343-
assert almost_equal((func * y)(x), expected_result, places=PLACES)
344+
assert almost_equal(func_times_y(x), expected_result, places=PLACES)
345+
346+
# Test for the gradient.
347+
# Explicit calculations: 2*y*y*x
348+
expected_result = 2.0 * y * y * x
349+
assert all_almost_equal(func_times_y.gradient(x), expected_result,
350+
places=PLACES)
351+
352+
# Test for conjugate_functional
353+
cc_func_times_y = func_times_y.conjugate_functional
354+
# Explicit calculations: 1/4 * ||x/y||_2^2
355+
expected_result = 1.0 / 4.0 * (x / y).norm()**2
356+
assert almost_equal(cc_func_times_y(x), expected_result, places=PLACES)
344357

345358
# Make sure that right muliplication is not allowed with vector from
346359
# another space

0 commit comments

Comments
 (0)