CUQIpy 0.4.0__tar.gz → 0.4.0.post0.dev19__tar.gz

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

Potentially problematic release.


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

Files changed (92) hide show
  1. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/CUQIpy.egg-info/PKG-INFO +1 -1
  2. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/PKG-INFO +1 -1
  3. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/_version.py +3 -3
  4. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/pde/_pde.py +59 -16
  5. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_pde.py +87 -14
  6. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/CUQIpy.egg-info/SOURCES.txt +0 -0
  7. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/CUQIpy.egg-info/dependency_links.txt +0 -0
  8. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/CUQIpy.egg-info/requires.txt +0 -0
  9. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/CUQIpy.egg-info/top_level.txt +0 -0
  10. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/LICENSE +0 -0
  11. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/README.md +0 -0
  12. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/__init__.py +0 -0
  13. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/_messages.py +0 -0
  14. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/array/__init__.py +0 -0
  15. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/array/_array.py +0 -0
  16. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/config.py +0 -0
  17. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/data/__init__.py +0 -0
  18. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/data/_data.py +0 -0
  19. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/data/astronaut.npz +0 -0
  20. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/data/camera.npz +0 -0
  21. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/data/cat.npz +0 -0
  22. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/data/satellite.mat +0 -0
  23. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/density/__init__.py +0 -0
  24. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/density/_density.py +0 -0
  25. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/diagnostics.py +0 -0
  26. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/__init__.py +0 -0
  27. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_beta.py +0 -0
  28. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_cauchy.py +0 -0
  29. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_cmrf.py +0 -0
  30. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_custom.py +0 -0
  31. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_distribution.py +0 -0
  32. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_gamma.py +0 -0
  33. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_gaussian.py +0 -0
  34. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_gmrf.py +0 -0
  35. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_inverse_gamma.py +0 -0
  36. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_joint_distribution.py +0 -0
  37. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_laplace.py +0 -0
  38. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_lmrf.py +0 -0
  39. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_lognormal.py +0 -0
  40. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_normal.py +0 -0
  41. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_posterior.py +0 -0
  42. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/distribution/_uniform.py +0 -0
  43. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/geometry/__init__.py +0 -0
  44. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/geometry/_geometry.py +0 -0
  45. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/likelihood/__init__.py +0 -0
  46. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/likelihood/_likelihood.py +0 -0
  47. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/model/__init__.py +0 -0
  48. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/model/_model.py +0 -0
  49. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/operator/__init__.py +0 -0
  50. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/operator/_operator.py +0 -0
  51. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/pde/__init__.py +0 -0
  52. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/problem/__init__.py +0 -0
  53. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/problem/_problem.py +0 -0
  54. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/__init__.py +0 -0
  55. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_conjugate.py +0 -0
  56. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_conjugate_approx.py +0 -0
  57. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_cwmh.py +0 -0
  58. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_gibbs.py +0 -0
  59. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_hmc.py +0 -0
  60. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_langevin_algorithm.py +0 -0
  61. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_laplace_approximation.py +0 -0
  62. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_mh.py +0 -0
  63. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_pcn.py +0 -0
  64. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_rto.py +0 -0
  65. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/sampler/_sampler.py +0 -0
  66. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/samples/__init__.py +0 -0
  67. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/samples/_samples.py +0 -0
  68. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/solver/__init__.py +0 -0
  69. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/solver/_solver.py +0 -0
  70. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/testproblem/__init__.py +0 -0
  71. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/testproblem/_testproblem.py +0 -0
  72. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/utilities/__init__.py +0 -0
  73. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/utilities/_get_python_variable_name.py +0 -0
  74. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/cuqi/utilities/_utilities.py +0 -0
  75. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/pyproject.toml +0 -0
  76. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/requirements.txt +0 -0
  77. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/setup.cfg +0 -0
  78. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/setup.py +0 -0
  79. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_abstract_distribution_density.py +0 -0
  80. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_bayesian_inversion.py +0 -0
  81. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_density.py +0 -0
  82. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_distribution.py +0 -0
  83. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_geometry.py +0 -0
  84. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_joint_distribution.py +0 -0
  85. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_likelihood.py +0 -0
  86. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_model.py +0 -0
  87. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_problem.py +0 -0
  88. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_sampler.py +0 -0
  89. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_samples.py +0 -0
  90. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_solver.py +0 -0
  91. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_testproblem.py +0 -0
  92. {CUQIpy-0.4.0 → CUQIpy-0.4.0.post0.dev19}/tests/test_utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: CUQIpy
