Commit 41879da1 authored by Martin Reinecke's avatar Martin Reinecke
Browse files

DiagonalOperator takes and returns diagonal without volume factors

parent 88872f64
Pipeline #19209 failed with stage
...@@ -72,7 +72,7 @@ if __name__ == "__main__": ...@@ -72,7 +72,7 @@ if __name__ == "__main__":
R = AdjointFFTResponse(fft, Instrument) R = AdjointFFTResponse(fft, Instrument)
noise = 1. noise = 1.
N = ift.DiagonalOperator(ift.Field(s_space,noise).weight(1)) N = ift.DiagonalOperator(ift.Field(s_space,noise))
n = ift.Field.from_random(domain=s_space, n = ift.Field.from_random(domain=s_space,
random_type='normal', random_type='normal',
std=np.sqrt(noise), std=np.sqrt(noise),
......
...@@ -38,7 +38,7 @@ if __name__ == "__main__": ...@@ -38,7 +38,7 @@ if __name__ == "__main__":
R_harmonic = ift.ComposedOperator([fft, R]) R_harmonic = ift.ComposedOperator([fft, R])
# Setting up the noise covariance and drawing a random noise realization # Setting up the noise covariance and drawing a random noise realization
ndiag = ift.Field(data_domain, mock_signal.var()/signal_to_noise).weight(1) ndiag = ift.Field(data_domain, mock_signal.var()/signal_to_noise)
N = ift.DiagonalOperator(ndiag) N = ift.DiagonalOperator(ndiag)
noise = ift.Field.from_random(domain=data_domain, random_type='normal', noise = ift.Field.from_random(domain=data_domain, random_type='normal',
std=mock_signal.std()/np.sqrt(signal_to_noise), mean=0) std=mock_signal.std()/np.sqrt(signal_to_noise), mean=0)
......
...@@ -62,7 +62,7 @@ if __name__ == "__main__": ...@@ -62,7 +62,7 @@ if __name__ == "__main__":
diagonal = mock_power.power_synthesize_special(spaces=(0, 1))**2 diagonal = mock_power.power_synthesize_special(spaces=(0, 1))**2
diagonal = diagonal.real diagonal = diagonal.real
S = ift.DiagonalOperator(diagonal) S = ift.DiagonalOperator(diagonal.weight(-1))
np.random.seed(10) np.random.seed(10)
...@@ -84,7 +84,7 @@ if __name__ == "__main__": ...@@ -84,7 +84,7 @@ if __name__ == "__main__":
R_harmonic = ift.ComposedOperator([fft, R]) R_harmonic = ift.ComposedOperator([fft, R])
# Setting up the noise covariance and drawing a random noise realization # Setting up the noise covariance and drawing a random noise realization
ndiag = ift.Field(data_domain, mock_signal.var()/signal_to_noise).weight(1) ndiag = ift.Field(data_domain, mock_signal.var()/signal_to_noise)
N = ift.DiagonalOperator(ndiag) N = ift.DiagonalOperator(ndiag)
noise = ift.Field.from_random(domain=data_domain, random_type='normal', noise = ift.Field.from_random(domain=data_domain, random_type='normal',
std=mock_signal.std()/np.sqrt(signal_to_noise), std=mock_signal.std()/np.sqrt(signal_to_noise),
...@@ -93,7 +93,7 @@ if __name__ == "__main__": ...@@ -93,7 +93,7 @@ if __name__ == "__main__":
# Wiener filter # Wiener filter
j = R_harmonic.adjoint_times(N.inverse_times(data)) j = R_harmonic.adjoint_times(N.inverse_times(data))
ctrl = ift.GradientNormController(verbose=True, iteration_limit=100) ctrl = ift.GradientNormController(verbose=True, tol_abs_gradnorm=0.1)
inverter = ift.ConjugateGradient(controller=ctrl) inverter = ift.ConjugateGradient(controller=ctrl)
wiener_curvature = ift.library.WienerFilterCurvature(S=S, N=N, R=R_harmonic, inverter=inverter) wiener_curvature = ift.library.WienerFilterCurvature(S=S, N=N, R=R_harmonic, inverter=inverter)
......
...@@ -37,7 +37,7 @@ if __name__ == "__main__": ...@@ -37,7 +37,7 @@ if __name__ == "__main__":
R_harmonic = ift.ComposedOperator([fft, R]) R_harmonic = ift.ComposedOperator([fft, R])
# Setting up the noise covariance and drawing a random noise realization # Setting up the noise covariance and drawing a random noise realization
ndiag = ift.Field(data_domain, mock_signal.var()/signal_to_noise).weight(1) ndiag = ift.Field(data_domain, mock_signal.var()/signal_to_noise)
N = ift.DiagonalOperator(ndiag) N = ift.DiagonalOperator(ndiag)
noise = ift.Field.from_random(domain=data_domain, random_type='normal', noise = ift.Field.from_random(domain=data_domain, random_type='normal',
std=mock_signal.std()/np.sqrt(signal_to_noise), mean=0) std=mock_signal.std()/np.sqrt(signal_to_noise), mean=0)
......
...@@ -47,7 +47,7 @@ if __name__ == "__main__": ...@@ -47,7 +47,7 @@ if __name__ == "__main__":
data_domain = R.target[0] data_domain = R.target[0]
R_harmonic = ift.ComposedOperator([fft, R]) R_harmonic = ift.ComposedOperator([fft, R])
N = ift.DiagonalOperator(ift.Field(data_domain,mock_signal.var()/signal_to_noise).weight(1)) N = ift.DiagonalOperator(ift.Field(data_domain,mock_signal.var()/signal_to_noise))
noise = ift.Field.from_random(domain=data_domain, noise = ift.Field.from_random(domain=data_domain,
random_type='normal', random_type='normal',
std=mock_signal.std()/np.sqrt(signal_to_noise), std=mock_signal.std()/np.sqrt(signal_to_noise),
......
...@@ -62,7 +62,7 @@ if __name__ == "__main__": ...@@ -62,7 +62,7 @@ if __name__ == "__main__":
# Adding a harmonic transformation to the instrument # Adding a harmonic transformation to the instrument
R = AdjointFFTResponse(fft, Instrument) R = AdjointFFTResponse(fft, Instrument)
signal_to_noise = 1. signal_to_noise = 1.
ndiag = ift.Field(s_space, ss.var()/signal_to_noise).weight(1) ndiag = ift.Field(s_space, ss.var()/signal_to_noise)
N = ift.DiagonalOperator(ndiag) N = ift.DiagonalOperator(ndiag)
n = ift.Field.from_random(domain=s_space, n = ift.Field.from_random(domain=s_space,
random_type='normal', random_type='normal',
......
...@@ -420,7 +420,7 @@ class Field(object): ...@@ -420,7 +420,7 @@ class Field(object):
res *= tmp res *= tmp
return res return res
def weight(self, power=1, inplace=False, spaces=None): def weight(self, power=1, spaces=None, out=None):
""" Weights the pixels of `self` with their invidual pixel-volume. """ Weights the pixels of `self` with their invidual pixel-volume.
Parameters Parameters
...@@ -428,20 +428,25 @@ class Field(object): ...@@ -428,20 +428,25 @@ class Field(object):
power : number power : number
The pixels get weighted with the volume-factor**power. The pixels get weighted with the volume-factor**power.
inplace : boolean
If True, `self` will be weighted and returned. Otherwise, a copy
is made.
spaces : tuple of ints spaces : tuple of ints
Determines on which subspace the operation takes place. Determines on which subspace the operation takes place.
out : Field or None
if not None, the result is returned in a new Field
otherwise the contents of "out" are overwritten with the result.
"out" may be identical to "self"!
Returns Returns
------- -------
out : Field out : Field
The weighted field. The weighted field.
""" """
new_field = self if inplace else self.copy() if out is None:
out = self.copy()
else:
if out is not self:
out.copy_content_from(self)
if spaces is None: if spaces is None:
spaces = range(len(self.domain)) spaces = range(len(self.domain))
...@@ -458,12 +463,12 @@ class Field(object): ...@@ -458,12 +463,12 @@ class Field(object):
new_shape[self.domain.axes[ind][0]: new_shape[self.domain.axes[ind][0]:
self.domain.axes[ind][-1]+1] = wgt.shape self.domain.axes[ind][-1]+1] = wgt.shape
wgt = wgt.reshape(new_shape) wgt = wgt.reshape(new_shape)
new_field *= wgt**power out *= wgt**power
fct = fct**power fct = fct**power
if fct != 1.: if fct != 1.:
new_field *= fct out *= fct
return new_field return out
def vdot(self, x=None, spaces=None): def vdot(self, x=None, spaces=None):
""" Computes the volume-factor-aware dot product of 'self' with x. """ Computes the volume-factor-aware dot product of 'self' with x.
...@@ -474,8 +479,8 @@ class Field(object): ...@@ -474,8 +479,8 @@ class Field(object):
The domain of x must contain `self.domain` The domain of x must contain `self.domain`
spaces : tuple of ints spaces : tuple of ints
If the domain of `self` and `x` are not the same, `spaces` specfies If the domain of `self` and `x` are not the same, `spaces` defines
the mapping. which domains of `x` are mapped to those of `self`.
Returns Returns
------- -------
...@@ -487,9 +492,9 @@ class Field(object): ...@@ -487,9 +492,9 @@ class Field(object):
"the NIFTy field class") "the NIFTy field class")
# Compute the dot respecting the fact of discrete/continuous spaces # Compute the dot respecting the fact of discrete/continuous spaces
fct = 1.
tmp = self.scalar_weight(spaces) tmp = self.scalar_weight(spaces)
if tmp is None: if tmp is None:
fct = 1.
y = self.weight(power=1) y = self.weight(power=1)
else: else:
y = self y = self
...@@ -498,13 +503,14 @@ class Field(object): ...@@ -498,13 +503,14 @@ class Field(object):
if spaces is None: if spaces is None:
return fct*dobj.vdot(y.val.ravel(), x.val.ravel()) return fct*dobj.vdot(y.val.ravel(), x.val.ravel())
else: else:
# create a diagonal operator which is capable of taking care of the spaces = utilities.cast_iseq_to_tuple(spaces)
# axes-matching active_axes = []
from .operators.diagonal_operator import DiagonalOperator for i in spaces:
diag = DiagonalOperator(y.conjugate(), self.domain, active_axes += self.domain.axes[i]
spaces=spaces, copy=False) res = 0.
dotted = diag(x) for sl in utilities.get_slice_list(self.shape, active_axes):
return fct*dotted.sum(spaces=spaces) res += dobj.vdot(y.val, x.val[sl])
return res*fct
def norm(self): def norm(self):
""" Computes the L2-norm of the field values. """ Computes the L2-norm of the field values.
...@@ -515,7 +521,7 @@ class Field(object): ...@@ -515,7 +521,7 @@ class Field(object):
The L2-norm of the field values. The L2-norm of the field values.
""" """
return dobj.sqrt(dobj.abs(self.vdot(x=self))) return np.sqrt(np.abs(self.vdot(x=self)))
def conjugate(self): def conjugate(self):
""" Returns the complex conjugate of the field. """ Returns the complex conjugate of the field.
...@@ -566,6 +572,10 @@ class Field(object): ...@@ -566,6 +572,10 @@ class Field(object):
def sum(self, spaces=None): def sum(self, spaces=None):
return self._contraction_helper('sum', spaces) return self._contraction_helper('sum', spaces)
def integrate(self, spaces=None):
tmp = self.weight(1, spaces=spaces)
return tmp.sum(spaces)
def prod(self, spaces=None): def prod(self, spaces=None):
return self._contraction_helper('prod', spaces) return self._contraction_helper('prod', spaces)
......
...@@ -88,7 +88,7 @@ class CriticalPowerEnergy(Energy): ...@@ -88,7 +88,7 @@ class CriticalPowerEnergy(Energy):
@property @property
def gradient(self): def gradient(self):
gradient = -self._theta.weight(-1) gradient = -self._theta.weight(-1)
gradient += (self._rho_prime).weight(-1) gradient += self._rho_prime.weight(-1)
gradient += self._Tt gradient += self._Tt
gradient = gradient.real gradient = gradient.real
return gradient return gradient
......
...@@ -37,8 +37,12 @@ class DiagonalOperator(EndomorphicOperator): ...@@ -37,8 +37,12 @@ class DiagonalOperator(EndomorphicOperator):
---------- ----------
diagonal : Field diagonal : Field
The diagonal entries of the operator. The diagonal entries of the operator.
copy : boolean domain : tuple of DomainObjects, i.e. Spaces and FieldTypes
Internal copy of the diagonal (default: True) The domain on which the Operator's input Field lives.
If None, use the domain of "diagonal".
spaces : tuple of int
The elements of "domain" on which the operator acts.
If None, it acts on all elements.
Attributes Attributes
---------- ----------
...@@ -60,7 +64,7 @@ class DiagonalOperator(EndomorphicOperator): ...@@ -60,7 +64,7 @@ class DiagonalOperator(EndomorphicOperator):
# ---Overwritten properties and methods--- # ---Overwritten properties and methods---
def __init__(self, diagonal, domain=None, spaces=None, copy=True): def __init__(self, diagonal, domain=None, spaces=None):
super(DiagonalOperator, self).__init__() super(DiagonalOperator, self).__init__()
if not isinstance(diagonal, Field): if not isinstance(diagonal, Field):
...@@ -87,7 +91,7 @@ class DiagonalOperator(EndomorphicOperator): ...@@ -87,7 +91,7 @@ class DiagonalOperator(EndomorphicOperator):
if diagonal.domain[i] != self._domain[j]: if diagonal.domain[i] != self._domain[j]:
raise ValueError("domain mismatch") raise ValueError("domain mismatch")
self._diagonal = diagonal if not copy else diagonal.copy() self._diagonal = diagonal.weight(1)
self._self_adjoint = None self._self_adjoint = None
self._unitary = None self._unitary = None
...@@ -103,21 +107,16 @@ class DiagonalOperator(EndomorphicOperator): ...@@ -103,21 +107,16 @@ class DiagonalOperator(EndomorphicOperator):
def _adjoint_inverse_times(self, x): def _adjoint_inverse_times(self, x):
return self._times_helper(x, lambda z: z.conjugate().__rtruediv__) return self._times_helper(x, lambda z: z.conjugate().__rtruediv__)
def diagonal(self, copy=True): def diagonal(self):
""" Returns the diagonal of the Operator. """ Returns the diagonal of the Operator.
Parameters
----------
copy : boolean
Whether the returned Field should be copied or not.
Returns Returns
------- -------
out : Field out : Field
The diagonal of the Operator. The diagonal of the Operator.
""" """
return self._diagonal.copy() if copy else self._diagonal return self._diagonal.weight(-1)
# ---Mandatory properties and methods--- # ---Mandatory properties and methods---
......
...@@ -111,7 +111,7 @@ class LaplaceOperator(EndomorphicOperator): ...@@ -111,7 +111,7 @@ class LaplaceOperator(EndomorphicOperator):
ret /= np.sqrt(dposc) ret /= np.sqrt(dposc)
ret[prefix + (slice(None, 2),)] = 0. ret[prefix + (slice(None, 2),)] = 0.
ret[prefix + (-1,)] = 0. ret[prefix + (-1,)] = 0.
return Field(self.domain, val=ret).weight(-0.5, spaces=(self._space,)) return Field(self.domain, val=ret)
def _adjoint_times(self, x): def _adjoint_times(self, x):
axes = x.domain.axes[self._space] axes = x.domain.axes[self._space]
...@@ -122,7 +122,7 @@ class LaplaceOperator(EndomorphicOperator): ...@@ -122,7 +122,7 @@ class LaplaceOperator(EndomorphicOperator):
sl_r = prefix + (slice(1, None),) # "right" slice sl_r = prefix + (slice(1, None),) # "right" slice
dpos = self._dpos.reshape((1,)*axis + (nval-1,)) dpos = self._dpos.reshape((1,)*axis + (nval-1,))
dposc = self._dposc.reshape((1,)*axis + (nval,)) dposc = self._dposc.reshape((1,)*axis + (nval,))
y = x.copy().weight(power=0.5, spaces=(self._space,)).val y = x.val.copy()
y /= np.sqrt(dposc) y /= np.sqrt(dposc)
y[prefix + (slice(None, 2),)] = 0. y[prefix + (slice(None, 2),)] = 0.
y[prefix + (-1,)] = 0. y[prefix + (-1,)] = 0.
...@@ -131,4 +131,4 @@ class LaplaceOperator(EndomorphicOperator): ...@@ -131,4 +131,4 @@ class LaplaceOperator(EndomorphicOperator):
ret[sl_l] = deriv ret[sl_l] = deriv
ret[prefix + (-1,)] = 0. ret[prefix + (-1,)] = 0.
ret[sl_r] -= deriv ret[sl_r] -= deriv
return Field(self.domain, val=ret).weight(-1, spaces=(self._space,)) return Field(self.domain, val=ret)
...@@ -62,7 +62,7 @@ class ResponseOperator(LinearOperator): ...@@ -62,7 +62,7 @@ class ResponseOperator(LinearOperator):
space=spaces[x]) space=spaces[x])
for x in range(nsigma)] for x in range(nsigma)]
kernel_exposure = [DiagonalOperator(Field(self._domain[spaces[x]], kernel_exposure = [DiagonalOperator(Field(self._domain[spaces[x]],
exposure[x]), exposure[x]).weight(-1),
domain=self._domain, domain=self._domain,
spaces=(spaces[x],)) spaces=(spaces[x],))
for x in range(nsigma)] for x in range(nsigma)]
......
...@@ -26,11 +26,27 @@ from . import Space,\ ...@@ -26,11 +26,27 @@ from . import Space,\
FFTOperator,\ FFTOperator,\
sqrt sqrt
__all__ = ['create_power_operator', __all__ = ['create_power_field',
'create_power_operator',
'generate_posterior_sample', 'generate_posterior_sample',
'create_composed_fft_operator'] 'create_composed_fft_operator']
def create_power_field(domain, power_spectrum, dtype=None):
if not callable(power_spectrum):
raise TypeError("power_spectrum must be callable")
power_domain = PowerSpace(domain)
fp = Field(power_domain, val=power_spectrum(power_domain.k_lengths),
dtype=dtype)
f = fp.power_synthesize_special()
if not issubclass(fp.dtype.type, np.complexfloating):
f = f.real
f **= 2
return f
def create_power_operator(domain, power_spectrum, dtype=None): def create_power_operator(domain, power_spectrum, dtype=None):
""" Creates a diagonal operator with the given power spectrum. """ Creates a diagonal operator with the given power spectrum.
...@@ -53,21 +69,7 @@ def create_power_operator(domain, power_spectrum, dtype=None): ...@@ -53,21 +69,7 @@ def create_power_operator(domain, power_spectrum, dtype=None):
DiagonalOperator : An operator that implements the given power spectrum. DiagonalOperator : An operator that implements the given power spectrum.
""" """
return DiagonalOperator(create_power_field(domain, power_spectrum, dtype))
if not callable(power_spectrum):
raise TypeError("power_spectrum must be callable")
power_domain = PowerSpace(domain)
fp = Field(power_domain, val=power_spectrum(power_domain.k_lengths),
dtype=dtype)
f = fp.power_synthesize_special()
if not issubclass(fp.dtype.type, np.complexfloating):
f = f.real
f **= 2
return DiagonalOperator(Field(domain,f).weight(1))
def generate_posterior_sample(mean, covariance): def generate_posterior_sample(mean, covariance):
""" Generates a posterior sample from a Gaussian distribution with given """ Generates a posterior sample from a Gaussian distribution with given
...@@ -96,10 +98,10 @@ def generate_posterior_sample(mean, covariance): ...@@ -96,10 +98,10 @@ def generate_posterior_sample(mean, covariance):
R = covariance.R R = covariance.R
N = covariance.N N = covariance.N
power = sqrt(S.diagonal().power_analyze()) power = sqrt(S.diagonal().weight(1).power_analyze())
mock_signal = power.power_synthesize(real_signal=True) mock_signal = power.power_synthesize(real_signal=True)
noise = N.diagonal().weight(-1) noise = N.diagonal()
mock_noise = Field.from_random(random_type="normal", domain=N.domain, mock_noise = Field.from_random(random_type="normal", domain=N.domain,
dtype=noise.dtype.type) dtype=noise.dtype.type)
......
...@@ -32,7 +32,7 @@ from nifty2go import Field,\ ...@@ -32,7 +32,7 @@ from nifty2go import Field,\
DomainTuple,\ DomainTuple,\
DiagonalOperator DiagonalOperator
from nifty2go.sugar import create_power_operator from nifty2go.sugar import create_power_field
from test.common import expand from test.common import expand
...@@ -80,8 +80,8 @@ class Test_Functionality(unittest.TestCase): ...@@ -80,8 +80,8 @@ class Test_Functionality(unittest.TestCase):
sk = fp.power_synthesize(spaces=(0, 1), real_signal=True) sk = fp.power_synthesize(spaces=(0, 1), real_signal=True)
sp = sk.power_analyze(spaces=(0, 1), keep_phase_information=False) sp = sk.power_analyze(spaces=(0, 1), keep_phase_information=False)
ps1 += sp.sum(spaces=1)/fp2.sum() ps1 += sp.integrate(spaces=1)/fp2.sum()
ps2 += sp.sum(spaces=0)/fp1.sum() ps2 += sp.integrate(spaces=0)/fp1.sum()
assert_allclose(ps1.val/samples, fp1.val, rtol=0.2) assert_allclose(ps1.val/samples, fp1.val, rtol=0.2)
assert_allclose(ps2.val/samples, fp2.val, rtol=0.2) assert_allclose(ps2.val/samples, fp2.val, rtol=0.2)
...@@ -104,12 +104,10 @@ class Test_Functionality(unittest.TestCase): ...@@ -104,12 +104,10 @@ class Test_Functionality(unittest.TestCase):
spec2 = lambda k: 42/(1+k)**3 spec2 = lambda k: 42/(1+k)**3
fp2 = Field(p2, val=spec2(p2.k_lengths)) fp2 = Field(p2, val=spec2(p2.k_lengths))
S_1 = create_power_operator(space1, lambda x: np.sqrt(spec1(x))) S_1 = create_power_field(space1, lambda x: np.sqrt(spec1(x)))
S_2 = create_power_operator(space2, lambda x: np.sqrt(spec2(x))) S_1 = DiagonalOperator(S_1.weight(-1), domain=fulldomain, spaces=0)
S_1 = DiagonalOperator(S_1.diagonal().weight(-1),domain=fulldomain, S_2 = create_power_field(space2, lambda x: np.sqrt(spec2(x)))
spaces=0) S_2 = DiagonalOperator(S_2.weight(-1), domain=fulldomain, spaces=1)
S_2 = DiagonalOperator(S_2.diagonal().weight(-1),domain=fulldomain,
spaces=1)
samples = 500 samples = 500
ps1 = 0. ps1 = 0.
...@@ -119,8 +117,8 @@ class Test_Functionality(unittest.TestCase): ...@@ -119,8 +117,8 @@ class Test_Functionality(unittest.TestCase):
rand_k = Field.from_random('normal', domain=fulldomain) rand_k = Field.from_random('normal', domain=fulldomain)
sk = S_1.times(S_2.times(rand_k)) sk = S_1.times(S_2.times(rand_k))
sp = sk.power_analyze(spaces=(0, 1), keep_phase_information=False) sp = sk.power_analyze(spaces=(0, 1), keep_phase_information=False)
ps1 += sp.sum(spaces=1)/fp2.sum() ps1 += sp.integrate(spaces=1)/fp2.sum()
ps2 += sp.sum(spaces=0)/fp1.sum() ps2 += sp.integrate(spaces=0)/fp1.sum()
assert_allclose(ps1.val/samples, fp1.val, rtol=0.2) assert_allclose(ps1.val/samples, fp1.val, rtol=0.2)
assert_allclose(ps2.val/samples, fp2.val, rtol=0.2) assert_allclose(ps2.val/samples, fp2.val, rtol=0.2)
......
...@@ -21,7 +21,7 @@ class Test_Minimizers(unittest.TestCase): ...@@ -21,7 +21,7 @@ class Test_Minimizers(unittest.TestCase):
starting_point = ift.Field.from_random('normal', domain=space)*10 starting_point = ift.Field.from_random('normal', domain=space)*10
covariance_diagonal = ift.Field.from_random( covariance_diagonal = ift.Field.from_random(
'uniform', domain=space) + 0.5 'uniform', domain=space) + 0.5
covariance = ift.DiagonalOperator(covariance_diagonal) covariance = ift.DiagonalOperator(covariance_diagonal.weight(-1))
required_result = ift.Field(space, val=1.) required_result = ift.Field(space, val=1.)
IC = ift.GradientNormController(tol_abs_gradnorm=1e-5) IC = ift.GradientNormController(tol_abs_gradnorm=1e-5)
......
...@@ -17,8 +17,8 @@ from test.common import expand ...@@ -17,8 +17,8 @@ from test.common import expand
class DiagonalOperator_Tests(unittest.TestCase): class DiagonalOperator_Tests(unittest.TestCase):
spaces = generate_spaces()