geone 1.3.0__py313-none-manylinux_2_35_x86_64.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.
geone/pgs.py ADDED
@@ -0,0 +1,1258 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # -------------------------------------------------------------------------
5
+ # Python module: 'pgs.py'
6
+ # author: Julien Straubhaar
7
+ # date: may-2022
8
+ # -------------------------------------------------------------------------
9
+
10
+ """
11
+ Module for plurig-Gaussian simulations in 1D, 2D and 3D.
12
+ """
13
+
14
+ import numpy as np
15
+ from geone import covModel as gcm
16
+ from geone import multiGaussian
17
+
18
+ # ============================================================================
19
+ class PgsError(Exception):
20
+ """
21
+ Custom exception related to `pgs` module.
22
+ """
23
+ pass
24
+ # ============================================================================
25
+
26
+ # ----------------------------------------------------------------------------
27
+ def pluriGaussianSim_unconditional(
28
+ cov_model_T1, cov_model_T2, flag_value,
29
+ dimension, spacing=None, origin=None,
30
+ algo_T1='fft', params_T1={},
31
+ algo_T2='fft', params_T2={},
32
+ nreal=1,
33
+ full_output=True,
34
+ verbose=1,
35
+ logger=None):
36
+ """
37
+ Generates unconditional pluri-Gaussian simulations.
38
+
39
+ The simulated variable Z at a point x is defined as
40
+
41
+ * Z(x) = flag_value(T1(x), T2(x))
42
+
43
+ where
44
+
45
+ * T1, T2 are two multi-Gaussian random fields (latent fields)
46
+ * `flag_value` is a function of two variables defining the final value \
47
+ (given as a "flag")
48
+
49
+ Z and T1, T2 are fields in 1D, 2D or 3D.
50
+
51
+ Parameters
52
+ ----------
53
+ cov_model_T1 : :class:`geone.covModel.CovModel<d>D`
54
+ covariance model for T1, in 1D or 2D or 3D (same space dimension for T1 and T2);
55
+ note: if `algo_T1='deterministic'`, `cov_model_T1` can be `None` (unused)
56
+
57
+ cov_model_T2 : :class:`geone.covModel.CovModel<d>D`
58
+ covariance model for T2, in 1D or 2D or 3D (same space dimension for T1 and T2);
59
+ note: if `algo_T2='deterministic'`, `cov_model_T2` can be `None` (unused)
60
+
61
+ flag_value : function (`callable`)
62
+ function of tow arguments (xi, yi) that returns the "flag_value" at
63
+ location (xi, yi)
64
+
65
+ dimension : [sequence of] int(s)
66
+ number of cells along each axis, for simulation in:
67
+
68
+ - 1D: `dimension=nx`
69
+ - 2D: `dimension=(nx, ny)`
70
+ - 3D: `dimension=(nx, ny, nz)`
71
+
72
+ spacing : [sequence of] float(s), optional
73
+ cell size along each axis, for simulation in:
74
+
75
+ - 1D: `spacing=sx`
76
+ - 2D: `spacing=(sx, sy)`
77
+ - 3D: `spacing=(sx, sy, sz)`
78
+
79
+ by default (`None`): 1.0 along each axis
80
+
81
+ origin : [sequence of] float(s), optional
82
+ origin of the grid ("corner of the first cell"), for simulation in:
83
+
84
+ - 1D: `origin=ox`
85
+ - 2D: `origin=(ox, oy)`
86
+ - 3D: `origin=(ox, oy, oz)`
87
+
88
+ by default (`None`): 0.0 along each axis
89
+
90
+ algo_T1 : str {'fft', 'classic', 'deterministic'}, default: 'fft'
91
+ defines the algorithm used for T1:
92
+
93
+ - 'fft': algorithm based on circulant embedding and FFT, function \
94
+ called for <d>D (d = 1, 2, or 3): 'geone.grf.grf<d>D'
95
+ - 'classic': "classic" algorithm, based on the resolution of \
96
+ kriging system considered points in a search ellipsoid, function called \
97
+ for <d>D (d = 1, 2, or 3): \
98
+ 'geone.geoscalassicinterface.simulate<d>D'
99
+ - 'deterministic': use a deterministic field, given by `param_T1['mean']`
100
+
101
+ algo_T2 : str {'fft', 'classic', 'deterministic'}, default: 'fft'
102
+ defines the algorithm used for T2 (see `algo_T1` for detail)
103
+
104
+ params_T1 : dict
105
+ keyword arguments (additional parameters) to be passed to the function
106
+ that is called (according to `algo_T1` and space dimension) for simulation
107
+ of T1
108
+
109
+ params_T2 : dict
110
+ keyword arguments (additional parameters) to be passed to the function
111
+ that is called (according to `algo_T2` and space dimension) for simulation
112
+ of T2
113
+
114
+ nreal : int, default: 1
115
+ number of realization(s)
116
+
117
+ full_output : bool, default: True
118
+ - if `True`: simulation(s) of Z, T1, and T2 are retrieved in output
119
+ - if `False`: simulation(s) of Z only is retrieved in output
120
+
121
+ verbose : int, default: 1
122
+ verbose mode, higher implies more printing (info)
123
+
124
+ logger : :class:`logging.Logger`, optional
125
+ logger (see package `logging`)
126
+ if specified, messages are written via `logger` (no print)
127
+
128
+ Returns
129
+ -------
130
+ Z : ndarray
131
+ array of shape
132
+
133
+ - for 1D: (nreal, nx)
134
+ - for 2D: (nreal, ny, nx)
135
+ - for 3D: (nreal, nz, ny, nx)
136
+
137
+ Z[k] is the k-th realization of Z
138
+
139
+ T1 : ndarray, optional
140
+ array of shape
141
+
142
+ - for 1D: (nreal, nx)
143
+ - for 2D: (nreal, ny, nx)
144
+ - for 3D: (nreal, nz, ny, nx)
145
+
146
+ T1[k] is the k-th realization of T1;
147
+ returned if `full_output=True`
148
+
149
+ T2 : ndarray, optional
150
+ array of shape
151
+
152
+ - for 1D: (nreal, nx)
153
+ - for 2D: (nreal, ny, nx)
154
+ - for 3D: (nreal, nz, ny, nx)
155
+
156
+ T2[k] is the k-th realization of T2;
157
+ returned if `full_output=True`
158
+ """
159
+ fname = 'pluriGaussianSim_unconditional'
160
+
161
+ if not callable(flag_value):
162
+ err_msg = f'{fname}: `flag_value` invalid, should be a function (callable) of two arguments'
163
+ if logger: logger.error(err_msg)
164
+ raise PgsError(err_msg)
165
+
166
+ if algo_T1 not in ('fft', 'FFT', 'classic', 'CLASSIC', 'deterministic', 'DETERMINISTIC'):
167
+ err_msg = f"{fname}: `algo_T1` invalid, should be 'fft' (default) or 'classic' or 'deterministic'"
168
+ if logger: logger.error(err_msg)
169
+ raise PgsError(err_msg)
170
+
171
+ if algo_T2 not in ('fft', 'FFT', 'classic', 'CLASSIC', 'deterministic', 'DETERMINISTIC'):
172
+ err_msg = f"{fname}: `algo_T2` invalid, should be 'fft' (default) or 'classic' or 'deterministic'"
173
+ if logger: logger.error(err_msg)
174
+ raise PgsError(err_msg)
175
+
176
+ # Ignore covariance model if 'algo' is deterministic for T1, T2
177
+ if algo_T1 in ('deterministic', 'DETERMINISTIC'):
178
+ cov_model_T1 = None
179
+
180
+ if algo_T2 in ('deterministic', 'DETERMINISTIC'):
181
+ cov_model_T2 = None
182
+
183
+ # Set space dimension (of grid) according to covariance model for T1
184
+ d = 0
185
+ if cov_model_T1 is None:
186
+ if algo_T1 not in ('deterministic', 'DETERMINISTIC'):
187
+ err_msg = f"{fname}: `cov_model_T1` is `None`, then `algo_T1` must be 'deterministic'"
188
+ if logger: logger.error(err_msg)
189
+ raise PgsError(err_msg)
190
+
191
+ elif isinstance(cov_model_T1, gcm.CovModel1D):
192
+ d = 1
193
+ elif isinstance(cov_model_T1, gcm.CovModel2D):
194
+ d = 2
195
+ elif isinstance(cov_model_T1, gcm.CovModel3D):
196
+ d = 3
197
+ else:
198
+ err_msg = f'{fname}: `cov_model_T1` invalid, should be a class `geone.covModel.CovModel1D`, `geone.covModel.CovModel2D` or `geone.covModel.CovModel3D`'
199
+ if logger: logger.error(err_msg)
200
+ raise PgsError(err_msg)
201
+
202
+ if cov_model_T2 is None:
203
+ if algo_T2 not in ('deterministic', 'DETERMINISTIC'):
204
+ err_msg = f"{fname}: `cov_model_T2` is `None`, then `algo_T2` must be 'deterministic'"
205
+ if logger: logger.error(err_msg)
206
+ raise PgsError(err_msg)
207
+
208
+ # if d == 0:
209
+ # err_msg = f'{fname}: `cov_model_T1` and `cov_model_T2` are `None`, at least one covariance model is required'
210
+ # if logger: logger.error(err_msg)
211
+ # raise PgsError(err_msg)
212
+
213
+ elif (d == 1 and not isinstance(cov_model_T2, gcm.CovModel1D)) or (d == 2 and not isinstance(cov_model_T2, gcm.CovModel2D)) or (d == 3 and not isinstance(cov_model_T2, gcm.CovModel3D)):
214
+ err_msg = f'{fname}: `cov_model_T1` and `cov_model_T2` not compatible (dimensions differ)'
215
+ if logger: logger.error(err_msg)
216
+ raise PgsError(err_msg)
217
+
218
+ if d == 0:
219
+ # Set space dimension (of grid) according to 'dimension'
220
+ if hasattr(dimension, '__len__'):
221
+ d = len(dimension)
222
+ else:
223
+ d = 1
224
+
225
+ # Check argument 'dimension'
226
+ if hasattr(dimension, '__len__') and len(dimension) != d:
227
+ err_msg = f'{fname}: `dimension` of incompatible length'
228
+ if logger: logger.error(err_msg)
229
+ raise PgsError(err_msg)
230
+
231
+ # Check (or set) argument 'spacing'
232
+ if spacing is None:
233
+ if d == 1:
234
+ spacing = 1.0
235
+ else:
236
+ spacing = tuple(np.ones(d))
237
+ else:
238
+ if hasattr(spacing, '__len__') and len(spacing) != d:
239
+ err_msg = f'{fname}: `spacing` of incompatible length'
240
+ if logger: logger.error(err_msg)
241
+ raise PgsError(err_msg)
242
+
243
+ # Check (or set) argument 'origin'
244
+ if origin is None:
245
+ if d == 1:
246
+ origin = 0.0
247
+ else:
248
+ origin = tuple(np.zeros(d))
249
+ else:
250
+ if hasattr(origin, '__len__') and len(origin) != d:
251
+ err_msg = f'{fname}: `origin` of incompatible length'
252
+ if logger: logger.error(err_msg)
253
+ raise PgsError(err_msg)
254
+
255
+ # if not cov_model_T1.is_stationary(): # prevent calculation if covariance model is not stationary
256
+ # if verbose > 0:
257
+ # print(f"ERROR ({fname}): `cov_model_T1` is not stationary")
258
+
259
+ # if not cov_model_T2.is_stationary(): # prevent calculation if covariance model is not stationary
260
+ # if verbose > 0:
261
+ # print(f"ERROR ({fname}): `cov_model_T2` is not stationary")
262
+
263
+ # Set default parameter 'verbose' for params_T1, params_T2
264
+ if 'verbose' not in params_T1.keys():
265
+ params_T1['verbose'] = 0
266
+ # params_T1['verbose'] = verbose
267
+ if 'verbose' not in params_T2.keys():
268
+ params_T2['verbose'] = 0
269
+ # params_T2['verbose'] = verbose
270
+
271
+ # Generate T1
272
+ if cov_model_T1 is not None:
273
+ try:
274
+ sim_T1 = multiGaussian.multiGaussianRun(
275
+ cov_model_T1, dimension, spacing, origin,
276
+ mode='simulation', algo=algo_T1, output_mode='array',
277
+ **params_T1, nreal=nreal)
278
+ except Exception as exc:
279
+ err_msg = f'{fname}: simulation of T1 failed'
280
+ if logger: logger.error(err_msg)
281
+ raise PgsError(err_msg) from exc
282
+
283
+ else:
284
+ sim_T1 = np.array([params_T1['mean'].reshape(1,*dimension[::-1]) for _ in range(nreal)])
285
+ # -> sim_T1: nd-array of shape
286
+ # (nreal_T, dimension) (for T1 in 1D)
287
+ # (nreal_T, dimension[1], dimension[0]) (for T1 in 2D)
288
+ # (nreal_T, dimension[2], dimension[1], dimension[0]) (for T1 in 3D)
289
+ #
290
+ # Generate T2
291
+ if cov_model_T2 is not None:
292
+ try:
293
+ sim_T2 = multiGaussian.multiGaussianRun(
294
+ cov_model_T2, dimension, spacing, origin,
295
+ mode='simulation', algo=algo_T2, output_mode='array',
296
+ **params_T2, nreal=nreal)
297
+ except Exception as exc:
298
+ err_msg = f'{fname}: simulation of T2 failed'
299
+ if logger: logger.error(err_msg)
300
+ raise PgsError(err_msg) from exc
301
+ else:
302
+ sim_T2 = np.array([params_T2['mean'].reshape(1,*dimension[::-1]) for _ in range(nreal)])
303
+ # -> sim_T2: nd-array of shape
304
+ # (nreal_T, dimension) (for T2 in 1D)
305
+ # (nreal_T, dimension[1], dimension[0]) (for T2 in 2D)
306
+ # (nreal_T, dimension[2], dimension[1], dimension[0]) (for T2 in 3D)
307
+
308
+ # Generate Z
309
+ if verbose > 1:
310
+ if logger:
311
+ logger.info(f'{fname}: retrieving Z...')
312
+ else:
313
+ print(f'{fname}: retrieving Z...')
314
+ Z = flag_value(sim_T1, sim_T2)
315
+ # Z = np.asarray(Z).reshape(len(Z), *np.atleast_1d(dimension)[::-1])
316
+
317
+ if full_output:
318
+ return Z, sim_T1, sim_T2
319
+ else:
320
+ return Z
321
+ # ----------------------------------------------------------------------------
322
+
323
+ # ----------------------------------------------------------------------------
324
+ def pluriGaussianSim(
325
+ cov_model_T1, cov_model_T2, flag_value,
326
+ dimension, spacing=None, origin=None,
327
+ x=None, v=None,
328
+ algo_T1='fft', params_T1={},
329
+ algo_T2='fft', params_T2={},
330
+ accept_init=0.25, accept_pow=2.0,
331
+ mh_iter_min=100, mh_iter_max=200,
332
+ ntry_max=1,
333
+ retrieve_real_anyway=False,
334
+ nreal=1,
335
+ full_output=True,
336
+ verbose=1,
337
+ logger=None):
338
+ """
339
+ Generates (conditional) pluri-Gaussian simulations.
340
+
341
+ The simulated variable Z at a point x is defined as
342
+
343
+ * Z(x) = flag_value(T1(x), T2(x))
344
+
345
+ where
346
+
347
+ * T1, T2 are two multi-Gaussian random fields (latent fields)
348
+ * `flag_value` is a function of two variables defining the final value \
349
+ (given as a "flag")
350
+
351
+ Z and T1, T2 are fields in 1D, 2D or 3D.
352
+
353
+ Parameters
354
+ ----------
355
+ cov_model_T1 : :class:`geone.covModel.CovModel<d>D`
356
+ covariance model for T1, in 1D or 2D or 3D (same space dimension for T1 and T2);
357
+ note: if `algo_T1='deterministic'`, `cov_model_T1` can be `None` (unused)
358
+
359
+ cov_model_T2 : :class:`geone.covModel.CovModel<d>D`
360
+ covariance model for T2, in 1D or 2D or 3D (same space dimension for T1 and T2);
361
+ note: if `algo_T2='deterministic'`, `cov_model_T2` can be `None` (unused)
362
+
363
+ flag_value : function (`callable`)
364
+ function of tow arguments (xi, yi) that returns the "flag_value" at
365
+ location (xi, yi)
366
+
367
+ dimension : [sequence of] int(s)
368
+ number of cells along each axis, for simulation in:
369
+
370
+ - 1D: `dimension=nx`
371
+ - 2D: `dimension=(nx, ny)`
372
+ - 3D: `dimension=(nx, ny, nz)`
373
+
374
+ spacing : [sequence of] float(s), optional
375
+ cell size along each axis, for simulation in:
376
+
377
+ - 1D: `spacing=sx`
378
+ - 2D: `spacing=(sx, sy)`
379
+ - 3D: `spacing=(sx, sy, sz)`
380
+
381
+ by default (`None`): 1.0 along each axis
382
+
383
+ origin : [sequence of] float(s), optional
384
+ origin of the grid ("corner of the first cell"), for simulation in:
385
+
386
+ - 1D: `origin=ox`
387
+ - 2D: `origin=(ox, oy)`
388
+ - 3D: `origin=(ox, oy, oz)`
389
+
390
+ by default (`None`): 0.0 along each axis
391
+
392
+ x : array-like of floats, optional
393
+ data points locations (float coordinates), for simulation in:
394
+
395
+ - 1D: 1D array-like of floats
396
+ - 2D: 2D array-like of floats of shape (n, 2)
397
+ - 3D: 2D array-like of floats of shape (n, 3)
398
+
399
+ note: if one point (n=1), a float in 1D, a 1D array of shape (2,) in 2D,
400
+ a 1D array of shape (3,) in 3D, is accepted
401
+
402
+ v : 1D array-like of floats, optional
403
+ data values at `x` (`v[i]` is the data value at `x[i]`)
404
+
405
+ algo_T1 : str {'fft', 'classic', 'deterministic'}, default: 'fft'
406
+ defines the algorithm used for T1:
407
+
408
+ - 'fft': algorithm based on circulant embedding and FFT, function \
409
+ called for <d>D (d = 1, 2, or 3): 'geone.grf.grf<d>D'
410
+ - 'classic': "classic" algorithm, based on the resolution of \
411
+ kriging system considered points in a search ellipsoid, function called \
412
+ for <d>D (d = 1, 2, or 3): \
413
+ 'geone.geoscalassicinterface.simulate<d>D'
414
+ - 'deterministic': use a deterministic field, given by `param_T1['mean']`
415
+
416
+ algo_T2 : str {'fft', 'classic', 'deterministic'}, default: 'fft'
417
+ defines the algorithm used for T2 (see `algo_T1` for detail)
418
+
419
+ params_T1 : dict
420
+ keyword arguments (additional parameters) to be passed to the function
421
+ that is called (according to `algo_T1` and space dimension) for simulation
422
+ of T1
423
+
424
+ params_T2 : dict
425
+ keyword arguments (additional parameters) to be passed to the function
426
+ that is called (according to `algo_T2` and space dimension) for simulation
427
+ of T2
428
+
429
+ accept_init : float, default: 0.25
430
+ initial acceptation probability
431
+ (see parameters `mh_iter_min`, `mh_iter_max`)
432
+
433
+ accept_pow : float, default: 2.0
434
+ power for computing acceptation probability
435
+ (see parameters `mh_iter_min`, `mh_iter_max`)
436
+
437
+ mh_iter_min : int, default: 100
438
+ see parameter `mh_iter_max`
439
+
440
+ mh_iter_max : int, default: 200
441
+ `mh_iter_min` and `mh_iter_max` are the number of iterations
442
+ (min and max) for Metropolis-Hasting algorithm
443
+ (for conditional simulation) when updating T1 and T2 at conditioning
444
+ locations at iteration `nit` (in 0, ..., `mh_iter_max-1`):
445
+
446
+ * if `nit < mh_iter_min`: for any k:
447
+ - simulate new candidate at `x[k]`: `(T1(x[k]), T2(x[k]))`
448
+ - if `flag_value(T1(x[k]), T2(x[k])=v[k]` (conditioning ok): \
449
+ accept the new candidate
450
+ - else (conditioning not ok): \
451
+ accept the new candidate with probability
452
+ * p = `accept_init * (1 - 1/mh_iter_min)**accept_pow`
453
+
454
+ * if nit >= mh_iter_min:
455
+ - if conditioning ok at every `x[k]`: stop and exit the loop,
456
+ - else: for any k:
457
+ - if conditioning ok at `x[k]`: skip
458
+ - else:
459
+ * simulate new candidate at `x[k]`: `(T1(x[k]), T2(x[k]))`
460
+ * if `flag_value(T1(x[k]), T2(x[k])=v[k]` (conditioning ok): \
461
+ accept the new candidate
462
+ * else (conditioning not ok): \
463
+ reject the new candidate
464
+
465
+ ntry_max : int, default: 1
466
+ number of trial(s) per realization before giving up if something goes
467
+ wrong
468
+
469
+ retrieve_real_anyway : bool, default: False
470
+ if after `ntry_max` trial(s) a conditioning data is not honoured, then
471
+ the realization is:
472
+
473
+ - retrieved, if `retrieve_real_anyway=True`
474
+ - not retrieved (missing realization), if `retrieve_real_anyway=False`
475
+
476
+ nreal : int, default: 1
477
+ number of realization(s)
478
+
479
+ full_output : bool, default: True
480
+ - if `True`: simulation(s) of Z, T1, T2, and `n_cond_ok` are \
481
+ retrieved in output
482
+ - if `False`: simulation(s) of Z only is retrieved in output
483
+
484
+ verbose : int, default: 1
485
+ verbose mode, higher implies more printing (info)
486
+
487
+ logger : :class:`logging.Logger`, optional
488
+ logger (see package `logging`)
489
+ if specified, messages are written via `logger` (no print)
490
+
491
+ Returns
492
+ -------
493
+ Z : ndarray
494
+ array of shape
495
+
496
+ - for 1D: (nreal, nx)
497
+ - for 2D: (nreal, ny, nx)
498
+ - for 3D: (nreal, nz, ny, nx)
499
+
500
+ Z[k] is the k-th realization of Z
501
+
502
+ T1 : ndarray, optional
503
+ array of shape
504
+
505
+ - for 1D: (nreal, nx)
506
+ - for 2D: (nreal, ny, nx)
507
+ - for 3D: (nreal, nz, ny, nx)
508
+
509
+ T1[k] is the k-th realization of T1;
510
+ returned if `full_output=True`
511
+
512
+ T2 : ndarray, optional
513
+ array of shape
514
+
515
+ - for 1D: (nreal, nx)
516
+ - for 2D: (nreal, ny, nx)
517
+ - for 3D: (nreal, nz, ny, nx)
518
+
519
+ T2[k] is the k-th realization of T2;
520
+ returned if `full_output=True`
521
+
522
+ n_cond_ok : list of 1D array
523
+ list of length `nreal`
524
+
525
+ - n_cond_ok[k]: 1D array of ints
526
+ number of conditioning locations honoured at each iteration of the
527
+ Metropolis-Hasting algorithm for the k-th realization, in particular
528
+ `len(n_cond_ok[k])` is the number of iteration done,
529
+ `n_cond_ok[k][-1]` is the number of conditioning locations honoured
530
+ at the end;
531
+
532
+ returned if `full_output=True`
533
+ """
534
+ fname = 'pluriGaussianSim'
535
+
536
+ if not callable(flag_value):
537
+ err_msg = f'{fname}: `flag_value` invalid, should be a function (callable) of two arguments'
538
+ if logger: logger.error(err_msg)
539
+ raise PgsError(err_msg)
540
+
541
+ if algo_T1 not in ('fft', 'FFT', 'classic', 'CLASSIC', 'deterministic', 'DETERMINISTIC'):
542
+ err_msg = f"{fname}: `algo_T1` invalid, should be 'fft' (default) or 'classic' or 'deterministic'"
543
+ if logger: logger.error(err_msg)
544
+ raise PgsError(err_msg)
545
+
546
+ if algo_T2 not in ('fft', 'FFT', 'classic', 'CLASSIC', 'deterministic', 'DETERMINISTIC'):
547
+ err_msg = f"{fname}: `algo_T2` invalid, should be 'fft' (default) or 'classic' or 'deterministic'"
548
+ if logger: logger.error(err_msg)
549
+ raise PgsError(err_msg)
550
+
551
+ # Ignore covariance model if 'algo' is deterministic for T1, T2
552
+ if algo_T1 in ('deterministic', 'DETERMINISTIC'):
553
+ cov_model_T1 = None
554
+
555
+ if algo_T2 in ('deterministic', 'DETERMINISTIC'):
556
+ cov_model_T2 = None
557
+
558
+ # Set space dimension (of grid) according to covariance model for T1
559
+ d = 0
560
+ if cov_model_T1 is None:
561
+ if algo_T1 not in ('deterministic', 'DETERMINISTIC'):
562
+ err_msg = f"{fname}: `cov_model_T1` is `None`, then `algo_T1` must be 'deterministic'"
563
+ if logger: logger.error(err_msg)
564
+ raise PgsError(err_msg)
565
+
566
+ elif isinstance(cov_model_T1, gcm.CovModel1D):
567
+ d = 1
568
+ elif isinstance(cov_model_T1, gcm.CovModel2D):
569
+ d = 2
570
+ elif isinstance(cov_model_T1, gcm.CovModel3D):
571
+ d = 3
572
+ else:
573
+ err_msg = f'{fname}: `cov_model_T1` invalid, should be a class `geone.covModel.CovModel1D`, `geone.covModel.CovModel2D` or `geone.covModel.CovModel3D`'
574
+ if logger: logger.error(err_msg)
575
+ raise PgsError(err_msg)
576
+
577
+ if cov_model_T2 is None:
578
+ if algo_T2 not in ('deterministic', 'DETERMINISTIC'):
579
+ err_msg = f"{fname}: `cov_model_T2` is `None`, then `algo_T2` must be 'deterministic'"
580
+ if logger: logger.error(err_msg)
581
+ raise PgsError(err_msg)
582
+
583
+ # if d == 0:
584
+ # err_msg = f'{fname}: `cov_model_T1` and `cov_model_T2` are `None`, at least one covariance model is required'
585
+ # if logger: logger.error(err_msg)
586
+ # raise PgsError(err_msg)
587
+
588
+ elif (d == 1 and not isinstance(cov_model_T2, gcm.CovModel1D)) or (d == 2 and not isinstance(cov_model_T2, gcm.CovModel2D)) or (d == 3 and not isinstance(cov_model_T2, gcm.CovModel3D)):
589
+ err_msg = f'{fname}: `cov_model_T1` and `cov_model_T2` not compatible (dimensions differ)'
590
+ if logger: logger.error(err_msg)
591
+ raise PgsError(err_msg)
592
+
593
+ if d == 0:
594
+ # Set space dimension (of grid) according to 'dimension'
595
+ if hasattr(dimension, '__len__'):
596
+ d = len(dimension)
597
+ else:
598
+ d = 1
599
+
600
+ # Check argument 'dimension'
601
+ if hasattr(dimension, '__len__') and len(dimension) != d:
602
+ err_msg = f'{fname}: `dimension` of incompatible length'
603
+ if logger: logger.error(err_msg)
604
+ raise PgsError(err_msg)
605
+
606
+ if d == 1:
607
+ grid_size = dimension
608
+ else:
609
+ grid_size = np.prod(dimension)
610
+
611
+ # Check (or set) argument 'spacing'
612
+ if spacing is None:
613
+ if d == 1:
614
+ spacing = 1.0
615
+ else:
616
+ spacing = tuple(np.ones(d))
617
+ else:
618
+ if hasattr(spacing, '__len__') and len(spacing) != d:
619
+ err_msg = f'{fname}: `spacing` of incompatible length'
620
+ if logger: logger.error(err_msg)
621
+ raise PgsError(err_msg)
622
+
623
+ # Check (or set) argument 'origin'
624
+ if origin is None:
625
+ if d == 1:
626
+ origin = 0.0
627
+ else:
628
+ origin = tuple(np.zeros(d))
629
+ else:
630
+ if hasattr(origin, '__len__') and len(origin) != d:
631
+ err_msg = f'{fname}: `origin` of incompatible length'
632
+ if logger: logger.error(err_msg)
633
+ raise PgsError(err_msg)
634
+
635
+ # if not cov_model_T1.is_stationary(): # prevent calculation if covariance model is not stationary
636
+ # if verbose > 0:
637
+ # print(f"ERROR ({fname}): `cov_model_T1` is not stationary")
638
+
639
+ # if not cov_model_T2.is_stationary(): # prevent calculation if covariance model is not stationary
640
+ # if verbose > 0:
641
+ # print(f"ERROR ({fname}): `cov_model_T2` is not stationary")
642
+
643
+ # Compute meshgrid over simulation domain if needed (see below)
644
+ if ('mean' in params_T1.keys() and callable(params_T1['mean'])) or ('var' in params_T1.keys() and callable(params_T1['var'])) \
645
+ or ('mean' in params_T2.keys() and callable(params_T2['mean'])) or ('var' in params_T2.keys() and callable(params_T2['var'])):
646
+ if d == 1:
647
+ xi = origin + spacing*(0.5+np.arange(dimension)) # x-coordinate of cell center
648
+ elif d == 2:
649
+ xi = origin[0] + spacing[0]*(0.5+np.arange(dimension[0])) # x-coordinate of cell center
650
+ yi = origin[1] + spacing[1]*(0.5+np.arange(dimension[1])) # y-coordinate of cell center
651
+ yyi, xxi = np.meshgrid(yi, xi, indexing='ij')
652
+ elif d == 3:
653
+ xi = origin[0] + spacing[0]*(0.5+np.arange(dimension[0])) # x-coordinate of cell center
654
+ yi = origin[1] + spacing[1]*(0.5+np.arange(dimension[1])) # y-coordinate of cell center
655
+ zi = origin[2] + spacing[2]*(0.5+np.arange(dimension[2])) # z-coordinate of cell center
656
+ zzi, yyi, xxi = np.meshgrid(zi, yi, xi, indexing='ij')
657
+
658
+ # Set mean_T1 (as array) from params_T1
659
+ if 'mean' not in params_T1.keys():
660
+ mean_T1 = np.array([0.0])
661
+ else:
662
+ mean_T1 = params_T1['mean']
663
+ if mean_T1 is None:
664
+ mean_T1 = np.array([0.0])
665
+ elif callable(mean_T1):
666
+ if d == 1:
667
+ mean_T1 = mean_T1(xi).reshape(-1) # replace function 'mean_T1' by its evaluation on the grid
668
+ elif d == 2:
669
+ mean_T1 = mean_T1(xxi, yyi).reshape(-1) # replace function 'mean_T1' by its evaluation on the grid
670
+ elif d == 3:
671
+ mean_T1 = mean_T1(xxi, yyi, zzi).reshape(-1) # replace function 'mean_T1' by its evaluation on the grid
672
+ else:
673
+ mean_T1 = np.asarray(mean_T1).reshape(-1)
674
+ if mean_T1.size not in (1, grid_size):
675
+ err_msg = f"{fname}: 'mean' parameter for T1 (in `params_T1`) has incompatible size"
676
+ if logger: logger.error(err_msg)
677
+ raise PgsError(err_msg)
678
+
679
+ # Set var_T1 (as array) from params_T1, if given
680
+ var_T1 = None
681
+ if 'var' in params_T1.keys():
682
+ var_T1 = params_T1['var']
683
+ if var_T1 is not None:
684
+ if callable(var_T1):
685
+ if d == 1:
686
+ var_T1 = var_T1(xi).reshape(-1) # replace function 'var_T1' by its evaluation on the grid
687
+ elif d == 2:
688
+ var_T1 = var_T1(xxi, yyi).reshape(-1) # replace function 'var_T1' by its evaluation on the grid
689
+ elif d == 3:
690
+ var_T1 = var_T1(xxi, yyi, zzi).reshape(-1) # replace function 'var_T1' by its evaluation on the grid
691
+ else:
692
+ var_T1 = np.asarray(var_T1).reshape(-1)
693
+ if var_T1.size not in (1, grid_size):
694
+ err_msg = f"{fname}: 'var' parameter for T1 (in `params_T1`) has incompatible size"
695
+ if logger: logger.error(err_msg)
696
+ raise PgsError(err_msg)
697
+
698
+ # Set mean_T2 (as array) from params_T2
699
+ if 'mean' not in params_T2.keys():
700
+ mean_T2 = np.array([0.0])
701
+ else:
702
+ mean_T2 = params_T2['mean']
703
+ if mean_T2 is None:
704
+ mean_T2 = np.array([0.0])
705
+ elif callable(mean_T2):
706
+ if d == 1:
707
+ mean_T2 = mean_T2(xi).reshape(-1) # replace function 'mean_T2' by its evaluation on the grid
708
+ elif d == 2:
709
+ mean_T2 = mean_T2(xxi, yyi).reshape(-1) # replace function 'mean_T2' by its evaluation on the grid
710
+ elif d == 3:
711
+ mean_T2 = mean_T2(xxi, yyi, zzi).reshape(-1) # replace function 'mean_T2' by its evaluation on the grid
712
+ else:
713
+ mean_T2 = np.asarray(mean_T2).reshape(-1)
714
+ if mean_T2.size not in (1, grid_size):
715
+ err_msg = f"{fname}: 'mean' parameter for T2 (in `params_T2`) has incompatible size"
716
+ if logger: logger.error(err_msg)
717
+ raise PgsError(err_msg)
718
+
719
+ # Set var_T2 (as array) from params_T2, if given
720
+ var_T2 = None
721
+ if 'var' in params_T2.keys():
722
+ var_T2 = params_T2['var']
723
+ if var_T2 is not None:
724
+ if callable(var_T2):
725
+ if d == 1:
726
+ var_T2 = var_T2(xi).reshape(-1) # replace function 'var_T2' by its evaluation on the grid
727
+ elif d == 2:
728
+ var_T2 = var_T2(xxi, yyi).reshape(-1) # replace function 'var_T2' by its evaluation on the grid
729
+ elif d == 3:
730
+ var_T2 = var_T2(xxi, yyi, zzi).reshape(-1) # replace function 'var_T2' by its evaluation on the grid
731
+ else:
732
+ var_T2 = np.asarray(var_T2).reshape(-1)
733
+ if var_T2.size not in (1, grid_size):
734
+ err_msg = f"{fname}: 'var' parameter for T2 (in `params_T2`) has incompatible size"
735
+ if logger: logger.error(err_msg)
736
+ raise PgsError(err_msg)
737
+
738
+ # Note: format of data (x, v) not checked !
739
+
740
+ if x is None:
741
+ if v is not None:
742
+ err_msg = f'{fname}: `x` is not given (`None`) but `v` is given (not `None`)'
743
+ if logger: logger.error(err_msg)
744
+ raise PgsError(err_msg)
745
+
746
+ else:
747
+ # Preparation for conditional case
748
+ if v is None:
749
+ err_msg = f'{fname}: `x` is given (not `None`) but `v` is not given (`None`)'
750
+ if logger: logger.error(err_msg)
751
+ raise PgsError(err_msg)
752
+
753
+ x = np.asarray(x, dtype='float').reshape(-1, d) # cast in d-dimensional array if needed
754
+ v = np.asarray(v, dtype='float').reshape(-1) # cast in 1-dimensional array if needed
755
+ if len(v) != x.shape[0]:
756
+ err_msg = f'{fname}: length of `v` is not valid'
757
+ if logger: logger.error(err_msg)
758
+ raise PgsError(err_msg)
759
+
760
+ # Compute
761
+ # indc: node index of conditioning node (nearest node),
762
+ # rounded to lower index if between two grid node and index is positive
763
+ indc_f = (x-origin)/spacing
764
+ indc = indc_f.astype(int)
765
+ indc = indc - 1 * np.all((indc == indc_f, indc > 0), axis=0)
766
+ if d == 1:
767
+ indc = 1 * indc[:, 0] # multiply by 1.0 makes a copy of the array !
768
+ elif d == 2:
769
+ indc = indc[:, 0] + dimension[0] * indc[:, 1]
770
+ elif d == 3:
771
+ indc = indc[:, 0] + dimension[0] * (indc[:, 1] + dimension[1] * indc[:, 2])
772
+ indc_unique, indc_inv = np.unique(indc, return_inverse=True)
773
+ if len(indc_unique) != len(x):
774
+ if np.any([len(np.unique(v[indc_inv==j])) > 1 for j in range(len(indc_unique))]):
775
+ err_msg = f'{fname}: more than one conditioning point fall in a same grid cell and have different conditioning values'
776
+ if logger: logger.error(err_msg)
777
+ raise PgsError(err_msg)
778
+
779
+ else:
780
+ if verbose > 0:
781
+ if logger:
782
+ logger.warning(f'{fname}: more than one conditioning point fall in a same grid cell with same conditioning value (consistent)')
783
+ else:
784
+ print(f'{fname}: WARNING: more than one conditioning point fall in a same grid cell with same conditioning value (consistent)')
785
+ x = np.array([x[indc_inv==j][0] for j in range(len(indc_unique))])
786
+ v = np.array([v[indc_inv==j][0] for j in range(len(indc_unique))])
787
+
788
+ # Number of conditioning points
789
+ npt = x.shape[0]
790
+ #
791
+ # Get index in mean_T1 for each conditioning point
792
+ x_mean_T1_grid_ind = None
793
+ if mean_T1.size == 1:
794
+ x_mean_T1_grid_ind = np.zeros(npt, dtype='int')
795
+ else:
796
+ indc_f = (x-origin)/spacing
797
+ indc = indc_f.astype(int)
798
+ indc = indc - 1 * np.all((indc == indc_f, indc > 0), axis=0)
799
+ if d == 1:
800
+ x_mean_T1_grid_ind = 1 * indc[:, 0] # multiply by 1.0 makes a copy of the array !
801
+ elif d == 2:
802
+ x_mean_T1_grid_ind = indc[:, 0] + dimension[0] * indc[:, 1]
803
+ elif d == 3:
804
+ x_mean_T1_grid_ind = indc[:, 0] + dimension[0] * (indc[:, 1] + dimension[1] * indc[:, 2])
805
+
806
+ # Get index in var_T1 (if not None) for each conditioning point
807
+ if var_T1 is not None:
808
+ if var_T1.size == 1:
809
+ x_var_T1_grid_ind = np.zeros(npt, dtype='int')
810
+ else:
811
+ if x_mean_T1_grid_ind is not None:
812
+ x_var_T1_grid_ind = x_mean_T1_grid_ind
813
+ else:
814
+ indc_f = (x-origin)/spacing
815
+ indc = indc_f.astype(int)
816
+ indc = indc - 1 * np.all((indc == indc_f, indc > 0), axis=0)
817
+ if d == 1:
818
+ x_var_T1_grid_ind = 1 * indc[:, 0] # multiply by 1.0 makes a copy of the array !
819
+ elif d == 2:
820
+ x_var_T1_grid_ind = indc[:, 0] + dimension[0] * indc[:, 1]
821
+ elif d == 3:
822
+ x_var_T1_grid_ind = indc[:, 0] + dimension[0] * (indc[:, 1] + dimension[1] * indc[:, 2])
823
+
824
+ # Get index in mean_T2 for each conditioning point
825
+ x_mean_T2_grid_ind = None
826
+ if mean_T2.size == 1:
827
+ x_mean_T2_grid_ind = np.zeros(npt, dtype='int')
828
+ else:
829
+ indc_f = (x-origin)/spacing
830
+ indc = indc_f.astype(int)
831
+ indc = indc - 1 * np.all((indc == indc_f, indc > 0), axis=0)
832
+ if d == 1:
833
+ x_mean_T2_grid_ind = 1 * indc[:, 0] # multiply by 1.0 makes a copy of the array !
834
+ elif d == 2:
835
+ x_mean_T2_grid_ind = indc[:, 0] + dimension[0] * indc[:, 1]
836
+ elif d == 3:
837
+ x_mean_T2_grid_ind = indc[:, 0] + dimension[0] * (indc[:, 1] + dimension[1] * indc[:, 2])
838
+
839
+ # Get index in var_T2 (if not None) for each conditioning point
840
+ if var_T2 is not None:
841
+ if var_T2.size == 1:
842
+ x_var_T2_grid_ind = np.zeros(npt, dtype='int')
843
+ else:
844
+ if x_mean_T2_grid_ind is not None:
845
+ x_var_T2_grid_ind = x_mean_T2_grid_ind
846
+ else:
847
+ indc_f = (x-origin)/spacing
848
+ indc = indc_f.astype(int)
849
+ indc = indc - 1 * np.all((indc == indc_f, indc > 0), axis=0)
850
+ if d == 1:
851
+ x_var_T2_grid_ind = 1 * indc[:, 0] # multiply by 1.0 makes a copy of the array !
852
+ elif d == 2:
853
+ x_var_T2_grid_ind = indc[:, 0] + dimension[0] * indc[:, 1]
854
+ elif d == 3:
855
+ x_var_T2_grid_ind = indc[:, 0] + dimension[0] * (indc[:, 1] + dimension[1] * indc[:, 2])
856
+
857
+ # Get covariance function for T1, T2 and Y, and their evaluation at 0
858
+ if cov_model_T1 is not None:
859
+ cov_func_T1 = cov_model_T1.func() # covariance function
860
+ cov0_T1 = cov_func_T1(np.zeros(d))
861
+ if cov_model_T2 is not None:
862
+ cov_func_T2 = cov_model_T2.func() # covariance function
863
+ cov0_T2 = cov_func_T2(np.zeros(d))
864
+
865
+ if cov_model_T1 is not None:
866
+ # Set kriging matrix for T1 (mat_T1) of order npt, "over every conditioining point"
867
+ mat_T1 = np.ones((npt, npt))
868
+ for i in range(npt-1):
869
+ # lag between x[i] and x[j], j=i+1, ..., npt-1
870
+ h = x[(i+1):] - x[i]
871
+ cov_h_T1 = cov_func_T1(h)
872
+ mat_T1[i, (i+1):npt] = cov_h_T1
873
+ mat_T1[(i+1):npt, i] = cov_h_T1
874
+ mat_T1[i, i] = cov0_T1
875
+
876
+ mat_T1[-1,-1] = cov0_T1
877
+
878
+ if var_T1 is not None:
879
+ varUpdate = np.sqrt(var_T1[x_var_T1_grid_ind]/cov0_T1)
880
+ mat_T1 = varUpdate*(mat_T1.T*varUpdate).T
881
+
882
+ if cov_model_T2 is not None:
883
+ # Set kriging matrix for T2 (mat_T2) of order npt, "over every conditioining point"
884
+ mat_T2 = np.ones((npt, npt))
885
+ for i in range(npt-1):
886
+ # lag between x[i] and x[j], j=i+1, ..., npt-1
887
+ h = x[(i+1):] - x[i]
888
+ cov_h_T2 = cov_func_T2(h)
889
+ mat_T2[i, (i+1):npt] = cov_h_T2
890
+ mat_T2[(i+1):npt, i] = cov_h_T2
891
+ mat_T2[i, i] = cov0_T2
892
+
893
+ mat_T2[-1,-1] = cov0_T2
894
+
895
+ if var_T2 is not None:
896
+ varUpdate = np.sqrt(var_T2[x_var_T2_grid_ind]/cov0_T2)
897
+ mat_T2 = varUpdate*(mat_T2.T*varUpdate).T
898
+
899
+ # Set (again if given) default parameter 'mean' and 'var' for T1, T2
900
+ if cov_model_T1 is not None:
901
+ params_T1['mean'] = mean_T1
902
+ params_T1['var'] = var_T1
903
+ else:
904
+ if mean_T1.size == grid_size:
905
+ params_T1['mean'] = mean_T1.reshape(*dimension[::-1])
906
+ else:
907
+ params_T1['mean'] = mean_T1 * np.ones(dimension[::-1])
908
+ if cov_model_T2 is not None:
909
+ params_T2['mean'] = mean_T2
910
+ params_T2['var'] = var_T2
911
+ else:
912
+ if mean_T2.size == grid_size:
913
+ params_T2['mean'] = mean_T2.reshape(*dimension[::-1])
914
+ else:
915
+ params_T2['mean'] = mean_T2 * np.ones(dimension[::-1])
916
+
917
+ # Set default parameter 'verbose' for params_T1, params_T2
918
+ if 'verbose' not in params_T1.keys():
919
+ params_T1['verbose'] = 0
920
+ # params_T1['verbose'] = verbose
921
+ if 'verbose' not in params_T2.keys():
922
+ params_T2['verbose'] = 0
923
+ # params_T2['verbose'] = verbose
924
+
925
+ # Initialization for output
926
+ Z = []
927
+ if full_output:
928
+ T1 = []
929
+ T2 = []
930
+ n_cond_ok = []
931
+
932
+ for ireal in range(nreal):
933
+ # Generate ireal-th realization
934
+ if verbose > 1:
935
+ if logger:
936
+ logger.info(f'{fname}: simulation {ireal+1} of {nreal}...')
937
+ else:
938
+ print(f'{fname}: simulation {ireal+1} of {nreal}...')
939
+ for ntry in range(ntry_max):
940
+ sim_ok = True
941
+ nhd_ok = [] # to be appended for full output...
942
+ if verbose > 2 and ntry > 0:
943
+ if logger:
944
+ logger.info(f'{fname}: ... new trial ({ntry+1} of {ntry_max}) for simulation {ireal+1} of {nreal}...')
945
+ else:
946
+ print(f'{fname}: ... new trial ({ntry+1} of {ntry_max}) for simulation {ireal+1} of {nreal}...')
947
+ if x is None:
948
+ # Unconditional case
949
+ # ------------------
950
+ # Generate T1 (one real)
951
+ if cov_model_T1 is not None:
952
+ try:
953
+ sim_T1 = multiGaussian.multiGaussianRun(
954
+ cov_model_T1, dimension, spacing, origin,
955
+ mode='simulation', algo=algo_T1, output_mode='array',
956
+ **params_T1, nreal=1)
957
+ except:
958
+ sim_ok = False
959
+ if verbose > 2:
960
+ if logger:
961
+ logger.info(f'{fname}: ... simulation of T1 failed')
962
+ else:
963
+ print(f'{fname}: ... simulation of T1 failed')
964
+ continue
965
+ # except Exception as exc:
966
+ # err_msg = f'{fname}: simulation of T1 failed'
967
+ # if logger: logger.error(err_msg)
968
+ # raise PgsError(err_msg) from exc
969
+
970
+ else:
971
+ sim_T1 = params_T1['mean'].reshape(1,*dimension[::-1])
972
+ # -> sim_T1: nd-array of shape
973
+ # (1, dimension) (for T1 in 1D)
974
+ # (1, dimension[1], dimension[0]) (for T1 in 2D)
975
+ # (1, dimension[2], dimension[1], dimension[0]) (for T1 in 3D)
976
+
977
+ # Generate T2 (one real)
978
+ if cov_model_T2 is not None:
979
+ try:
980
+ sim_T2 = multiGaussian.multiGaussianRun(
981
+ cov_model_T2, dimension, spacing, origin,
982
+ mode='simulation', algo=algo_T2, output_mode='array',
983
+ **params_T2, nreal=1)
984
+ except:
985
+ sim_ok = False
986
+ if verbose > 2:
987
+ if logger:
988
+ logger.info(f'{fname}: ... simulation of T2 failed')
989
+ else:
990
+ print(f'{fname}: ... simulation of T2 failed')
991
+ continue
992
+ # except Exception as exc:
993
+ # err_msg = f'{fname}: simulation of T2 failed'
994
+ # if logger: logger.error(err_msg)
995
+ # raise PgsError(err_msg) from exc
996
+
997
+ else:
998
+ sim_T2 = params_T2['mean'].reshape(1,*dimension[::-1])
999
+ # -> sim_T2: nd-array of shape
1000
+ # (1, dimension) (for T2 in 1D)
1001
+ # (1, dimension[1], dimension[0]) (for T2 in 2D)
1002
+ # (1, dimension[2], dimension[1], dimension[0]) (for T2 in 3D)
1003
+
1004
+ else:
1005
+ # Conditional case
1006
+ # ----------------
1007
+ v_T = np.zeros((npt, 2))
1008
+ # Initialize: unconditional simulation of T1 at x (values in v_T[:,0])
1009
+ ind = np.random.permutation(npt)
1010
+ for j, k in enumerate(ind):
1011
+ if cov_model_T1 is not None:
1012
+ # Simulate value at x[k] (= x[ind[j]]), conditionally to the previous ones
1013
+ # Solve the kriging system (for T1)
1014
+ try:
1015
+ w = np.linalg.solve(
1016
+ mat_T1[ind[:j], :][:, ind[:j]], # kriging matrix
1017
+ mat_T1[ind[:j], ind[j]], # second member
1018
+ )
1019
+ except:
1020
+ sim_ok = False
1021
+ break
1022
+
1023
+ # Mean (kriged) value at x[k]
1024
+ mu_T1_k = mean_T1[x_mean_T1_grid_ind[k]] + (v_T[ind[:j], 0] - mean_T1[x_mean_T1_grid_ind[ind[:j]]]).dot(w)
1025
+ # Standard deviation (of kriging) at x[k]
1026
+ std_T1_k = np.sqrt(np.maximum(0, cov0_T1 - np.dot(w, mat_T1[ind[:j], ind[j]])))
1027
+ # Draw value in N(mu_T1_k, std_T1_k^2)
1028
+ v_T[k, 0] = np.random.normal(loc=mu_T1_k, scale=std_T1_k)
1029
+ else:
1030
+ v_T[k, 0] = mean_T1[x_mean_T1_grid_ind[k]]
1031
+
1032
+ if not sim_ok:
1033
+ if verbose > 2:
1034
+ if logger:
1035
+ logger.info(f'{fname}: ... cannot solve kriging system (for T1, initialization)')
1036
+ else:
1037
+ print(f'{fname}: ... cannot solve kriging system (for T1, initialization)')
1038
+ continue
1039
+
1040
+ # Initialize: unconditional simulation of T2 at x (values in v_T[:,1])
1041
+ ind = np.random.permutation(npt)
1042
+ for j, k in enumerate(ind):
1043
+ if cov_model_T2 is not None:
1044
+ # Simulate value at x[k] (= x[ind[j]]), conditionally to the previous ones
1045
+ # Solve the kriging system (for T2)
1046
+ try:
1047
+ w = np.linalg.solve(
1048
+ mat_T2[ind[:j], :][:, ind[:j]], # kriging matrix
1049
+ mat_T2[ind[:j], ind[j]], # second member
1050
+ )
1051
+ except:
1052
+ sim_ok = False
1053
+ break
1054
+
1055
+ # Mean (kriged) value at x[k]
1056
+ mu_T2_k = mean_T2[x_mean_T2_grid_ind[k]] + (v_T[ind[:j], 1] - mean_T2[x_mean_T2_grid_ind[ind[:j]]]).dot(w)
1057
+ # Standard deviation (of kriging) at x[k]
1058
+ std_T2_k = np.sqrt(np.maximum(0, cov0_T2 - np.dot(w, mat_T2[ind[:j], ind[j]])))
1059
+ # Draw value in N(mu_T2_k, std_T2_k^2)
1060
+ v_T[k, 1] = np.random.normal(loc=mu_T2_k, scale=std_T2_k)
1061
+ else:
1062
+ v_T[k, 1] = mean_T2[x_mean_T2_grid_ind[k]]
1063
+
1064
+ if not sim_ok:
1065
+ if verbose > 2:
1066
+ if logger:
1067
+ logger.info(f'{fname}: ... cannot solve kriging system (for T2, initialization)')
1068
+ else:
1069
+ print(f'{fname}: ... cannot solve kriging system (for T2, initialization)')
1070
+ continue
1071
+
1072
+ # Update simulated values v_T at x using Metropolis-Hasting (MH) algorithm
1073
+ v_T_k_new = np.zeros(2)
1074
+ stop_mh = False
1075
+ for nit in range(mh_iter_max):
1076
+ #hd_ok = np.array([flag_value(v_T[k, 0], v_T[k, 1]) == v[k] for k in range(npt)])
1077
+ hd_ok = flag_value(v_T[:, 0], v_T[:, 1]) == v
1078
+ nhd_ok.append(np.sum(hd_ok))
1079
+ if nit >= mh_iter_min:
1080
+ if nhd_ok[-1] == npt:
1081
+ stop_mh = True
1082
+ break
1083
+ else:
1084
+ # Set acceptation probability for bad case
1085
+ p_accept = accept_init * np.power(1.0 - nit/mh_iter_min, accept_pow)
1086
+ if verbose > 3:
1087
+ if logger:
1088
+ logger.info(f'{fname}: ... sim {ireal+1} of {nreal}: MH iter {nit+1} of {mh_iter_min}, {mh_iter_max}...')
1089
+ else:
1090
+ print(f'{fname}: ... sim {ireal+1} of {nreal}: MH iter {nit+1} of {mh_iter_min}, {mh_iter_max}...')
1091
+ ind = np.random.permutation(npt)
1092
+ for k in ind:
1093
+ if nit >= mh_iter_min and hd_ok[k]:
1094
+ #print('skip')
1095
+ continue
1096
+ #
1097
+ # Sequence of indexes without k
1098
+ indmat = np.hstack((np.arange(k), np.arange(k+1, npt)))
1099
+ # Simulate possible new value v_T_new at x[k], conditionally to all the ohter ones
1100
+ #
1101
+ if cov_model_T1 is not None:
1102
+ # Solve the kriging system for T1
1103
+ try:
1104
+ w = np.linalg.solve(
1105
+ mat_T1[indmat, :][:, indmat], # kriging matrix
1106
+ mat_T1[indmat, k], # second member
1107
+ )
1108
+ except:
1109
+ sim_ok = False
1110
+ if verbose > 2:
1111
+ if logger:
1112
+ logger.info(f'{fname}: ... cannot solve kriging system (for T1)')
1113
+ else:
1114
+ print(f'{fname}: ... cannot solve kriging system (for T1)')
1115
+ break
1116
+ #
1117
+ # Mean (kriged) value at x[k]
1118
+ mu_T1_k = mean_T1[x_mean_T1_grid_ind[k]] + (v_T[indmat, 0] - mean_T1[x_mean_T1_grid_ind[indmat]]).dot(w)
1119
+ # Standard deviation (of kriging) at x[k]
1120
+ std_T1_k = np.sqrt(np.maximum(0, cov0_T1 - np.dot(w, mat_T1[indmat, k])))
1121
+ # Draw value in N(mu, std^2)
1122
+ v_T_k_new[0] = np.random.normal(loc=mu_T1_k, scale=std_T1_k)
1123
+ else:
1124
+ v_T_k_new[0] = mean_T1[x_mean_T1_grid_ind[k]]
1125
+ #
1126
+ # Solve the kriging system for T2
1127
+ if cov_model_T2 is not None:
1128
+ try:
1129
+ w = np.linalg.solve(
1130
+ mat_T2[indmat, :][:, indmat], # kriging matrix
1131
+ mat_T2[indmat, k], # second member
1132
+ )
1133
+ except:
1134
+ sim_ok = False
1135
+ if verbose > 2:
1136
+ if logger:
1137
+ logger.info(f'{fname}: ... cannot solve kriging system (for T2)')
1138
+ else:
1139
+ print(f'{fname}: ... cannot solve kriging system (for T2)')
1140
+ break
1141
+ #
1142
+ # Mean (kriged) value at x[k]
1143
+ mu_T2_k = mean_T2[x_mean_T2_grid_ind[k]] + (v_T[indmat, 1] - mean_T2[x_mean_T2_grid_ind[indmat]]).dot(w)
1144
+ # Standard deviation (of kriging) at x[k]
1145
+ std_T2_k = np.sqrt(np.maximum(0, cov0_T2 - np.dot(w, mat_T2[indmat, k])))
1146
+ # Draw value in N(mu, std^2)
1147
+ v_T_k_new[1] = np.random.normal(loc=mu_T2_k, scale=std_T2_k)
1148
+ else:
1149
+ v_T_k_new[1] = mean_T2[x_mean_T2_grid_ind[k]]
1150
+ #
1151
+ # Accept or not the new candidate
1152
+ if flag_value(v_T_k_new[0], v_T_k_new[1]) == v[k]:
1153
+ # Accept the new candidate
1154
+ v_T[k] = v_T_k_new
1155
+ elif nit < mh_iter_min and np.random.random() < p_accept:
1156
+ # Accept the new candidate
1157
+ v_T[k] = v_T_k_new
1158
+
1159
+ if not sim_ok:
1160
+ break
1161
+
1162
+ if not sim_ok:
1163
+ continue
1164
+
1165
+ if not stop_mh:
1166
+ hd_ok = flag_value(v_T[:, 0], v_T[:, 1]) == v
1167
+ nhd_ok.append(np.sum(hd_ok))
1168
+ if nhd_ok[-1] != npt:
1169
+ # sim_ok kept to True
1170
+ if verbose > 2:
1171
+ if logger:
1172
+ logger.info(f'{fname}: ... conditioning failed')
1173
+ else:
1174
+ print(f'{fname}: ... conditioning failed')
1175
+
1176
+ if ntry < ntry_max - 1 or not retrieve_real_anyway:
1177
+ continue
1178
+
1179
+ # Generate T1 conditional to (x, v_T[:, 0]) (one real)
1180
+ if cov_model_T1 is not None:
1181
+ try:
1182
+ sim_T1 = multiGaussian.multiGaussianRun(
1183
+ cov_model_T1, dimension, spacing, origin, x=x, v=v_T[:, 0],
1184
+ mode='simulation', algo=algo_T1, output_mode='array',
1185
+ **params_T1, nreal=1)
1186
+ except:
1187
+ sim_ok = False
1188
+ if verbose > 2:
1189
+ if logger:
1190
+ logger.info(f'{fname}: ... conditional simulation of T1 failed')
1191
+ else:
1192
+ print(f'{fname}: ... conditional simulation of T1 failed')
1193
+ continue
1194
+
1195
+ else:
1196
+ sim_T1 = params_T1['mean'].reshape(1,*dimension[::-1])
1197
+ # -> sim_T1: nd-array of shape
1198
+ # (1, dimension) (for T1 in 1D)
1199
+ # (1, dimension[1], dimension[0]) (for T1 in 2D)
1200
+ # (1, dimension[2], dimension[1], dimension[0]) (for T1 in 3D)
1201
+
1202
+ # Generate T2 conditional to (x, v_T[:, 1]) (one real)
1203
+ if cov_model_T2 is not None:
1204
+ try:
1205
+ sim_T2 = multiGaussian.multiGaussianRun(
1206
+ cov_model_T2, dimension, spacing, origin, x=x, v=v_T[:, 1],
1207
+ mode='simulation', algo=algo_T2, output_mode='array',
1208
+ **params_T2, nreal=1)
1209
+ except:
1210
+ sim_ok = False
1211
+ if verbose > 2:
1212
+ if logger:
1213
+ logger.info(f'{fname}: ... conditional simulation of T2 failed')
1214
+ else:
1215
+ print(f'{fname}: ... conditional simulation of T2 failed')
1216
+ continue
1217
+ else:
1218
+ sim_T2 = params_T2['mean'].reshape(1,*dimension[::-1])
1219
+ # -> sim_T2: nd-array of shape
1220
+ # (1, dimension) (for T2 in 1D)
1221
+ # (1, dimension[1], dimension[0]) (for T2 in 2D)
1222
+ # (1, dimension[2], dimension[1], dimension[0]) (for T2 in 3D)
1223
+
1224
+ # Generate Z (one real)
1225
+ if sim_ok:
1226
+ if x is not None:
1227
+ if nhd_ok[-1] != npt:
1228
+ if not retrieve_real_anyway:
1229
+ break
1230
+ else:
1231
+ if verbose > 0:
1232
+ if logger:
1233
+ logger.warning(f'{fname}: realization does not honoured all data, but retrieved anyway')
1234
+ else:
1235
+ print(f'{fname}: WARNING: realization does not honoured all data, but retrieved anyway')
1236
+ Z_real = flag_value(sim_T1[0], sim_T2[0])
1237
+ Z.append(Z_real)
1238
+ if full_output:
1239
+ T1.append(sim_T1[0])
1240
+ T2.append(sim_T2[0])
1241
+ n_cond_ok.append(np.asarray(nhd_ok))
1242
+ break
1243
+
1244
+ # Get Z
1245
+ if verbose > 0 and len(Z) < nreal:
1246
+ if logger:
1247
+ logger.warning(f'{fname}: some realization failed (missing)')
1248
+ else:
1249
+ print(f'{fname}: WARNING: some realization failed (missing)')
1250
+ Z = np.asarray(Z).reshape(len(Z), *np.atleast_1d(dimension)[::-1])
1251
+
1252
+ if full_output:
1253
+ T1 = np.asarray(T1).reshape(len(T1), *np.atleast_1d(dimension)[::-1])
1254
+ T2 = np.asarray(T2).reshape(len(T2), *np.atleast_1d(dimension)[::-1])
1255
+ return Z, T1, T2, n_cond_ok
1256
+ else:
1257
+ return Z
1258
+ # ----------------------------------------------------------------------------