CUQIpy 1.3.0.post0.dev277__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.
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/PKG-INFO +1 -1
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/PKG-INFO +1 -1
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/_version.py +3 -3
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/model/_model.py +73 -21
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/pde/_pde.py +71 -5
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/utilities/_utilities.py +1 -1
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_model.py +237 -32
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/SOURCES.txt +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/dependency_links.txt +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/requires.txt +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/top_level.txt +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/LICENSE +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/README.md +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/_messages.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/array/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/array/_array.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/config.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/data/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/data/_data.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/data/astronaut.npz +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/data/camera.npz +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/data/cat.npz +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/data/cookie.png +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/data/satellite.mat +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/density/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/density/_density.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/diagnostics.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_beta.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_cauchy.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_cmrf.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_custom.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_distribution.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_gamma.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_gaussian.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_gmrf.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_inverse_gamma.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_joint_distribution.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_laplace.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_lmrf.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_lognormal.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_modifiedhalfnormal.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_normal.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_posterior.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_smoothed_laplace.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_truncated_normal.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_uniform.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/_recommender.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/_ast.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/_orderedset.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/_randomvariable.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/geometry/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/geometry/_productgeometry.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_conjugate.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_conjugate_approx.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_cwmh.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_direct.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_gibbs.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_hmc.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_langevin_algorithm.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_laplace_approximation.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_mh.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_pcn.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_rto.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_sampler.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/geometry/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/geometry/_geometry.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_regularizedGMRF.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_regularizedGaussian.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_regularizedUnboundedUniform.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_restorator.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/likelihood/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/likelihood/_likelihood.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/model/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/operator/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/operator/_operator.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/pde/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/problem/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/problem/_problem.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_conjugate.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_conjugate_approx.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_cwmh.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_gibbs.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_hmc.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_langevin_algorithm.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_laplace_approximation.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_mh.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_pcn.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_rto.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_sampler.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/samples/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/samples/_samples.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/solver/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/solver/_solver.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/testproblem/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/testproblem/_testproblem.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/utilities/__init__.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/utilities/_get_python_variable_name.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/pyproject.toml +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/requirements.txt +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/setup.cfg +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/setup.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_MRFs.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_abstract_distribution_density.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_bayesian_inversion.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_density.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_distribution.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_distributions_shape.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_geometry.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_implicit_priors.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_joint_distribution.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_likelihood.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_pde.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_posterior.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_problem.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_sampler.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_samples.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_solver.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_testproblem.py +0 -0
- {cuqipy-1.3.0.post0.dev277 → 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.
|
|
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.
|
|
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-
|
|
11
|
+
"date": "2025-07-04T09:41:59+0300",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "1.3.0.post0.
|
|
14
|
+
"full-revisionid": "7536b0d728d704a7ab46bca7ff4756075ba0e573",
|
|
15
|
+
"version": "1.3.0.post0.dev292"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -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
|
|
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 =
|
|
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]*
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
1490
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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))
|
|
@@ -7,15 +7,15 @@ import cuqi
|
|
|
7
7
|
import pytest
|
|
8
8
|
from scipy import optimize
|
|
9
9
|
from copy import copy, deepcopy
|
|
10
|
-
from cuqi.geometry import _identity_geometries, _DefaultGeometry1D, _DefaultGeometry2D, Geometry, Discrete, Image2D
|
|
10
|
+
from cuqi.geometry import _identity_geometries, _DefaultGeometry1D, _DefaultGeometry2D, Geometry, Discrete, Image2D, KLExpansion
|
|
11
11
|
from cuqi.utilities import force_ndarray
|
|
12
12
|
from cuqi.experimental.geometry import _ProductGeometry
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
""" Test that the PDE model can accept multiple inputs specified as positional arguments or keyword arguments """
|
|
14
|
+
def test_steady_state_PDE_model_multiple_input():
|
|
15
|
+
""" Test that the steady state PDE model and gradient can accept multiple inputs specified as positional arguments or keyword arguments """
|
|
16
16
|
pde_test_model = MultipleInputTestModel.helper_build_steady_state_PDE_test_model()
|
|
17
17
|
pde_test_model.populate_model_variations()
|
|
18
|
-
CUQI_pde = pde_test_model.model_variations[
|
|
18
|
+
CUQI_pde = pde_test_model.model_variations[1] # PDE model with multiple inputs
|
|
19
19
|
|
|
20
20
|
# Check that the model has correct parameter name
|
|
21
21
|
assert CUQI_pde._non_default_args == ['mag', 'kappa_scale']
|
|
@@ -29,6 +29,54 @@ def test_PDE_model_multiple_input():
|
|
|
29
29
|
# Check that the two outputs are the same
|
|
30
30
|
assert np.allclose(output1, output2)
|
|
31
31
|
|
|
32
|
+
# Assert evaluating gradient works
|
|
33
|
+
direction = np.random.randn(CUQI_pde.range_dim)
|
|
34
|
+
|
|
35
|
+
# Make sure gradient can be computed with positional or keyword arguments
|
|
36
|
+
grad1 = CUQI_pde.gradient(direction, mag=2, kappa_scale=2)
|
|
37
|
+
grad2 = CUQI_pde.gradient(direction, 2, 2)
|
|
38
|
+
|
|
39
|
+
# Passing wrong kwargs should raise an error
|
|
40
|
+
with pytest.raises(
|
|
41
|
+
ValueError,
|
|
42
|
+
match=r"The gradient input is specified by a direction and keywords arguments \['mag', 'kappa'\] that does not match the non_default_args of the model \['mag', 'kappa_scale'\].",
|
|
43
|
+
):
|
|
44
|
+
CUQI_pde.gradient(direction, mag=2, kappa=2)
|
|
45
|
+
|
|
46
|
+
def test_time_dependent_PDE_model_multiple_input():
|
|
47
|
+
""" Test that the time dependent PDE model and gradient can accept multiple inputs specified as positional arguments or keyword arguments """
|
|
48
|
+
pde_test_model = MultipleInputTestModel.helper_build_time_dependent_PDE_test_model()
|
|
49
|
+
pde_test_model.populate_model_variations()
|
|
50
|
+
CUQI_pde = pde_test_model.model_variations[1] # PDE model with multiple inputs
|
|
51
|
+
|
|
52
|
+
# Check that the model has correct parameter name
|
|
53
|
+
assert CUQI_pde._non_default_args == ['mag', 'IC']
|
|
54
|
+
|
|
55
|
+
# Check that we can provide parameter names when evaluating the model
|
|
56
|
+
mag = 2
|
|
57
|
+
IC_ = np.random.randn(CUQI_pde.domain_geometry.geometries[1].par_dim)
|
|
58
|
+
output1 = CUQI_pde(mag=mag, IC=IC_)
|
|
59
|
+
|
|
60
|
+
# And check that we can provide positional arguments
|
|
61
|
+
output2 = CUQI_pde(mag, IC_)
|
|
62
|
+
|
|
63
|
+
# Check that the two outputs are the same
|
|
64
|
+
assert np.allclose(output1, output2)
|
|
65
|
+
|
|
66
|
+
# Assert evaluating gradient works
|
|
67
|
+
direction = np.random.randn(CUQI_pde.range_dim)
|
|
68
|
+
|
|
69
|
+
# Make sure gradient can be computed with positional or keyword arguments
|
|
70
|
+
grad1 = CUQI_pde.gradient(direction, mag=mag, IC=IC_)
|
|
71
|
+
grad2 = CUQI_pde.gradient(direction, mag, IC_)
|
|
72
|
+
|
|
73
|
+
# Passing wrong kwargs should raise an error
|
|
74
|
+
with pytest.raises(
|
|
75
|
+
ValueError,
|
|
76
|
+
match=r"The gradient input is specified by a direction and keywords arguments \['mag', 'IC_value'\] that does not match the non_default_args of the model \['mag', 'IC'\].",
|
|
77
|
+
):
|
|
78
|
+
CUQI_pde.gradient(direction, mag=mag, IC_value=IC_)
|
|
79
|
+
|
|
32
80
|
def test_constructing_gradient_from_jacobian():
|
|
33
81
|
""" Test that the gradient is correctly constructed from the
|
|
34
82
|
jacobian when only the jacobian is specified """
|
|
@@ -392,18 +440,17 @@ class MultipleInputTestModel:
|
|
|
392
440
|
|
|
393
441
|
def populate_model_variations(self):
|
|
394
442
|
"""Populate the `model_variations` list with different variations of the model that share the same forward map but differ in some other aspect like gradient, jacobian, etc."""
|
|
443
|
+
|
|
395
444
|
if self.pde is not None:
|
|
396
|
-
self.
|
|
397
|
-
|
|
398
|
-
self.
|
|
445
|
+
first_kwarg = {"PDE": self.pde}
|
|
446
|
+
else:
|
|
447
|
+
first_kwarg = {"forward": self.forward_map}
|
|
399
448
|
|
|
400
|
-
def populate_general_model_variations(self):
|
|
401
|
-
"""Populate the `model_variations` for general models that are not PDE models."""
|
|
402
449
|
# Model with forward only
|
|
403
450
|
model = self.model_class(
|
|
404
|
-
|
|
451
|
+
**first_kwarg,
|
|
405
452
|
domain_geometry=self.domain_geometry,
|
|
406
|
-
range_geometry=self.range_geometry
|
|
453
|
+
range_geometry=self.range_geometry
|
|
407
454
|
)
|
|
408
455
|
model._do_test_gradient = False # do not test this model for gradient
|
|
409
456
|
self.model_variations.append(model)
|
|
@@ -411,7 +458,7 @@ class MultipleInputTestModel:
|
|
|
411
458
|
# Model with gradient of from 1 (callable)
|
|
412
459
|
if self.gradient_form1 is not None:
|
|
413
460
|
model = self.model_class(
|
|
414
|
-
|
|
461
|
+
**first_kwarg,
|
|
415
462
|
gradient=self.gradient_form1,
|
|
416
463
|
domain_geometry=self.domain_geometry,
|
|
417
464
|
range_geometry=self.range_geometry,
|
|
@@ -421,7 +468,7 @@ class MultipleInputTestModel:
|
|
|
421
468
|
# Model with gradient of from 2 (tuple of callables)
|
|
422
469
|
if self.gradient_form2 is not None:
|
|
423
470
|
model = self.model_class(
|
|
424
|
-
|
|
471
|
+
**first_kwarg,
|
|
425
472
|
gradient=self.gradient_form2,
|
|
426
473
|
domain_geometry=self.domain_geometry,
|
|
427
474
|
range_geometry=self.range_geometry,
|
|
@@ -431,7 +478,7 @@ class MultipleInputTestModel:
|
|
|
431
478
|
# Model with gradient of from 2 incomplete (tuple of callables with some None elements)
|
|
432
479
|
if self.gradient_form2_incomplete is not None:
|
|
433
480
|
model = self.model_class(
|
|
434
|
-
|
|
481
|
+
**first_kwarg,
|
|
435
482
|
gradient=self.gradient_form2_incomplete,
|
|
436
483
|
domain_geometry=self.domain_geometry,
|
|
437
484
|
range_geometry=self.range_geometry,
|
|
@@ -441,7 +488,7 @@ class MultipleInputTestModel:
|
|
|
441
488
|
# Model with jacobian of from 1 (callable)
|
|
442
489
|
if self.jacobian_form1 is not None:
|
|
443
490
|
model = self.model_class(
|
|
444
|
-
|
|
491
|
+
**first_kwarg,
|
|
445
492
|
jacobian=self.jacobian_form1,
|
|
446
493
|
domain_geometry=self.domain_geometry,
|
|
447
494
|
range_geometry=self.range_geometry,
|
|
@@ -451,7 +498,7 @@ class MultipleInputTestModel:
|
|
|
451
498
|
# Model with jacobian of from 2 (tuple of callables)
|
|
452
499
|
if self.jacobian_form2 is not None:
|
|
453
500
|
model = self.model_class(
|
|
454
|
-
|
|
501
|
+
**first_kwarg,
|
|
455
502
|
jacobian=self.jacobian_form2,
|
|
456
503
|
domain_geometry=self.domain_geometry,
|
|
457
504
|
range_geometry=self.range_geometry,
|
|
@@ -461,24 +508,13 @@ class MultipleInputTestModel:
|
|
|
461
508
|
# Model with jacobian of from 2 incomplete (tuple of callables with some None elements)
|
|
462
509
|
if self.jacobian_form2_incomplete is not None:
|
|
463
510
|
model = self.model_class(
|
|
464
|
-
|
|
511
|
+
**first_kwarg,
|
|
465
512
|
jacobian=self.jacobian_form2_incomplete,
|
|
466
513
|
domain_geometry=self.domain_geometry,
|
|
467
514
|
range_geometry=self.range_geometry,
|
|
468
515
|
)
|
|
469
516
|
self.model_variations.append(model)
|
|
470
517
|
|
|
471
|
-
def populate_pde_model_variations(self):
|
|
472
|
-
"""Populate the `model_variations` for PDE models."""
|
|
473
|
-
# Model with PDE (no gradient)
|
|
474
|
-
model = self.model_class(
|
|
475
|
-
self.pde,
|
|
476
|
-
domain_geometry=self.domain_geometry,
|
|
477
|
-
range_geometry=self.range_geometry,
|
|
478
|
-
)
|
|
479
|
-
model._do_test_gradient = False # do not test this model for gradient
|
|
480
|
-
self.model_variations.append(model)
|
|
481
|
-
|
|
482
518
|
@staticmethod
|
|
483
519
|
def create_model_test_case_combinations():
|
|
484
520
|
"""Create all combinations of test model variations and test cases
|
|
@@ -510,6 +546,16 @@ class MultipleInputTestModel:
|
|
|
510
546
|
TestCase.create_test_cases_for_test_model(test_model)
|
|
511
547
|
test_model_list.append(test_model)
|
|
512
548
|
|
|
549
|
+
# Model 4
|
|
550
|
+
test_model = MultipleInputTestModel.helper_build_time_dependent_PDE_test_model()
|
|
551
|
+
test_model.populate_model_variations()
|
|
552
|
+
test_model.input_bounds = [
|
|
553
|
+
0.1,
|
|
554
|
+
4,
|
|
555
|
+
] # choose input from uniform distribution in [0.1, 4]
|
|
556
|
+
TestCase.create_test_cases_for_test_model(test_model)
|
|
557
|
+
test_model_list.append(test_model)
|
|
558
|
+
|
|
513
559
|
# Append all combinations of test model variations and test cases
|
|
514
560
|
# to model_test_case_combinations
|
|
515
561
|
for test_model in test_model_list:
|
|
@@ -589,7 +635,7 @@ class MultipleInputTestModel:
|
|
|
589
635
|
|
|
590
636
|
@staticmethod
|
|
591
637
|
def helper_build_steady_state_PDE_test_model():
|
|
592
|
-
"""Build a PDE model with a steady state Poisson equation and two inputs: mag and kappa_scale.
|
|
638
|
+
"""Build a PDE model with a steady state Poisson equation and two inputs: mag and kappa_scale."""
|
|
593
639
|
|
|
594
640
|
# Poisson equation setup
|
|
595
641
|
dim = 20 # Number of nodes
|
|
@@ -635,6 +681,68 @@ class MultipleInputTestModel:
|
|
|
635
681
|
test_model.domain_geometry = (Discrete(["mag"]), Discrete(["kappa_scale"]))
|
|
636
682
|
test_model.range_geometry = Continuous1D(len(grid_obs))
|
|
637
683
|
|
|
684
|
+
# Gradient with respect to mag
|
|
685
|
+
def gradient_mag(direction, mag, kappa_scale):
|
|
686
|
+
def fwd_mag(mag_):
|
|
687
|
+
CUQI_pde.assemble(mag_, kappa_scale)
|
|
688
|
+
u, _ = CUQI_pde.solve()
|
|
689
|
+
obs_u = CUQI_pde.observe(u)
|
|
690
|
+
return obs_u
|
|
691
|
+
mag = mag.to_numpy() if isinstance(mag, CUQIarray) else mag
|
|
692
|
+
return direction @ cuqi.utilities.approx_derivative(fwd_mag, mag)
|
|
693
|
+
|
|
694
|
+
# Gradient with respect to kappa_scale
|
|
695
|
+
def gradient_kappa_scale(direction, mag, kappa_scale):
|
|
696
|
+
def fwd_kappa_scale(kappa_scale_):
|
|
697
|
+
CUQI_pde.assemble(mag, kappa_scale_)
|
|
698
|
+
u, _ = CUQI_pde.solve()
|
|
699
|
+
obs_u = CUQI_pde.observe(u)
|
|
700
|
+
return obs_u
|
|
701
|
+
kappa_scale = kappa_scale.to_numpy() if isinstance(kappa_scale, CUQIarray) else kappa_scale
|
|
702
|
+
return direction @ cuqi.utilities.approx_derivative(fwd_kappa_scale, kappa_scale)
|
|
703
|
+
|
|
704
|
+
# Gradient with respect to all inputs (form 1, callable)
|
|
705
|
+
def gradient_form1(direction, mag, kappa_scale):
|
|
706
|
+
grad_mag = gradient_mag(direction, mag, kappa_scale)
|
|
707
|
+
grad_kappa_scale = gradient_kappa_scale(direction, mag, kappa_scale)
|
|
708
|
+
return (grad_mag, grad_kappa_scale)
|
|
709
|
+
|
|
710
|
+
# Assign the gradient functions to the test model
|
|
711
|
+
test_model.gradient_form1 = gradient_form1
|
|
712
|
+
test_model.gradient_form2 = (gradient_mag, gradient_kappa_scale)
|
|
713
|
+
test_model.gradient_form2_incomplete = (gradient_mag, None)
|
|
714
|
+
|
|
715
|
+
# Jacobian with respect to mag
|
|
716
|
+
def jacobian_mag(mag, kappa_scale):
|
|
717
|
+
def fwd_mag(mag_):
|
|
718
|
+
CUQI_pde.assemble(mag_, kappa_scale)
|
|
719
|
+
u, _ = CUQI_pde.solve()
|
|
720
|
+
obs_u = CUQI_pde.observe(u)
|
|
721
|
+
return obs_u
|
|
722
|
+
mag = mag.to_numpy() if isinstance(mag, CUQIarray) else mag
|
|
723
|
+
return cuqi.utilities.approx_derivative(fwd_mag, mag).reshape(-1, 1)
|
|
724
|
+
|
|
725
|
+
# Jacobian with respect to kappa_scale
|
|
726
|
+
def jacobian_kappa_scale(mag, kappa_scale):
|
|
727
|
+
def fwd_kappa_scale(kappa_scale_):
|
|
728
|
+
CUQI_pde.assemble(mag, kappa_scale_)
|
|
729
|
+
u, _ = CUQI_pde.solve()
|
|
730
|
+
obs_u = CUQI_pde.observe(u)
|
|
731
|
+
return obs_u
|
|
732
|
+
kappa_scale = kappa_scale.to_numpy() if isinstance(kappa_scale, CUQIarray) else kappa_scale
|
|
733
|
+
return cuqi.utilities.approx_derivative(fwd_kappa_scale, kappa_scale).reshape(-1, 1)
|
|
734
|
+
|
|
735
|
+
# Jacobian with respect to all inputs (form 1, callable)
|
|
736
|
+
def jacobian_form1(mag, kappa_scale):
|
|
737
|
+
jac_mag = jacobian_mag(mag, kappa_scale)
|
|
738
|
+
jac_kappa_scale = jacobian_kappa_scale(mag, kappa_scale)
|
|
739
|
+
return (jac_mag, jac_kappa_scale)
|
|
740
|
+
|
|
741
|
+
# Assign the jacobian functions to the test model
|
|
742
|
+
test_model.jacobian_form1 = jacobian_form1
|
|
743
|
+
test_model.jacobian_form2 = (jacobian_mag, jacobian_kappa_scale)
|
|
744
|
+
test_model.jacobian_form2_incomplete = (jacobian_mag, None)
|
|
745
|
+
|
|
638
746
|
return test_model
|
|
639
747
|
|
|
640
748
|
@staticmethod
|
|
@@ -699,6 +807,103 @@ class MultipleInputTestModel:
|
|
|
699
807
|
test_model.model_class = cuqi.model.Model
|
|
700
808
|
return test_model
|
|
701
809
|
|
|
810
|
+
@staticmethod
|
|
811
|
+
def helper_build_time_dependent_PDE_test_model():
|
|
812
|
+
"""Build a PDE model with a time-dependent PDE and two inputs: mag, and IC."""
|
|
813
|
+
|
|
814
|
+
# Prepare PDE form
|
|
815
|
+
N = 20 # Number of solution nodes
|
|
816
|
+
endpoint = 1.0 # Length of the domain
|
|
817
|
+
max_time = 0.1 # Maximum time
|
|
818
|
+
dx = endpoint/(N+1) # space step size
|
|
819
|
+
cfl = 5/11 # the cfl condition to have a stable solution
|
|
820
|
+
dt_approx = cfl*dx**2 # defining approximate time step size
|
|
821
|
+
max_iter = int(max_time/dt_approx) # number of time steps
|
|
822
|
+
Dxx_matr = (np.diag( -2*np.ones(N) ) + np.diag(np.ones(N-1),-1) + np.diag(np.ones(N-1),1))/dx**2
|
|
823
|
+
Dxx = lambda mag: mag * Dxx_matr # FD diffusion operator
|
|
824
|
+
|
|
825
|
+
# Grids for model
|
|
826
|
+
grid_domain = np.linspace(dx, endpoint, N, endpoint=False)
|
|
827
|
+
grid_range = np.linspace(dx, endpoint, N, endpoint=False)
|
|
828
|
+
time_steps = np.linspace(0,max_time,max_iter+1,endpoint=True)
|
|
829
|
+
|
|
830
|
+
# PDE form (mag, IC, time)
|
|
831
|
+
def PDE_form(mag, IC, t): return (Dxx(mag), np.zeros(N), IC)
|
|
832
|
+
PDE = cuqi.pde.TimeDependentLinearPDE(
|
|
833
|
+
PDE_form, time_steps, grid_sol=grid_domain, grid_obs=grid_range, method='backward_euler')
|
|
834
|
+
|
|
835
|
+
# Build the test model
|
|
836
|
+
test_model = MultipleInputTestModel()
|
|
837
|
+
test_model.model_class = cuqi.model.PDEModel
|
|
838
|
+
test_model.pde = PDE
|
|
839
|
+
test_model.domain_geometry = (Discrete(["mag"]), Continuous1D(grid_domain))
|
|
840
|
+
test_model.range_geometry = Continuous1D(grid_range)
|
|
841
|
+
|
|
842
|
+
test_model.model_class = cuqi.model.PDEModel
|
|
843
|
+
|
|
844
|
+
# Gradient with respect to mag
|
|
845
|
+
def gradient_mag(direction, mag, IC):
|
|
846
|
+
def fwd_mag(mag_):
|
|
847
|
+
PDE.assemble(mag_, IC)
|
|
848
|
+
u, _ = PDE.solve()
|
|
849
|
+
obs_u = PDE.observe(u)
|
|
850
|
+
return obs_u
|
|
851
|
+
mag = mag.to_numpy() if isinstance(mag, CUQIarray) else mag
|
|
852
|
+
return direction @ cuqi.utilities.approx_derivative(fwd_mag, mag)
|
|
853
|
+
|
|
854
|
+
# Gradient with respect to IC
|
|
855
|
+
def gradient_IC(direction, mag, IC):
|
|
856
|
+
def fwd_IC(IC_):
|
|
857
|
+
PDE.assemble(mag, IC_)
|
|
858
|
+
u, _ = PDE.solve()
|
|
859
|
+
obs_u = PDE.observe(u)
|
|
860
|
+
return obs_u
|
|
861
|
+
IC = IC.to_numpy() if isinstance(IC, CUQIarray) else IC
|
|
862
|
+
return direction @ cuqi.utilities.approx_derivative(fwd_IC, IC)
|
|
863
|
+
|
|
864
|
+
# Gradient with respect to all inputs (form 1, callable)
|
|
865
|
+
def gradient_form1(direction, mag, IC):
|
|
866
|
+
grad_mag = gradient_mag(direction, mag, IC)
|
|
867
|
+
grad_IC = gradient_IC(direction, mag, IC)
|
|
868
|
+
return (grad_mag, grad_IC)
|
|
869
|
+
|
|
870
|
+
# Assign the gradient functions to the test model
|
|
871
|
+
test_model.gradient_form1 = gradient_form1
|
|
872
|
+
test_model.gradient_form2 = (gradient_mag, gradient_IC)
|
|
873
|
+
test_model.gradient_form2_incomplete = (gradient_mag, None)
|
|
874
|
+
|
|
875
|
+
# Jacobian with respect to mag
|
|
876
|
+
def jacobian_mag(mag, IC):
|
|
877
|
+
def fwd_mag(mag_):
|
|
878
|
+
PDE.assemble(mag_, IC)
|
|
879
|
+
u, _ = PDE.solve()
|
|
880
|
+
obs_u = PDE.observe(u)
|
|
881
|
+
return obs_u
|
|
882
|
+
mag = mag.to_numpy() if isinstance(mag, CUQIarray) else mag
|
|
883
|
+
return cuqi.utilities.approx_derivative(fwd_mag, mag)
|
|
884
|
+
|
|
885
|
+
# Jacobian with respect to IC
|
|
886
|
+
def jacobian_IC(mag, IC):
|
|
887
|
+
def fwd_IC(IC_):
|
|
888
|
+
PDE.assemble(mag, IC_)
|
|
889
|
+
u, _ = PDE.solve()
|
|
890
|
+
obs_u = PDE.observe(u)
|
|
891
|
+
return obs_u
|
|
892
|
+
IC = IC.to_numpy() if isinstance(IC, CUQIarray) else IC
|
|
893
|
+
return cuqi.utilities.approx_derivative(fwd_IC, IC)
|
|
894
|
+
|
|
895
|
+
# Jacobian with respect to all inputs (form 1, callable)
|
|
896
|
+
def jacobian_form1(mag, IC):
|
|
897
|
+
jac_mag = jacobian_mag(mag, IC)
|
|
898
|
+
jac_IC = jacobian_IC(mag, IC)
|
|
899
|
+
return (jac_mag, jac_IC)
|
|
900
|
+
|
|
901
|
+
# Assign the jacobian functions to the test model
|
|
902
|
+
test_model.jacobian_form1 = jacobian_form1
|
|
903
|
+
test_model.jacobian_form2 = (jacobian_mag, jacobian_IC)
|
|
904
|
+
test_model.jacobian_form2_incomplete = (jacobian_mag, None)
|
|
905
|
+
|
|
906
|
+
return test_model
|
|
702
907
|
|
|
703
908
|
class TestCase:
|
|
704
909
|
"""Class representing a test case for a test model. A test case consists of the input values, the expected output values, and the expected output types and error messages."""
|
|
@@ -1754,7 +1959,7 @@ def test_linear_model_allow_other_parameter_names():
|
|
|
1754
1959
|
# Check providing the wrong parameter name raises an error
|
|
1755
1960
|
with pytest.raises(
|
|
1756
1961
|
ValueError,
|
|
1757
|
-
match=r"The model input is specified by
|
|
1962
|
+
match=r"The model input is specified by keywords arguments \['y'\] that does not match the non_default_args of the model \['x'\].",
|
|
1758
1963
|
):
|
|
1759
1964
|
model_x_w(y=1)
|
|
1760
1965
|
|
|
@@ -1779,7 +1984,7 @@ def test_linear_model_allow_other_parameter_names():
|
|
|
1779
1984
|
# Check providing the wrong parameter name raises an error
|
|
1780
1985
|
with pytest.raises(
|
|
1781
1986
|
ValueError,
|
|
1782
|
-
match=r"The adjoint input is specified by
|
|
1987
|
+
match=r"The adjoint input is specified by keywords arguments \['v'\] that does not match the non_default_args of the adjoint \['w'\].",
|
|
1783
1988
|
):
|
|
1784
1989
|
model_x_w.adjoint(v=1)
|
|
1785
1990
|
|
|
@@ -1938,4 +2143,4 @@ def test_setting_domain_and_range_geometry(domain_geometry, range_geometry, num_
|
|
|
1938
2143
|
|
|
1939
2144
|
# Check the model domain and range geometries are of the expected type
|
|
1940
2145
|
assert model.domain_geometry == expected_domain_geometry
|
|
1941
|
-
assert model.range_geometry == expected_range_geometry
|
|
2146
|
+
assert model.range_geometry == expected_range_geometry
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/CUQIpy.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_joint_distribution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_modifiedhalfnormal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_smoothed_laplace.py
RENAMED
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/distribution/_truncated_normal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/_orderedset.py
RENAMED
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/algebra/_randomvariable.py
RENAMED
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/geometry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_conjugate.py
RENAMED
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/experimental/mcmc/_conjugate_approx.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_regularizedGMRF.py
RENAMED
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/implicitprior/_regularizedGaussian.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/sampler/_laplace_approximation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/cuqi/utilities/_get_python_variable_name.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cuqipy-1.3.0.post0.dev277 → cuqipy-1.3.0.post0.dev292}/tests/test_abstract_distribution_density.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|