Commit 19469582 authored by Theo Steininger's avatar Theo Steininger

Merge branch 'real_fft_really_integrated' into 'master'

Real fft really integrated

See merge request !184
parents ea9b75ba 790fb1fc
Pipeline #16553 passed with stages
in 16 minutes and 16 seconds
......@@ -2,22 +2,23 @@ import numpy as np
from nifty import RGSpace, PowerSpace, Field, FFTOperator, ComposedOperator,\
DiagonalOperator, ResponseOperator, plotting,\
create_power_operator
create_power_operator, nifty_configuration
from nifty.library import WienerFilterCurvature
if __name__ == "__main__":
distribution_strategy = 'not'
nifty_configuration['default_distribution_strategy'] = 'fftw'
nifty_configuration['harmonic_rg_base'] = 'real'
# Setting up variable parameters
# Typical distance over which the field is correlated
correlation_length = 0.01
correlation_length = 0.05
# Variance of field in position space sqrt(<|s_x|^2>)
field_variance = 2.
# smoothing length of response (in same unit as L)
response_sigma = 0.1
response_sigma = 0.01
# The signal to noise ratio
signal_to_noise = 0.7
......@@ -36,19 +37,17 @@ if __name__ == "__main__":
signal_space = RGSpace([N_pixels, N_pixels], distances=L/N_pixels)
harmonic_space = FFTOperator.get_default_codomain(signal_space)
fft = FFTOperator(harmonic_space, target=signal_space,
domain_dtype=np.complex, target_dtype=np.float)
power_space = PowerSpace(harmonic_space,
distribution_strategy=distribution_strategy)
fft = FFTOperator(harmonic_space, target=signal_space)
power_space = PowerSpace(harmonic_space)
# Creating the mock data
S = create_power_operator(harmonic_space, power_spectrum=power_spectrum,
distribution_strategy=distribution_strategy)
S = create_power_operator(harmonic_space, power_spectrum=power_spectrum)
mock_power = Field(power_space, val=power_spectrum,
distribution_strategy=distribution_strategy)
mock_power = Field(power_space, val=power_spectrum)
np.random.seed(43)
mock_harmonic = mock_power.power_synthesize(real_signal=True)
if nifty_configuration['harmonic_rg_base'] == 'real':
mock_harmonic = mock_harmonic.real
mock_signal = fft(mock_harmonic)
R = ResponseOperator(signal_space, sigma=(response_sigma,))
......@@ -73,9 +72,11 @@ if __name__ == "__main__":
m_s = fft(m)
plotter = plotting.RG2DPlotter()
plotter.title = 'mock_signal.html';
plotter(mock_signal)
plotter.title = 'data.html'
plotter(Field(signal_space,
val=data.val.get_full_data().reshape(signal_space.shape)))
plotter.title = 'map.html'; plotter(m_s)
\ No newline at end of file
plotter.path = 'mock_signal.html'
plotter(mock_signal.real)
plotter.path = 'data.html'
plotter(Field(
signal_space,
val=data.val.get_full_data().real.reshape(signal_space.shape)))
plotter.path = 'map.html'
plotter(m_s.real)
......@@ -70,11 +70,18 @@ variable_default_distribution_strategy = keepers.Variable(
if z == 'fftw' else True),
genus='str')
variable_harmonic_rg_base = keepers.Variable(
'harmonic_rg_base',
['real', 'complex'],
lambda z: z in ['real', 'complex'],
genus='str')
nifty_configuration = keepers.get_Configuration(
name='NIFTy',
variables=[variable_fft_module,
variable_default_field_dtype,
variable_default_distribution_strategy],
variable_default_distribution_strategy,
variable_harmonic_rg_base],
file_name='NIFTy.conf',
search_paths=[os.path.expanduser('~') + "/.config/nifty/",
os.path.expanduser('~') + "/.config/",
......
......@@ -19,7 +19,6 @@
from __future__ import division
import ast
import itertools
import numpy as np
from keepers import Versionable,\
......
......@@ -17,4 +17,4 @@
# and financially supported by the Studienstiftung des deutschen Volkes.
from transformations import *
from fft_operator import FFTOperator
from .fft_operator import FFTOperator
......@@ -142,17 +142,11 @@ class FFTOperator(LinearOperator):
backward_class, self.target[0], self.domain[0], module=module)
# Store the dtype information
if domain_dtype is None:
self.logger.info("Setting domain_dtype to np.complex.")
self.domain_dtype = np.complex
else:
self.domain_dtype = np.dtype(domain_dtype)
self.domain_dtype = \
None if domain_dtype is None else np.dtype(domain_dtype)
if target_dtype is None:
self.logger.info("Setting target_dtype to np.complex.")
self.target_dtype = np.complex
else:
self.target_dtype = np.dtype(target_dtype)
self.target_dtype = \
None if target_dtype is None else np.dtype(target_dtype)
def _times(self, x, spaces):
spaces = utilities.cast_axis_to_tuple(spaces, len(x.domain))
......@@ -172,8 +166,10 @@ class FFTOperator(LinearOperator):
result_domain = list(x.domain)
result_domain[spaces[0]] = self.target[0]
result_dtype = \
new_val.dtype if self.target_dtype is None else self.target_dtype
result_field = x.copy_empty(domain=result_domain,
dtype=self.target_dtype)
dtype=result_dtype)
result_field.set_val(new_val=new_val, copy=True)
return result_field
......@@ -196,8 +192,11 @@ class FFTOperator(LinearOperator):
result_domain = list(x.domain)
result_domain[spaces[0]] = self.domain[0]
result_dtype = \
new_val.dtype if self.domain_dtype is None else self.domain_dtype
result_field = x.copy_empty(domain=result_domain,
dtype=self.domain_dtype)
dtype=result_dtype)
result_field.set_val(new_val=new_val, copy=True)
return result_field
......
......@@ -260,7 +260,7 @@ class MPIFFT(Transform):
p()
if p.has_output:
result = p.output_array
result = p.output_array.copy()
if result.shape != val.shape:
raise ValueError("Output shape is different than input shape. "
"Maybe fftw tries to optimize the "
......
......@@ -43,6 +43,8 @@ class RGRGTransformation(Transformation):
else:
raise ValueError('Unsupported FFT module:' + module)
self.harmonic_base = nifty_configuration['harmonic_rg_base']
# ---Mandatory properties and methods---
@property
......@@ -144,7 +146,31 @@ class RGRGTransformation(Transformation):
val = self._transform.domain.weight(val, power=1, axes=axes)
# Perform the transformation
Tval = self._transform.transform(val, axes, **kwargs)
if self.harmonic_base == 'complex':
Tval = self._transform.transform(val, axes, **kwargs)
else:
if issubclass(val.dtype.type, np.complexfloating):
Tval_real = self._transform.transform(val.real, axes,
**kwargs)
Tval_imag = self._transform.transform(val.imag, axes,
**kwargs)
if self.codomain.harmonic:
Tval_real.data.real += Tval_real.data.imag
Tval_real.data.imag = \
Tval_imag.data.real + Tval_imag.data.imag
else:
Tval_real.data.real -= Tval_real.data.imag
Tval_real.data.imag = \
Tval_imag.data.real - Tval_imag.data.imag
Tval = Tval_real
else:
Tval = self._transform.transform(val, axes, **kwargs)
if self.codomain.harmonic:
Tval.data.real += Tval.data.imag
else:
Tval.data.real -= Tval.data.imag
Tval = Tval.real
if not self._transform.codomain.harmonic:
# correct for inverse fft.
......
......@@ -36,7 +36,7 @@ from d2o import distributed_data_object,\
STRATEGIES as DISTRIBUTION_STRATEGIES
from nifty.spaces.space import Space
from nifty.config import nifty_configuration
class RGSpace(Space):
"""
......@@ -122,33 +122,36 @@ class RGSpace(Space):
# return fixed_points
def hermitianize_inverter(self, x, axes):
# calculate the number of dimensions the input array has
dimensions = len(x.shape)
# prepare the slicing object which will be used for mirroring
slice_primitive = [slice(None), ] * dimensions
# copy the input data
y = x.copy()
# flip in the desired directions
for k in range(len(axes)):
i = axes[k]
slice_picker = slice_primitive[:]
slice_inverter = slice_primitive[:]
if (not self.zerocenter[k]) or self.shape[k] % 2 == 0:
slice_picker[i] = slice(1, None, None)
slice_inverter[i] = slice(None, 0, -1)
else:
slice_picker[i] = slice(None)
slice_inverter[i] = slice(None, None, -1)
slice_picker = tuple(slice_picker)
slice_inverter = tuple(slice_inverter)
try:
y.set_data(to_key=slice_picker, data=y,
from_key=slice_inverter)
except(AttributeError):
y[slice_picker] = y[slice_inverter]
return y
if nifty_configuration['harmonic_rg_base'] == 'real':
return x
else:
# calculate the number of dimensions the input array has
dimensions = len(x.shape)
# prepare the slicing object which will be used for mirroring
slice_primitive = [slice(None), ] * dimensions
# copy the input data
y = x.copy()
# flip in the desired directions
for k in range(len(axes)):
i = axes[k]
slice_picker = slice_primitive[:]
slice_inverter = slice_primitive[:]
if (not self.zerocenter[k]) or self.shape[k] % 2 == 0:
slice_picker[i] = slice(1, None, None)
slice_inverter[i] = slice(None, 0, -1)
else:
slice_picker[i] = slice(None)
slice_inverter[i] = slice(None, None, -1)
slice_picker = tuple(slice_picker)
slice_inverter = tuple(slice_inverter)
try:
y.set_data(to_key=slice_picker, data=y,
from_key=slice_inverter)
except(AttributeError):
y[slice_picker] = y[slice_inverter]
return y
# ---Mandatory properties and methods---
......
......@@ -16,6 +16,8 @@
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
import numpy as np
from nifty import Space,\
PowerSpace,\
Field,\
......@@ -71,6 +73,10 @@ def create_power_operator(domain, power_spectrum, dtype=None,
distribution_strategy='not')
f = fp.power_synthesize(mean=1, std=0, real_signal=False,
distribution_strategy=distribution_strategy)
if not issubclass(fp.dtype.type, np.complexfloating):
f = f.real
f **= 2
return DiagonalOperator(domain, diagonal=f, bare=True)
......
......@@ -22,13 +22,15 @@ import numpy as np
from numpy.testing import assert_,\
assert_almost_equal,\
assert_allclose
from nose.plugins.skip import SkipTest
from itertools import product
from nifty import Field,\
RGSpace,\
LMSpace,\
PowerSpace
PowerSpace,\
nifty_configuration
from d2o import distributed_data_object
......@@ -62,10 +64,12 @@ class Test_Functionality(unittest.TestCase):
[(1,), (4,), (5,)], [(1,), (6,), (7,)]))
def test_hermitian_decomposition(self, z1, z2, preserve, complexdata,
s1, s2):
np.random.seed(123)
r1 = RGSpace(s1, harmonic=True, zerocenter=(z1,))
r2 = RGSpace(s2, harmonic=True, zerocenter=(z2,))
ra = RGSpace(s1+s2, harmonic=True, zerocenter=(z1, z2))
try:
r1 = RGSpace(s1, harmonic=True, zerocenter=(z1,))
r2 = RGSpace(s2, harmonic=True, zerocenter=(z2,))
ra = RGSpace(s1+s2, harmonic=True, zerocenter=(z1, z2))
except ValueError:
raise SkipTest
if preserve:
complexdata=True
......@@ -89,11 +93,14 @@ class Test_Functionality(unittest.TestCase):
@expand(product([RGSpace((8,), harmonic=True,
zerocenter=False),
RGSpace((8, 8), harmonic=True, distances=0.123,
zerocenter=True)],
zerocenter=False)],
[RGSpace((8,), harmonic=True,
zerocenter=False),
LMSpace(12)]))
def test_power_synthesize_analyze(self, space1, space2):
LMSpace(12)],
['real', 'complex']))
def test_power_synthesize_analyze(self, space1, space2, base):
nifty_configuration['harmonic_rg_base'] = base
p1 = PowerSpace(space1)
spec1 = lambda k: 42/(1+k)**2
fp1 = Field(p1, val=spec1)
......
......@@ -31,14 +31,7 @@ from itertools import product
from test.common import expand
from nose.plugins.skip import SkipTest
def _harmonic_type(itp):
otp = itp
if otp == np.float64:
otp = np.complex128
elif otp == np.float32:
otp = np.complex64
return otp
from d2o import STRATEGIES
def _get_rtol(tp):
......@@ -49,100 +42,119 @@ def _get_rtol(tp):
class FFTOperatorTests(unittest.TestCase):
@expand(product([10, 11], [False, True], [0.1, 1, 3.7]))
def test_RG_distance_1D(self, dim1, zc1, d):
foo = RGSpace([dim1], zerocenter=zc1, distances=d)
res = foo.get_distance_array('not')
assert_equal(res[zc1 * (dim1 // 2)], 0.)
@expand(product([10, 11], [9, 28], [False, True], [False, True],
[0.1, 1, 3.7]))
def test_RG_distance_2D(self, dim1, dim2, zc1, zc2, d):
foo = RGSpace([dim1, dim2], zerocenter=[zc1, zc2], distances=d)
res = foo.get_distance_array('not')
assert_equal(res[zc1 * (dim1 // 2), zc2 * (dim2 // 2)], 0.)
@expand(product(["numpy", "fftw", "fftw_mpi"],
[16, ], [False, True], [False, True],
[0.1, 1, 3.7],
[np.float64, np.complex128, np.float32, np.complex64]))
def test_fft1D(self, module, dim1, zc1, zc2, d, itp):
[16, ], [0.1, 1, 3.7], STRATEGIES['global'],
[np.float64, np.float32, np.complex64, np.complex128],
['real', 'complex']))
def test_fft1D(self, module, dim1, d, distribution_strategy, itp, base):
if module == "fftw_mpi":
if not hasattr(gdi.get('fftw'), 'FFTW_MPI'):
raise SkipTest
if module == "fftw" and "fftw" not in gdi:
raise SkipTest
tol = _get_rtol(itp)
a = RGSpace(dim1, zerocenter=zc1, distances=d)
b = RGSpace(dim1, zerocenter=zc2, distances=1./(dim1*d), harmonic=True)
fft = FFTOperator(domain=a, target=b, domain_dtype=itp,
target_dtype=_harmonic_type(itp), module=module)
a = RGSpace(dim1, distances=d)
b = RGSpace(dim1, distances=1./(dim1*d), harmonic=True)
fft = FFTOperator(domain=a, target=b, module=module)
fft._forward_transformation.harmonic_base = base
fft._backward_transformation.harmonic_base = base
np.random.seed(16)
inp = Field.from_random(domain=a, random_type='normal', std=7, mean=3,
dtype=itp)
dtype=itp,
distribution_strategy=distribution_strategy)
out = fft.adjoint_times(fft.times(inp))
assert_allclose(inp.val.get_full_data(),
out.val.get_full_data(),
rtol=tol, atol=tol)
@expand(product(["numpy", "fftw", "fftw_mpi"],
[12, 15], [9, 12], [False, True],
[False, True], [False, True], [False, True], [0.1, 1, 3.7],
[0.4, 1, 2.7],
[np.float64, np.complex128, np.float32, np.complex64]))
def test_fft2D(self, module, dim1, dim2, zc1, zc2, zc3, zc4, d1, d2, itp):
[12, 15], [9, 12], [0.1, 1, 3.7],
[0.4, 1, 2.7], STRATEGIES['global'],
[np.float64, np.float32, np.complex64, np.complex128],
['real', 'complex']))
def test_fft2D(self, module, dim1, dim2, d1, d2, distribution_strategy,
itp, base):
if module == "fftw_mpi":
if not hasattr(gdi.get('fftw'), 'FFTW_MPI'):
raise SkipTest
if module == "fftw" and "fftw" not in gdi:
raise SkipTest
tol = _get_rtol(itp)
a = RGSpace([dim1, dim2], zerocenter=[zc1, zc2], distances=[d1, d2])
b = RGSpace([dim1, dim2], zerocenter=[zc3, zc4],
a = RGSpace([dim1, dim2], distances=[d1, d2])
b = RGSpace([dim1, dim2],
distances=[1./(dim1*d1), 1./(dim2*d2)], harmonic=True)
fft = FFTOperator(domain=a, target=b, domain_dtype=itp,
target_dtype=_harmonic_type(itp), module=module)
fft = FFTOperator(domain=a, target=b, module=module)
fft._forward_transformation.harmonic_base = base
fft._backward_transformation.harmonic_base = base
inp = Field.from_random(domain=a, random_type='normal', std=7, mean=3,
dtype=itp)
dtype=itp,
distribution_strategy=distribution_strategy)
out = fft.adjoint_times(fft.times(inp))
assert_allclose(inp.val, out.val, rtol=tol, atol=tol)
@expand(product(["numpy", "fftw", "fftw_mpi"],
[0, 1, 2],
STRATEGIES['global'],
[np.float64, np.float32, np.complex64, np.complex128],
['real', 'complex']))
def test_composed_fft(self, module, index, distribution_strategy, dtype,
base):
if module == "fftw_mpi":
if not hasattr(gdi.get('fftw'), 'FFTW_MPI'):
raise SkipTest
if module == "fftw" and "fftw" not in gdi:
raise SkipTest
tol = _get_rtol(dtype)
a = [a1, a2, a3] = [RGSpace((32,)), RGSpace((4, 4)), RGSpace((5, 6))]
fft = FFTOperator(domain=a[index], module=module,
default_spaces=(index,))
fft._forward_transformation.harmonic_base = base
fft._backward_transformation.harmonic_base = base
inp = Field.from_random(domain=(a1, a2, a3), random_type='normal',
std=7, mean=3, dtype=dtype,
distribution_strategy=distribution_strategy)
out = fft.adjoint_times(fft.times(inp))
assert_allclose(inp.val, out.val, rtol=tol, atol=tol)
@expand(product([0, 3, 6, 11, 30],
[np.float64, np.complex128, np.float32, np.complex64]))
[np.float64, np.float32, np.complex64, np.complex128]))
def test_sht(self, lm, tp):
if 'pyHealpix' not in gdi:
raise SkipTest
tol = _get_rtol(tp)
a = LMSpace(lmax=lm)
b = GLSpace(nlat=lm+1)
fft = FFTOperator(domain=a, target=b, domain_dtype=tp, target_dtype=tp)
fft = FFTOperator(domain=a, target=b)
inp = Field.from_random(domain=a, random_type='normal', std=7, mean=3,
dtype=tp)
out = fft.adjoint_times(fft.times(inp))
assert_allclose(inp.val, out.val, rtol=tol, atol=tol)
@expand(product([128, 256],
[np.float64, np.complex128, np.float32, np.complex64]))
[np.float64, np.float32, np.complex64, np.complex128]))
def test_sht2(self, lm, tp):
if 'pyHealpix' not in gdi:
raise SkipTest
a = LMSpace(lmax=lm)
b = HPSpace(nside=lm//2)
fft = FFTOperator(domain=a, target=b, domain_dtype=tp, target_dtype=tp)
fft = FFTOperator(domain=a, target=b)
inp = Field.from_random(domain=a, random_type='normal', std=1, mean=0,
dtype=tp)
out = fft.adjoint_times(fft.times(inp))
assert_allclose(inp.val, out.val, rtol=1e-3, atol=1e-1)
@expand(product([128, 256],
[np.float64, np.complex128, np.float32, np.complex64]))
[np.float64, np.float32, np.complex64, np.complex128]))
def test_dotsht(self, lm, tp):
if 'pyHealpix' not in gdi:
raise SkipTest
tol = _get_rtol(tp)
a = LMSpace(lmax=lm)
b = GLSpace(nlat=lm+1)
fft = FFTOperator(domain=a, target=b, domain_dtype=tp, target_dtype=tp)
fft = FFTOperator(domain=a, target=b)
inp = Field.from_random(domain=a, random_type='normal', std=1, mean=0,
dtype=tp)
out = fft.times(inp)
......@@ -151,14 +163,14 @@ class FFTOperatorTests(unittest.TestCase):
assert_allclose(v1, v2, rtol=tol, atol=tol)
@expand(product([128, 256],
[np.float64, np.complex128, np.float32, np.complex64]))
[np.float64, np.float32, np.complex64, np.complex128]))
def test_dotsht2(self, lm, tp):
if 'pyHealpix' not in gdi:
raise SkipTest
tol = _get_rtol(tp)
a = LMSpace(lmax=lm)
b = HPSpace(nside=lm//2)
fft = FFTOperator(domain=a, target=b, domain_dtype=tp, target_dtype=tp)
fft = FFTOperator(domain=a, target=b)
inp = Field.from_random(domain=a, random_type='normal', std=1, mean=0,
dtype=tp)
out = fft.times(inp)
......
......@@ -33,15 +33,11 @@ from nifty import dependency_injector as gdi
from nose.plugins.skip import SkipTest
HARMONIC_SPACES = [RGSpace((8,), harmonic=True),
RGSpace((7,), harmonic=True, zerocenter=True),
RGSpace((8,), harmonic=True, zerocenter=True),
RGSpace((7, 8), harmonic=True),
RGSpace((7, 8), harmonic=True, zerocenter=True),
RGSpace((6, 6), harmonic=True, zerocenter=True),
RGSpace((7, 5), harmonic=True, zerocenter=True),
RGSpace((5, 5), harmonic=True),
RGSpace((4, 5, 7), harmonic=True),
RGSpace((4, 5, 7), harmonic=True, zerocenter=True),
LMSpace(6),
LMSpace(9)]
......
......@@ -23,8 +23,9 @@ import numpy as np
from d2o import distributed_data_object
from numpy.testing import assert_, assert_equal, assert_almost_equal
from nifty import RGSpace
from numpy.testing import assert_, assert_equal, assert_almost_equal, \
assert_array_equal
from nifty import RGSpace, nifty_configuration
from test.common import expand
from itertools import product
from nose.plugins.skip import SkipTest
......@@ -67,23 +68,23 @@ CONSTRUCTOR_CONFIGS = [
'dim': 8,
'total_volume': 96.0
}],
[(11, 11), (False, True), None, False,
[(11, 11), False, None, False,
{
'shape': (11, 11),
'zerocenter': (False, True),
'zerocenter': (False, False),
'distances': (1/11, 1/11),
'harmonic': False,
'dim': 121,
'total_volume': 1.0
}],
[(11, 11), True, (1.3, 1.3), True,
[(12, 12), True, (1.3, 1.3), True,
{
'shape': (11, 11),
'shape': (12, 12),
'zerocenter': (True, True),
'distances': (1.3, 1.3),
'harmonic': True,
'dim': 121,
'total_volume': 204.49
'dim': 144,
'total_volume': 243.36
}]
]
......@@ -157,29 +158,35 @@ class RGSpaceFunctionalityTests(unittest.TestCase):
@expand(product([(10,), (11,), (1, 1), (4, 4), (5, 7), (8, 12), (7, 16),
(4, 6, 8), (17, 5, 3)],
[True, False]))
def test_hermitianize_inverter(self, shape, zerocenter):
[True, False],
['real', 'complex']))
def test_hermitianize_inverter(self, shape, zerocenter, base):
try:
r = RGSpace(shape, harmonic=True, zerocenter=zerocenter)
except ValueError:
raise SkipTest
v = distributed_data_object(global_shape=shape, dtype=np.complex128)
v[:] = np.random.random(shape) + 1j*np.random.random(shape)
nifty_configuration['harmonic_rg_base'] = base
inverted = r.hermitianize_inverter(v, axes=range(len(shape)))
# test hermitian flipping of `inverted`
it = np.nditer(v, flags=['multi_index'])
while not it.finished:
i1 = it.multi_index
i2 = []
for i in range(len(i1)):
if r.zerocenter[i] and r.shape[i] % 2 != 0:
i2.append(v.shape[i]-i1[i]-1)
else:
i2.append(v.shape[i]-i1[i] if i1[i] > 0 else 0)
i2 = tuple(i2)
assert_almost_equal(inverted[i1], v[i2])
it.iternext()
if base == 'complex':
# test hermitian flipping of `inverted`
it = np.nditer(v, flags=['multi_index'])
while not it.finished:
i1 = it.multi_index
i2 = []
for i in range(len(i1)):
if r.zerocenter[i] and r.shape[i] % 2 != 0:
i2.append(v.shape[i]-i1[i]-1)
else:
i2.append(v.shape[i]-i1[i] if i1[i] > 0 else 0)
i2 = tuple(i2)
assert_almost_equal(inverted[i1], v[i2])
it.iternext()
else:
assert_array_equal(v, inverted)
@expand(get_distance_array_configs())
def test_distance_array(self, shape, distances, zerocenter, expected):
......
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