CUQIpy 1.1.1.post0.dev36__py3-none-any.whl → 1.4.1.post0.dev124__py3-none-any.whl
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.
- cuqi/__init__.py +2 -0
- cuqi/_version.py +3 -3
- cuqi/algebra/__init__.py +2 -0
- cuqi/algebra/_abstract_syntax_tree.py +358 -0
- cuqi/algebra/_ordered_set.py +82 -0
- cuqi/algebra/_random_variable.py +457 -0
- cuqi/array/_array.py +4 -13
- cuqi/config.py +7 -0
- cuqi/density/_density.py +9 -1
- cuqi/distribution/__init__.py +3 -2
- cuqi/distribution/_beta.py +7 -11
- cuqi/distribution/_cauchy.py +2 -2
- cuqi/distribution/_custom.py +0 -6
- cuqi/distribution/_distribution.py +31 -45
- cuqi/distribution/_gamma.py +7 -3
- cuqi/distribution/_gaussian.py +2 -12
- cuqi/distribution/_inverse_gamma.py +4 -10
- cuqi/distribution/_joint_distribution.py +112 -15
- cuqi/distribution/_lognormal.py +0 -7
- cuqi/distribution/{_modifiedhalfnormal.py → _modified_half_normal.py} +23 -23
- cuqi/distribution/_normal.py +34 -7
- cuqi/distribution/_posterior.py +9 -0
- cuqi/distribution/_truncated_normal.py +129 -0
- cuqi/distribution/_uniform.py +47 -1
- cuqi/experimental/__init__.py +2 -2
- cuqi/experimental/_recommender.py +216 -0
- cuqi/geometry/__init__.py +2 -0
- cuqi/geometry/_geometry.py +15 -1
- cuqi/geometry/_product_geometry.py +181 -0
- cuqi/implicitprior/__init__.py +5 -3
- cuqi/implicitprior/_regularized_gaussian.py +483 -0
- cuqi/implicitprior/{_regularizedGMRF.py → _regularized_gmrf.py} +4 -2
- cuqi/implicitprior/{_regularizedUnboundedUniform.py → _regularized_unbounded_uniform.py} +3 -2
- cuqi/implicitprior/_restorator.py +269 -0
- cuqi/legacy/__init__.py +2 -0
- cuqi/{experimental/mcmc → legacy/sampler}/__init__.py +7 -11
- cuqi/legacy/sampler/_conjugate.py +55 -0
- cuqi/legacy/sampler/_conjugate_approx.py +52 -0
- cuqi/legacy/sampler/_cwmh.py +196 -0
- cuqi/legacy/sampler/_gibbs.py +231 -0
- cuqi/legacy/sampler/_hmc.py +335 -0
- cuqi/{experimental/mcmc → legacy/sampler}/_langevin_algorithm.py +82 -111
- cuqi/legacy/sampler/_laplace_approximation.py +184 -0
- cuqi/legacy/sampler/_mh.py +190 -0
- cuqi/legacy/sampler/_pcn.py +244 -0
- cuqi/{experimental/mcmc → legacy/sampler}/_rto.py +132 -90
- cuqi/legacy/sampler/_sampler.py +182 -0
- cuqi/likelihood/_likelihood.py +9 -1
- cuqi/model/__init__.py +1 -1
- cuqi/model/_model.py +1361 -359
- cuqi/pde/__init__.py +4 -0
- cuqi/pde/_observation_map.py +36 -0
- cuqi/pde/_pde.py +134 -33
- cuqi/problem/_problem.py +93 -87
- cuqi/sampler/__init__.py +120 -8
- cuqi/sampler/_conjugate.py +376 -35
- cuqi/sampler/_conjugate_approx.py +40 -16
- cuqi/sampler/_cwmh.py +132 -138
- cuqi/{experimental/mcmc → sampler}/_direct.py +1 -1
- cuqi/sampler/_gibbs.py +288 -130
- cuqi/sampler/_hmc.py +328 -201
- cuqi/sampler/_langevin_algorithm.py +284 -100
- cuqi/sampler/_laplace_approximation.py +87 -117
- cuqi/sampler/_mh.py +47 -157
- cuqi/sampler/_pcn.py +65 -213
- cuqi/sampler/_rto.py +211 -142
- cuqi/sampler/_sampler.py +553 -136
- cuqi/samples/__init__.py +1 -1
- cuqi/samples/_samples.py +24 -18
- cuqi/solver/__init__.py +6 -4
- cuqi/solver/_solver.py +230 -26
- cuqi/testproblem/_testproblem.py +2 -3
- cuqi/utilities/__init__.py +6 -1
- cuqi/utilities/_get_python_variable_name.py +2 -2
- cuqi/utilities/_utilities.py +182 -2
- {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info}/METADATA +10 -6
- cuqipy-1.4.1.post0.dev124.dist-info/RECORD +101 -0
- {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info}/WHEEL +1 -1
- CUQIpy-1.1.1.post0.dev36.dist-info/RECORD +0 -92
- cuqi/experimental/mcmc/_conjugate.py +0 -197
- cuqi/experimental/mcmc/_conjugate_approx.py +0 -81
- cuqi/experimental/mcmc/_cwmh.py +0 -191
- cuqi/experimental/mcmc/_gibbs.py +0 -268
- cuqi/experimental/mcmc/_hmc.py +0 -470
- cuqi/experimental/mcmc/_laplace_approximation.py +0 -156
- cuqi/experimental/mcmc/_mh.py +0 -78
- cuqi/experimental/mcmc/_pcn.py +0 -89
- cuqi/experimental/mcmc/_sampler.py +0 -561
- cuqi/experimental/mcmc/_utilities.py +0 -17
- cuqi/implicitprior/_regularizedGaussian.py +0 -323
- {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info/licenses}/LICENSE +0 -0
- {CUQIpy-1.1.1.post0.dev36.dist-info → cuqipy-1.4.1.post0.dev124.dist-info}/top_level.txt +0 -0
cuqi/pde/__init__.py
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import scipy
|
|
2
|
+
import numpy as np
|
|
3
|
+
"""
|
|
4
|
+
This module contains observation map examples for PDE problems. The map can
|
|
5
|
+
be passed to the `PDE` object initializer via the `observation_map` argument.
|
|
6
|
+
|
|
7
|
+
For example on how to use set observation maps in time dependent PDEs, see
|
|
8
|
+
`demos/howtos/time_dependent_linear_pde.py`.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# 1. Steady State Observation Maps
|
|
12
|
+
# --------------------------------
|
|
13
|
+
|
|
14
|
+
# 2. Time-Dependent Observation Maps
|
|
15
|
+
# -----------------------------------
|
|
16
|
+
def FD_spatial_gradient(sol, grid, times):
|
|
17
|
+
"""Time dependent observation map that computes the finite difference (FD) spatial gradient of a solution given at grid points (grid) and times (times). This map is supported for 1D spatial domains only.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
sol : np.ndarray
|
|
22
|
+
The solution array of shape (number of grid points, number of time steps).
|
|
23
|
+
|
|
24
|
+
grid : np.ndarray
|
|
25
|
+
The spatial grid points of shape (number of grid points,).
|
|
26
|
+
|
|
27
|
+
times : np.ndarray
|
|
28
|
+
The discretized time steps of shape (number of time steps,)."""
|
|
29
|
+
|
|
30
|
+
if len(grid.shape) != 1:
|
|
31
|
+
raise ValueError("FD_spatial_gradient only supports 1D spatial domains.")
|
|
32
|
+
observed_quantity = np.zeros((len(grid)-1, len(times)))
|
|
33
|
+
for i in range(observed_quantity.shape[0]):
|
|
34
|
+
observed_quantity[i, :] = ((sol[i, :] - sol[i+1, :])/
|
|
35
|
+
(grid[i] - grid[i+1]))
|
|
36
|
+
return observed_quantity
|
cuqi/pde/_pde.py
CHANGED
|
@@ -3,6 +3,8 @@ 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
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class PDE(ABC):
|
|
8
10
|
"""
|
|
@@ -13,14 +15,15 @@ class PDE(ABC):
|
|
|
13
15
|
PDE_form : callable function
|
|
14
16
|
Callable function which returns a tuple of the needed PDE components (expected components are explained in the subclasses)
|
|
15
17
|
|
|
16
|
-
observation_map: a function handle
|
|
17
|
-
A function that takes the PDE solution as input and the returns the observed solution. e.g. `observation_map=lambda u: u**2` or `observation_map=lambda u: u[0]`
|
|
18
|
-
|
|
19
18
|
grid_sol: np.ndarray
|
|
20
19
|
The grid on which solution is defined
|
|
21
20
|
|
|
22
21
|
grid_obs: np.ndarray
|
|
23
|
-
The grid on which the observed solution should be interpolated (currently only supported for 1D problems).
|
|
22
|
+
The grid on which the observed solution should be interpolated (currently only supported for 1D problems).
|
|
23
|
+
|
|
24
|
+
observation_map: a function handle
|
|
25
|
+
A function that takes the PDE solution, interpolated on `grid_obs`, as input and returns the observed solution. e.g., `observation_map=lambda u, grid_obs: u**2`.
|
|
26
|
+
|
|
24
27
|
"""
|
|
25
28
|
|
|
26
29
|
def __init__(self, PDE_form, grid_sol=None, grid_obs=None, observation_map=None):
|
|
@@ -28,9 +31,10 @@ class PDE(ABC):
|
|
|
28
31
|
self.grid_sol = grid_sol
|
|
29
32
|
self.grid_obs = grid_obs
|
|
30
33
|
self.observation_map = observation_map
|
|
34
|
+
self._stored_non_default_args = None
|
|
31
35
|
|
|
32
36
|
@abstractmethod
|
|
33
|
-
def assemble(self,
|
|
37
|
+
def assemble(self, *args, **kwargs):
|
|
34
38
|
pass
|
|
35
39
|
|
|
36
40
|
@abstractmethod
|
|
@@ -63,6 +67,13 @@ class PDE(ABC):
|
|
|
63
67
|
|
|
64
68
|
return equal_arrays
|
|
65
69
|
|
|
70
|
+
@property
|
|
71
|
+
def _non_default_args(self):
|
|
72
|
+
"""Returns the non-default arguments of the PDE_form function"""
|
|
73
|
+
if self._stored_non_default_args is None:
|
|
74
|
+
self._stored_non_default_args = get_non_default_args(self.PDE_form)
|
|
75
|
+
return self._stored_non_default_args
|
|
76
|
+
|
|
66
77
|
@property
|
|
67
78
|
def grid_sol(self):
|
|
68
79
|
if hasattr(self,"_grid_sol"):
|
|
@@ -93,6 +104,48 @@ class PDE(ABC):
|
|
|
93
104
|
def grids_equal(self):
|
|
94
105
|
return self._grids_equal
|
|
95
106
|
|
|
107
|
+
def _parse_args_add_to_kwargs(
|
|
108
|
+
self, *args, map_name, **kwargs):
|
|
109
|
+
""" Private function that parses the input arguments and adds them as
|
|
110
|
+
keyword arguments matching (the order of) the non default arguments of
|
|
111
|
+
the pde class.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
# If any args are given, add them to kwargs
|
|
115
|
+
if len(args) > 0:
|
|
116
|
+
if len(kwargs) > 0:
|
|
117
|
+
raise ValueError(
|
|
118
|
+
+ map_name.lower()
|
|
119
|
+
+ " input is specified both as positional and keyword arguments. This is not supported."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Check if the number of args does not match the number of
|
|
123
|
+
# non_default_args of the model
|
|
124
|
+
if len(args) != len(self._non_default_args):
|
|
125
|
+
raise ValueError(
|
|
126
|
+
"The number of positional arguments does not match the number of non-default arguments of "
|
|
127
|
+
+ map_name.lower()
|
|
128
|
+
+ "."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Add args to kwargs following the order of non_default_args
|
|
132
|
+
for idx, arg in enumerate(args):
|
|
133
|
+
kwargs[self._non_default_args[idx]] = arg
|
|
134
|
+
|
|
135
|
+
# Check kwargs matches non_default_args
|
|
136
|
+
if set(list(kwargs.keys())) != set(self._non_default_args):
|
|
137
|
+
error_msg = (
|
|
138
|
+
map_name.lower()
|
|
139
|
+
+ f" input is specified by keywords arguments {list(kwargs.keys())} that does not match the non_default_args of "
|
|
140
|
+
+ map_name
|
|
141
|
+
+ f" {self._non_default_args}."
|
|
142
|
+
)
|
|
143
|
+
raise ValueError(error_msg)
|
|
144
|
+
|
|
145
|
+
# Make sure order of kwargs is the same as non_default_args
|
|
146
|
+
kwargs = {k: kwargs[k] for k in self._non_default_args}
|
|
147
|
+
|
|
148
|
+
return kwargs
|
|
96
149
|
|
|
97
150
|
class LinearPDE(PDE):
|
|
98
151
|
"""
|
|
@@ -135,6 +188,10 @@ class LinearPDE(PDE):
|
|
|
135
188
|
info = None
|
|
136
189
|
|
|
137
190
|
return solution, info
|
|
191
|
+
|
|
192
|
+
def interpolate_on_observed_domain(self, solution):
|
|
193
|
+
"""Interpolate solution on observed space domain."""
|
|
194
|
+
raise NotImplementedError("interpolate_on_observed_domain method is not implemented for LinearPDE base class.")
|
|
138
195
|
|
|
139
196
|
class SteadyStateLinearPDE(LinearPDE):
|
|
140
197
|
"""Linear steady state PDE.
|
|
@@ -142,22 +199,28 @@ class SteadyStateLinearPDE(LinearPDE):
|
|
|
142
199
|
Parameters
|
|
143
200
|
-----------
|
|
144
201
|
PDE_form : callable function
|
|
145
|
-
Callable function with signature `PDE_form(
|
|
202
|
+
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.
|
|
203
|
+
|
|
204
|
+
observation_map: a function handle
|
|
205
|
+
A function that takes the PDE solution, interpolated on `grid_obs`, as input and returns the observed solution. e.g. `observation_map=lambda u, grid_obs: u**2`.
|
|
146
206
|
|
|
147
207
|
kwargs:
|
|
148
208
|
See :class:`~cuqi.pde.LinearPDE` for the remaining keyword arguments.
|
|
149
209
|
|
|
150
210
|
Example
|
|
151
211
|
--------
|
|
152
|
-
See demo demos/demo24_fwd_poisson.py for an illustration on how to use SteadyStateLinearPDE with varying solver choices. And demos demos/
|
|
212
|
+
See demo demos/demo24_fwd_poisson.py for an illustration on how to use SteadyStateLinearPDE with varying solver choices. And demos demos/demo25_fwd_poisson_2d.py and demos/demo26_fwd_poisson_mixed_bc.py for examples with mixed (Dirichlet and Neumann) boundary conditions problems. demos/demo25_fwd_poisson_2d.py also illustrates how to observe on a specific boundary, for example.
|
|
153
213
|
"""
|
|
154
214
|
|
|
155
|
-
def __init__(self, PDE_form, **kwargs):
|
|
156
|
-
super().__init__(PDE_form, **kwargs)
|
|
215
|
+
def __init__(self, PDE_form, observation_map=None, **kwargs):
|
|
216
|
+
super().__init__(PDE_form, observation_map=observation_map, **kwargs)
|
|
157
217
|
|
|
158
|
-
def assemble(self,
|
|
218
|
+
def assemble(self, *args, **kwargs):
|
|
159
219
|
"""Assembles differential operator and rhs according to PDE_form"""
|
|
160
|
-
|
|
220
|
+
kwargs = self._parse_args_add_to_kwargs(
|
|
221
|
+
*args, map_name="assemble", **kwargs
|
|
222
|
+
)
|
|
223
|
+
self.diff_op, self.rhs = self.PDE_form(**kwargs)
|
|
161
224
|
|
|
162
225
|
def solve(self):
|
|
163
226
|
"""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."""
|
|
@@ -166,26 +229,34 @@ class SteadyStateLinearPDE(LinearPDE):
|
|
|
166
229
|
|
|
167
230
|
return self._solve_linear_system(self.diff_op, self.rhs, self._linalg_solve, self._linalg_solve_kwargs)
|
|
168
231
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
232
|
+
def interpolate_on_observed_domain(self, solution):
|
|
233
|
+
"""Interpolate solution on observed space grid."""
|
|
172
234
|
if self.grids_equal:
|
|
173
235
|
solution_obs = solution
|
|
174
236
|
else:
|
|
175
237
|
solution_obs = interp1d(self.grid_sol, solution, kind='quadratic')(self.grid_obs)
|
|
238
|
+
return solution_obs
|
|
239
|
+
|
|
240
|
+
def observe(self, solution):
|
|
241
|
+
"""Apply observation operator to the solution. This includes
|
|
242
|
+
interpolation to observation points (if different from the
|
|
243
|
+
solution grid) then applying the observation map (if provided)."""
|
|
244
|
+
|
|
245
|
+
# Interpolate solution on observed domain
|
|
246
|
+
solution_obs = self.interpolate_on_observed_domain(solution)
|
|
176
247
|
|
|
177
248
|
if self.observation_map is not None:
|
|
178
|
-
solution_obs = self.observation_map(solution_obs)
|
|
179
|
-
|
|
249
|
+
solution_obs = self.observation_map(solution_obs, self.grid_obs)
|
|
250
|
+
|
|
180
251
|
return solution_obs
|
|
181
|
-
|
|
252
|
+
|
|
182
253
|
class TimeDependentLinearPDE(LinearPDE):
|
|
183
254
|
"""Time Dependent Linear PDE with fixed time stepping using Euler method (backward or forward).
|
|
184
255
|
|
|
185
256
|
Parameters
|
|
186
257
|
-----------
|
|
187
258
|
PDE_form : callable function
|
|
188
|
-
Callable function with signature `PDE_form(
|
|
259
|
+
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`.
|
|
189
260
|
|
|
190
261
|
time_steps : ndarray
|
|
191
262
|
An array of the discretized times corresponding to the time steps that starts with the initial time and ends with the final time
|
|
@@ -196,16 +267,20 @@ class TimeDependentLinearPDE(LinearPDE):
|
|
|
196
267
|
method: str
|
|
197
268
|
Time stepping method. Currently two options are available `forward_euler` and `backward_euler`.
|
|
198
269
|
|
|
270
|
+
observation_map: a function handle
|
|
271
|
+
A function that takes the PDE solution, interpolated on `grid_obs` and `time_obs`, as input and returns the observed solution. e.g. `observation_map=lambda u, grid_obs, time_obs: u**2`.
|
|
272
|
+
|
|
199
273
|
kwargs:
|
|
200
274
|
See :class:`~cuqi.pde.LinearPDE` for the remaining keyword arguments
|
|
201
275
|
|
|
202
276
|
Example
|
|
203
277
|
-----------
|
|
204
|
-
See demos/
|
|
278
|
+
See demos/howtos/time_dependent_linear_pde.py for 1D heat and 1D wave equations examples. It demonstrates setting up `TimeDependentLinearPDE` objects, including the choice of time stepping methods, observation domain, and observation map.
|
|
205
279
|
"""
|
|
206
280
|
|
|
207
|
-
def __init__(self, PDE_form, time_steps, time_obs='final',
|
|
208
|
-
|
|
281
|
+
def __init__(self, PDE_form, time_steps, time_obs='final',
|
|
282
|
+
method='forward_euler', observation_map=None, **kwargs):
|
|
283
|
+
super().__init__(PDE_form, observation_map=observation_map, **kwargs)
|
|
209
284
|
|
|
210
285
|
self.time_steps = time_steps
|
|
211
286
|
self.method = method
|
|
@@ -227,6 +302,18 @@ class TimeDependentLinearPDE(LinearPDE):
|
|
|
227
302
|
def method(self):
|
|
228
303
|
return self._method
|
|
229
304
|
|
|
305
|
+
@property
|
|
306
|
+
def _non_default_args(self):
|
|
307
|
+
"""Returns the non-default arguments of the PDE_form function"""
|
|
308
|
+
if self._stored_non_default_args is None:
|
|
309
|
+
self._stored_non_default_args = get_non_default_args(self.PDE_form)
|
|
310
|
+
# Remove the time argument from the non-default arguments
|
|
311
|
+
# since it is provided automatically by `solve` method and is not
|
|
312
|
+
# an argument to be inferred in Bayesian inference setting.
|
|
313
|
+
if 't' in self._stored_non_default_args:
|
|
314
|
+
self._stored_non_default_args.remove('t')
|
|
315
|
+
return self._stored_non_default_args
|
|
316
|
+
|
|
230
317
|
@method.setter
|
|
231
318
|
def method(self, value):
|
|
232
319
|
if value.lower() != 'forward_euler' and value.lower() != 'backward_euler':
|
|
@@ -234,13 +321,16 @@ class TimeDependentLinearPDE(LinearPDE):
|
|
|
234
321
|
"method can be set to either `forward_euler` or `backward_euler`")
|
|
235
322
|
self._method = value
|
|
236
323
|
|
|
237
|
-
def assemble(self,
|
|
324
|
+
def assemble(self, *args, **kwargs):
|
|
238
325
|
"""Assemble PDE"""
|
|
239
|
-
self.
|
|
326
|
+
kwargs = self._parse_args_add_to_kwargs(*args, map_name="assemble", **kwargs)
|
|
327
|
+
self._parameter_kwargs = kwargs
|
|
240
328
|
|
|
241
329
|
def assemble_step(self, t):
|
|
242
330
|
"""Assemble time step at time t"""
|
|
243
|
-
self.diff_op, self.rhs, self.initial_condition = self.PDE_form(
|
|
331
|
+
self.diff_op, self.rhs, self.initial_condition = self.PDE_form(
|
|
332
|
+
**self._parameter_kwargs, t=t
|
|
333
|
+
)
|
|
244
334
|
|
|
245
335
|
def solve(self):
|
|
246
336
|
"""Solve PDE by time-stepping"""
|
|
@@ -269,8 +359,8 @@ class TimeDependentLinearPDE(LinearPDE):
|
|
|
269
359
|
|
|
270
360
|
return u, info
|
|
271
361
|
|
|
272
|
-
def
|
|
273
|
-
|
|
362
|
+
def interpolate_on_observed_domain(self, solution):
|
|
363
|
+
"""Interpolate solution on observed time and space points."""
|
|
274
364
|
# If observation grid is the same as solution grid and observation time
|
|
275
365
|
# is the final time step then no need to interpolate
|
|
276
366
|
if self.grids_equal and np.all(self.time_steps[-1:] == self._time_obs):
|
|
@@ -279,7 +369,7 @@ class TimeDependentLinearPDE(LinearPDE):
|
|
|
279
369
|
# Interpolate solution in time and space to the observation
|
|
280
370
|
# time and space
|
|
281
371
|
else:
|
|
282
|
-
# Raise error if solution is 2D or 3D in space
|
|
372
|
+
# Raise error if solution is 2D or 3D in space
|
|
283
373
|
if len(solution.shape) > 2:
|
|
284
374
|
raise ValueError("Interpolation of solutions of 2D and 3D "+
|
|
285
375
|
"space dimensions based on the provided "+
|
|
@@ -287,19 +377,30 @@ class TimeDependentLinearPDE(LinearPDE):
|
|
|
287
377
|
"You can, instead, pass a custom "+
|
|
288
378
|
"observation_map and pass grid_obs and "+
|
|
289
379
|
"time_obs as None.")
|
|
290
|
-
|
|
380
|
+
|
|
291
381
|
# Interpolate solution in space and time to the observation
|
|
292
382
|
# time and space
|
|
293
383
|
solution_obs = scipy.interpolate.RectBivariateSpline(
|
|
294
|
-
self.grid_sol, self.time_steps, solution
|
|
295
|
-
|
|
384
|
+
self.grid_sol, self.time_steps, solution
|
|
385
|
+
)(self.grid_obs, self._time_obs)
|
|
386
|
+
|
|
387
|
+
return solution_obs
|
|
388
|
+
|
|
389
|
+
def observe(self, solution):
|
|
390
|
+
"""Apply observation operator to the solution. This includes
|
|
391
|
+
interpolation to observation points (if different from the
|
|
392
|
+
solution grid) then applying the observation map (if provided)."""
|
|
296
393
|
|
|
394
|
+
# Interpolate solution on observed domain
|
|
395
|
+
solution_obs = self.interpolate_on_observed_domain(solution)
|
|
396
|
+
|
|
297
397
|
# Apply observation map
|
|
298
398
|
if self.observation_map is not None:
|
|
299
|
-
solution_obs = self.observation_map(solution_obs
|
|
300
|
-
|
|
399
|
+
solution_obs = self.observation_map(solution_obs, self.grid_obs,
|
|
400
|
+
self._time_obs)
|
|
401
|
+
|
|
301
402
|
# squeeze if only one time observation
|
|
302
403
|
if len(self._time_obs) == 1:
|
|
303
404
|
solution_obs = solution_obs.squeeze()
|
|
304
405
|
|
|
305
|
-
return solution_obs
|
|
406
|
+
return solution_obs
|