CUQIpy 1.3.0.post0.dev266__tar.gz → 1.3.0.post0.dev292__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 (126) hide show
  1. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/PKG-INFO +1 -1
  2. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/PKG-INFO +1 -1
  3. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/_version.py +3 -3
  4. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_gibbs.py +28 -2
  5. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/model/_model.py +73 -21
  6. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/pde/_pde.py +71 -5
  7. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/utilities/_utilities.py +1 -1
  8. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_implicit_priors.py +3 -3
  9. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_model.py +237 -32
  10. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/SOURCES.txt +0 -0
  11. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/dependency_links.txt +0 -0
  12. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/requires.txt +0 -0
  13. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/top_level.txt +0 -0
  14. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/LICENSE +0 -0
  15. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/README.md +0 -0
  16. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/__init__.py +0 -0
  17. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/_messages.py +0 -0
  18. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/array/__init__.py +0 -0
  19. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/array/_array.py +0 -0
  20. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/config.py +0 -0
  21. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/data/__init__.py +0 -0
  22. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/data/_data.py +0 -0
  23. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/data/astronaut.npz +0 -0
  24. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/data/camera.npz +0 -0
  25. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/data/cat.npz +0 -0
  26. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/data/cookie.png +0 -0
  27. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/data/satellite.mat +0 -0
  28. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/density/__init__.py +0 -0
  29. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/density/_density.py +0 -0
  30. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/diagnostics.py +0 -0
  31. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/__init__.py +0 -0
  32. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_beta.py +0 -0
  33. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_cauchy.py +0 -0
  34. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_cmrf.py +0 -0
  35. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_custom.py +0 -0
  36. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_distribution.py +0 -0
  37. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_gamma.py +0 -0
  38. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_gaussian.py +0 -0
  39. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_gmrf.py +0 -0
  40. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_inverse_gamma.py +0 -0
  41. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_joint_distribution.py +0 -0
  42. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_laplace.py +0 -0
  43. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_lmrf.py +0 -0
  44. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_lognormal.py +0 -0
  45. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_modifiedhalfnormal.py +0 -0
  46. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_normal.py +0 -0
  47. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_posterior.py +0 -0
  48. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_smoothed_laplace.py +0 -0
  49. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_truncated_normal.py +0 -0
  50. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_uniform.py +0 -0
  51. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/__init__.py +0 -0
  52. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/_recommender.py +0 -0
  53. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/__init__.py +0 -0
  54. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/_ast.py +0 -0
  55. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/_orderedset.py +0 -0
  56. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/_randomvariable.py +0 -0
  57. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/geometry/__init__.py +0 -0
  58. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/geometry/_productgeometry.py +0 -0
  59. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/__init__.py +0 -0
  60. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_conjugate.py +0 -0
  61. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_conjugate_approx.py +0 -0
  62. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_cwmh.py +0 -0
  63. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_direct.py +0 -0
  64. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_hmc.py +0 -0
  65. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_langevin_algorithm.py +0 -0
  66. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_laplace_approximation.py +0 -0
  67. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_mh.py +0 -0
  68. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_pcn.py +0 -0
  69. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_rto.py +0 -0
  70. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_sampler.py +0 -0
  71. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/geometry/__init__.py +0 -0
  72. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/geometry/_geometry.py +0 -0
  73. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/__init__.py +0 -0
  74. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_regularizedGMRF.py +0 -0
  75. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_regularizedGaussian.py +0 -0
  76. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_regularizedUnboundedUniform.py +0 -0
  77. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_restorator.py +0 -0
  78. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/likelihood/__init__.py +0 -0
  79. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/likelihood/_likelihood.py +0 -0
  80. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/model/__init__.py +0 -0
  81. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/operator/__init__.py +0 -0
  82. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/operator/_operator.py +0 -0
  83. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/pde/__init__.py +0 -0
  84. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/problem/__init__.py +0 -0
  85. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/problem/_problem.py +0 -0
  86. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/__init__.py +0 -0
  87. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_conjugate.py +0 -0
  88. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_conjugate_approx.py +0 -0
  89. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_cwmh.py +0 -0
  90. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_gibbs.py +0 -0
  91. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_hmc.py +0 -0
  92. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_langevin_algorithm.py +0 -0
  93. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_laplace_approximation.py +0 -0
  94. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_mh.py +0 -0
  95. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_pcn.py +0 -0
  96. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_rto.py +0 -0
  97. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_sampler.py +0 -0
  98. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/samples/__init__.py +0 -0
  99. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/samples/_samples.py +0 -0
  100. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/solver/__init__.py +0 -0
  101. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/solver/_solver.py +0 -0
  102. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/testproblem/__init__.py +0 -0
  103. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/testproblem/_testproblem.py +0 -0
  104. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/utilities/__init__.py +0 -0
  105. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/cuqi/utilities/_get_python_variable_name.py +0 -0
  106. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/pyproject.toml +0 -0
  107. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/requirements.txt +0 -0
  108. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/setup.cfg +0 -0
  109. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/setup.py +0 -0
  110. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_MRFs.py +0 -0
  111. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_abstract_distribution_density.py +0 -0
  112. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_bayesian_inversion.py +0 -0
  113. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_density.py +0 -0
  114. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_distribution.py +0 -0
  115. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_distributions_shape.py +0 -0
  116. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_geometry.py +0 -0
  117. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_joint_distribution.py +0 -0
  118. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_likelihood.py +0 -0
  119. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_pde.py +0 -0
  120. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_posterior.py +0 -0
  121. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_problem.py +0 -0
  122. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_sampler.py +0 -0
  123. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_samples.py +0 -0
  124. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_solver.py +0 -0
  125. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_testproblem.py +0 -0
  126. {cuqipy-1.3.0.post0.dev266 → cuqipy-1.3.0.post0.dev292}/tests/test_utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CUQIpy
3
- Version: 1.3.0.post0.dev266
3
+ Version: 1.3.0.post0.dev292
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>, Chao Zhang <chaz@dtu.dk>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CUQIpy
3
- Version: 1.3.0.post0.dev266
3
+ Version: 1.3.0.post0.dev292
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>, Chao Zhang <chaz@dtu.dk>
6
6
  License: Apache License
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-06-23T11:22:39+0300",
11
+ "date": "2025-07-04T09:41:59+0300",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "0ad94a56cb0776e3b0964f9aebbc01c1b7b41ac1",
15
- "version": "1.3.0.post0.dev266"
14
+ "full-revisionid": "7536b0d728d704a7ab46bca7ff4756075ba0e573",
15
+ "version": "1.3.0.post0.dev292"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -42,6 +42,10 @@ class HybridGibbs:
42
42
  fully stateful at this point. This means samplers like NUTS will lose
43
43
  their internal state between Gibbs steps.
44
44
 
45
+ The order in which the conditionals are sampled is the order of the
46
+ variables in the sampling strategy, unless a different sampling order
47
+ is specified by the parameter `scan_order`
48
+
45
49
  Parameters
46
50
  ----------
47
51
  target : cuqi.distribution.JointDistribution
@@ -58,6 +62,11 @@ class HybridGibbs:
58
62
  will call its step method in each Gibbs step.
59
63
  Default is 1 for all variables.
60
64
 
65
+ scan_order : list or str, *optional*
66
+ Order in which the conditional distributions are sampled.
67
+ If set to "random", use a random ordering at each step.
68
+ If not specified, it will be the order in the sampling_strategy.
69
+
61
70
  callback : callable, optional
62
71
  A function that will be called after each sampling step. It can be useful for monitoring the sampler during sampling.
63
72
  The function should take three arguments: the sampler object, the index of the current sampling step, the total number of requested samples. The last two arguments are integers. An example of the callback function signature is: `callback(sampler, sample_index, num_of_samples)`.
@@ -107,7 +116,7 @@ class HybridGibbs:
107
116
 
108
117
  """
109
118
 
110
- def __init__(self, target: JointDistribution, sampling_strategy: Dict[str, Sampler], num_sampling_steps: Dict[str, int] = None, callback=None):
119
+ def __init__(self, target: JointDistribution, sampling_strategy: Dict[str, Sampler], num_sampling_steps: Dict[str, int] = None, scan_order = None, callback=None):
111
120
 
112
121
  # Store target and allow conditioning to reduce to a single density
113
122
  self.target = target() # Create a copy of target distribution (to avoid modifying the original)
@@ -121,6 +130,13 @@ class HybridGibbs:
121
130
  # Store parameter names
122
131
  self.par_names = self.target.get_parameter_names()
123
132
 
133
+ # Store the scan order
134
+ self._scan_order = scan_order
135
+
136
+ # Check that the parameters of the target align with the sampling_strategy and scan_order
137
+ if set(self.par_names) != set(self.scan_order):
138
+ raise ValueError("Parameter names in JointDistribution do not equal the names in the scan order.")
139
+
124
140
  # Initialize sampler (after target is set)
125
141
  self._initialize()
126
142
 
@@ -148,6 +164,16 @@ class HybridGibbs:
148
164
  # Validate all targets for samplers.
149
165
  self.validate_targets()
150
166
 
167
+ @property
168
+ def scan_order(self):
169
+ if self._scan_order is None:
170
+ return list(self.samplers.keys())
171
+ if self._scan_order == "random":
172
+ arr = list(self.samplers.keys())
173
+ np.random.shuffle(arr) # Shuffle works in-place
174
+ return arr
175
+ return self._scan_order
176
+
151
177
  # ------------ Public methods ------------
152
178
  def validate_targets(self):
153
179
  """ Validate each of the conditional targets used in the Gibbs steps """
@@ -217,7 +243,7 @@ class HybridGibbs:
217
243
  """ Sequentially go through all parameters and sample them conditionally on each other """
218
244
 
219
245
  # Sample from each conditional distribution
220
- for par_name in self.par_names:
246
+ for par_name in self.scan_order:
221
247
 
222
248
  # Set target for current parameter
223
249
  self._set_target(par_name)
@@ -179,7 +179,7 @@ class Model(object):
179
179
  self._stored_non_default_args =\
180
180
  cuqi.utilities.get_non_default_args(self._forward_func)
181
181
  return self._stored_non_default_args
182
-
182
+
183
183
  @property
184
184
  def number_of_inputs(self):
185
185
  """ The number of inputs of the model. """
@@ -422,7 +422,7 @@ class Model(object):
422
422
  # Use CUQIarray funvals if geometry is consistent
423
423
  if isinstance(v, CUQIarray) and v.geometry == geometries[i]:
424
424
  kwargs[k] = v.funvals
425
- # Else, if we still need to convert to function value (is_par[i] is True)
425
+ # Else, if we still need to convert to function value (is_par[i] is True)
426
426
  # we use the geometry par2fun method
427
427
  elif is_par[i] and v is not None:
428
428
  kwargs[k] = geometries[i].par2fun(v)
@@ -496,7 +496,7 @@ class Model(object):
496
496
  # Use CUQIarray parameters if geometry is consistent
497
497
  if isinstance(v, CUQIarray) and v.geometry == geometries[i]:
498
498
  v = v.parameters
499
- # Else, if we still need to convert to parameter value (is_par[i] is False)
499
+ # Else, if we still need to convert to parameter value (is_par[i] is False)
500
500
  # we use the geometry fun2par method
501
501
  elif not is_par[i] and v is not None:
502
502
  v = geometries[i].fun2par(v)
@@ -665,7 +665,7 @@ class Model(object):
665
665
  error_msg = (
666
666
  "The "
667
667
  + map_name.lower()
668
- + f" input is specified by a keywords arguments {list(kwargs.keys())} that does not match the non_default_args of the "
668
+ + f" input is specified by keywords arguments {list(kwargs.keys())} that does not match the non_default_args of the "
669
669
  + map_name
670
670
  + f" {non_default_args}."
671
671
  )
@@ -808,7 +808,11 @@ class Model(object):
808
808
  new_model = copy(self)
809
809
 
810
810
  # Store the original non_default_args of the model
811
- new_model._original_non_default_args = self._non_default_args
811
+ new_model._original_non_default_args = (
812
+ self._original_non_default_args
813
+ if hasattr(self, "_original_non_default_args")
814
+ else self._non_default_args
815
+ )
812
816
 
813
817
  # Update the non_default_args of the model to match the distribution
814
818
  # names. Defaults to x in the case of only one distribution that has no
@@ -1052,7 +1056,7 @@ class Model(object):
1052
1056
 
1053
1057
  # turn grad_is_par to a tuple of bools if it is not already
1054
1058
  if isinstance(grad_is_par, bool):
1055
- grad_is_par = tuple([grad_is_par]*len(grad))
1059
+ grad_is_par = tuple([grad_is_par]*self.number_of_inputs)
1056
1060
 
1057
1061
  # If the domain geometry is a _ProductGeometry and the gradient is
1058
1062
  # stacked, split it
@@ -1451,7 +1455,7 @@ class PDEModel(Model):
1451
1455
  :ivar range_geometry: The geometry representing the range.
1452
1456
  :ivar domain_geometry: The geometry representing the domain.
1453
1457
  """
1454
- def __init__(self, PDE: cuqi.pde.PDE, range_geometry, domain_geometry):
1458
+ def __init__(self, PDE: cuqi.pde.PDE, range_geometry, domain_geometry, **kwargs):
1455
1459
 
1456
1460
  if not isinstance(PDE, cuqi.pde.PDE):
1457
1461
  raise ValueError("PDE needs to be a cuqi PDE.")
@@ -1460,23 +1464,30 @@ class PDEModel(Model):
1460
1464
  self.pde = PDE
1461
1465
  self._stored_non_default_args = None
1462
1466
 
1463
- super().__init__(self._forward_func, range_geometry, domain_geometry)
1467
+ # If gradient or jacobian is not provided, we create it from the PDE
1468
+ if not np.any([k in kwargs.keys() for k in ["gradient", "jacobian"]]):
1469
+ # Create gradient or jacobian function to pass to the Model based on
1470
+ # the PDE object. The dictionary derivative_kwarg contains the
1471
+ # created function along with the function type (either "gradient"
1472
+ # or "jacobian")
1473
+ derivative_kwarg = self._create_derivative_function()
1474
+ # append derivative_kwarg to kwargs
1475
+ kwargs.update(derivative_kwarg)
1476
+
1477
+ super().__init__(forward=self._forward_func_pde,
1478
+ range_geometry=range_geometry,
1479
+ domain_geometry=domain_geometry,
1480
+ **kwargs)
1464
1481
 
1465
1482
  @property
1466
1483
  def _non_default_args(self):
1467
1484
  if self._stored_non_default_args is None:
1468
1485
  # extract the non-default arguments of the PDE
1469
- self._stored_non_default_args = cuqi.utilities.get_non_default_args(
1470
- self.pde.PDE_form
1471
- )
1472
- # remove t from the non-default arguments
1473
- self._stored_non_default_args = self._non_default_args
1474
- if "t" in self._non_default_args:
1475
- self._stored_non_default_args.remove("t")
1486
+ self._stored_non_default_args = self.pde._non_default_args
1476
1487
 
1477
1488
  return self._stored_non_default_args
1478
1489
 
1479
- def _forward_func(self, **kwargs):
1490
+ def _forward_func_pde(self, **kwargs):
1480
1491
 
1481
1492
  self.pde.assemble(**kwargs)
1482
1493
 
@@ -1486,14 +1497,55 @@ class PDEModel(Model):
1486
1497
 
1487
1498
  return obs
1488
1499
 
1489
- def _gradient_func(self, direction, wrt):
1490
- """ Compute direction-Jacobian product (gradient) of the model. """
1500
+ def _create_derivative_function(self):
1501
+ """Private function that creates the derivative function (gradient or
1502
+ jacobian) based on the PDE object. The derivative function is created as
1503
+ a lambda function that takes the direction and the parameters as input
1504
+ and returns the gradient or jacobian of the PDE. This private function
1505
+ returns a dictionary with the created function and the function type
1506
+ (either "gradient" or "jacobian")."""
1507
+
1491
1508
  if hasattr(self.pde, "gradient_wrt_parameter"):
1492
- return self.pde.gradient_wrt_parameter(direction, wrt)
1509
+ # Build the string that will be used to create the lambda function
1510
+ function_str = (
1511
+ "lambda direction, "
1512
+ + ", ".join(self._non_default_args)
1513
+ + ", pde_func: pde_func(direction, "
1514
+ + ", ".join(self._non_default_args)
1515
+ + ")"
1516
+ )
1517
+
1518
+ # create the lambda function from the string
1519
+ function = eval(function_str)
1520
+
1521
+ # create partial function from the lambda function with gradient_wrt_parameter
1522
+ # as the first argument
1523
+ grad_func = partial(function, pde_func=self.pde.gradient_wrt_parameter)
1524
+
1525
+ # Return the gradient function
1526
+ return {"gradient": grad_func}
1527
+
1493
1528
  elif hasattr(self.pde, "jacobian_wrt_parameter"):
1494
- return direction@self.pde.jacobian_wrt_parameter(wrt)
1529
+ # Build the string that will be used to create the lambda function
1530
+ function_str = (
1531
+ "lambda "
1532
+ + ", ".join(self._non_default_args)
1533
+ + ", pde_func: pde_func( "
1534
+ + ", ".join(self._non_default_args)
1535
+ + ")"
1536
+ )
1537
+
1538
+ # create the lambda function from the string
1539
+ function = eval(function_str)
1540
+
1541
+ # create partial function from the lambda function with jacobian_wrt_parameter
1542
+ # as the first argument
1543
+ jacobian_func = partial(function, pde_func=self.pde.jacobian_wrt_parameter)
1544
+
1545
+ # Return the jacobian function
1546
+ return {"jacobian": jacobian_func}
1495
1547
  else:
1496
- raise NotImplementedError("Gradient is not implemented for this model.")
1548
+ return {} # empty dictionary if no gradient or jacobian is found
1497
1549
 
1498
1550
  # Add the underlying PDE class name to the repr.
1499
1551
  def __repr__(self) -> str:
@@ -3,6 +3,7 @@ import scipy
3
3
  from inspect import getsource
4
4
  from scipy.interpolate import interp1d
5
5
  import numpy as np
6
+ from cuqi.utilities import get_non_default_args
6
7
 
7
8
 
8
9
  class PDE(ABC):
@@ -29,6 +30,7 @@ class PDE(ABC):
29
30
  self.grid_sol = grid_sol
30
31
  self.grid_obs = grid_obs
31
32
  self.observation_map = observation_map
33
+ self._stored_non_default_args = None
32
34
 
33
35
  @abstractmethod
34
36
  def assemble(self, *args, **kwargs):
@@ -64,6 +66,13 @@ class PDE(ABC):
64
66
 
65
67
  return equal_arrays
66
68
 
69
+ @property
70
+ def _non_default_args(self):
71
+ """Returns the non-default arguments of the PDE_form function"""
72
+ if self._stored_non_default_args is None:
73
+ self._stored_non_default_args = get_non_default_args(self.PDE_form)
74
+ return self._stored_non_default_args
75
+
67
76
  @property
68
77
  def grid_sol(self):
69
78
  if hasattr(self,"_grid_sol"):
@@ -94,6 +103,48 @@ class PDE(ABC):
94
103
  def grids_equal(self):
95
104
  return self._grids_equal
96
105
 
106
+ def _parse_args_add_to_kwargs(
107
+ self, *args, map_name, **kwargs):
108
+ """ Private function that parses the input arguments and adds them as
109
+ keyword arguments matching (the order of) the non default arguments of
110
+ the pde class.
111
+ """
112
+
113
+ # If any args are given, add them to kwargs
114
+ if len(args) > 0:
115
+ if len(kwargs) > 0:
116
+ raise ValueError(
117
+ + map_name.lower()
118
+ + " input is specified both as positional and keyword arguments. This is not supported."
119
+ )
120
+
121
+ # Check if the number of args does not match the number of
122
+ # non_default_args of the model
123
+ if len(args) != len(self._non_default_args):
124
+ raise ValueError(
125
+ "The number of positional arguments does not match the number of non-default arguments of "
126
+ + map_name.lower()
127
+ + "."
128
+ )
129
+
130
+ # Add args to kwargs following the order of non_default_args
131
+ for idx, arg in enumerate(args):
132
+ kwargs[self._non_default_args[idx]] = arg
133
+
134
+ # Check kwargs matches non_default_args
135
+ if set(list(kwargs.keys())) != set(self._non_default_args):
136
+ error_msg = (
137
+ map_name.lower()
138
+ + f" input is specified by keywords arguments {list(kwargs.keys())} that does not match the non_default_args of "
139
+ + map_name
140
+ + f" {self._non_default_args}."
141
+ )
142
+ raise ValueError(error_msg)
143
+
144
+ # Make sure order of kwargs is the same as non_default_args
145
+ kwargs = {k: kwargs[k] for k in self._non_default_args}
146
+
147
+ return kwargs
97
148
 
98
149
  class LinearPDE(PDE):
99
150
  """
@@ -143,7 +194,7 @@ class SteadyStateLinearPDE(LinearPDE):
143
194
  Parameters
144
195
  -----------
145
196
  PDE_form : callable function
146
- Callable function with signature `PDE_form(parameter)` where `parameter` is the Bayesian parameter. The function returns a tuple with the discretized differential operator A and right-hand-side b. The types of A and b are determined by what the method :meth:`linalg_solve` accepts as first and second parameters, respectively.
197
+ Callable function with signature `PDE_form(parameter1, parameter2, ...)` where `parameter1`, `parameter2`, etc. are the Bayesian unknown parameters (the user can choose any names for these parameters, e.g. `a`, `b`, etc.). The function returns a tuple with the discretized differential operator A and right-hand-side b. The types of A and b are determined by what the method :meth:`linalg_solve` accepts as first and second parameters, respectively.
147
198
 
148
199
  kwargs:
149
200
  See :class:`~cuqi.pde.LinearPDE` for the remaining keyword arguments.
@@ -158,7 +209,10 @@ class SteadyStateLinearPDE(LinearPDE):
158
209
 
159
210
  def assemble(self, *args, **kwargs):
160
211
  """Assembles differential operator and rhs according to PDE_form"""
161
- self.diff_op, self.rhs = self.PDE_form(*args, **kwargs)
212
+ kwargs = self._parse_args_add_to_kwargs(
213
+ *args, map_name="assemble", **kwargs
214
+ )
215
+ self.diff_op, self.rhs = self.PDE_form(**kwargs)
162
216
 
163
217
  def solve(self):
164
218
  """Solve the PDE and returns the solution and an information variable `info` which is a tuple of all variables returned by the function `linalg_solve` after the solution."""
@@ -186,7 +240,7 @@ class TimeDependentLinearPDE(LinearPDE):
186
240
  Parameters
187
241
  -----------
188
242
  PDE_form : callable function
189
- Callable function with signature `PDE_form(parameter, t)` where `parameter` is the Bayesian parameter and `t` is the time at which the PDE form is evaluated. The function returns a tuple of (`differential_operator`, `source_term`, `initial_condition`) where `differential_operator` is the linear operator at time `t`, `source_term` is the source term at time `t`, and `initial_condition` is the initial condition. The types of `differential_operator` and `source_term` are determined by what the method :meth:`linalg_solve` accepts as linear operator and right-hand side, respectively. The type of `initial_condition` should be the same type as the solution returned by :meth:`linalg_solve`.
243
+ Callable function with signature `PDE_form(parameter1, parameter2, ..., t)` where `parameter1`, `parameter2`, etc. are the Bayesian unknown parameters (the user can choose any names for these parameters, e.g. `a`, `b`, etc.) and `t` is the time at which the PDE form is evaluated. The function returns a tuple of (`differential_operator`, `source_term`, `initial_condition`) where `differential_operator` is the linear operator at time `t`, `source_term` is the source term at time `t`, and `initial_condition` is the initial condition. The types of `differential_operator` and `source_term` are determined by what the method :meth:`linalg_solve` accepts as linear operator and right-hand side, respectively. The type of `initial_condition` should be the same type as the solution returned by :meth:`linalg_solve`.
190
244
 
191
245
  time_steps : ndarray
192
246
  An array of the discretized times corresponding to the time steps that starts with the initial time and ends with the final time
@@ -228,6 +282,18 @@ class TimeDependentLinearPDE(LinearPDE):
228
282
  def method(self):
229
283
  return self._method
230
284
 
285
+ @property
286
+ def _non_default_args(self):
287
+ """Returns the non-default arguments of the PDE_form function"""
288
+ if self._stored_non_default_args is None:
289
+ self._stored_non_default_args = get_non_default_args(self.PDE_form)
290
+ # Remove the time argument from the non-default arguments
291
+ # since it is provided automatically by `solve` method and is not
292
+ # an argument to be inferred in Bayesian inference setting.
293
+ if 't' in self._stored_non_default_args:
294
+ self._stored_non_default_args.remove('t')
295
+ return self._stored_non_default_args
296
+
231
297
  @method.setter
232
298
  def method(self, value):
233
299
  if value.lower() != 'forward_euler' and value.lower() != 'backward_euler':
@@ -237,13 +303,13 @@ class TimeDependentLinearPDE(LinearPDE):
237
303
 
238
304
  def assemble(self, *args, **kwargs):
239
305
  """Assemble PDE"""
306
+ kwargs = self._parse_args_add_to_kwargs(*args, map_name="assemble", **kwargs)
240
307
  self._parameter_kwargs = kwargs
241
- self._parameter_args = args
242
308
 
243
309
  def assemble_step(self, t):
244
310
  """Assemble time step at time t"""
245
311
  self.diff_op, self.rhs, self.initial_condition = self.PDE_form(
246
- *self._parameter_args, **self._parameter_kwargs, t=t
312
+ **self._parameter_kwargs, t=t
247
313
  )
248
314
 
249
315
  def solve(self):
@@ -188,7 +188,7 @@ def approx_derivative(func, wrt, direction=None, epsilon=np.sqrt(np.finfo(float)
188
188
  # We compute the Jacobian matrix of func using forward differences.
189
189
  # If the function is scalar-valued, we compute the gradient instead.
190
190
  # If the direction is provided, we compute the direction-Jacobian product.
191
- wrt = np.asarray(wrt)
191
+ wrt = force_ndarray(wrt, flatten=True)
192
192
  f0 = func(wrt)
193
193
  Matr = np.zeros([infer_len(wrt), infer_len(f0)])
194
194
  dx = np.zeros(len(wrt))
@@ -210,8 +210,8 @@ def test_regression_increasing():
210
210
  posterior = joint(y=y_obs)
211
211
 
212
212
  sampling_strategy = {
213
+ 'd': cuqi.experimental.mcmc.Conjugate(),
213
214
  'x': cuqi.experimental.mcmc.RegularizedLinearRTO(maxit=50, penalty_parameter=20, adaptive = False),
214
- 'd': cuqi.experimental.mcmc.Conjugate()
215
215
  }
216
216
  sampler = cuqi.experimental.mcmc.HybridGibbs(posterior, sampling_strategy)
217
217
 
@@ -241,8 +241,8 @@ def test_regression_convex():
241
241
  posterior = joint(y=y_obs)
242
242
 
243
243
  sampling_strategy = {
244
- 'x': cuqi.experimental.mcmc.RegularizedLinearRTO(maxit=50, penalty_parameter=20, adaptive = False),
245
- 'd': cuqi.experimental.mcmc.Conjugate()
244
+ 'd': cuqi.experimental.mcmc.Conjugate(),
245
+ 'x': cuqi.experimental.mcmc.RegularizedLinearRTO(maxit=50, penalty_parameter=20, adaptive = False)
246
246
  }
247
247
  sampler = cuqi.experimental.mcmc.HybridGibbs(posterior, sampling_strategy)
248
248