3
- Version: 0.4.0
3
+ Version: 0.4.0.post0.dev19
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: CUQIpy
3
- Version: 0.4.0
3
+ Version: 0.4.0.post0.dev19
4
4
  Summary: Computational Uncertainty Quantification for Inverse problems in Python
5
5
  Maintainer-email: "Nicolai A. B. Riis" <nabr@dtu.dk>, "Jakob S. Jørgensen" <jakj@dtu.dk>, "Amal M. Alghamdi" <amaal@dtu.dk>
6
6
  License: Apache License
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2023-05-26T15:28:04+0200",
11
+ "date": "2023-06-09T10:16:49+0200",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "c13be03edb3eb06da1d67ca35ebbcdc1ccf84f5b",
15
- "version": "0.4.0"
14
+ "full-revisionid": "d7e4a312b9c749d530f7cf3c06068857ab6b2fe2",
15
+ "version": "0.4.0.post0.dev19"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -84,6 +84,8 @@ class PDE(ABC):
84
84
 
85
85
  @grid_obs.setter
86
86
  def grid_obs(self,value):
87
+ if value is None:
88
+ value = self.grid_sol
87
89
  self._grids_equal = self._compare_grid(value,self.grid_sol)
88
90
  self._grid_obs = value
89
91
 
@@ -186,7 +188,10 @@ class TimeDependentLinearPDE(LinearPDE):
186
188
  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`.
187
189
 
188
190
  time_steps : ndarray
189
- An array of the discretized times corresponding to the time steps that starts with the initial time and ends with the final time.
191
+ An array of the discretized times corresponding to the time steps that starts with the initial time and ends with the final time
192
+
193
+ time_obs : array_like or str
194
+ If passed as an array_like, it is an array of the times at which the solution is observed. If passed as a string it can be set to `final` to observe at the final time step, or `all` to observe at all time steps. Default is `final`.
190
195
 
191
196
  method: str
192
197
  Time stepping method. Currently two options are available `forward_euler` and `backward_euler`.
@@ -199,12 +204,25 @@ class TimeDependentLinearPDE(LinearPDE):
199
204
  See demos/demo34_TimeDependentLinearPDE.py for 1D heat and 1D wave equations.
200
205
  """
201
206
 
202
- def __init__(self, PDE_form, time_steps, method='forward_euler', **kwargs):
207
+ def __init__(self, PDE_form, time_steps, time_obs='final', method='forward_euler', **kwargs):
203
208
  super().__init__(PDE_form, **kwargs)
204
209
 
205
210
  self.time_steps = time_steps
206
211
  self.method = method
207
212
 
213
+ # Set time_obs
214
+ if time_obs is None:
215
+ raise ValueError("time_obs cannot be None")
216
+ elif isinstance(time_obs, str):
217
+ if time_obs.lower() == 'final':
218
+ time_obs = time_steps[-1:]
219
+ elif time_obs.lower() == 'all':
220
+ time_obs = time_steps
221
+ else:
222
+ raise ValueError("if time_obs is a string, it can only be set "
223
+ +"to `final` or `all`")
224
+ self._time_obs = time_obs
225
+
208
226
  @property
209
227
  def method(self):
210
228
  return self._method
@@ -226,37 +244,62 @@ class TimeDependentLinearPDE(LinearPDE):
226
244
 
227
245
  def solve(self):
228
246
  """Solve PDE by time-stepping"""
247
+ # initialize time-dependent solution
248
+ self.assemble_step(self.time_steps[0])
249
+ u = np.empty((len(self.initial_condition), len(self.time_steps)))
250
+ u[:, 0] = self.initial_condition
229
251
 
230
252
  if self.method == 'forward_euler':
231
253
  for idx, t in enumerate(self.time_steps[:-1]):
232
254
  dt = self.time_steps[idx+1] - t
233
255
  self.assemble_step(t)
234
- if idx == 0:
235
- u = self.initial_condition
236
- u = (dt*self.diff_op + np.eye(len(u)))@u + dt*self.rhs # from u at time t, gives u at t+dt
256
+ u_pre = u[:, idx]
257
+ u[:, idx+1] = (dt*self.diff_op + np.eye(len(u_pre)))@u_pre + dt*self.rhs # from u at time t, gives u at t+dt
237
258
  info = None
238
259
 
239
260
  if self.method == 'backward_euler':
240
261
  for idx, t in enumerate(self.time_steps[1:]):
241
262
  dt = t - self.time_steps[idx]
242
263
  self.assemble_step(t)
243
- if idx == 0:
244
- u = self.initial_condition
245
- A = np.eye(len(u)) - dt*self.diff_op
264
+ u_pre = u[:, idx]
265
+ A = np.eye(len(u_pre)) - dt*self.diff_op
246
266
  # from u at time t-dt, gives u at t
247
- u, info = self._solve_linear_system(
248
- A, u + dt*self.rhs, self._linalg_solve, self._linalg_solve_kwargs)
267
+ u[:, idx+1], info = self._solve_linear_system(
268
+ A, u_pre + dt*self.rhs, self._linalg_solve, self._linalg_solve_kwargs)
249
269
 
250
270
  return u, info
251
271
 
252
272
  def observe(self, solution):
253
-
254
- if self.grids_equal:
255
- solution_obs = solution
273
+
274
+ # If observation grid is the same as solution grid and observation time
275
+ # is the final time step then no need to interpolate
276
+ if self.grids_equal and np.all(self.time_steps[-1:] == self._time_obs):
277
+ solution_obs = solution[..., -1]
278
+
279
+ # Interpolate solution in time and space to the observation
280
+ # time and space
256
281
  else:
257
- solution_obs = interp1d(self.grid_sol, solution, kind='quadratic')(self.grid_obs)
282
+ # Raise error if solution is 2D or 3D in space
283
+ if len(solution.shape) > 2:
284
+ raise ValueError("Interpolation of solutions of 2D and 3D "+
285
+ "space dimensions based on the provided "+
286
+ "grid_obs and time_obs are not supported. "+
287
+ "You can, instead, pass a custom "+
288
+ "observation_map and pass grid_obs and "+
289
+ "time_obs as None.")
290
+
291
+ # Interpolate solution in space and time to the observation
292
+ # time and space
293
+ solution_obs = scipy.interpolate.RectBivariateSpline(
294
+ self.grid_sol, self.time_steps, solution)(self.grid_obs,
295
+ self._time_obs)
258
296
 
297
+ # Apply observation map
259
298
  if self.observation_map is not None:
260
299
  solution_obs = self.observation_map(solution_obs)
261
-
262
- return solution_obs
300
+
301
+ # squeeze if only one time observation
302
+ if len(self._time_obs) == 1:
303
+ solution_obs = solution_obs.squeeze()
304
+
305
+ return solution_obs
@@ -181,14 +181,26 @@ def test_observe():
181
181
  assert(np.all(np.isclose(observed_sol, expected_observed_sol)))
182
182
 
183
183
 
184
- @pytest.mark.parametrize("method, time_steps, parametrization, expected_sol",
185
- [('forward_euler', 'fixed', 'initial_condition', 'sol1'),
186
- ('backward_euler', 'fixed', 'initial_condition', 'sol2'),
187
- ('backward_euler', 'varying',
188
- 'initial_condition', 'sol3'),
189
- ('backward_euler', 'fixed', 'source_term1', 'sol4'),
190
- ('backward_euler', 'fixed', 'source_term2', 'sol5')])
191
- def test_TimeDependentLinearPDE_heat1D(copy_reference, method, time_steps, parametrization, expected_sol):
184
+ @pytest.mark.parametrize(
185
+ "method, time_steps, parametrization, expected_sol",
186
+ [('forward_euler', 'fixed', 'initial_condition', 'sol1'),
187
+ ('backward_euler', 'fixed', 'initial_condition', 'sol2'),
188
+ ('backward_euler', 'varying',
189
+ 'initial_condition', 'sol3'),
190
+ ('backward_euler', 'fixed', 'source_term1', 'sol4'),
191
+ ('backward_euler', 'fixed', 'source_term2', 'sol5')])
192
+ @pytest.mark.parametrize(
193
+ "grid_obs, time_obs, observation_map, expected_obs",
194
+ [(None, 'final', None, 'obs1'),
195
+ (None, 'final', lambda x: x**2, 'obs2'),
196
+ ('half_grid', 'FINAL', None, 'obs3'),
197
+ ('half_grid', 'every_5', None, 'obs4'),
198
+ (None, 'every_5', lambda x: x**2, 'obs5'),
199
+ (np.array([3, 4.9]), np.array([0.9, 1]), lambda x: x**2, 'obs6')])
200
+ def test_TimeDependentLinearPDE_heat1D(copy_reference, method, time_steps,
201
+ parametrization, expected_sol,
202
+ grid_obs, time_obs, observation_map,
203
+ expected_obs):
192
204
  """ Compute the final time solution of a 1D heat equation and
193
205
  compare it with previously stored solution (for 5 different set up choices).
194
206
  """
@@ -197,6 +209,7 @@ def test_TimeDependentLinearPDE_heat1D(copy_reference, method, time_steps, param
197
209
  L = 5 # 1D domain length
198
210
  max_time = 1 # Final time
199
211
  dx = L/(dim+1) # Space step size
212
+ grid_sol = np.linspace(dx, L-dx, dim) # Solution grid
200
213
 
201
214
  if method == 'forward_euler':
202
215
  cfl = 5/11 # The cfl condition to have a stable solution
@@ -234,18 +247,78 @@ def test_TimeDependentLinearPDE_heat1D(copy_reference, method, time_steps, param
234
247
  elif parametrization == 'source_term2':
235
248
  parameters = np.ones(dim)
236
249
 
237
- # 4. Create a PDE object
250
+ # 4. Set up the observation parameters
251
+ if grid_obs == 'half_grid':
252
+ grid_obs = grid_sol[int(dim/2):]
253
+
254
+ if time_obs == 'every_5':
255
+ time_obs = time_steps[::5]
256
+
257
+ # 5. Create a PDE object
238
258
  PDE = cuqi.pde.TimeDependentLinearPDE(
239
- PDE_form, time_steps, method=method)
259
+ PDE_form, time_steps, method=method,
260
+ grid_sol=grid_sol,
261
+ grid_obs=grid_obs, time_obs=time_obs,
262
+ observation_map=observation_map)
240
263
 
241
- # 5 Solve the PDE
264
+ # 6. Solve the PDE
242
265
  PDE.assemble(parameters)
243
266
  sol, info = PDE.solve()
244
267
 
245
- # 6 Compare the obtained solution with previously stored solution
246
- solution_file = copy_reference("data/Heat1D_5solutions.npz")
268
+ # 7. Compare the obtained solution with previously stored solution
269
+ solution_file = copy_reference("data/Heat1D_data/Heat1D_5solutions.npz")
247
270
  expected_sols = np.load(solution_file)
248
- assert(np.allclose(sol, expected_sols[expected_sol]))
271
+ assert (np.allclose(sol[:, -1], expected_sols[expected_sol]))
272
+
273
+ # 8. Compute the observed solution and compare it with previously
274
+ # stored solution
275
+
276
+ # compute the observed solution using the PDE object
277
+ obs_sol = PDE.observe(sol)
278
+
279
+ # compute the expected observed solution (for comparison)
280
+ if isinstance(time_obs, str) and time_obs.lower() == 'final':
281
+ time_obs = time_steps[-1:]
282
+ if grid_obs is None:
283
+ grid_obs = grid_sol
284
+
285
+ idx_x = [True if x in grid_obs else False for x in grid_sol]
286
+ idx_t = [True if t in time_obs else False for t in time_steps]
287
+
288
+ if sum(idx_x) != len(grid_obs) or sum(idx_t) != len(time_obs):
289
+ expected_observed_sol = scipy.interpolate.RectBivariateSpline(
290
+ grid_sol, time_steps, sol)(grid_obs, time_obs
291
+ )
292
+ else:
293
+ expected_observed_sol = sol[idx_x, :][:, idx_t]
294
+
295
+ if observation_map is not None:
296
+ expected_observed_sol = observation_map(expected_observed_sol)
297
+
298
+ if len(PDE._time_obs) == 1:
299
+ expected_observed_sol = expected_observed_sol.squeeze()
300
+
301
+ # load expected observed solution (for comparison)
302
+ # Skip sol1 due to its large size (not stored in file to save space)
303
+ if expected_sol != 'sol1':
304
+ obs_sol_file = copy_reference("data/Heat1D_data/Heat1D_obs_sol_"
305
+ + expected_sol+"_"
306
+ + expected_obs+".npz")
307
+ expected_observed_sol_from_file = np.load(obs_sol_file)["obs_sol"]
308
+
309
+ if len(PDE._time_obs) == 1:
310
+ expected_observed_sol_from_file = \
311
+ expected_observed_sol_from_file.squeeze()
312
+
313
+ # Compare the observed solution with the two expected observed solution
314
+ # (computed and loaded from file)
315
+ assert (np.allclose(obs_sol, expected_observed_sol))
316
+
317
+ if expected_sol != 'sol1':
318
+ assert (np.allclose(obs_sol, expected_observed_sol_from_file))
319
+ else:
320
+ assert expected_sol == 'sol1'
321
+
249
322
 
250
323
  @pytest.mark.xfail(reason="Test fails due to difficult to compare values (1e-6 to 1e-42)")
251
324
  def test_TimeDependentLinearPDE_wave1D(copy_reference):
File without changes
File without changes
File without changes
File without changes