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/__init__.py +32 -0
- geone/_version.py +6 -0
- geone/blockdata.py +250 -0
- geone/covModel.py +15529 -0
- geone/customcolors.py +508 -0
- geone/deesse_core/__init__.py +5 -0
- geone/deesse_core/_deesse.so +0 -0
- geone/deesse_core/deesse.py +2450 -0
- geone/deesseinterface.py +11323 -0
- geone/geosclassic_core/__init__.py +5 -0
- geone/geosclassic_core/_geosclassic.so +0 -0
- geone/geosclassic_core/geosclassic.py +1429 -0
- geone/geosclassicinterface.py +20092 -0
- geone/grf.py +5927 -0
- geone/img.py +7152 -0
- geone/imgplot.py +1464 -0
- geone/imgplot3d.py +1918 -0
- geone/markovChain.py +666 -0
- geone/multiGaussian.py +388 -0
- geone/pgs.py +1258 -0
- geone/randProcess.py +1258 -0
- geone/srf.py +3661 -0
- geone/tools.py +861 -0
- geone-1.3.0.dist-info/METADATA +207 -0
- geone-1.3.0.dist-info/RECORD +28 -0
- geone-1.3.0.dist-info/WHEEL +5 -0
- geone-1.3.0.dist-info/licenses/LICENSE +58 -0
- geone-1.3.0.dist-info/top_level.txt +1 -0
geone/markovChain.py
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# -------------------------------------------------------------------------
|
|
5
|
+
# Python module: 'markovChain.py'
|
|
6
|
+
# author: Julien Straubhaar
|
|
7
|
+
# date: sep-2024
|
|
8
|
+
# -------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Module for simulation of Markov Chain on finite sets of states.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
# ============================================================================
|
|
17
|
+
class MarkovChainError(Exception):
|
|
18
|
+
"""
|
|
19
|
+
Custom exception related to `markovChain` module.
|
|
20
|
+
"""
|
|
21
|
+
pass
|
|
22
|
+
# ============================================================================
|
|
23
|
+
|
|
24
|
+
# ----------------------------------------------------------------------------
|
|
25
|
+
def mc_kernel1(n, p, return_pinv=False):
|
|
26
|
+
"""
|
|
27
|
+
Sets the following symmetric transition kernel of order n for a Markov chain:
|
|
28
|
+
|
|
29
|
+
.. math::
|
|
30
|
+
P = \\left(\\begin{array}{cccc}
|
|
31
|
+
p & \\frac{1-p}{n-1} & \\ldots & \\frac{1-p}{n-1}\\\\
|
|
32
|
+
\\frac{1-p}{n-1} & \\ddots & \\ddots & \\vdots\\\\
|
|
33
|
+
\\vdots & \\ddots & \\ddots & \\frac{1-p}{n-1}\\\\
|
|
34
|
+
\\frac{1-p}{n-1} & \\ldots & \\frac{1-p}{n-1} & p
|
|
35
|
+
\\end{array}\\right)
|
|
36
|
+
|
|
37
|
+
where :math:`0\\leqslant p < 1` is a parameter.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
n : int
|
|
42
|
+
order of the kernel, number of states
|
|
43
|
+
|
|
44
|
+
p : float
|
|
45
|
+
number in the interval [0, 1[
|
|
46
|
+
|
|
47
|
+
return_pinv : bool
|
|
48
|
+
indicates if the invariant distribution is returned
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
kernel : 2d-array of shape (n, n)
|
|
53
|
+
transition kernel (P) above
|
|
54
|
+
|
|
55
|
+
pinv : (1d-array of shape (n,)
|
|
56
|
+
invariant distibution of the kernel, that is
|
|
57
|
+
|
|
58
|
+
- [1/n, ... , 1/n]
|
|
59
|
+
|
|
60
|
+
returned if `return_pinv=True`
|
|
61
|
+
"""
|
|
62
|
+
# fname = 'mc_kernel1'
|
|
63
|
+
|
|
64
|
+
if p < 0 or p >= 1:
|
|
65
|
+
return None
|
|
66
|
+
r = (1.0 - p)/(n-1)
|
|
67
|
+
x = [p] + (n-1)*[r]
|
|
68
|
+
kernel = np.array([x[-i:] + x[0:-i] for i in range(n)])
|
|
69
|
+
if return_pinv:
|
|
70
|
+
pinv = 1/n * np.ones(n)
|
|
71
|
+
if return_pinv:
|
|
72
|
+
out = kernel, pinv
|
|
73
|
+
else:
|
|
74
|
+
out = kernel
|
|
75
|
+
return out
|
|
76
|
+
# ----------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
# ----------------------------------------------------------------------------
|
|
79
|
+
def mc_kernel2(n, p, return_pinv=False):
|
|
80
|
+
"""
|
|
81
|
+
Sets the following transition kernel of order n for a Markov chain:
|
|
82
|
+
|
|
83
|
+
.. math::
|
|
84
|
+
P = \\left(\\begin{array}{cccccc}
|
|
85
|
+
p & 1-p & 0 & \\ldots & 0 & 0\\\\
|
|
86
|
+
\\frac{1-p}{2} & p & \\frac{1-p}{2} & \\ddots & & 0\\\\
|
|
87
|
+
0 & \\ddots & \\ddots & \\ddots & \\ddots & \\vdots\\\\
|
|
88
|
+
\\vdots & \\ddots & \\ddots & \\ddots & \\ddots & 0\\\\
|
|
89
|
+
0 & & \\ddots & \\frac{1-p}{2} & p & \\frac{1-p}{2}\\\\
|
|
90
|
+
0 & 0 & \\ldots & 0 & 1-p & p
|
|
91
|
+
\\end{array}\\right)
|
|
92
|
+
|
|
93
|
+
where :math:`0\\leqslant p < 1` is a parameter.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
n : int
|
|
98
|
+
order of the kernel, number of states
|
|
99
|
+
|
|
100
|
+
p : float
|
|
101
|
+
number in the interval [0, 1[
|
|
102
|
+
|
|
103
|
+
return_pinv : bool
|
|
104
|
+
indicates if the invariant distribution is returned
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
kernel : 2d-array of shape (n, n)
|
|
109
|
+
transition kernel (P) above
|
|
110
|
+
|
|
111
|
+
pinv : (1d-array of shape (n,)
|
|
112
|
+
invariant distibution of the kernel, that is
|
|
113
|
+
|
|
114
|
+
- [1/(2(n-1)), 1/(n-1), ... , 1/(n-1), 1/(2(n-1))]
|
|
115
|
+
|
|
116
|
+
returned if `return_pinv=True`
|
|
117
|
+
"""
|
|
118
|
+
# fname = 'mc_kernel2'
|
|
119
|
+
|
|
120
|
+
if p < 0 or p >= 1:
|
|
121
|
+
return None
|
|
122
|
+
r = (1.0 - p)/2.0
|
|
123
|
+
xa = [p, 2*r] + (n-2)*[0]
|
|
124
|
+
x = [r, p, r] + (n-3)*[0]
|
|
125
|
+
xb = (n-2)*[0] + [2*r, p]
|
|
126
|
+
kernel = np.array([xa] + [x[-i:] + x[0:-i] for i in range(n-2)] + [xb])
|
|
127
|
+
if return_pinv:
|
|
128
|
+
t = 1.0 / (2.0*(n-1))
|
|
129
|
+
pinv = np.hstack((np.array([t]), 1/(n-1) * np.ones(n-2), np.array([t])))
|
|
130
|
+
if return_pinv:
|
|
131
|
+
out = kernel, pinv
|
|
132
|
+
else:
|
|
133
|
+
out = kernel
|
|
134
|
+
return out
|
|
135
|
+
# ----------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
# ----------------------------------------------------------------------------
|
|
138
|
+
def mc_kernel3(n, p, q, return_pinv=False):
|
|
139
|
+
"""
|
|
140
|
+
Sets the following transition kernel of order n for a Markov chain:
|
|
141
|
+
|
|
142
|
+
.. math::
|
|
143
|
+
P = \\left(\\begin{array}{ccccc}
|
|
144
|
+
p & (1-p)q & 0 & \\ldots & (1-p)(1-q)\\\\
|
|
145
|
+
(1-p)(1-q) & \\ddots & \\ddots & & 0\\\\
|
|
146
|
+
0 & \\ddots & \\ddots & \\ddots & \\vdots\\\\
|
|
147
|
+
\\vdots & \\ddots & \\ddots & \\ddots & 0\\\\
|
|
148
|
+
0 & & \\ddots & \\ddots & (1-p)q\\\\
|
|
149
|
+
(1-p)q & 0 & \\ldots & (1-p)(1-q) & p
|
|
150
|
+
\\end{array}\\right)
|
|
151
|
+
|
|
152
|
+
where :math:`0\\leqslant p < 1` and :math:`0\\leqslant q \\leqslant 1` are
|
|
153
|
+
two parameters.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
n : int
|
|
158
|
+
order of the kernel, number of states
|
|
159
|
+
|
|
160
|
+
p : float
|
|
161
|
+
number in the interval [0, 1[
|
|
162
|
+
|
|
163
|
+
q : float
|
|
164
|
+
number in the interval [0, 1]
|
|
165
|
+
|
|
166
|
+
return_pinv : bool
|
|
167
|
+
indicates if the invariant distribution is returned
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
kernel : 2d-array of shape (n, n)
|
|
172
|
+
transition kernel (P) above
|
|
173
|
+
|
|
174
|
+
pinv : (1d-array of shape (n,)
|
|
175
|
+
invariant distibution of the kernel, that is
|
|
176
|
+
|
|
177
|
+
- [1/n, ... , 1/n]
|
|
178
|
+
|
|
179
|
+
returned if `return_pinv=True`
|
|
180
|
+
"""
|
|
181
|
+
# fname = 'mc_kernel3'
|
|
182
|
+
|
|
183
|
+
if p < 0 or p >= 1 or q < 0 or q > 1:
|
|
184
|
+
return None
|
|
185
|
+
r = (1.0 - p)*q
|
|
186
|
+
s = (1.0 - p)*(1.0 - q)
|
|
187
|
+
x = [p, r] + (n-3)*[0] + [s]
|
|
188
|
+
kernel = np.array([x[-i:] + x[0:-i] for i in range(n)])
|
|
189
|
+
if return_pinv:
|
|
190
|
+
pinv = 1/n * np.ones(n)
|
|
191
|
+
if return_pinv:
|
|
192
|
+
out = kernel, pinv
|
|
193
|
+
else:
|
|
194
|
+
out = kernel
|
|
195
|
+
return out
|
|
196
|
+
# ----------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
# ----------------------------------------------------------------------------
|
|
199
|
+
def mc_kernel4(n, p, q, return_pinv=False):
|
|
200
|
+
"""
|
|
201
|
+
Sets the following transition kernel of order n for a Markov chain:
|
|
202
|
+
|
|
203
|
+
.. math::
|
|
204
|
+
P = \\left(\\begin{array}{ccccc}
|
|
205
|
+
q & 0 & \\ldots & 0 & 1-q\\\\
|
|
206
|
+
0 & \\ddots & \\ddots & \\vdots & \\vdots\\\\
|
|
207
|
+
\\vdots & \\ddots & \\ddots & 0 & \\vdots\\\\
|
|
208
|
+
0 & \\ldots & 0 & q & 1-q\\\\
|
|
209
|
+
\\frac{1-p}{n-1} & \\ldots & \\ldots &\\frac{1-p}{n-1} & p
|
|
210
|
+
\\end{array}\\right)
|
|
211
|
+
|
|
212
|
+
where :math:`0\\leqslant p, q < 1` are two parameters.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
n : int
|
|
217
|
+
order of the kernel, number of states
|
|
218
|
+
|
|
219
|
+
p : float
|
|
220
|
+
number in the interval [0, 1[
|
|
221
|
+
|
|
222
|
+
q : float
|
|
223
|
+
number in the interval [0, 1[
|
|
224
|
+
|
|
225
|
+
return_pinv : bool
|
|
226
|
+
indicates if the invariant distribution is returned
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
kernel : 2d-array of shape (n, n)
|
|
231
|
+
transition kernel (P) above
|
|
232
|
+
|
|
233
|
+
pinv : (1d-array of shape (n,)
|
|
234
|
+
invariant distibution of the kernel, that is
|
|
235
|
+
|
|
236
|
+
- 1/(2-p-q) * [(1-p)/(n-1), ... , (1-p)/(n-1), 1-q]
|
|
237
|
+
|
|
238
|
+
returned if `return_pinv=True`
|
|
239
|
+
"""
|
|
240
|
+
# fname = 'mc_kernel4'
|
|
241
|
+
|
|
242
|
+
if p < 0 or p >= 1 or q < 0 or q >= 1:
|
|
243
|
+
return None
|
|
244
|
+
kernel = np.vstack((np.hstack((np.diag(q*np.ones(n-1)), (1-q)*np.ones((n-1, 1)))),[(n-1)*[(1-p)/(n-1)]+[p]]))
|
|
245
|
+
if return_pinv:
|
|
246
|
+
pinv = 1/(2-p-q)*np.array((n-1)*[(1-p)/(n-1)] + [1-q])
|
|
247
|
+
if return_pinv:
|
|
248
|
+
out = kernel, pinv
|
|
249
|
+
else:
|
|
250
|
+
out = kernel
|
|
251
|
+
return out
|
|
252
|
+
# ----------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
# ----------------------------------------------------------------------------
|
|
255
|
+
def compute_mc_pinv(kernel, logger=None):
|
|
256
|
+
"""
|
|
257
|
+
Computes the invariant distribution of a Markov chain for a given kernel.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
kernel : 2d-array of shape (n, n)
|
|
262
|
+
transition kernel of a Markov chain on a set of states
|
|
263
|
+
:math:`S=\\{0, \\ldots, n-1\\}`; the element at row `i` and column `j` is
|
|
264
|
+
the probability to have the state of index `j` at the next step given
|
|
265
|
+
the state `i` at the current step, i.e.
|
|
266
|
+
|
|
267
|
+
- :math:`kernel[i][j] = P(X_{k+1}=j\\ \\vert\\ X_{k}=i)`
|
|
268
|
+
|
|
269
|
+
where the sequence of random variables :math:`(X_k)` is a Markov chain
|
|
270
|
+
on `S` defined by the kernel `kernel`.
|
|
271
|
+
|
|
272
|
+
In particular, every element of `kernel` is positive or zero, and its
|
|
273
|
+
rows sum to one.
|
|
274
|
+
|
|
275
|
+
logger : :class:`logging.Logger`, optional
|
|
276
|
+
logger (see package `logging`)
|
|
277
|
+
if specified, messages are written via `logger` (no print)
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
pinv : 1d-array of shape (n,)
|
|
282
|
+
invariant distribution of the Markov chain, i.e. `pinv` is an eigen
|
|
283
|
+
vector of eigen value `1` of the transpose of `kernel`:
|
|
284
|
+
:math:`pinv \\cdot kernel= pinv`; note:
|
|
285
|
+
|
|
286
|
+
- `None` is returned if no eigen value is equal to 1
|
|
287
|
+
- if more than one eigen value is equal to 1, only the first corresponding\
|
|
288
|
+
eigen vector computed by `numpy.linalg.eig` is returned
|
|
289
|
+
"""
|
|
290
|
+
fname = 'compute_mc_pinv'
|
|
291
|
+
|
|
292
|
+
valt, st = np.linalg.eig(kernel.T)
|
|
293
|
+
ind = np.where(np.isclose(valt, 1.0))[0]
|
|
294
|
+
if len(ind) == 0:
|
|
295
|
+
err_msg = f'{fname}: no invariant distribution found'
|
|
296
|
+
if logger: logger.error(err_msg)
|
|
297
|
+
raise MarkovChainError(err_msg)
|
|
298
|
+
|
|
299
|
+
pinv = np.real(st[:,ind[0]]/np.sum(st[:,ind[0]]))
|
|
300
|
+
return pinv
|
|
301
|
+
# ----------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
# ----------------------------------------------------------------------------
|
|
304
|
+
def compute_mc_kernel_rev(kernel, pinv=None, logger=None):
|
|
305
|
+
"""
|
|
306
|
+
Computes the reverse transition kernel of a Markov chain for a given kernel.
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
kernel : 2d-array of shape (n, n)
|
|
311
|
+
transition kernel of a Markov chain on a set of states
|
|
312
|
+
:math:`S=\\{0, \\ldots, n-1\\}`; the element at row `i` and column `j` is
|
|
313
|
+
the probability to have the state of index `j` at the next step given
|
|
314
|
+
the state `i` at the current step, i.e.
|
|
315
|
+
|
|
316
|
+
- :math:`kernel[i][j] = P(X_{k+1}=j\\ \\vert\\ X_{k}=i)`
|
|
317
|
+
|
|
318
|
+
where the sequence of random variables :math:`(X_k)` is a Markov chain
|
|
319
|
+
on `S` defined by the kernel `kernel`.
|
|
320
|
+
|
|
321
|
+
In particular, every element of `kernel` is positive or zero, and its
|
|
322
|
+
rows sum to one.
|
|
323
|
+
|
|
324
|
+
pinv : 1d-array of shape (n,), optional
|
|
325
|
+
invariant distribution of the Markov chain;
|
|
326
|
+
by default (`None`): `pinv` is automatically computed
|
|
327
|
+
|
|
328
|
+
logger : :class:`logging.Logger`, optional
|
|
329
|
+
logger (see package `logging`)
|
|
330
|
+
if specified, messages are written via `logger` (no print)
|
|
331
|
+
|
|
332
|
+
Returns
|
|
333
|
+
-------
|
|
334
|
+
kernel_rev : 2d-array of shape (n, n)
|
|
335
|
+
reverse transition kernel of the Markov chain, i.e.
|
|
336
|
+
|
|
337
|
+
- :math:`kernel\\_rev[i, j] = pinv[i]^{-1} \\cdot kernel[j, i] \\cdot pinv[j]`
|
|
338
|
+
"""
|
|
339
|
+
fname = 'compute_mc_kernel_rev'
|
|
340
|
+
|
|
341
|
+
if pinv is None:
|
|
342
|
+
try:
|
|
343
|
+
pinv = compute_mc_pinv(kernel, logger=logger)
|
|
344
|
+
except Exception as exc:
|
|
345
|
+
err_msg = f'{fname}: computing invariant distribution failed'
|
|
346
|
+
if logger: logger.error(err_msg)
|
|
347
|
+
raise MarkovChainError(err_msg) from exc
|
|
348
|
+
|
|
349
|
+
if pinv is None or np.any(np.isclose(pinv, 0)):
|
|
350
|
+
err_msg = f'{fname}: kernel not reversible'
|
|
351
|
+
if logger: logger.error(err_msg)
|
|
352
|
+
raise MarkovChainError(err_msg)
|
|
353
|
+
|
|
354
|
+
kernel_rev = (kernel/pinv).T*pinv
|
|
355
|
+
|
|
356
|
+
return kernel_rev
|
|
357
|
+
# ----------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
# ----------------------------------------------------------------------------
|
|
360
|
+
def compute_mc_cov(kernel, pinv=None, nsteps=1, logger=None):
|
|
361
|
+
"""
|
|
362
|
+
Computes covariances of indicators for a Markov chain, accross time steps.
|
|
363
|
+
|
|
364
|
+
Parameters
|
|
365
|
+
----------
|
|
366
|
+
kernel : 2d-array of shape (n, n)
|
|
367
|
+
transition kernel of a Markov chain on a set of states
|
|
368
|
+
:math:`S=\\{0, \\ldots, n-1\\}`; the element at row `i` and column `j` is
|
|
369
|
+
the probability to have the state of index `j` at the next step given
|
|
370
|
+
the state `i` at the current step, i.e.
|
|
371
|
+
|
|
372
|
+
- :math:`kernel[i][j] = P(X_{k+1}=j\\ \\vert\\ X_{k}=i)`
|
|
373
|
+
|
|
374
|
+
where the sequence of random variables :math:`(X_k)` is a Markov chain
|
|
375
|
+
on `S` defined by the kernel `kernel`.
|
|
376
|
+
|
|
377
|
+
In particular, every element of `kernel` is positive or zero, and its
|
|
378
|
+
rows sum to one.
|
|
379
|
+
|
|
380
|
+
pinv : 1d-array of shape (n,), optional
|
|
381
|
+
invariant distribution of the Markov chain;
|
|
382
|
+
by default (`None`): `pinv` is automatically computed
|
|
383
|
+
|
|
384
|
+
nsteps : int, default: 1
|
|
385
|
+
number of (time) steps for which the covariance is computed
|
|
386
|
+
|
|
387
|
+
logger : :class:`logging.Logger`, optional
|
|
388
|
+
logger (see package `logging`)
|
|
389
|
+
if specified, messages are written via `logger` (no print)
|
|
390
|
+
|
|
391
|
+
Returns
|
|
392
|
+
-------
|
|
393
|
+
mc_cov : 3d-array of shape (nstep, n, n)
|
|
394
|
+
covariances of indicators for a Markov chain X built from the kernel
|
|
395
|
+
and its invariant distribution:
|
|
396
|
+
|
|
397
|
+
- :math:`mc\\_cov[l, i, j] = \\operatorname{Cov}(X_k=i, X_{k+l}=j) = pinv[i] \\cdot ((kernel^l)[i,j]- pinv[j])`
|
|
398
|
+
|
|
399
|
+
for `l=0, \\ldots, nsteps-1`, and :math:`0\\leqslant i, j \\leqslant n-1`
|
|
400
|
+
"""
|
|
401
|
+
fname = 'compute_mc_cov'
|
|
402
|
+
|
|
403
|
+
# Diagonalization of kernel: kernel = s.dot(diag(val)).dot(sinv)
|
|
404
|
+
val, s = np.linalg.eig(kernel)
|
|
405
|
+
val = np.real(val) # take the real part (if needed)
|
|
406
|
+
s = np.real(s) # take the real part (if needed)
|
|
407
|
+
sinv = np.linalg.inv(s)
|
|
408
|
+
|
|
409
|
+
if pinv is None:
|
|
410
|
+
try:
|
|
411
|
+
pinv = compute_mc_pinv(kernel, logger=logger)
|
|
412
|
+
except Exception as exc:
|
|
413
|
+
err_msg = f'{fname}: computing invariant distribution failed'
|
|
414
|
+
if logger: logger.error(err_msg)
|
|
415
|
+
raise MarkovChainError(err_msg) from exc
|
|
416
|
+
|
|
417
|
+
n = kernel.shape[0]
|
|
418
|
+
mc_cov = np.zeros((nsteps, n, n))
|
|
419
|
+
for m in range(nsteps):
|
|
420
|
+
km = s.dot(np.diag(val**m)).dot(sinv)
|
|
421
|
+
mc_cov[m, :, :] = np.repeat(pinv, n).reshape(n, n)*km - np.outer(pinv, pinv)
|
|
422
|
+
|
|
423
|
+
return mc_cov
|
|
424
|
+
# ----------------------------------------------------------------------------
|
|
425
|
+
|
|
426
|
+
# ----------------------------------------------------------------------------
|
|
427
|
+
def simulate_mc(
|
|
428
|
+
kernel,
|
|
429
|
+
nsteps,
|
|
430
|
+
categVal=None,
|
|
431
|
+
data_ind=None, data_val=None,
|
|
432
|
+
pstep0=None,
|
|
433
|
+
pinv=None,
|
|
434
|
+
kernel_rev=None, kernel_pow=None,
|
|
435
|
+
nreal=1,
|
|
436
|
+
logger=None):
|
|
437
|
+
"""
|
|
438
|
+
Generates (conditional) Markov chains for a given kernel.
|
|
439
|
+
|
|
440
|
+
Parameters
|
|
441
|
+
----------
|
|
442
|
+
kernel : 2d-array of shape (n, n)
|
|
443
|
+
transition kernel of a Markov chain on a set of states
|
|
444
|
+
:math:`S=\\{0, \\ldots, n-1\\}`; the element at row `i` and column `j` is
|
|
445
|
+
the probability to have the state of index `j` at the next step given
|
|
446
|
+
the state `i` at the current step, i.e.
|
|
447
|
+
|
|
448
|
+
- :math:`kernel[i][j] = P(X_{k+1}=j\\ \\vert\\ X_{k}=i)`
|
|
449
|
+
|
|
450
|
+
where the sequence of random variables :math:`(X_k)` is a Markov chain
|
|
451
|
+
on `S` defined by the kernel `kernel`.
|
|
452
|
+
|
|
453
|
+
In particular, every element of `kernel` is positive or zero, and its
|
|
454
|
+
rows sum to one.
|
|
455
|
+
|
|
456
|
+
nsteps : int
|
|
457
|
+
length of each generated chain, number of time steps
|
|
458
|
+
|
|
459
|
+
categVal :1d-array of shape (n,), optional
|
|
460
|
+
values of categories (one value for each state `0, ..., n-1`);
|
|
461
|
+
by default (`None`) : `categVal` is set to `[0, ..., n-1]`
|
|
462
|
+
|
|
463
|
+
data_ind : sequence, optional
|
|
464
|
+
index (time step) of conditioning locations; each index should be in
|
|
465
|
+
`{0, 1, ..., nsteps-1}`
|
|
466
|
+
|
|
467
|
+
data_val : sequence, optional
|
|
468
|
+
values at conditioning locations (same length as `data_ind`); each value
|
|
469
|
+
should be in `categVal`
|
|
470
|
+
|
|
471
|
+
pstep0 : 1d-array of shape (n,), optional
|
|
472
|
+
distribution for step 0 of the chain, for unconditional simulation
|
|
473
|
+
(used only if `data_ind=None` and `data_val=None`)
|
|
474
|
+
by default (`None`): `pinv` is used (see below)
|
|
475
|
+
|
|
476
|
+
pinv : 1d-array of shape (n,), optional
|
|
477
|
+
invariant distribution of the Markov chain;
|
|
478
|
+
by default (`None`): `pinv` is automatically computed
|
|
479
|
+
|
|
480
|
+
kernel_rev : 2d-array of shape (n, n), optional
|
|
481
|
+
reverse transition kernel of the Markov chain;
|
|
482
|
+
by default (`None`): `kernel_rev` is automatically computed;
|
|
483
|
+
note: only used for conditional simulation
|
|
484
|
+
|
|
485
|
+
kernel_pow : 3d-array of shape (m, n, n), optional
|
|
486
|
+
pre-computed kernel raised to power 0, 1, ..., m-1:
|
|
487
|
+
|
|
488
|
+
- :math:`kernel\\_pow[k] = kernel^k`
|
|
489
|
+
|
|
490
|
+
note: only used for conditional simulation
|
|
491
|
+
|
|
492
|
+
nreal : int, default: 1
|
|
493
|
+
number of realization(s), number of generated chain(s)
|
|
494
|
+
|
|
495
|
+
logger : :class:`logging.Logger`, optional
|
|
496
|
+
logger (see package `logging`)
|
|
497
|
+
if specified, messages are written via `logger` (no print)
|
|
498
|
+
|
|
499
|
+
Returns
|
|
500
|
+
-------
|
|
501
|
+
x : 3d-array of shape (nreal, nsteps)
|
|
502
|
+
generated Markov chain (conditional to `data_ind, data_val` if present), `x[i]` is
|
|
503
|
+
the i-th realization
|
|
504
|
+
"""
|
|
505
|
+
fname = 'simulate_mc'
|
|
506
|
+
|
|
507
|
+
# Number of categories (order of the kernel)
|
|
508
|
+
n = kernel.shape[0]
|
|
509
|
+
|
|
510
|
+
# Check category values
|
|
511
|
+
if categVal is None:
|
|
512
|
+
categVal = np.arange(n)
|
|
513
|
+
else:
|
|
514
|
+
categVal = np.asarray(categVal)
|
|
515
|
+
if categVal.ndim != 1 or categVal.shape[0] != n:
|
|
516
|
+
err_msg = f'{fname}: `categVal` invalid'
|
|
517
|
+
if logger: logger.error(err_msg)
|
|
518
|
+
raise MarkovChainError(err_msg)
|
|
519
|
+
|
|
520
|
+
if len(np.unique(categVal)) != len(categVal):
|
|
521
|
+
err_msg = f'{fname}: `categVal` contains duplicated values'
|
|
522
|
+
if logger: logger.error(err_msg)
|
|
523
|
+
raise MarkovChainError(err_msg)
|
|
524
|
+
|
|
525
|
+
# Conditioning
|
|
526
|
+
if (data_ind is None and data_val is not None) or (data_ind is not None and data_val is None):
|
|
527
|
+
err_msg = f'{fname}: `data_ind` and `data_val` must both be specified'
|
|
528
|
+
if logger: logger.error(err_msg)
|
|
529
|
+
raise MarkovChainError(err_msg)
|
|
530
|
+
|
|
531
|
+
if data_ind is None:
|
|
532
|
+
nhd = 0
|
|
533
|
+
else:
|
|
534
|
+
data_ind = np.asarray(data_ind, dtype='int').reshape(-1) # cast in 1-dimensional array if needed
|
|
535
|
+
data_val = np.asarray(data_val, dtype='float').reshape(-1) # cast in 1-dimensional array if needed
|
|
536
|
+
nhd = len(data_ind)
|
|
537
|
+
if len(data_ind) != len(data_val):
|
|
538
|
+
err_msg = f'{fname}: length of `data_ind` and length of `data_val` differ'
|
|
539
|
+
if logger: logger.error(err_msg)
|
|
540
|
+
raise MarkovChainError(err_msg)
|
|
541
|
+
|
|
542
|
+
# Check values
|
|
543
|
+
if not np.all([xv in categVal for xv in data_val]):
|
|
544
|
+
err_msg = f'{fname}: `data_val` contains an invalid value'
|
|
545
|
+
if logger: logger.error(err_msg)
|
|
546
|
+
raise MarkovChainError(err_msg)
|
|
547
|
+
|
|
548
|
+
# Replace values by their index in categVal
|
|
549
|
+
data_val = np.array([np.where(categVal==xv)[0][0] for xv in data_val], dtype='int')
|
|
550
|
+
|
|
551
|
+
if nhd == 0:
|
|
552
|
+
# Compute invariant distribution for kernel
|
|
553
|
+
if pinv is None:
|
|
554
|
+
try:
|
|
555
|
+
pinv = compute_mc_pinv(kernel, logger=logger)
|
|
556
|
+
except Exception as exc:
|
|
557
|
+
err_msg = f'{fname}: computing invariant distribution failed'
|
|
558
|
+
if logger: logger.error(err_msg)
|
|
559
|
+
raise MarkovChainError(err_msg) from exc
|
|
560
|
+
|
|
561
|
+
# Compute cdf for x0
|
|
562
|
+
if pstep0 is not None:
|
|
563
|
+
x0_cdf = np.cumsum(pstep0)
|
|
564
|
+
else:
|
|
565
|
+
x0_cdf = np.cumsum(pinv)
|
|
566
|
+
|
|
567
|
+
# Compute conditional cdf from the transition kernel
|
|
568
|
+
kernel_cdf = np.cumsum(kernel, axis=1)
|
|
569
|
+
|
|
570
|
+
# Generate X
|
|
571
|
+
x = []
|
|
572
|
+
for _ in range(nreal):
|
|
573
|
+
# Generate one chain (realization)
|
|
574
|
+
xk = - np.ones(nsteps, dtype='int')
|
|
575
|
+
u = np.random.random(size=nsteps)
|
|
576
|
+
xk[0] = np.where(u[0] < x0_cdf)[0][0]
|
|
577
|
+
for i in range(1, nsteps):
|
|
578
|
+
xk[i] = np.where(u[i] < kernel_cdf[int(xk[i-1])])[0][0]
|
|
579
|
+
# Append realization to x
|
|
580
|
+
x.append(xk)
|
|
581
|
+
|
|
582
|
+
else:
|
|
583
|
+
inds = np.argsort(data_ind)
|
|
584
|
+
if data_ind[inds[0]] > 0:
|
|
585
|
+
# Compute reverse transition kernel
|
|
586
|
+
if kernel_rev is None:
|
|
587
|
+
try:
|
|
588
|
+
kernel_rev = compute_mc_kernel_rev(kernel, pinv=pinv, logger=logger)
|
|
589
|
+
except Exception as exc:
|
|
590
|
+
err_msg = f'{fname}: computing reverse transition kernel failed'
|
|
591
|
+
if logger: logger.error(err_msg)
|
|
592
|
+
raise MarkovChainError(err_msg) from exc
|
|
593
|
+
|
|
594
|
+
# Compute conditional cdf from the reverse transition kernel
|
|
595
|
+
kernel_rev_cdf = np.cumsum(kernel_rev, axis=1)
|
|
596
|
+
|
|
597
|
+
if data_ind[inds[-1]] < nsteps-1:
|
|
598
|
+
# compute conditional cdf from the transition kernel
|
|
599
|
+
kernel_cdf = np.cumsum(kernel, axis=1)
|
|
600
|
+
|
|
601
|
+
if nhd > 1:
|
|
602
|
+
# Compute list of kernel raise to power 0, 1, 2, ..., m-1 for further computation
|
|
603
|
+
m = max(np.diff([data_ind[j] for j in inds])) + 1
|
|
604
|
+
if kernel_pow is None:
|
|
605
|
+
kernel_pow = np.zeros((m, n, n))
|
|
606
|
+
kernel_pow[0] = np.eye(n)
|
|
607
|
+
m0 = 1
|
|
608
|
+
else:
|
|
609
|
+
m0 = kernel_pow.shape[0]
|
|
610
|
+
if m0 < m:
|
|
611
|
+
kernel_pow = np.concatenate((kernel_pow, np.zeros((m-m0, n, n))), axis=0)
|
|
612
|
+
for i in range(m0, m):
|
|
613
|
+
kernel_pow[i] = kernel_pow[i-1].dot(kernel)
|
|
614
|
+
#
|
|
615
|
+
# With k1 < k2 < k3, we have:
|
|
616
|
+
# Prob(x[k2]=i2 | x[k1]=i1, x[k3]=i3) = kernel^(k2-k1)[i1, i2] * kernel^(k3-k2)[i2, i3] / kernel^(k3-k1)[i1, i3]
|
|
617
|
+
# Check validity of conditioning points:
|
|
618
|
+
# check that the denominator above is positive for each pair (k1, k3) of consecutive conditioning points, i.e.
|
|
619
|
+
# Prob(x[k1]=i1 | x[k3]=i3) = kernel^(k3-k1)[i1, i3] > 0
|
|
620
|
+
if np.any(np.isclose([kernel_pow[data_ind[inds[i+1]]-data_ind[inds[i]], int(data_val[inds[i]]), int(data_val[inds[i+1]])] for i in range(nhd-1)], 0)):
|
|
621
|
+
# if np.any([kernel_pow[data_ind[inds[i+1]]-data_ind[inds[i]], int(data_val[inds[i]]), int(data_val[inds[i+1]])] < 1.e-20 for i in range(nhd-1)]):
|
|
622
|
+
err_msg = f'{fname}: invalid conditioning points wrt. kernel'
|
|
623
|
+
if logger: logger.error(err_msg)
|
|
624
|
+
raise MarkovChainError(err_msg)
|
|
625
|
+
|
|
626
|
+
# Generate X
|
|
627
|
+
x = []
|
|
628
|
+
for _ in range(nreal):
|
|
629
|
+
# Generate one chain (realization)
|
|
630
|
+
# Initialization
|
|
631
|
+
xk = - np.ones(nsteps, dtype='int')
|
|
632
|
+
xk[data_ind] = data_val
|
|
633
|
+
#
|
|
634
|
+
# Random numbers in [0,1[
|
|
635
|
+
u = np.random.random(size=nsteps)
|
|
636
|
+
#
|
|
637
|
+
# Simulate in reverse order the values before the first conditioning point (sorted)
|
|
638
|
+
for i in range(data_ind[inds[0]]-1, -1, -1):
|
|
639
|
+
xk[i] = np.where(u[i] < kernel_rev_cdf[int(xk[i+1])])[0][0]
|
|
640
|
+
# Simulate the values between the pairs of consecutive conditioning points (sorted)
|
|
641
|
+
# With k1 < k2 < k3, we have:
|
|
642
|
+
# Prob(x[k2]=i2 | x[k1]=i1, x[k3]=i3) = kernel^(k2-k1)[i1, i2] * kernel^(k3-k2)[i2, i3] / kernel^(k3-k1)[i1, i3]
|
|
643
|
+
for ii in range(nhd-1):
|
|
644
|
+
# Simulate the values between the ii-th and (ii+1)-th conditioning points (sorted)
|
|
645
|
+
xend_ind = data_ind[inds[ii+1]]
|
|
646
|
+
xend_val = int(data_val[inds[ii+1]])
|
|
647
|
+
for i in range(data_ind[inds[ii]]+1, data_ind[inds[ii+1]]):
|
|
648
|
+
prob = np.array([kernel_pow[1, int(xk[i-1]), j]*kernel_pow[xend_ind-i, j, xend_val]/kernel_pow[xend_ind-i+1, int(xk[i-1]), xend_val] for j in range(n)])
|
|
649
|
+
cdf = np.cumsum(prob)
|
|
650
|
+
# if i==11:
|
|
651
|
+
# print(k, i, kernel_pow[xend_ind-i+1, int(xk[i-1]), xend_val], prob, cdf)
|
|
652
|
+
xk[i] = np.where(u[i] < cdf)[0][0]
|
|
653
|
+
# Simulate the values after the last conditioning point (sorted)
|
|
654
|
+
for i in range(data_ind[inds[-1]]+1, nsteps):
|
|
655
|
+
xk[i] = np.where(u[i] < kernel_cdf[int(xk[i-1])])[0][0]
|
|
656
|
+
|
|
657
|
+
# Append realization to x
|
|
658
|
+
x.append(xk)
|
|
659
|
+
|
|
660
|
+
x = np.asarray(x)
|
|
661
|
+
|
|
662
|
+
# Set original values
|
|
663
|
+
x = categVal[x]
|
|
664
|
+
|
|
665
|
+
return x
|
|
666
|
+
# ----------------------------------------------------------------------------
|