1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| def markov(mat, init, nbr=0, seed=0):
"""
Generator yielding elements of Markov suite, given a (square) matrix of
changes probabilities, and an initial state.
Note: Here, states are assumed null or positive integers only!
If nbr is a positive integer, yields only that number of elements, else
yield infinitely.
seed is used to initialize the random generator, so that you will always
get the same suite if you give the same mat, init and seed.
"""
# Initiate random generator.
import random
random.seed(seed)
# This function returns the index in the weights table corresponding to
# the range into which the uniform random value lays.
# weights is assumed a list of length ordered elements, like that:
# [p1, p2,
, pn-1, 1.0]
def _weighted_random(weights, length):
val = random.random()
# Note: this implements a dichotomous search, for fun...
split = idx = length // 2
while 1:
split = max(1, split // 2)
if val > weights[idx]:
idx += split
elif idx and val < weights[idx - 1]:
idx -= split
else:
return idx
# Builds a custom matrix, more suited than the raw one for our algo.
# Also checks the given one is square!
n = len(mat)
_mat = [[0.0] * n for i in range(n)]
for i, line in enumerate(mat):
if len(line) != n:
raise ValueError("Invalid probability matrix (line {}) is not "
"same length as matrixs high ({})!"
"".format(i, n))
tot = sum(line)
sum_prob = 0.0
for j, prob in enumerate(line):
sum_prob += prob
_mat[i][j] = sum_prob / tot
# Now, _mat is in form [[0.2, 0.7, 1.0],
# [0.4, 0.7, 1.0],
# [0.3, 0.5, 1.0]]
# Start yielding elements!
# XXX States (elements) are assumed null or positive integers only, else
# we would need a mapping to convert from their real value to/from numeric
# indices...
nbr_done = 0.0
el = init
yield el # Generate also the init element!
while nbr_done < nbr:
# Get a new random element, weighted by the current one's
# probabilities...
el = _weighted_random(_mat[el], n)
yield el
nbr_done += 1
# Test code!
import time
mat = [[0.2, 0.4, 0.4],
[0.5, 0.2, 0.3],
[0.3, 0.3, 0.4]]
init_el = 0 # First state.
print(list(markov(mat, init_el, nbr=10, seed=time.time())))
init_el = 1 # Second state.
print(list(markov(mat, init_el, nbr=10, seed=time.time())))
init_el = 2 # Third state.
print(list(markov(mat, init_el, nbr=10, seed=time.time()))) |
Partager