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/randProcess.py ADDED
@@ -0,0 +1,1258 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # -------------------------------------------------------------------------
5
+ # Python module: 'randProcess.py'
6
+ # author: Julien Straubhaar
7
+ # date: may-2022
8
+ # -------------------------------------------------------------------------
9
+
10
+ """
11
+ Module for miscellaneous algorithms based on random processes.
12
+ """
13
+
14
+ import numpy as np
15
+ import scipy
16
+
17
+ # ============================================================================
18
+ class RandProcessError(Exception):
19
+ """
20
+ Custom exception related to `randProcess` module.
21
+ """
22
+ pass
23
+ # ============================================================================
24
+
25
+ # ----------------------------------------------------------------------------
26
+ def acceptRejectSampler(
27
+ n, xmin, xmax, f,
28
+ c=None,
29
+ g=None, g_rvs=None,
30
+ return_accept_ratio=False,
31
+ max_trial=None,
32
+ verbose=0,
33
+ show_progress=None,
34
+ opt_kwargs=None,
35
+ logger=None):
36
+ """
37
+ Generates samples according to a given density function.
38
+
39
+ This function generates `n` points (which can be multi-variate) in a
40
+ box-shape domain of lower bound(s) `xmin` and upper bound(s) `xmax`,
41
+ according to a density proportional to the function `f`, based on the
42
+ accept-reject algorithm.
43
+
44
+ Let `g_rvs` a function returning random variates sample(s) from an
45
+ instrumental distribution with density proportional to `g`, and `c` a
46
+ constant such that `c*g(x) >= f(x)` for any `x` (in `[xmin, xmax[` (can be
47
+ multi-dimensional), i.e. `x[i]` in `[xmin[i], xmax[i][` for any i). Let `fd`
48
+ (resp. `gd`) the density function proportional to `f` (resp. `g`); the
49
+ alogrithm consists in the following steps to generate samples `x ~ fd`:
50
+
51
+ - generate `y ~ gd` (using `g_rvs`)
52
+ - generate `u ~ Unif([0,1])`
53
+ - if `u < f(y)/c*g(y)`, then accept `x` (reject `x` otherwise)
54
+
55
+ The default instrumental distribution (if both `g` and `g_rvs` set to `None`)
56
+ is the uniform distribution (`g=1`). If the domain (`[xmin, xmax[`) is
57
+ infinite, the instrumental distribution (`g`, and `g_rvs`) and `c` must be
58
+ specified.
59
+
60
+ Parameters
61
+ ----------
62
+ n : int
63
+ number of sample points
64
+
65
+ xmin : float (or int), or array-like of shape(m,)
66
+ lower bound of each coordinate (m is the space dimension);
67
+ note: component(s) can be set to `-np.inf`
68
+
69
+ xmax : float (or int), or array-like of shape(m,)
70
+ upper bound of each coordinate (m is the space dimension)
71
+ note: component(s) can be set to `np.inf`
72
+
73
+ f : function (`callable`)
74
+ function proportional to target density, `f(x)` returns the target
75
+ density (times a constant) at `x`; with `x` array_like, the last
76
+ axis of `x` denotes the components of the points where the function is
77
+ evaluated
78
+
79
+ c : float (or int), optional
80
+ constant such that (not checked)) `c*g(x) >= f(x)` for all x in
81
+ [xmin, xmax[, with `g(x)=1` if `g` is not specified (`g=None`);
82
+ by default (`c=None`), the domain (`[xmin, xmax[`) must be finite and
83
+ `c` is automatically computed (using the function
84
+ `scipy.optimize.differential_evolution`)
85
+
86
+ g : function (callable), optional
87
+ function proportional to the instrumental density on `[xmin, xmax[`,
88
+ `g(x)` returns the instrumental density (times a constant) at `x`;
89
+ with `x` array_like, the last axis of `x` denotes the components of the
90
+ points where the function is evaluated;
91
+ by default (`g=None`), the domain (`[xmin, xmax[`) must be finite and
92
+ the instrumental distribution considered is uniform (constant
93
+ function `g=1` is considered)
94
+
95
+ g_rvs : function (`callable`), optional
96
+ function returning samples from the instrumental distribution with
97
+ density proportional to `g` on `[xmin, xmax[` (restricted on this
98
+ domain if needed); `g_rvs` must have the keyword arguments `size`
99
+ (the number of sample(s) to draw);
100
+ by default (`None`), uniform instrumental distribution is considered
101
+ (see `g`);
102
+ note: both `g` and `g_rvs` must be specified (or both set to `None`)
103
+
104
+ return_accept_ratio : bool, default: False
105
+ indicates if the acceptance ratio is returned
106
+
107
+ verbose : int, default: 0
108
+ verbose mode, higher implies more printing (info)
109
+
110
+ show_progress : bool, optional
111
+ deprecated, use `verbose` instead;
112
+
113
+ - if `show_progress=False`, `verbose` is set to 1 (overwritten)
114
+ - if `show_progress=True`, `verbose` is set to 2 (overwritten)
115
+ - if `show_progress=None` (default): not used
116
+
117
+ opt_kwargs : dict, optional
118
+ keyword arguments to be passed to `scipy.optimize.differential_evolution`
119
+ (do not set `'bounds'` key, bounds are set according to `xmin`, `xmax`)
120
+
121
+ logger : :class:`logging.Logger`, optional
122
+ logger (see package `logging`)
123
+ if specified, messages are written via `logger` (no print)
124
+
125
+ Returns
126
+ -------
127
+ x : 2d-array of shape (n, m), or 1d-array of shape (n,)
128
+ samples according to the target density proportional to `f on the
129
+ domain `[xmin, max[`, `x[i]` is the i-th sample point;
130
+ notes:
131
+
132
+ - if dimension m >= 2: `x` is a 2d-array of shape (n, m)
133
+ - if diemnsion is 1: `x` is an array of shape (n,)
134
+
135
+ t : float, optional
136
+ acceptance ratio, returned if `return_accept_ratio=True`, i.e.
137
+ `t = n/ntot` where `ntot` is the number of points draws in the
138
+ instrumental distribution
139
+ """
140
+ fname = 'acceptRejectSampler'
141
+
142
+ # Set verbose mode according to show_progress (if given)
143
+ if show_progress is not None:
144
+ if show_progress:
145
+ verbose = 2
146
+ else:
147
+ verbose = 1
148
+
149
+ xmin = np.atleast_1d(xmin)
150
+ xmax = np.atleast_1d(xmax)
151
+
152
+ if xmin.ndim != xmax.ndim or np.any(np.isnan(xmin)) or np.any(np.isnan(xmax)) or np.any(xmin >= xmax):
153
+ err_msg = f'{fname}: `xmin`, `xmax` invalid'
154
+ if logger: logger.error(err_msg)
155
+ raise RandProcessError(err_msg)
156
+
157
+ lx = xmax - xmin
158
+ dim = len(xmin)
159
+
160
+ if np.any(np.isinf(lx)):
161
+ dom_finite = False
162
+ else:
163
+ dom_finite = True
164
+
165
+ if n <= 0:
166
+ x = np.zeros((n, dim))
167
+ if dim == 1:
168
+ x = x.reshape(-1)
169
+ if return_accept_ratio:
170
+ return x, 1.0
171
+ else:
172
+ return x
173
+
174
+ # Set g, g_rvs
175
+ if (g is None and g_rvs is not None) or (g is not None and g_rvs is None):
176
+ err_msg = f'{fname}: `g` and `g_rvs` should both be specified'
177
+ if logger: logger.error(err_msg)
178
+ raise RandProcessError(err_msg)
179
+
180
+ if g is None:
181
+ if not dom_finite:
182
+ err_msg = f'{fname}: `g` and `g_rvs` must be specified when infinite domain is considered'
183
+ if logger: logger.error(err_msg)
184
+ raise RandProcessError(err_msg)
185
+
186
+ # g
187
+ g = lambda x: 1.0
188
+ # g_rvs
189
+ if dim == 1:
190
+ def g_rvs(size=1):
191
+ return xmin[0] + scipy.stats.uniform.rvs(size=size) * lx[0]
192
+ else:
193
+ def g_rvs(size=1):
194
+ return xmin + scipy.stats.uniform.rvs(size=(size,dim)) * lx
195
+
196
+ if c is None:
197
+ if not dom_finite:
198
+ err_msg = f'{fname}: `c` must be specified when infinite domain is considered'
199
+ if logger: logger.error(err_msg)
200
+ raise RandProcessError(err_msg)
201
+
202
+ h = lambda x: -f(x)/g(x)
203
+ # Compute the min of h(x) with the function scipy.optimize.differential_evolution
204
+ if opt_kwargs is None:
205
+ opt_kwargs = {}
206
+ res = scipy.optimize.differential_evolution(h, bounds=list(zip(xmin, xmax)), **opt_kwargs)
207
+ if not res.success:
208
+ err_msg = f'{fname}: `scipy.optimize.differential_evolution` failed {res.message})'
209
+ if logger: logger.error(err_msg)
210
+ raise RandProcessError(err_msg)
211
+
212
+ # -> res.x realizes the minimum of h(x)
213
+ # -> res.fun is the minimum of h(x)
214
+ # Set c such that c > f(x)/g(x) for all x in the domain
215
+ c = -res.fun + 1.e-3 # add small number to ensure the inequality
216
+
217
+ # Apply accept-reject algo
218
+ naccept = 0
219
+ ntot = 0
220
+ x = []
221
+ if max_trial is None:
222
+ max_trial = np.inf
223
+ if verbose > 1:
224
+ progress = 0
225
+ progressOld = -1
226
+ while naccept < n:
227
+ nn = n - naccept
228
+ ntot = ntot+nn
229
+ xnew = g_rvs(size=nn)
230
+ ind = np.all((xnew >= xmin, xnew < xmax), axis=0)
231
+ if dim > 1:
232
+ ind = np.all(ind, axis=-1)
233
+ xnew = xnew[ind]
234
+ nn = len(xnew)
235
+ if nn == 0:
236
+ continue
237
+ u = np.random.random(size=nn)
238
+ xnew = xnew[u < (f(xnew)/(c*g(xnew))).reshape(nn)]
239
+ nn = len(xnew)
240
+ if nn == 0:
241
+ continue
242
+ x.extend(xnew)
243
+ naccept = naccept+nn
244
+ if verbose > 1:
245
+ progress = int(100*naccept/n)
246
+ if progress > progressOld:
247
+ if logger:
248
+ logger.info(f'{fname}: A-R algo, progress: {progress:3d} %')
249
+ else:
250
+ print(f'{fname}: A-R algo, progress: {progress:3d} %')
251
+ progressOld = progress
252
+ if ntot >= max_trial:
253
+ break
254
+
255
+ x = np.asarray(x)
256
+
257
+ if naccept < n and verbose > 0:
258
+ if logger:
259
+ logger.warning(f'{fname}: sample size is only {naccept}! (increase `max_trial`)')
260
+ else:
261
+ print(f'{fname}: WARNING: sample size is only {naccept}! (increase `max_trial`)')
262
+
263
+ if return_accept_ratio:
264
+ accept_ratio = naccept/ntot
265
+ return x, accept_ratio
266
+ else:
267
+ return x
268
+ # ----------------------------------------------------------------------------
269
+
270
+ # ----------------------------------------------------------------------------
271
+ def poissonPointProcess(mu, xmin=0.0, xmax=1.0, ninterval=None, logger=None):
272
+ """
273
+ Generates random points following a Poisson point process.
274
+
275
+ Random points are in `[xmin, xmax[` (can be multi-dimensional).
276
+
277
+ Parameters
278
+ ----------
279
+ mu : function (`callable`), or ndarray of floats, or float
280
+ intensity of the Poisson process, i.e. the mean number of points per
281
+ unitary volume:
282
+
283
+ - if a function: (non-homogeneous Poisson point process) \
284
+ `mu(x)` returns the intensity at `x`; with `x` array_like, the last \
285
+ axis of `x` denotes the components of the points where the function is \
286
+ evaluated
287
+ - if a ndarray: (non-homogeneous Poisson point process) \
288
+ `mu[i_n, ..., i_0]` is the intensity on the box \
289
+ `[xmin[j]+i_j*(xmax[j]-xmin[j])/mu.shape[n-j]]`, j = 0,..., n
290
+ - if a float: homogeneous Poisson point process
291
+
292
+ xmin : float (or int), or array-like of shape(m,)
293
+ lower bound of each coordinate
294
+
295
+ xmax : float (or int), or array-like of shape(m,)
296
+ upper bound of each coordinate
297
+
298
+ ninterval : int, or array-like of ints of shape (m,), optional
299
+ used only if `mu` is a function (callable);
300
+ `ninterval` contains the number of interval(s) in which the domain
301
+ `[xmin, xmax[` is subdivided along each axis
302
+
303
+ logger : :class:`logging.Logger`, optional
304
+ logger (see package `logging`)
305
+ if specified, messages are written via `logger` (no print)
306
+
307
+ Returns
308
+ -------
309
+ pts : 2D array of shape (npts, m)
310
+ each row is a random point in the domain `[xmin, xmax[`, the number of
311
+ points (`npts`) follows a Poisson law of the given intensity (`mu`) and
312
+ m is the dimension of the domain
313
+ """
314
+ fname = 'poissonPointProcess'
315
+
316
+ xmin = np.atleast_1d(xmin)
317
+ xmax = np.atleast_1d(xmax)
318
+
319
+ if xmin.ndim != xmax.ndim or xmin.ndim != 1:
320
+ err_msg = f'{fname}: `xmin`, `xmax` not valid (dimension or shape)'
321
+ if logger: logger.error(err_msg)
322
+ raise RandProcessError(err_msg)
323
+
324
+ if np.any(xmin >= xmax):
325
+ err_msg = f'{fname}: `xmin`, `xmax` not valid ((component of) xmin less than or equal to xmax)'
326
+ if logger: logger.error(err_msg)
327
+ raise RandProcessError(err_msg)
328
+
329
+ # dimension
330
+ dim = len(xmin)
331
+
332
+ if callable(mu):
333
+ if ninterval is None:
334
+ err_msg = f'{fname}: `ninterval` must be specified when a function is passed for the intensity (`mu`)'
335
+ if logger: logger.error(err_msg)
336
+ raise RandProcessError(err_msg)
337
+
338
+ ninterval = np.asarray(ninterval, dtype=int) # possibly 0-dimensional
339
+ if ninterval.size == 1:
340
+ ninterval = ninterval.flat[0] * np.ones(dim)
341
+ elif ninterval.size != dim:
342
+ err_msg = f'{fname}: `ninterval` does not have an acceptable size'
343
+ if logger: logger.error(err_msg)
344
+ raise RandProcessError(err_msg)
345
+
346
+ if np.any(ninterval < 1):
347
+ err_msg = f'{fname}: `ninterval` has negative or zero value'
348
+ if logger: logger.error(err_msg)
349
+ raise RandProcessError(err_msg)
350
+
351
+ elif isinstance(mu, np.ndarray):
352
+ if mu.ndim != dim:
353
+ err_msg = f'{fname}: inconsistent number of dimension for the ndarray `mu`'
354
+ if logger: logger.error(err_msg)
355
+ raise RandProcessError(err_msg)
356
+
357
+ ninterval = mu.shape[::-1]
358
+
359
+ else: # mu is a float
360
+ mu = np.atleast_1d(mu)
361
+ for i in range(dim-1):
362
+ mu = mu[np.newaxis,...]
363
+ # mu is a ndarray with dim dimension of shape (1,...,1) --> grid with one cell
364
+
365
+ ninterval = mu.shape
366
+
367
+ # spacing of the grid cell along each axis
368
+ spa = [(b-a)/n for a, b, n in zip(xmin, xmax, ninterval)]
369
+ # cell volume
370
+ vol_cell = np.prod(spa)
371
+ # cell center along each axis
372
+ x_cell_center = [a + (0.5 + np.arange(n)) * s for a, n, s in zip(xmin, ninterval, spa)]
373
+ # center of each grid cell
374
+ xx_cell_center = np.meshgrid(*x_cell_center[::-1], indexing='ij')[::-1]
375
+ xx_cell_center = np.array([xx.reshape(-1) for xx in xx_cell_center]).T # shape: ncell x dim
376
+
377
+ # Poisson parameter (intensity) for each grid cell
378
+ if callable(mu):
379
+ mu_cell = mu(xx_cell_center)*vol_cell
380
+ else:
381
+ mu_cell = mu.reshape(-1) * vol_cell
382
+
383
+ # Generate number of points in each grid cell (Poisson)
384
+ npts_cell = np.array([scipy.stats.poisson.rvs(m) for m in mu_cell])
385
+
386
+ # Generate random points (uniformly) in each cell
387
+ pts = np.array([np.hstack(
388
+ [a + spa[i] * (np.random.random(size=npts) - 0.5) for a, npts in zip(xx_cell_center[:,i], npts_cell)]
389
+ ) for i in range(dim)]).T
390
+
391
+ return pts
392
+ # ----------------------------------------------------------------------------
393
+
394
+ # ----------------------------------------------------------------------------
395
+ def chentsov1D(
396
+ n_mean,
397
+ dimension,
398
+ spacing=1.0,
399
+ origin=0.0,
400
+ direction_origin=None,
401
+ p_min=None,
402
+ p_max=None,
403
+ nreal=1,
404
+ verbose=0,
405
+ logger=None):
406
+ """
407
+ Generates a Chentsov's simulation in 1D.
408
+
409
+ The domain of simulation is `[xmin, xmax]`, with `nx` cells along x axis,
410
+ each cell having a length of `dx`, the left side is the origin:
411
+
412
+ - along x axis:
413
+ - `nx = dimension`
414
+ - `dx = spacing`
415
+ - `xmin = origin`
416
+ - `xmax = origin + nx*dx`
417
+
418
+ The simulation consists in:
419
+
420
+ 1. Drawing random hyper-plane (i.e. points in 1D) in the space
421
+ [`p_min`, `p_max`] following a Poisson point process with intensity:
422
+
423
+ * mu = `n_mean` / vol([`p_min`, `p_max`]);
424
+
425
+ the points are given in the parametrized form: p;
426
+ then, for each point p, and with direction_origin = x0
427
+ (the center of the simulation domain by default), the hyper-plane
428
+ (point)
429
+
430
+ * {x : x-x0 = p} (i.e. the point x0 + p)
431
+
432
+ is considered
433
+
434
+ 2. Each hyper-plane (point x0+p) splits the space (R) in two parts
435
+ (two half lines); the value = +1 is set to one part (chosen
436
+ randomly) and the value -1 is set to the other part. Denoting V_i
437
+ the value over the space (R) associated to the i-th hyper-plane
438
+ (point), the value assigned to a grid cell of center x is set to
439
+
440
+ * Z(x) = 0.5 * sum_{i} (V_i(x) - V_i(x0))
441
+
442
+ It corresponds to the number of hyper-planes (points) cut by the
443
+ segment [x0, x].
444
+
445
+ Parameters
446
+ ----------
447
+ n_mean : float
448
+ mean number of hyper-plane drawn (via Poisson process)
449
+
450
+ dimension : int
451
+ `dimension=nx`, number of cells in the 1D simulation grid
452
+
453
+ spacing : float, default: 1.0
454
+ `spacing=dx`, cell size
455
+
456
+ origin : float, default: 0.0
457
+ `origin=ox`, origin of the 1D simulation grid (left border)
458
+
459
+ direction_origin : float, optional
460
+ origin from which the "points" are drawn in the Poisson process
461
+ (see above);
462
+ by default (`None`): the center of the 1D simulation domain is used
463
+
464
+ p_min : float, optional
465
+ minimal value for p (see above);
466
+ by default (`None`): `p_min` is set automatically to "minus half of the
467
+ length of the 1D simulation domain"
468
+
469
+ p_max : float, optional
470
+ maximal value for p (see above);
471
+ by default (`None`): `p_max` is set automatically to "plus half of the
472
+ length of the 1D simulation domain
473
+
474
+ nreal : int, default: 1
475
+ number of realization(s)
476
+
477
+ verbose : int, default: 0
478
+ verbose mode, higher implies more printing (info)
479
+
480
+ logger : :class:`logging.Logger`, optional
481
+ logger (see package `logging`)
482
+ if specified, messages are written via `logger` (no print)
483
+
484
+ Returns
485
+ -------
486
+ sim : 2D array of floats of shape (nreal, nx)
487
+ simulations of Z (see above);
488
+ `sim[i, j]`: value of the i-th realisation at grid cell of index j
489
+
490
+ n : 1D array of shape (nreal,)
491
+ numbers of hyper-planes (points) drawn, `n[i]` is the number of
492
+ hyper-planes for the i-th realization
493
+ """
494
+ fname = 'chentsov1D'
495
+
496
+ # Number of realization(s)
497
+ nreal = int(nreal) # cast to int if needed
498
+
499
+ if nreal <= 0:
500
+ if verbose > 0:
501
+ if logger:
502
+ logger.warning(f'{fname}: `nreal` <= 0: `None`, `None` is returned')
503
+ else:
504
+ print(f'{fname}: WARNING: `nreal` <= 0: `None`, `None` is returned')
505
+ return None, None
506
+
507
+ nx = dimension
508
+ dx = spacing
509
+ ox = origin
510
+
511
+ if direction_origin is None:
512
+ direction_origin = ox+0.5*nx*dx
513
+
514
+ if p_min is None or p_max is None:
515
+ d = 0.5*nx*dx
516
+ if p_min is None:
517
+ p_min = -d
518
+ if p_max is None:
519
+ p_max = d
520
+
521
+ if p_min >= p_max:
522
+ err_msg = f'{fname}: `p_min` is greater than or equal to `p_max`'
523
+ if logger: logger.error(err_msg)
524
+ raise RandProcessError(err_msg)
525
+
526
+ # center of each grid cell of the simulation domain
527
+ xc = ox + (0.5 + np.arange(nx)) * dx
528
+
529
+ # Volume of [p_min, p_max]
530
+ vol_poisson_domain = (p_max - p_min)
531
+
532
+ # Set intensity of Poisson process
533
+ mu = n_mean / vol_poisson_domain
534
+
535
+ # Initialization
536
+ z = np.zeros((nreal, nx))
537
+ n = np.zeros(nreal, dtype='int')
538
+
539
+ for k in range(nreal):
540
+ # Draw points via Poisson process
541
+ try:
542
+ pts = poissonPointProcess(mu, p_min, p_max, logger=logger)
543
+ except Exception as exc:
544
+ err_msg = f'{fname}: Poisson point process failed'
545
+ if logger: logger.error(err_msg)
546
+ raise RandProcessError(err_msg) from exc
547
+
548
+ n[k] = pts.shape[0]
549
+
550
+ # Defines values of Z in each grid cell
551
+ random_sign = (-1)**np.random.randint(2, size=n[k])
552
+ for i in range(n[k]):
553
+ z[k] = z[k] + (np.sign((xc-direction_origin)-pts[i])+np.sign(pts[i]))*random_sign[i]
554
+
555
+ z = 0.5*z
556
+
557
+ return z, n
558
+ # ----------------------------------------------------------------------------
559
+
560
+ # ----------------------------------------------------------------------------
561
+ def chentsov2D(
562
+ n_mean,
563
+ dimension,
564
+ spacing=(1.0, 1.0),
565
+ origin=(0.0, 0.0),
566
+ direction_origin=None,
567
+ phi_min=0.0,
568
+ phi_max=np.pi,
569
+ p_min=None,
570
+ p_max=None,
571
+ nreal=1,
572
+ verbose=0,
573
+ logger=None):
574
+ """
575
+ Generates a Chentsov's simulation in 2D.
576
+
577
+ The domain of simulation is `[xmin, xmax]` x `[ymin x ymax]`,
578
+ with `nx` and `ny` cells along x axis and y axis respectively, each cell
579
+ being a box of size `dx` x `dy`, the lower-left corner is the origin:
580
+
581
+ - along x axis:
582
+ - `nx = dimension[0]`
583
+ - `dx = spacing[0]`
584
+ - `xmin = origin[0]`
585
+ - `xmax = origin[0] + nx*dx`
586
+
587
+ - along y axis:
588
+ - `ny = dimension[1]`
589
+ - `dy = spacing[1]`
590
+ - `ymin = origin[1]`
591
+ - `ymax = origin[1] + ny*dy`
592
+
593
+ The simulation consists in:
594
+
595
+ 1. Drawing random hyper-plane (i.e. lines in 2D):
596
+ considering the space S x [`p_min`, `p_max`], where S is a part of
597
+ the circle of radius 1 in the plane (by default: half circle),
598
+ parametrized via
599
+
600
+ * phi -> (cos(phi), sin(phi)), with phi in [`phi_min`, `phi_max`],
601
+
602
+ some points are drawn randomly in S x [`p_min`, `p_max`] following a
603
+ Poisson point process with intensity
604
+
605
+ * mu = `n_mean` / vol(S x [`p_min`, `p_max`])
606
+
607
+ the points are given in the parametrized form: (phi, p);
608
+ then, for each point (phi, p), and with direction_origin = (x0, y0)
609
+ (the center of the simulation domain by default), the hyper-plane
610
+ (line)
611
+
612
+ * {(x, y) : dot([x-x0, y-y0], [cos(phi), sin(phi)]) = p}
613
+
614
+ (i.e. point (x, y) s.t. the orthogonal projection of (x-x0, y-y0)
615
+ onto the direction (cos(phi), sin(phi)) is equal to p) is considered
616
+
617
+ 2. Each hyper-plane (line) splits the space (R^2) in two parts (two half
618
+ planes); the value = +1 is set to one part (chosen randomly) and the
619
+ value -1 is set to the other part. Denoting V_i the value over the
620
+ space (R^2) associated to the i-th hyper-plane (line), the value
621
+ assigned to a grid cell of center (x, y) is set to
622
+
623
+ * Z(x, y) = 0.5 * sum_{i} (V_i(x, y) - V_i(x0, y0))
624
+
625
+ It corresponds to the number of hyper-planes cut by the segment
626
+ [(x0, y0), (x, y)].
627
+
628
+ Parameters
629
+ ----------
630
+ n_mean : float
631
+ mean number of hyper-plane drawn (via Poisson process)
632
+
633
+ dimension : 2-tuple of ints
634
+ `dimension=(nx, ny)`, number of cells in the 2D simulation grid along
635
+ each axis
636
+
637
+ spacing : 2-tuple of floats, default: (1.0, 1.0)
638
+ `spacing=(dx, dy)`, cell size along each axis
639
+
640
+ origin : 2-tuple of floats, default: (0.0, 0.0)
641
+ `origin=(ox, oy)`, origin of the 2D simulation grid (lower-left corner)
642
+
643
+ direction_origin : sequence of 2 floats, optional
644
+ origin from which the directions are drawn in the Poisson process
645
+ (see above);
646
+ by default (`None`): the center of the 2D simulation domain is used
647
+
648
+ phi_min : float, default: 0.0
649
+ minimal angle for the parametrization of S (part of circle) defining
650
+ the direction (see above)
651
+
652
+ phi_max : float, default: `numpy.pi`
653
+ maximal angle for the parametrization of S (part of circle) defining
654
+ the direction (see above)
655
+
656
+ p_min : float, optional
657
+ minimal value for orthogonal projection (see above);
658
+ by default (`None`): `p_min` is set automatically to "minus half of the
659
+ diagonal of the 2D simulation domain"
660
+
661
+ p_max : float, optional
662
+ maximal value for orthogonal projection (see above);
663
+ by default (`None`): `p_min` is set automatically to "plus half of the
664
+ diagonal of the 2D simulation domain"
665
+
666
+ nreal : int, default: 1
667
+ number of realization(s)
668
+
669
+ verbose : int, default: 0
670
+ verbose mode, higher implies more printing (info)
671
+
672
+ logger : :class:`logging.Logger`, optional
673
+ logger (see package `logging`)
674
+ if specified, messages are written via `logger` (no print)
675
+
676
+ Returns
677
+ -------
678
+ sim : 3D array of floats of shape (nreal, ny, nx)
679
+ simulations of Z (see above);
680
+ `sim[i, iy, ix]`: value of the i-th realisation at grid cell of index ix
681
+ (resp. iy) along x (resp. y) axis
682
+
683
+ n : 1D array of shape (nreal,)
684
+ numbers of hyper-planes (lines) drawn, `n[i]` is the number of
685
+ hyper-planes for the i-th realization
686
+ """
687
+ fname = 'chentsov2D'
688
+
689
+ # Number of realization(s)
690
+ nreal = int(nreal) # cast to int if needed
691
+
692
+ if nreal <= 0:
693
+ if verbose > 0:
694
+ if logger:
695
+ logger.warning(f'{fname}: `nreal` <= 0: `None`, `None` is returned')
696
+ else:
697
+ print(f'{fname}: WARNING: `nreal` <= 0: `None`, `None` is returned')
698
+ return None, None
699
+
700
+ nx, ny = dimension
701
+ dx, dy = spacing
702
+ ox, oy = origin
703
+
704
+ if direction_origin is None:
705
+ direction_origin = [ox+0.5*nx*dx, oy+0.5*ny*dy]
706
+
707
+ if p_min is None or p_max is None:
708
+ d = 0.5*np.sqrt((nx*dx)**2+(ny*dy)**2)
709
+ if p_min is None:
710
+ p_min = -d
711
+ if p_max is None:
712
+ p_max = d
713
+
714
+ if p_min >= p_max:
715
+ err_msg = f'{fname}: `p_min` is greater than or equal to `p_max`'
716
+ if logger: logger.error(err_msg)
717
+ raise RandProcessError(err_msg)
718
+
719
+ if phi_min >= phi_max:
720
+ err_msg = f'{fname}: `phi_min` is greater than or equal to `phi_max`'
721
+ if logger: logger.error(err_msg)
722
+ raise RandProcessError(err_msg)
723
+
724
+ # center of each grid cell of the simulation domain
725
+ yc, xc = np.meshgrid(oy + (0.5 + np.arange(ny)) * dy, ox + (0.5 + np.arange(nx)) * dx, indexing='ij')
726
+ xyc = np.array([xc.reshape(-1), yc.reshape(-1)]).T # shape: ncell x 2
727
+
728
+ # Volume of S x [p_min, p_max], (S being parametrized by phi in [phi_min, phi_max])
729
+ vol_poisson_domain = (phi_max - phi_min) * (p_max - p_min)
730
+
731
+ # Defines lines by random points in [phi_min, phi_max] x [p_min, p_max]
732
+ # if callable(mu):
733
+ # def mu_intensity(x):
734
+ # return mu(x) / vol_poisson_domain
735
+ # else:
736
+ # mu_intensity = mu / vol_poisson_domain
737
+
738
+ # Set intensity of Poisson process
739
+ mu = n_mean / vol_poisson_domain
740
+
741
+ # Initialization
742
+ z = np.zeros((nreal, nx*ny))
743
+ n = np.zeros(nreal, dtype='int')
744
+
745
+ for k in range(nreal):
746
+ # Draw points via Poisson process
747
+ try:
748
+ pts = poissonPointProcess(mu, [phi_min, p_min], [phi_max, p_max], logger=logger)
749
+ except Exception as exc:
750
+ err_msg = f'{fname}: Poisson point process failed'
751
+ if logger: logger.error(err_msg)
752
+ raise RandProcessError(err_msg) from exc
753
+
754
+ n[k] = pts.shape[0]
755
+
756
+ # Defines values of Z in each grid cell
757
+ random_sign = (-1)**np.random.randint(2, size=n[k])
758
+ # Equivalent method below (4/ is better!)
759
+ # 1/
760
+ # vp = np.sum([np.sign((xyc-direction_origin).dot(np.array([np.cos(a), np.sin(a)]))-p)*rs for a, p, rs in zip(pts[:,0], pts[:,1], random_sign)], axis=0)
761
+ # v0 = np.sum([np.sign(-p)*rs for p, rs in zip(pts[:,1], random_sign)])
762
+ # z = 0.5 *(vp - v0)
763
+ # 2/
764
+ # z = 0.5*np.sum([(np.sign((xyc-direction_origin).dot(np.array([np.cos(a), np.sin(a)]))-p)+np.sign(p))*rs for a, p, rs in zip(pts[:,0], pts[:,1], random_sign)], axis=0)
765
+ # 3/
766
+ # z = 0.5*np.sum([(np.sign((xyc-direction_origin).dot(np.array([np.cos(pts[i,0]), np.sin(pts[i,0])]))-pts[i,1])+np.sign(pts[i,1]))*random_sign[i] for i in range(n[k])], axis=0)
767
+ # 4/
768
+ for i in range(n[k]):
769
+ z[k] = z[k] + (np.sign((xyc-direction_origin).dot(np.array([np.cos(pts[i,0]), np.sin(pts[i,0])]))-pts[i,1])+np.sign(pts[i,1]))*random_sign[i]
770
+
771
+ z = 0.5*z
772
+
773
+ return z.reshape(nreal, ny, nx), n
774
+ # ----------------------------------------------------------------------------
775
+
776
+ # ----------------------------------------------------------------------------
777
+ def chentsov3D(
778
+ n_mean,
779
+ dimension,
780
+ spacing=(1.0, 1.0, 1.0),
781
+ origin=(0.0, 0.0, 0.0),
782
+ direction_origin=None,
783
+ phi_min=0.0,
784
+ phi_max=2.0*np.pi,
785
+ theta_min=0.0,
786
+ theta_max=0.5*np.pi,
787
+ p_min=None,
788
+ p_max=None,
789
+ ninterval_theta=100,
790
+ nreal=1,
791
+ verbose=0,
792
+ logger=None):
793
+ """
794
+ Generates a Chentsov's simulation in 3D.
795
+
796
+ The domain of simulation is
797
+ `[xmin, xmax]` x `[ymin x ymax]` x `[zmin x zmax]`,
798
+ with `nx`, `ny`, `nz` cells along x axis, y axis, z axis respectively, each
799
+ cell being a box of size `dx` x `dy` x `dy`, the bottom-lower-left corner is
800
+ the origin:
801
+
802
+ - along x axis:
803
+ - `nx = dimension[0]`
804
+ - `dx = spacing[0]`
805
+ - `xmin = origin[0]`
806
+ - `xmax = origin[0] + nx*dx`
807
+
808
+ - along y axis:
809
+ - `ny = dimension[1]`
810
+ - `dy = spacing[1]`
811
+ - `ymin = origin[1]`
812
+ - `ymax = origin[1] + ny*dy`
813
+
814
+ - along z axis:
815
+ - `nz = dimension[0]`
816
+ - `dz = spacing[0]`
817
+ - `zmin = origin[0]`
818
+ - `zmax = origin[0] + nz*dz`.
819
+
820
+ The simulation consists in:
821
+
822
+ 1. Drawing random hyper-plane (i.e. planes in 3D):
823
+ considering the space S x [`p_min`, `p_max`], where S is a part of
824
+ the sphere of radius 1 in the 3D space (by default: half sphere),
825
+ parametrized via
826
+
827
+ * (phi, theta) -> (cos(phi)cos(theta), sin(phi)cos(theta), sin(theta)), \
828
+ with phi in [`phi_min`, `phi_max`], theta in [`theta_min`, `theta_max`]
829
+
830
+ some points are drawn randomly in S x [`p_min`, `p_max`] following a
831
+ Poisson point process with intensity
832
+
833
+ * mu = `n_mean` / vol(S x [`p_min`, `p_max`]);
834
+
835
+ the points are given in the parametrized form: (phi, theta, p);
836
+ then, for each point (phi, theta, p), and with
837
+ direction_origin = (x0, y0, z0) (the center of the simulation domain
838
+ by default), the hyper-plane (plane)
839
+
840
+ * {(x, y, z) : dot([x-x0, y-y0, z-z0], [cos(phi)cos(theta), sin(phi)cos(theta), sin(theta)]) = p}
841
+
842
+ (i.e. point (x, y, z) s.t. the orthogonal projection of
843
+ (x-x0, y-y0, z-z0) onto the direction
844
+ (cos(phi)cos(theta), sin(phi)cos(theta), sin(theta)) is equal to p)
845
+ is considered;
846
+
847
+ 2. Each hyper-plane (plane) splits the space (R^3) in two parts;
848
+ the value = +1 is set to one part (chosen randomly) and the value -1
849
+ is set to the other part. Denoting V_i the value over the space (R^3)
850
+ associated to the i-th hyper-plane (plane), the value assigned to a
851
+ grid cell of center (x, y) is set to
852
+
853
+ * Z(x, y) = 0.5 * sum_{i} (V_i(x, y) - V_i(x0, y0))
854
+
855
+ It corresponds to the number of hyper-planes (planes) cut by the
856
+ segment [(x0, y0, z0), (x, y, z)].
857
+
858
+ Parameters
859
+ ----------
860
+ n_mean : float
861
+ mean number of hyper-plane drawn (via Poisson process)
862
+
863
+ dimension : 3-tuple of ints
864
+ `dimension=(nx, ny, nz)`, number of cells in the 3D simulation grid along
865
+ each axis
866
+
867
+ spacing : 3-tuple of floats, default: (1.0,1.0, 1.0)
868
+ `spacing=(dx, dy, dz)`, cell size along each axis
869
+
870
+ origin : 3-tuple of floats, default: (0.0, 0.0, 0.0)
871
+ `origin=(ox, oy, oz)`, origin of the 3D simulation grid (bottom-lower-left
872
+ corner)
873
+
874
+ direction_origin : sequence of 3 floats, optional
875
+ origin from which the directions are drawn in the Poisson process
876
+ (see above);
877
+ by default (`None`): the center of the 3D simulation domain is used
878
+
879
+ phi_min : float, default: 0.0
880
+ minimal angle for the parametrization of S (part of circle) defining
881
+ the direction (see above)
882
+
883
+ phi_max : float, default: `numpy.pi`
884
+ maximal angle for the parametrization of S (part of circle) defining
885
+ the direction (see above)
886
+
887
+ theta_min : float, default: 0.0
888
+ minimal angle for the parametrization of S (part of circle) defining
889
+ the direction (see above)
890
+
891
+ theta_max : float, default: `0.5*numpy.pi`
892
+ maximal angle for the parametrization of S (part of circle) defining
893
+ the direction (see above)
894
+
895
+ p_min : float, optional
896
+ minimal value for orthogonal projection (see above);
897
+ by default (`None`): `p_min` is set automatically to "minus half of the
898
+ diagonal of the 3D simulation domain"
899
+
900
+ p_max : float, optional
901
+ maximal value for orthogonal projection (see above);
902
+ by default (`None`): `p_min` is set automatically to "plus half of the
903
+ diagonal of the 3D simulation domain"
904
+
905
+ ninterval_theta : int, default: 100
906
+ number of sub-intervals in which the interval `[theta_min, theta_max]`
907
+ is subdivided for applying the Poisson process
908
+
909
+ nreal : int, default: 1
910
+ number of realization(s)
911
+
912
+ verbose : int, default: 0
913
+ verbose mode, higher implies more printing (info)
914
+
915
+ logger : :class:`logging.Logger`, optional
916
+ logger (see package `logging`)
917
+ if specified, messages are written via `logger` (no print)
918
+
919
+ Returns
920
+ -------
921
+ sim : 4D array of floats of shape (nreal, nz, ny, nx)
922
+ simulations of Z (see above);
923
+ `sim[i, iz, iy, ix]`: value of the i-th realisation at grid cell of
924
+ index ix (resp. iy, iz) along x (resp. y, z) axis
925
+
926
+ n : 1D array of shape (nreal,)
927
+ numbers of hyper-planes (planes) drawn, `n[i]` is the number of
928
+ hyper-planes for the i-th realization
929
+ """
930
+ fname = 'chentsov3D'
931
+
932
+ # Number of realization(s)
933
+ nreal = int(nreal) # cast to int if needed
934
+
935
+ if nreal <= 0:
936
+ if verbose > 0:
937
+ if logger:
938
+ logger.warning(f'{fname}: `nreal` <= 0: `None`, `None` is returned')
939
+ else:
940
+ print(f'{fname}: WARNING: `nreal` <= 0: `None`, `None` is returned')
941
+ return None, None
942
+
943
+ nx, ny, nz = dimension
944
+ dx, dy, dz = spacing
945
+ ox, oy, oz = origin
946
+
947
+ if direction_origin is None:
948
+ direction_origin = [ox+0.5*nx*dx, oy+0.5*ny*dy, oz+0.5*nz*dz]
949
+
950
+ if p_min is None or p_max is None:
951
+ d = 0.5*np.sqrt((nx*dx)**2+(ny*dy)**2+(nz*dz)**2)
952
+ if p_min is None:
953
+ p_min = -d
954
+ if p_max is None:
955
+ p_max = d
956
+
957
+ if p_min >= p_max:
958
+ err_msg = f'{fname}: `p_min` is greater than or equal to `p_max`'
959
+ if logger: logger.error(err_msg)
960
+ raise RandProcessError(err_msg)
961
+
962
+ if phi_min >= phi_max:
963
+ err_msg = f'{fname}: `phi_min` is greater than or equal to `phi_max`'
964
+ if logger: logger.error(err_msg)
965
+ raise RandProcessError(err_msg)
966
+
967
+ if theta_min >= theta_max:
968
+ err_msg = f'{fname}: `theta_min` is greater than or equal to `theta_max`'
969
+ if logger: logger.error(err_msg)
970
+ raise RandProcessError(err_msg)
971
+
972
+ # center of each grid cell of the simulation domain
973
+ zc, yc, xc = np.meshgrid(oz + (0.5 + np.arange(nz)) * dz, oy + (0.5 + np.arange(ny)) * dy, ox + (0.5 + np.arange(nx)) * dx, indexing='ij')
974
+ xyzc = np.array([xc.reshape(-1), yc.reshape(-1), zc.reshape(-1)]).T # shape: ncell x 3
975
+
976
+ # Volume of S x [p_min, p_max], (S being parametrized by phi in [phi_min, phi_max], and theta in [theta_min, theta_max])
977
+ vol_poisson_domain = (phi_max - phi_min) * (np.sin(theta_max) - np.sin(theta_min)) * (p_max - p_min)
978
+
979
+ # Set intensity of Poisson process as a function accounting for jacobian of the parametrization of S
980
+ def mu(x):
981
+ return n_mean * np.cos(x[:, 1])/ vol_poisson_domain # x = (phi, theta), cos(x[:, 1] = cos(theta)
982
+
983
+ # Initialization
984
+ z = np.zeros((nreal, nx*ny*nz))
985
+ n = np.zeros(nreal, dtype='int')
986
+
987
+ for k in range(nreal):
988
+ # Draw points via Poisson process
989
+ try:
990
+ pts = poissonPointProcess(mu, [phi_min, theta_min, p_min], [phi_max, theta_max, p_max], ninterval=[1, ninterval_theta, 1], logger=logger)
991
+ except Exception as exc:
992
+ err_msg = f'{fname}: Poisson point process failed'
993
+ if logger: logger.error(err_msg)
994
+ raise RandProcessError(err_msg) from exc
995
+
996
+ n[k] = pts.shape[0]
997
+
998
+ # Defines values of Z in each grid cell
999
+ random_sign = (-1)**np.random.randint(2, size=n[k])
1000
+ # 4/
1001
+ for i in range(n[k]):
1002
+ z[k] = z[k] + (np.sign((xyzc-direction_origin).dot(np.array([np.cos(pts[i,0])*np.cos(pts[i,1]), np.sin(pts[i,0])*np.cos(pts[i,1]), np.sin(pts[i,1])]))-pts[i,2])+np.sign(pts[i,2]))*random_sign[i]
1003
+ z = 0.5*z
1004
+
1005
+ return z.reshape(nreal, nz, ny, nx), n
1006
+ # ----------------------------------------------------------------------------
1007
+
1008
+ if __name__ == "__main__":
1009
+ print("Module 'geone.randProcess'.")
1010
+
1011
+ ##### OLD BELOW #####
1012
+ # # ----------------------------------------------------------------------------
1013
+ # def acceptRejectSampler(n, xmin, xmax, f, c=None, g=None, g_rvs=None,
1014
+ # return_accept_ratio=False,
1015
+ # max_trial=None, show_progress=False):
1016
+ # """
1017
+ # Generates samples according to a given density function.
1018
+ #
1019
+ # This function generates `n` points in a box-shape domain of lower bound(s)
1020
+ # `xmin` and upper bound(s) `xmax`, according to a density proportional to the
1021
+ # function `f` are generated, based on the accept-reject algorithm.
1022
+ #
1023
+ # Let `g_rvs` a function returning random variates sample(s) from an
1024
+ # instrumental distribution with density proportional to `g`, and `c` a
1025
+ # constant such that `c*g(x) >= f(x)` for any `x` (in `[xmin, xmax[` (can be
1026
+ # multi-dimensional), i.e. `x[i]` in `[xmin[i], xmax[i][` for any i). Let `fd`
1027
+ # (resp. `gd`) the density function proportional to `f` (resp. `g`); the
1028
+ # alogrithm consists in the following steps to generate samples `x ~ fd`:
1029
+ # - generate `y ~ gd` (using `g_rvs`)
1030
+ # - generate `u ~ Unif([0,1])`
1031
+ # - if `u < f(y)/c*g(y)`, then accept `x` (reject `x` otherwise)
1032
+ #
1033
+ # If the instrumental distribution is not specified (both `g` and `g_rvs` set
1034
+ # to `None`), then:
1035
+ # - the uniform distribution if the domain `[xmin, xmax[` is finite
1036
+ # - the multi-normal distribution, centered at a point maximizing `f`, with
1037
+ # a variance 1 (covariance matrix I), if the domain `[xmin, xmax[` is infinite
1038
+ #
1039
+ # Parameters
1040
+ # ----------
1041
+ # n : int
1042
+ # number of sample points
1043
+ # xmin : float (or int), or array-like of shape(m,)
1044
+ # lower bound of each coordinate (m is the space dimension);
1045
+ # note: component(s) can be set to `-np.inf`
1046
+ # xmax : float (or int), or array-like of shape(m,)
1047
+ # upper bound of each coordinate (m is the space dimension)
1048
+ # note: component(s) can be set to `np.inf`
1049
+ # f : function (callable)
1050
+ # function proportional to target density, `f(x)` returns the target
1051
+ # density (times a constant) at `x`; with `x` array_like, the last
1052
+ # axis of `x` denotes the components of the points where the function is
1053
+ # evaluated
1054
+ # c : float (or int), optional
1055
+ # constant such that (not checked)) `c*g(x) >= f(x)` for all x in
1056
+ # [xmin, xmax[, with `g(x)=1` if `g` is not specified (`g=None`);
1057
+ # by default (`c=None`), `c` is automatically computed (using the function
1058
+ # `scipy.optimize.minimize`)
1059
+ # g : function (callable), optional
1060
+ # function proportional to the instrumental density on `[xmin, xmax[`,
1061
+ # `g(x)` returns the instrumental density (times a constant) at `x`;
1062
+ # with `x` array_like, the last axis of `x` denotes the components of the
1063
+ # points where the function is evaluated;
1064
+ # by default (`g=None`): the instrumental distribution considered is
1065
+ # - uniform (constant function `g=1` is considered), if the domain
1066
+ # `[xmin, xmax[` is finite
1067
+ # - (multi-)normal density of variance 1 (covariance matrix I), centered
1068
+ # at a point maximizing `f`, otherwise;
1069
+ # g_rvs : function (callable), optional
1070
+ # function returning samples from the instrumental distribution with
1071
+ # density proportional to `g` on `[xmin, xmax[` (restricted on this
1072
+ # domain if needed); `g_rvs` must have the keyword arguments `size`
1073
+ # (the number of sample(s) to draw)
1074
+ # by default: uniform or non-correlated multi-normal instrumental
1075
+ # distribution is considered (see `g`);
1076
+ # note: both `g` and `g_rvs` must be specified (or both set to `None`)
1077
+ # return_accept_ratio : bool, default: False
1078
+ # indicates if the acceptance ratio is returned
1079
+ # show_progress : bool, default: False
1080
+ # indicates if progress is displayed (True) or not (False)
1081
+ #
1082
+ # Returns
1083
+ # -------
1084
+ # x : 2d-array of shape (n, m), or 1d-array of shape (n,)
1085
+ # samples according to the target density proportional to `f on the
1086
+ # domain `[xmin, max[`, `x[i]` is the i-th sample point;
1087
+ # notes:
1088
+ # - if dimension m >= 2: `x` is a 2d-array of shape (n, m)
1089
+ # - if diemnsion is 1: `x` is an array of shape (n,)
1090
+ # t : float, optional
1091
+ # acceptance ratio, returned if `return_accept_ratio=True`, i.e.
1092
+ # `t = n/ntot` where `ntot` is the number of points draws in the
1093
+ # instrumental distribution
1094
+ # """
1095
+ # fname = 'acceptRejectSampler'
1096
+ #
1097
+ # xmin = np.atleast_1d(xmin)
1098
+ # xmax = np.atleast_1d(xmax)
1099
+ #
1100
+ # if xmin.ndim != xmax.ndim or np.any(np.isnan(xmin)) or np.any(np.isnan(xmax)) or np.any(xmin >= xmax):
1101
+ # print(f'ERROR ({fname}): `xmin`, `xmax` not valid')
1102
+ # return None
1103
+ #
1104
+ # lx = xmax - xmin
1105
+ # dim = len(xmin)
1106
+ #
1107
+ # x = np.zeros((n, dim)) # initialize random samples (one sample by row)
1108
+ # if n <= 0:
1109
+ # if return_accept_ratio:
1110
+ # return x, 1.0
1111
+ # else:
1112
+ # return x
1113
+ #
1114
+ # # Set g, g_rvs, and c
1115
+ # if (g is None and g_rvs is not None) or (g is not None and g_rvs is None):
1116
+ # print(f'ERROR ({fname}): `g` and `g_rvs` should be both specified')
1117
+ # return None
1118
+ #
1119
+ # mu = None # not necessarily used
1120
+ # if c is None:
1121
+ # if g is None:
1122
+ # h = lambda x: -f(x)
1123
+ # else:
1124
+ # h = lambda x: -f(x)/g(x)
1125
+ # # Compute the min of h(x) with the function scipy.optimize.minimize
1126
+ # # x0: initial guess (random)
1127
+ # x0 = xmin + np.random.random(size=dim)*lx
1128
+ # for i, binf in enumerate(np.isinf(x0)):
1129
+ # if binf:
1130
+ # x0[i] = min(xmax[i], max(xmin[i], 0.0))
1131
+ # res = scipy.optimize.minimize(h, x0, bounds=list(zip(xmin, xmax)))
1132
+ # if not res.success:
1133
+ # print(f'ERROR ({fname}): `scipy.optimize.minimize` failed {res.message})')
1134
+ # return None
1135
+ # # -> res.x realizes the minimum of h(x)
1136
+ # # -> res.fun is the minimum of h(x)
1137
+ # mu = res.x
1138
+ # # Set c such that c > f(x)/g(x) for all x in the domain
1139
+ # c = -res.fun + 1.e-3 # add small number to ensure the inequality
1140
+ #
1141
+ # if g is None:
1142
+ # if np.any((np.isinf(xmin), np.isinf(xmax))):
1143
+ # if mu is None:
1144
+ # # Compute the min of h(x) = (f(x)-c)**2 with the function scipy.optimize.minimize
1145
+ # h = lambda x: (f(x)-c)**2
1146
+ # # x0: initial guess (random)
1147
+ # x0 = xmin + np.random.random(size=dim)*lx
1148
+ # for i, binf in enumerate(np.isinf(x0)):
1149
+ # if binf:
1150
+ # x0[i] = min(xmax[i], max(xmin[i], 0.0))
1151
+ # res = scipy.optimize.minimize(h, x0, bounds=list(zip(xmin, xmax)))
1152
+ # if not res.success:
1153
+ # print(f'ERROR ({fname}): `scipy.optimize.minimize` failed {res.message})')
1154
+ # return None
1155
+ # # -> res.x is the minimum of h(x)
1156
+ # mu = res.x
1157
+ # # Set instrumental pdf proportional to: g = exp(-1/2 sum_i((x[i]-mu[i])**2))
1158
+ # # Set g, g_rvs
1159
+ # g = lambda x: np.exp(-0.5*np.sum((np.atleast_2d(x)-mu)**2), axis=1)
1160
+ # g_rvs = scipy.stats.multivariate_normal(mean=mu).rvs
1161
+ #
1162
+ # # Update c
1163
+ # # Compute the min of h(x) = -f(x)/g(x) with the function scipy.optimize.minimize
1164
+ # h = lambda x: -f(x)/g(x)
1165
+ # # x0: initial guess
1166
+ # x0 = mu
1167
+ # res = scipy.optimize.minimize(h, x0, bounds=list(zip(xmin, xmax)))
1168
+ # if not res.success:
1169
+ # print(f'ERROR ({fname}): `scipy.optimize.minimize` failed {res.message})')
1170
+ # return None
1171
+ # # -> res.fun is the minimum of h(x)
1172
+ #
1173
+ # # Set c such that c > f(x)/g(x) for all x in the domain
1174
+ # c = -res.fun + 1.e-3 # add small number to ensure the inequality
1175
+ #
1176
+ # else:
1177
+ # # Set instrumental pdf proportional to: g = 1 (uniform distribution)
1178
+ # # Set g, g_rvs
1179
+ # g = lambda x: 1.0
1180
+ # def g_rvs(size=1):
1181
+ # return xmin + scipy.stats.uniform.rvs(size=(size,dim)) * lx
1182
+ #
1183
+ # # Apply accept-reject algo
1184
+ # naccept = 0
1185
+ # ntot = 0
1186
+ # x = []
1187
+ # if max_trial is None:
1188
+ # max_trial = np.inf
1189
+ # if show_progress:
1190
+ # progress = 0
1191
+ # progressOld = -1
1192
+ # while naccept < n:
1193
+ # nn = n - naccept
1194
+ # ntot = ntot+nn
1195
+ # xnew = g_rvs(size=nn)
1196
+ # # print('1', xnew)
1197
+ # # print('1b', np.all(np.all((xnew >= xmin, xnew < xmax), axis=0), axis=-1))
1198
+ # xnew = xnew[np.all(np.all((xnew >= xmin, xnew < xmax), axis=0), axis=-1)]
1199
+ # # print('2', xnew)
1200
+ # nn = len(xnew)
1201
+ # if nn == 0:
1202
+ # continue
1203
+ # u = np.random.random(size=nn)
1204
+ # xnew = xnew[u < (f(xnew)/(c*g(xnew))).reshape(nn)]
1205
+ # # print('3', xnew)
1206
+ # nn = len(xnew)
1207
+ # if nn == 0:
1208
+ # continue
1209
+ # x.extend(xnew)
1210
+ # naccept = naccept+nn
1211
+ # if show_progress:
1212
+ # progress = int(100*naccept/n)
1213
+ # if progress > progressOld:
1214
+ # print(f'A-R algo, progress: {progress:3d} %')
1215
+ # progressOld = progress
1216
+ # if ntot >= max_trial:
1217
+ # ok = False
1218
+ # break
1219
+ #
1220
+ # x = np.asarray(x)
1221
+ # if dim == 1:
1222
+ # x = x.reshape(-1)
1223
+ #
1224
+ # # # Apply accept-reject algo
1225
+ # # naccept = 0
1226
+ # # ntot = 0
1227
+ # # if max_trial is None:
1228
+ # # max_trial = np.inf
1229
+ # # if show_progress:
1230
+ # # progress = 0
1231
+ # # progressOld = -1
1232
+ # # while naccept < n:
1233
+ # # ntot = ntot+1
1234
+ # # xnew = g_rvs()
1235
+ # # if np.any((xnew < xmin, xnew >= xmax)):
1236
+ # # continue
1237
+ # # u = np.random.random()
1238
+ # # if u < f(xnew)/(c*g(xnew)):
1239
+ # # x[naccept] = xnew
1240
+ # # naccept = naccept+1
1241
+ # # if show_progress:
1242
+ # # progress = int(100*naccept/n)
1243
+ # # if progress > progressOld:
1244
+ # # print(f'A-R algo, progress: {progress:3d} %')
1245
+ # # progressOld = progress
1246
+ # # if ntot >= max_trial:
1247
+ # # ok = False
1248
+ # # break
1249
+ #
1250
+ # if naccept < n:
1251
+ # print(f'WARNING: sample size is only {naccept}! (increase `max_trial`)')
1252
+ #
1253
+ # if return_accept_ratio:
1254
+ # accept_ratio = naccept/ntot
1255
+ # return x, accept_ratio
1256
+ # else:
1257
+ # return x
1258
+ # # ----------------------------------------------------------------------------