Commit 781be410 authored by Martin Reinecke's avatar Martin Reinecke
Browse files

Merge branch 'NIFTy_6' into timing_helpers

parents 9587d27e af021fd3
Pipeline #64514 passed with stages
in 9 minutes and 52 seconds
...@@ -37,7 +37,7 @@ build_docker_from_cache: ...@@ -37,7 +37,7 @@ build_docker_from_cache:
test_serial: test_serial:
stage: test stage: test
script: script:
- pytest-3 -q --cov=nifty5 test - pytest-3 -q --cov=nifty6 test
- > - >
python3 -m coverage report --omit "*plot*,*distributed_do*" | tee coverage.txt python3 -m coverage report --omit "*plot*,*distributed_do*" | tee coverage.txt
- > - >
......
NIFTy - Numerical Information Field Theory NIFTy - Numerical Information Field Theory
========================================== ==========================================
[![build status](https://gitlab.mpcdf.mpg.de/ift/NIFTy/badges/NIFTy_5/build.svg)](https://gitlab.mpcdf.mpg.de/ift/NIFTy/commits/NIFTy_5) [![build status](https://gitlab.mpcdf.mpg.de/ift/NIFTy/badges/NIFTy_6/build.svg)](https://gitlab.mpcdf.mpg.de/ift/NIFTy/commits/NIFTy_6)
[![coverage report](https://gitlab.mpcdf.mpg.de/ift/NIFTy/badges/NIFTy_5/coverage.svg)](https://gitlab.mpcdf.mpg.de/ift/NIFTy/commits/NIFTy_5) [![coverage report](https://gitlab.mpcdf.mpg.de/ift/NIFTy/badges/NIFTy_6/coverage.svg)](https://gitlab.mpcdf.mpg.de/ift/NIFTy/commits/NIFTy_6)
**NIFTy** project homepage: **NIFTy** project homepage:
[http://ift.pages.mpcdf.de/nifty](http://ift.pages.mpcdf.de/nifty) [http://ift.pages.mpcdf.de/nifty](http://ift.pages.mpcdf.de/nifty)
...@@ -59,8 +59,8 @@ Optional dependencies: ...@@ -59,8 +59,8 @@ Optional dependencies:
### Sources ### Sources
The current version of Nifty5 can be obtained by cloning the repository and The current version of NIFTy6 can be obtained by cloning the repository and
switching to the NIFTy_5 branch: switching to the NIFTy_6 branch:
git clone https://gitlab.mpcdf.mpg.de/ift/nifty.git git clone https://gitlab.mpcdf.mpg.de/ift/nifty.git
...@@ -69,10 +69,10 @@ switching to the NIFTy_5 branch: ...@@ -69,10 +69,10 @@ switching to the NIFTy_5 branch:
In the following, we assume a Debian-based distribution. For other In the following, we assume a Debian-based distribution. For other
distributions, the "apt" lines will need slight changes. distributions, the "apt" lines will need slight changes.
NIFTy5 and its mandatory dependencies can be installed via: NIFTy6 and its mandatory dependencies can be installed via:
sudo apt-get install git python3 python3-pip python3-dev sudo apt-get install git python3 python3-pip python3-dev
pip3 install --user git+https://gitlab.mpcdf.mpg.de/ift/nifty.git@NIFTy_5 pip3 install --user git+https://gitlab.mpcdf.mpg.de/ift/nifty.git@NIFTy_6
pip3 install --user git+https://gitlab.mpcdf.mpg.de/mtr/pypocketfft pip3 install --user git+https://gitlab.mpcdf.mpg.de/mtr/pypocketfft
Plotting support is added via: Plotting support is added via:
...@@ -100,7 +100,7 @@ To run the tests, additional packages are required: ...@@ -100,7 +100,7 @@ To run the tests, additional packages are required:
Afterwards the tests (including a coverage report) can be run using the Afterwards the tests (including a coverage report) can be run using the
following command in the repository root: following command in the repository root:
pytest-3 --cov=nifty5 test pytest-3 --cov=nifty6 test
### First Steps ### First Steps
......
...@@ -141,7 +141,7 @@ ...@@ -141,7 +141,7 @@
"source": [ "source": [
"import numpy as np\n", "import numpy as np\n",
"np.random.seed(40)\n", "np.random.seed(40)\n",
"import nifty5 as ift\n", "import nifty6 as ift\n",
"import matplotlib.pyplot as plt\n", "import matplotlib.pyplot as plt\n",
"%matplotlib inline" "%matplotlib inline"
] ]
......
...@@ -3,7 +3,7 @@ from time import time ...@@ -3,7 +3,7 @@ from time import time
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
import nifty5 as ift import nifty6 as ift
np.random.seed(40) np.random.seed(40)
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
import numpy as np import numpy as np
import nifty5 as ift import nifty6 as ift
if __name__ == '__main__': if __name__ == '__main__':
np.random.seed(41) np.random.seed(41)
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
import numpy as np import numpy as np
import nifty5 as ift import nifty6 as ift
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
......
...@@ -25,7 +25,7 @@ import sys ...@@ -25,7 +25,7 @@ import sys
import numpy as np import numpy as np
import nifty5 as ift import nifty6 as ift
def make_checkerboard_mask(position_space): def make_checkerboard_mask(position_space):
......
...@@ -25,7 +25,7 @@ import sys ...@@ -25,7 +25,7 @@ import sys
import numpy as np import numpy as np
import nifty5 as ift import nifty6 as ift
def exposure_2d(): def exposure_2d():
......
...@@ -29,7 +29,7 @@ import sys ...@@ -29,7 +29,7 @@ import sys
import numpy as np import numpy as np
import nifty5 as ift import nifty6 as ift
def random_los(n_los): def random_los(n_los):
......
...@@ -29,7 +29,7 @@ import sys ...@@ -29,7 +29,7 @@ import sys
import numpy as np import numpy as np
import nifty5 as ift import nifty6 as ift
class SingleDomain(ift.LinearOperator): class SingleDomain(ift.LinearOperator):
......
import numpy as np import numpy as np
import nifty5 as ift import nifty6 as ift
def convtest(test_signal, delta, func): def convtest(test_signal, delta, func):
......
import nifty5 as ift import nifty6 as ift
import numpy as np import numpy as np
......
import nifty5 as ift import nifty6 as ift
import numpy as np import numpy as np
np.random.seed(42) np.random.seed(42)
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
import nifty5 as ift import nifty6 as ift
np.random.seed(12) np.random.seed(12)
......
rm -rf docs/build docs/source/mod rm -rf docs/build docs/source/mod
sphinx-apidoc -e -o docs/source/mod nifty5 sphinx-apidoc -e -o docs/source/mod nifty6
sphinx-build -b html docs/source/ docs/build/ sphinx-build -b html docs/source/ docs/build/
...@@ -2,8 +2,8 @@ NIFTy-related publications ...@@ -2,8 +2,8 @@ NIFTy-related publications
========================== ==========================
:: ::
@article{asclnifty5, @article{asclnifty6,
title={NIFTy5: Numerical Information Field Theory v5}, title={NIFTy6: Numerical Information Field Theory v5},
author={Arras, Philipp and Baltac, Mihai and Ensslin, Torsten A and Frank, Philipp and Hutschenreuter, Sebastian and Knollmueller, Jakob and Leike, Reimar and Newrzella, Max-Niklas and Platz, Lukas and Reinecke, Martin and others}, author={Arras, Philipp and Baltac, Mihai and Ensslin, Torsten A and Frank, Philipp and Hutschenreuter, Sebastian and Knollmueller, Jakob and Leike, Reimar and Newrzella, Max-Niklas and Platz, Lukas and Reinecke, Martin and others},
journal={Astrophysics Source Code Library}, journal={Astrophysics Source Code Library},
year={2019} year={2019}
...@@ -13,7 +13,7 @@ NIFTy-related publications ...@@ -13,7 +13,7 @@ NIFTy-related publications
author = {{Martin Reinecke, Theo Steininger, Marco Selig}}, author = {{Martin Reinecke, Theo Steininger, Marco Selig}},
title = {NIFTy -- Numerical Information Field TheorY}, title = {NIFTy -- Numerical Information Field TheorY},
url = {https://gitlab.mpcdf.mpg.de/ift/NIFTy}, url = {https://gitlab.mpcdf.mpg.de/ift/NIFTy},
version = {nifty5}, version = {nifty6},
date = {2018-04-05}, date = {2018-04-05},
} }
......
...@@ -36,9 +36,9 @@ Domains ...@@ -36,9 +36,9 @@ Domains
Abstract base class Abstract base class
------------------- -------------------
.. currentmodule:: nifty5.domains.domain .. currentmodule:: nifty6.domains.domain
One of the fundamental building blocks of the NIFTy5 framework is the *domain*. One of the fundamental building blocks of the NIFTy6 framework is the *domain*.
Its required capabilities are expressed by the abstract :py:class:`Domain` class. Its required capabilities are expressed by the abstract :py:class:`Domain` class.
A domain must be able to answer the following queries: A domain must be able to answer the following queries:
...@@ -52,7 +52,7 @@ A domain must be able to answer the following queries: ...@@ -52,7 +52,7 @@ A domain must be able to answer the following queries:
Unstructured domains Unstructured domains
-------------------- --------------------
.. currentmodule:: nifty5.domains.unstructured_domain .. currentmodule:: nifty6.domains.unstructured_domain
Domains can be either *structured* (i.e. there is geometrical information Domains can be either *structured* (i.e. there is geometrical information
associated with them, like position in space and volume factors), associated with them, like position in space and volume factors),
...@@ -65,7 +65,7 @@ Unstructured domains can be described by instances of NIFTy's ...@@ -65,7 +65,7 @@ Unstructured domains can be described by instances of NIFTy's
Structured domains Structured domains
------------------ ------------------
.. currentmodule:: nifty5.domains.structured_domain .. currentmodule:: nifty6.domains.structured_domain
In contrast to unstructured domains, these domains have an assigned geometry. In contrast to unstructured domains, these domains have an assigned geometry.
NIFTy requires them to provide the volume elements of their grid cells. NIFTy requires them to provide the volume elements of their grid cells.
...@@ -86,7 +86,7 @@ The additional methods are specified in the abstract class ...@@ -86,7 +86,7 @@ The additional methods are specified in the abstract class
NIFTy comes with several concrete subclasses of :class:`StructuredDomain`: NIFTy comes with several concrete subclasses of :class:`StructuredDomain`:
.. currentmodule:: nifty5.domains .. currentmodule:: nifty6.domains
- :class:`~rg_space.RGSpace` represents a regular Cartesian grid with an arbitrary - :class:`~rg_space.RGSpace` represents a regular Cartesian grid with an arbitrary
number of dimensions, which is supposed to be periodic in each dimension. number of dimensions, which is supposed to be periodic in each dimension.
...@@ -123,7 +123,7 @@ Some examples are: ...@@ -123,7 +123,7 @@ Some examples are:
(on a harmonic domain) and a few inferred model parameters (e.g. on an (on a harmonic domain) and a few inferred model parameters (e.g. on an
unstructured grid). unstructured grid).
.. currentmodule:: nifty5 .. currentmodule:: nifty6
Consequently, NIFTy defines a class called :class:`~domain_tuple.DomainTuple` Consequently, NIFTy defines a class called :class:`~domain_tuple.DomainTuple`
holding a sequence of :class:`~domains.domain.Domain` objects. The full domain is holding a sequence of :class:`~domains.domain.Domain` objects. The full domain is
...@@ -228,7 +228,7 @@ Advanced operators ...@@ -228,7 +228,7 @@ Advanced operators
NIFTy provides a library of commonly employed operators which can be used for NIFTy provides a library of commonly employed operators which can be used for
specific inference problems. Currently these are: specific inference problems. Currently these are:
.. currentmodule:: nifty5.library .. currentmodule:: nifty6.library
- :class:`~smooth_linear_amplitude.SLAmplitude`, which returns a smooth power spectrum. - :class:`~smooth_linear_amplitude.SLAmplitude`, which returns a smooth power spectrum.
- :class:`~inverse_gamma_operator.InverseGammaOperator`, which models point sources which are - :class:`~inverse_gamma_operator.InverseGammaOperator`, which models point sources which are
...@@ -240,9 +240,9 @@ specific inference problems. Currently these are: ...@@ -240,9 +240,9 @@ specific inference problems. Currently these are:
Linear Operators Linear Operators
================ ================
.. currentmodule:: nifty5.operators .. currentmodule:: nifty6.operators
A linear operator (represented by NIFTy5's abstract A linear operator (represented by NIFTy6's abstract
:class:`~linear_operator.LinearOperator` class) is derived from :class:`~linear_operator.LinearOperator` class) is derived from
:class:`~operator.Operator` and can be interpreted as an (implicitly defined) :class:`~operator.Operator` and can be interpreted as an (implicitly defined)
matrix. Since its operation is linear, it can provide some additional matrix. Since its operation is linear, it can provide some additional
...@@ -282,7 +282,7 @@ This functionality is provided by NIFTy's ...@@ -282,7 +282,7 @@ This functionality is provided by NIFTy's
:class:`~inversion_enabler.InversionEnabler` class, which is itself a linear :class:`~inversion_enabler.InversionEnabler` class, which is itself a linear
operator. operator.
.. currentmodule:: nifty5.operators.operator .. currentmodule:: nifty6.operators.operator
Direct multiplication and adjoint inverse multiplication transform a field Direct multiplication and adjoint inverse multiplication transform a field
defined on the operator's :attr:`~Operator.domain` to one defined on the defined on the operator's :attr:`~Operator.domain` to one defined on the
...@@ -290,7 +290,7 @@ operator's :attr:`~Operator.target`, whereas adjoint multiplication and inverse ...@@ -290,7 +290,7 @@ operator's :attr:`~Operator.target`, whereas adjoint multiplication and inverse
multiplication transform from :attr:`~Operator.target` to multiplication transform from :attr:`~Operator.target` to
:attr:`~Operator.domain`. :attr:`~Operator.domain`.
.. currentmodule:: nifty5.operators .. currentmodule:: nifty6.operators
Operators with identical domain and target can be derived from Operators with identical domain and target can be derived from
:class:`~endomorphic_operator.EndomorphicOperator`. Typical examples for this :class:`~endomorphic_operator.EndomorphicOperator`. Typical examples for this
...@@ -299,7 +299,7 @@ multiplies its input by a scalar value, and ...@@ -299,7 +299,7 @@ multiplies its input by a scalar value, and
:class:`~diagonal_operator.DiagonalOperator`, which multiplies every value of :class:`~diagonal_operator.DiagonalOperator`, which multiplies every value of
its input field with potentially different values. its input field with potentially different values.
.. currentmodule:: nifty5 .. currentmodule:: nifty6
Further operator classes provided by NIFTy are Further operator classes provided by NIFTy are
...@@ -317,7 +317,7 @@ Further operator classes provided by NIFTy are ...@@ -317,7 +317,7 @@ Further operator classes provided by NIFTy are
Syntactic sugar Syntactic sugar
--------------- ---------------
Nifty5 allows simple and intuitive construction of altered and combined NIFTy allows simple and intuitive construction of altered and combined
operators. operators.
As an example, if ``A``, ``B`` and ``C`` are of type :class:`~operators.linear_operator.LinearOperator` As an example, if ``A``, ``B`` and ``C`` are of type :class:`~operators.linear_operator.LinearOperator`
and ``f1`` and ``f2`` are of type :class:`~field.Field`, writing:: and ``f1`` and ``f2`` are of type :class:`~field.Field`, writing::
...@@ -325,7 +325,7 @@ and ``f1`` and ``f2`` are of type :class:`~field.Field`, writing:: ...@@ -325,7 +325,7 @@ and ``f1`` and ``f2`` are of type :class:`~field.Field`, writing::
X = A(B.inverse(A.adjoint)) + C X = A(B.inverse(A.adjoint)) + C
f2 = X(f1) f2 = X(f1)
.. currentmodule:: nifty5.operators.linear_operator .. currentmodule:: nifty6.operators.linear_operator
will perform the operation suggested intuitively by the notation, checking will perform the operation suggested intuitively by the notation, checking
domain compatibility while building the composed operator. domain compatibility while building the composed operator.
...@@ -349,12 +349,12 @@ Minimization ...@@ -349,12 +349,12 @@ Minimization
Most problems in IFT are solved by (possibly nested) minimizations of Most problems in IFT are solved by (possibly nested) minimizations of
high-dimensional functions, which are often nonlinear. high-dimensional functions, which are often nonlinear.
.. currentmodule:: nifty5.minimization .. currentmodule:: nifty6.minimization
Energy functionals Energy functionals
------------------ ------------------
In NIFTy5 such functions are represented by objects of type In NIFTy6 such functions are represented by objects of type
:class:`~energy.Energy`. These hold the prescription how to calculate the :class:`~energy.Energy`. These hold the prescription how to calculate the
function's :attr:`~energy.Energy.value`, :attr:`~energy.Energy.gradient` and function's :attr:`~energy.Energy.value`, :attr:`~energy.Energy.gradient` and
(optionally) :attr:`~energy.Energy.metric` at any given (optionally) :attr:`~energy.Energy.metric` at any given
...@@ -362,11 +362,11 @@ function's :attr:`~energy.Energy.value`, :attr:`~energy.Energy.gradient` and ...@@ -362,11 +362,11 @@ function's :attr:`~energy.Energy.value`, :attr:`~energy.Energy.gradient` and
floating-point scalars, gradients have the form of fields defined on the energy's floating-point scalars, gradients have the form of fields defined on the energy's
position domain, and metrics are represented by linear operator objects. position domain, and metrics are represented by linear operator objects.
.. currentmodule:: nifty5 .. currentmodule:: nifty6
Energies are classes that typically have to be provided by the user when Energies are classes that typically have to be provided by the user when
tackling new IFT problems. An example of concrete energy classes delivered with tackling new IFT problems. An example of concrete energy classes delivered with
NIFTy5 is :class:`~minimization.quadratic_energy.QuadraticEnergy` (with NIFTy6 is :class:`~minimization.quadratic_energy.QuadraticEnergy` (with
position-independent metric, mainly used with conjugate gradient minimization). position-independent metric, mainly used with conjugate gradient minimization).
For MGVI, NIFTy provides the :class:`~energy.Energy` subclass For MGVI, NIFTy provides the :class:`~energy.Energy` subclass
...@@ -394,14 +394,14 @@ functional for MGVI and minimize it. ...@@ -394,14 +394,14 @@ functional for MGVI and minimize it.
Iteration control Iteration control
----------------- -----------------
.. currentmodule:: nifty5.minimization.iteration_controllers .. currentmodule:: nifty6.minimization.iteration_controllers
Iterative minimization of an energy requires some means of Iterative minimization of an energy requires some means of
checking the quality of the current solution estimate and stopping once checking the quality of the current solution estimate and stopping once
it is sufficiently accurate. In case of numerical problems, the iteration needs it is sufficiently accurate. In case of numerical problems, the iteration needs
to be terminated as well, returning a suitable error description. to be terminated as well, returning a suitable error description.
In NIFTy5, this functionality is encapsulated in the abstract In NIFTy6, this functionality is encapsulated in the abstract
:class:`IterationController` class, which is provided with the initial energy :class:`IterationController` class, which is provided with the initial energy
object before starting the minimization, and is updated with the improved object before starting the minimization, and is updated with the improved
energy after every iteration. Based on this information, it can either continue energy after every iteration. Based on this information, it can either continue
...@@ -418,7 +418,7 @@ in NIFTy can be found below, but users have complete freedom to implement custom ...@@ -418,7 +418,7 @@ in NIFTy can be found below, but users have complete freedom to implement custom
Minimization algorithms Minimization algorithms
----------------------- -----------------------
.. currentmodule:: nifty5.minimization .. currentmodule:: nifty6.minimization
All minimization algorithms in NIFTy inherit from the abstract All minimization algorithms in NIFTy inherit from the abstract
:class:`~minimizer.Minimizer` class, which presents a minimalistic interface :class:`~minimizer.Minimizer` class, which presents a minimalistic interface
...@@ -459,7 +459,7 @@ available under the names :class:`~scipy_minimizer.ScipyCG` and ...@@ -459,7 +459,7 @@ available under the names :class:`~scipy_minimizer.ScipyCG` and
Application to operator inversion Application to operator inversion
--------------------------------- ---------------------------------
.. currentmodule:: nifty5 .. currentmodule:: nifty6
The machinery presented here cannot only be used for minimizing functionals The machinery presented here cannot only be used for minimizing functionals
derived from IFT, but also for the numerical inversion of linear operators, if derived from IFT, but also for the numerical inversion of linear operators, if
......
import nifty5 import nifty6
extensions = [ extensions = [
'sphinx.ext.napoleon', # Support for NumPy and Google style docstrings 'sphinx.ext.napoleon', # Support for NumPy and Google style docstrings
...@@ -15,11 +15,11 @@ napoleon_use_admonition_for_examples = True ...@@ -15,11 +15,11 @@ napoleon_use_admonition_for_examples = True
napoleon_use_admonition_for_references = True napoleon_use_admonition_for_references = True
napoleon_include_special_with_doc = True napoleon_include_special_with_doc = True
project = u'NIFTy5' project = u'NIFTy6'
copyright = u'2013-2019, Max-Planck-Society' copyright = u'2013-2019, Max-Planck-Society'
author = u'Martin Reinecke' author = u'Martin Reinecke'
release = nifty5.version.__version__ release = nifty6.version.__version__
version = release[:-2] version = release[:-2]
language = None language = None
...@@ -30,5 +30,5 @@ html_theme = "sphinx_rtd_theme" ...@@ -30,5 +30,5 @@ html_theme = "sphinx_rtd_theme"
html_logo = 'nifty_logo_black.png' html_logo = 'nifty_logo_black.png'
exclude_patterns = [ exclude_patterns = [
'mod/modules.rst', 'mod/nifty5.git_version.rst', 'mod/nifty5.logger.rst' 'mod/modules.rst', 'mod/nifty6.git_version.rst', 'mod/nifty6.logger.rst'
] ]
...@@ -174,7 +174,7 @@ It only requires minimizing the information Hamiltonian, e.g. by a gradient desc ...@@ -174,7 +174,7 @@ It only requires minimizing the information Hamiltonian, e.g. by a gradient desc
\frac{\partial \mathcal{H}(d,\xi)}{\partial \xi} = 0. \frac{\partial \mathcal{H}(d,\xi)}{\partial \xi} = 0.
NIFTy5 automatically calculates the necessary gradient from a generative model of the signal and the data and uses this to minimize the Hamiltonian. NIFTy6 automatically calculates the necessary gradient from a generative model of the signal and the data and uses this to minimize the Hamiltonian.
However, MAP often provides unsatisfactory results in cases of deep hirachical Bayesian networks. However, MAP often provides unsatisfactory results in cases of deep hirachical Bayesian networks.
The reason for this is that MAP ignores the volume factors in parameter space, which are not to be neglected in deciding whether a solution is reasonable or not. The reason for this is that MAP ignores the volume factors in parameter space, which are not to be neglected in deciding whether a solution is reasonable or not.
...@@ -224,7 +224,7 @@ Thus, only the gradient of the KL is needed with respect to this, which can be e ...@@ -224,7 +224,7 @@ Thus, only the gradient of the KL is needed with respect to this, which can be e
We stochastically estimate the KL-divergence and gradients with a set of samples drawn from the approximate posterior distribution. We stochastically estimate the KL-divergence and gradients with a set of samples drawn from the approximate posterior distribution.
The particular structure of the covariance allows us to draw independent samples solving a certain system of equations. The particular structure of the covariance allows us to draw independent samples solving a certain system of equations.
This KL-divergence for MGVI is implemented in the class :class:`~minimization.metric_gaussian_kl.MetricGaussianKL` within NIFTy5. This KL-divergence for MGVI is implemented in the class :class:`~minimization.metric_gaussian_kl.MetricGaussianKL` within NIFTy6.
The demo `getting_started_3.py` for example not only infers a field this way, but also the power spectrum of the process that has generated the field. The demo `getting_started_3.py` for example not only infers a field this way, but also the power spectrum of the process that has generated the field.
......
...@@ -31,4 +31,4 @@ Contents ...@@ -31,4 +31,4 @@ Contents
installation installation
code code
citations citations
Package Documentation <mod/nifty5> Package Documentation <mod/nifty6>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment