CUQIpy 0.5.0.post0.dev9__tar.gz → 0.5.0.post0.dev19__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of CUQIpy might be problematic. Click here for more details.

Files changed (95) hide show
  1. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/CUQIpy.egg-info/PKG-INFO +1 -1
  2. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/PKG-INFO +1 -1
  3. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/_version.py +3 -3
  4. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/geometry/_geometry.py +105 -26
  5. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_geometry.py +95 -5
  6. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/CUQIpy.egg-info/SOURCES.txt +0 -0
  7. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/CUQIpy.egg-info/dependency_links.txt +0 -0
  8. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/CUQIpy.egg-info/requires.txt +0 -0
  9. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/CUQIpy.egg-info/top_level.txt +0 -0
  10. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/LICENSE +0 -0
  11. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/README.md +0 -0
  12. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/__init__.py +0 -0
  13. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/_messages.py +0 -0
  14. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/array/__init__.py +0 -0
  15. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/array/_array.py +0 -0
  16. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/config.py +0 -0
  17. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/data/__init__.py +0 -0
  18. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/data/_data.py +0 -0
  19. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/data/astronaut.npz +0 -0
  20. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/data/camera.npz +0 -0
  21. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/data/cat.npz +0 -0
  22. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/data/satellite.mat +0 -0
  23. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/density/__init__.py +0 -0
  24. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/density/_density.py +0 -0
  25. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/diagnostics.py +0 -0
  26. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/__init__.py +0 -0
  27. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_beta.py +0 -0
  28. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_cauchy.py +0 -0
  29. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_cmrf.py +0 -0
  30. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_custom.py +0 -0
  31. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_distribution.py +0 -0
  32. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_gamma.py +0 -0
  33. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_gaussian.py +0 -0
  34. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_gmrf.py +0 -0
  35. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_inverse_gamma.py +0 -0
  36. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_joint_distribution.py +0 -0
  37. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_laplace.py +0 -0
  38. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_lmrf.py +0 -0
  39. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_lognormal.py +0 -0
  40. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_normal.py +0 -0
  41. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_posterior.py +0 -0
  42. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/distribution/_uniform.py +0 -0
  43. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/geometry/__init__.py +0 -0
  44. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/likelihood/__init__.py +0 -0
  45. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/likelihood/_likelihood.py +0 -0
  46. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/model/__init__.py +0 -0
  47. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/model/_model.py +0 -0
  48. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/operator/__init__.py +0 -0
  49. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/operator/_operator.py +0 -0
  50. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/pde/__init__.py +0 -0
  51. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/pde/_pde.py +0 -0
  52. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/problem/__init__.py +0 -0
  53. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/problem/_problem.py +0 -0
  54. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/__init__.py +0 -0
  55. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_conjugate.py +0 -0
  56. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_conjugate_approx.py +0 -0
  57. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_cwmh.py +0 -0
  58. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_gibbs.py +0 -0
  59. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_hmc.py +0 -0
  60. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_langevin_algorithm.py +0 -0
  61. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_laplace_approximation.py +0 -0
  62. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_mh.py +0 -0
  63. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_pcn.py +0 -0
  64. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_rto.py +0 -0
  65. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/sampler/_sampler.py +0 -0
  66. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/samples/__init__.py +0 -0
  67. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/samples/_samples.py +0 -0
  68. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/solver/__init__.py +0 -0
  69. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/solver/_solver.py +0 -0
  70. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/testproblem/__init__.py +0 -0
  71. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/testproblem/_testproblem.py +0 -0
  72. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/utilities/__init__.py +0 -0
  73. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/utilities/_get_python_variable_name.py +0 -0
  74. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/cuqi/utilities/_utilities.py +0 -0
  75. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/pyproject.toml +0 -0
  76. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/requirements.txt +0 -0
  77. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/setup.cfg +0 -0
  78. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/setup.py +0 -0
  79. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_MRFs.py +0 -0
  80. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_abstract_distribution_density.py +0 -0
  81. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_bayesian_inversion.py +0 -0
  82. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_density.py +0 -0
  83. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_distribution.py +0 -0
  84. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_distributions_shape.py +0 -0
  85. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_joint_distribution.py +0 -0
  86. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_likelihood.py +0 -0
  87. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_model.py +0 -0
  88. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_pde.py +0 -0
  89. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_posterior.py +0 -0
  90. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_problem.py +0 -0
  91. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_sampler.py +0 -0
  92. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_samples.py +0 -0
  93. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_solver.py +0 -0
  94. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_testproblem.py +0 -0
  95. {CUQIpy-0.5.0.post0.dev9 → CUQIpy-0.5.0.post0.dev19}/tests/test_utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: CUQIpy
3
- Version: 0.5.0.post0.dev9
3
+ Version: 0.5.0.post0.dev19
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: CUQIpy
3
- Version: 0.5.0.post0.dev9
3
+ Version: 0.5.0.post0.dev19
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>
6
6
  License: Apache License
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2023-09-20T08:59:50+0200",
11
+ "date": "2023-09-20T22:14:47+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "5aa5af171cc495556f93f0ceecc78606ad9b9185",
15
- "version": "0.5.0.post0.dev9"
14
+ "full-revisionid": "85066baa356c88fbab83075272f0337134b6d832",
15
+ "version": "0.5.0.post0.dev19"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -331,6 +331,36 @@ class Continuous(Geometry, ABC):
331
331
 
332
332
  def fun2par(self,funvals):
333
333
  return funvals
334
+
335
+ def _reshape_par2fun_input(self, pars):
336
+ """Make sure that the parameter last dimension reflects the number of
337
+ parameter vectors: 1 for a single parameter vector and n for n parameter
338
+ vectors."""
339
+ # Ensure pars shape is correct (either squeezed single vector or
340
+ # multiple vectors with the correct shape).
341
+ if pars.shape != self.par_shape and\
342
+ pars.shape[:-1] != self.par_shape:
343
+ raise ValueError(
344
+ f"pars must have shape {self.par_shape} or {self.par_shape}"
345
+ + "+(n,) where n is the number of parameter vectors")
346
+
347
+ # Add dim to pars if pars is a single parameter vector.
348
+ return pars.reshape(self.par_shape + (-1,))
349
+
350
+ def _reshape_fun2par_input(self, funvals):
351
+ """Make sure that the function value last dimension reflects the number
352
+ of functions: 1 for a single function and n for n functions."""
353
+ # Ensure funvals shape is correct (either squeezed single function or
354
+ # multiple functions with the correct shape).
355
+ if funvals.shape != self.fun_shape and\
356
+ funvals.shape[:-1] != self.fun_shape:
357
+ raise ValueError(
358
+ f"funvals must have shape {self.fun_shape} or {self.fun_shape}"
359
+ + "+(n,) where n is the number of functions")
360
+
361
+ # Add dim to funvals if funvals is a single function.
362
+ return funvals.reshape(self.fun_shape + (-1,))
363
+
334
364
 
335
365
  class Continuous1D(Continuous):
336
366
  """A class that represents a continuous 1D geometry.
@@ -769,6 +799,7 @@ class KLExpansion(Continuous1D):
769
799
  self._normalizer = normalizer # normalizer factor
770
800
  self._num_modes = num_modes # number of modes
771
801
  self._coefs = None
802
+ self._coefs_inverse = None
772
803
 
773
804
  @property
774
805
  def par_shape(self):
@@ -796,10 +827,22 @@ class KLExpansion(Continuous1D):
796
827
  # If the coefficients are not computed, compute them.
797
828
  if self._coefs is None or len(self._coefs) != self.num_modes:
798
829
  eigvals = np.array(range(1, self.par_dim+1)) # KL eigvals
799
- self._coefs = 1/np.float_power(eigvals, self.decay_rate)
830
+ self._coefs = np.diag(1/np.float_power(eigvals, self.decay_rate))
800
831
 
801
832
  # Return the coefficients.
802
833
  return self._coefs
834
+
835
+ @property
836
+ def coefs_inverse(self):
837
+ """Computes the inverse of the coefficients diagonal matrix."""
838
+
839
+ # If the matrix is not computed, compute it.
840
+ if self._coefs_inverse is None or\
841
+ len(self._coefs_inverse) != self.num_modes:
842
+ self._coefs_inverse = np.diag(np.float_power(np.diag(self.coefs), -1))
843
+
844
+ # Return the inverse coefficients matrix.
845
+ return self._coefs_inverse
803
846
 
804
847
  @property
805
848
  def num_modes(self):
@@ -816,19 +859,22 @@ class KLExpansion(Continuous1D):
816
859
 
817
860
  # computes the real function out of expansion coefs
818
861
  def par2fun(self, p):
819
- # Check that the input is of the correct shape
820
- if len(p) != self.par_dim:
821
- raise ValueError(
822
- "Input array p must have length {}".format(self.par_dim))
823
862
 
824
- modes = p*self.coefs/self.normalizer
863
+ # Reshape the parameter vector
864
+ p = self._reshape_par2fun_input(p)
825
865
 
826
- # pad the remaining modes with zeros
827
- modes = np.pad(modes, (0, self.fun_dim-self.par_dim),
866
+ modes = self.coefs@p/self.normalizer
867
+
868
+ # pad the remaining modes with zeros (for single or multiple parameter
869
+ # parameter vectors)
870
+ modes = np.pad(modes, ((0, self.fun_dim-self.par_dim), (0, 0)),
828
871
  'constant', constant_values=0)
829
872
 
830
- real = idst(modes)/2
831
- return real
873
+ real = idst(modes.T).T/2
874
+
875
+ # squeeze to return single function value if only one parameter vector
876
+ # was given
877
+ return real.squeeze()
832
878
 
833
879
  def fun2par(self, funvals):
834
880
  """The function to parameter map used to map function values back to
@@ -837,11 +883,9 @@ class KLExpansion(Continuous1D):
837
883
  always the inverse of `par2fun` but it is the closest estimation of the
838
884
  function on the KL expansion coefficient space."""
839
885
 
840
- # Check that the input is of the correct shape
841
- if len(funvals) != self.fun_dim:
842
- raise ValueError(
843
- "Input array funvals must have length {}".format(self.fun_dim))
844
-
886
+ # Reshape the function values
887
+ funvals = self._reshape_fun2par_input(funvals)
888
+
845
889
  warnings.warn(
846
890
  f"fun2par for {self.__class__} is a projection on "
847
891
  + "the KL expansion coefficients space where only "
@@ -865,10 +909,14 @@ class KLExpansion(Continuous1D):
865
909
  # However, if we use, for example, scipy.fft instead of scipy.fftpack,
866
910
  # then this scaling is not needed.
867
911
 
868
- p = dst(funvals*2)[:self.par_dim]\
869
- *self.normalizer/(self.coefs*2*self.fun_dim)
912
+ # Transform (single or multiple functions) to expansion coefficients
913
+ p_temp = dst(funvals.T*2).T[:self.par_dim,:]
914
+ p = self.coefs_inverse@p_temp*self.normalizer/(2*self.fun_dim)
915
+
916
+ # squeeze to return single parameter vector if only one function value
917
+ # was given
918
+ return p.squeeze()
870
919
 
871
- return p
872
920
 
873
921
  class KLExpansion_Full(Continuous1D):
874
922
  '''
@@ -1138,24 +1186,55 @@ class StepExpansion(Continuous1D):
1138
1186
  return self._n_steps
1139
1187
 
1140
1188
  def par2fun(self, p):
1141
- real = np.zeros(self.grid.shape)
1189
+
1190
+ # Reshape the parameter vector
1191
+ p = self._reshape_par2fun_input(p)
1192
+
1193
+ # Extended fun_shape to include multiple functions.
1194
+ ext_fun_shape = self.fun_shape + (p.shape[-1],)
1195
+
1196
+ # Initialize fun to zeros.
1197
+ fun = np.zeros(ext_fun_shape)
1198
+
1199
+ # Fill fun with the step function values.
1142
1200
  for i in range(self._n_steps):
1143
- real[self._indices[i]] = p[i]
1201
+ fun[self._indices[i],:] = p[i,:]
1202
+
1203
+ # Squeeze to return single evaluated function if only one parameter
1204
+ # vector was given.
1205
+ return fun.squeeze()
1144
1206
 
1145
- return real
1146
1207
 
1147
1208
  def fun2par(self,f):
1148
- val = np.zeros(self._n_steps)
1209
+
1210
+ # Reshape the function values
1211
+ f = self._reshape_fun2par_input(f)
1212
+
1213
+ # Extended par_shape to include multiple parameter vectors.
1214
+ ext_par_shape = self.par_shape + (f.shape[-1],)
1215
+
1216
+ # Initialize par to zeros.
1217
+ par = np.zeros(ext_par_shape)
1218
+
1219
+ # Fill par with the projection of the function values.
1149
1220
  for i in range(self._n_steps):
1150
1221
  if self._fun2par_projection.lower() == 'mean':
1151
- val[i] = np.mean(f[self._indices[i]])
1222
+ projection_method = np.mean
1152
1223
  elif self._fun2par_projection.lower() == 'max':
1153
- val[i] = np.max(f[self._indices[i]])
1224
+ projection_method = np.max
1154
1225
  elif self._fun2par_projection.lower() == 'min':
1155
- val[i] = np.min(f[self._indices[i]])
1226
+ projection_method = np.min
1156
1227
  else:
1157
1228
  raise ValueError("Invalid projection option.")
1158
- return val
1229
+
1230
+ # Apply projection method to the function values in the ith
1231
+ # interval.
1232
+ par[i,:] = projection_method(f[self._indices[i],:], axis=0)
1233
+
1234
+ # Squeeze to return single parameter vector if only one function value
1235
+ # was given.
1236
+ return par.squeeze()
1237
+
1159
1238
 
1160
1239
  def _check_grid_setup(self):
1161
1240
 
@@ -145,8 +145,8 @@ def test_geometry_equality(g1, g2, truth_value):
145
145
  """Ensure geometry arrays compare correctly"""
146
146
  assert (g1==g2) == truth_value
147
147
 
148
- @pytest.mark.parametrize("n_steps",[1,2,6,7,9,10,20, 21])
149
- def test_StepExpansion_geometry(n_steps):
148
+ @pytest.mark.parametrize("n_steps", [1, 2, 6, 7, 9, 10, 20, 21])
149
+ def test_StepExpansion_geometry(n_steps, copy_reference):
150
150
  """Check StepExpansion geometry correctness"""
151
151
  grid = np.linspace(0,1,20)
152
152
  if n_steps > np.size(grid):
@@ -159,8 +159,18 @@ def test_StepExpansion_geometry(n_steps):
159
159
  par = np.random.randn(n_steps)
160
160
  geom.plot(par,linestyle = '', marker='.')
161
161
 
162
- assert np.allclose(par, geom.fun2par(geom.par2fun(par))) \
162
+ fun = geom.par2fun(par)
163
+ par2 = geom.fun2par(fun)
164
+
165
+ assert np.allclose(par, par2) \
163
166
  and geom.par_dim == n_steps
167
+
168
+ # Assert fun and par2 matches the values in the data file
169
+ ref_file = copy_reference(
170
+ f"data/geometry/test_StepExpansion_{n_steps}.npz")
171
+ ref = np.load(ref_file)
172
+ assert np.allclose(fun, ref["fun"])
173
+ assert np.allclose(par2, ref["par2"])
164
174
 
165
175
  @pytest.mark.parametrize("projection, func",[('MiN', np.min),
166
176
  ('mAX', np.max),('mean', np.mean)])
@@ -186,8 +196,13 @@ def test_stepExpansion_fun2par(projection, func):
186
196
  assert np.allclose(p, qa_f.parameters)
187
197
 
188
198
  @pytest.mark.parametrize("num_modes",[1, 10, 20, 25])
189
- def test_KL_expansion(num_modes):
199
+ def test_KL_expansion(num_modes, copy_reference):
190
200
  """Check KL expansion geometry correctness"""
201
+
202
+ # File name for reference data
203
+ ref_fname = f"data/geometry/KL_expansion_{num_modes}.npz"
204
+
205
+ # Set up KL expansion geometry
191
206
  N = 20
192
207
  grid = np.linspace(0, 1, N)
193
208
  decay_rate = 2.5
@@ -199,6 +214,7 @@ def test_KL_expansion(num_modes):
199
214
  if num_modes > len(grid):
200
215
  num_modes = len(grid)
201
216
 
217
+ # Apply par2fun and check results
202
218
  p = np.random.randn(N)
203
219
  f_geom = geom.par2fun(p[:num_modes])
204
220
 
@@ -211,6 +227,10 @@ def test_KL_expansion(num_modes):
211
227
  assert geom.par_dim == geom.num_modes
212
228
  assert len(geom.grid) == geom.fun_dim
213
229
 
230
+ # Verify the KL expansion results against the reference data
231
+ ref_file = copy_reference(ref_fname)
232
+ ref = np.load(ref_file)
233
+ assert np.allclose(f_geom, ref["f_geom"])
214
234
 
215
235
  def test_KLExpansion_set_grid():
216
236
  """Check updating grid in KL expansion geometry"""
@@ -371,7 +391,7 @@ def test_create_CustomKL_geometry():
371
391
  geom.trunc_term==trunc_term
372
392
 
373
393
 
374
- def test_KLExpansion_projection():
394
+ def test_KLExpansion_projection(copy_reference):
375
395
  """Check KLExpansion geometry projection performed by the method fun2par)"""
376
396
  # Set up a KLExpansion geometry
377
397
  num_modes = 95
@@ -399,6 +419,11 @@ def test_KLExpansion_projection():
399
419
  rel_err = np.linalg.norm(signal-signal_proj)/np.linalg.norm(signal)
400
420
  assert np.isclose(rel_err, 0.0, atol=1e-5)
401
421
 
422
+ # Compare results with reference data
423
+ ref_file = copy_reference("data/geometry/test_KLExpansion_projection.npz")
424
+ ref = np.load(ref_file)
425
+ assert np.allclose(p, ref["p"])
426
+
402
427
  def test_DefaultGeometry2D_should_be_image2D():
403
428
  geom2D = cuqi.geometry._DefaultGeometry2D((100, 100))
404
429
 
@@ -458,3 +483,68 @@ def test_Continuous2D_plot_multiple_funvals_pass(geom2D_funvals):
458
483
  # Convert to parameters then plot
459
484
  multiple_funvals_topar = geom2D.fun2par(multiple_funvals)
460
485
  geom2D.plot(multiple_funvals_topar, is_par=True)
486
+
487
+ # Create fixture for KLExpansion geometry
488
+ @pytest.fixture
489
+ def geom_KL():
490
+ """Returns a KLExpansion geometry"""
491
+ N = 20
492
+ grid = np.linspace(0, 1, N)
493
+ decay_rate = 2.5
494
+ normalizer = 12
495
+ geom = cuqi.geometry.KLExpansion(grid,
496
+ decay_rate=decay_rate,
497
+ normalizer=normalizer,
498
+ num_modes=10)
499
+ return geom
500
+
501
+ # Create fixture for StepExpansion geometry
502
+ @pytest.fixture
503
+ def geom_Step():
504
+ """Returns a StepExpansion geometry"""
505
+ N = 20
506
+ grid = np.linspace(0, 1, N)
507
+ n_steps = 10
508
+ geom = cuqi.geometry.StepExpansion(grid, n_steps=n_steps)
509
+ return geom
510
+
511
+ # Create fixture to parametrize the tests by geometry
512
+ @pytest.fixture
513
+ def geom(request):
514
+ return request.getfixturevalue(request.param)
515
+
516
+ # Compare par2fun and fun2par for each geometry for individual parameters
517
+ # vectors and multiple parameter vectors
518
+ @pytest.mark.parametrize('geom', ['geom_KL', 'geom_Step'], indirect=True)
519
+ def test_par2fun_and_fun2par_correctness_for_multiple_values(geom):
520
+ """Check the correctness of the par2fun and fun2par methods for different
521
+ geometries for multiple parameter vectors"""
522
+
523
+ # Create random parameter values
524
+ np.random.seed(0)
525
+ n = 5
526
+ par = np.random.randn(geom.par_dim, n)
527
+
528
+ # run par2fun (first for multiple parameter vectors at once)
529
+ fun = geom.par2fun(par)
530
+
531
+ # run par2fun (for each parameter vector individually)
532
+ fun_ind = np.array([geom.par2fun(par[:, i]) for i in range(n)]).T
533
+
534
+ # Check that the results are the same
535
+ assert np.allclose(fun, fun_ind)
536
+
537
+ # Check plotting runs
538
+ geom.plot(fun, is_par=False)
539
+
540
+ # run fun2par (first for multiple functions at once)
541
+ par2 = geom.fun2par(fun)
542
+
543
+ # run fun2par (for each function individually)
544
+ par2_ind = np.array([geom.fun2par(fun[:, i]) for i in range(n)]).T
545
+
546
+ # Check that the results are the same
547
+ assert np.allclose(par2, par2_ind)
548
+
549
+ # Check plotting runs
550
+ geom.plot(par2, is_par=True)