"""Solve a random maze with Markovian Decision Process""" # ----------------------------------------------------------------------------- # Copyright 2019 (C) Nicolas P. Rougier & Anthony Strock # Released under a BSD two-clauses license # # References: Bellman, Richard (1957), A Markovian Decision Process. # Journal of Mathematics and Mechanics. Vol. 6, No. 5. # ----------------------------------------------------------------------------- #https://github.com/rougier/ML-Recipes/blob/master/recipes/MDP/value-iteration.py #https://en.wikipedia.org/wiki/Markov_decision_process import numpy as np from scipy.ndimage import generic_filter def maze(shape=(30, 50), complexity=0.8, density=0.8): shape = (np.array(shape)//2)*2 + 1 n_complexity = int(complexity*(shape[0]+shape[1])) n_density = int(density*(shape[0]*shape[1])) Z = np.ones(shape, dtype=bool) Z[1:-1, 1:-1] = 0 P = (np.dstack([np.random.randint(0, shape[0]+1, n_density), np.random.randint(0, shape[1]+1, n_density)])//2)*2 for (y,x) in P.squeeze(): Z[y, x] = 1 for j in range(n_complexity): neighbours = [] if x > 1: neighbours.append([(y, x-1), (y, x-2)]) if x < shape[1]-2: neighbours.append([(y, x+1), (y, x+2)]) if y > 1: neighbours.append([(y-1, x), (y-2, x)]) if y < shape[0]-2: neighbours.append([(y+1, x), (y+2, x)]) if len(neighbours): next_1, next_2 = neighbours[np.random.randint(len(neighbours))] if Z[next_2] == 0: Z[next_1] = Z[next_2] = 1 y, x = next_2 else: break return Z def solve(Z, start, goal): Z = 1 - Z G = np.zeros(Z.shape) G[start] = 1 # We iterate until value at exit is > 0. This requires the maze # to have a solution or it will be stuck in the loop. def diffuse(Z, gamma=0.99): return max(gamma*Z[0], gamma*Z[1], Z[2], gamma*Z[3], gamma*Z[4]) while G[goal] == 0.0: G = Z * generic_filter(G, diffuse, footprint=[[0, 1, 0], [1, 1, 1], [0, 1, 0]]) # Descent gradient to find shortest path from entrance to exit y, x = goal dirs = (0,-1), (0,+1), (-1,0), (+1,0) P = [] while (x, y) != start: P.append((y,x)) neighbours = [-1, -1, -1, -1] if x > 0: neighbours[0] = G[y, x-1] if x < G.shape[1]-1: neighbours[1] = G[y, x+1] if y > 0: neighbours[2] = G[y-1, x] if y < G.shape[0]-1: neighbours[3] = G[y+1, x] a = np.argmax(neighbours) x, y = x + dirs[a][1], y + dirs[a][0] P.append((y,x)) return P, G def show_solution3d(S, start, goal): from vedo import Text3D, Cube, Line, Grid, merge, show pts, cubes, txts = [], [], [] pts = [(x,-y) for y,x in S[0]] for y,line in enumerate(Z): for x,c in enumerate(line): if c: cubes.append(Cube([x,-y,0])) path = Line(pts).lw(6).c('red5') walls = merge(cubes).flat().c('orange1') sy, sx = S[1].shape gradient = np.flip(S[1], axis=0).ravel() grd = Grid(pos=((sx-1)/2, -(sy-1)/2, -0.49), s=[sx,sy], res=[sx,sy]) grd.lw(0).wireframe(False).cmap('gist_earth_r', gradient, on='cells') grd.add_scalarbar('Gradient', horizontal=True, c='k', nlabels=2) txts.append(__doc__) txts.append(Text3D('Start', pos=[start[1]-1,-start[0]+1.5,1], c='k')) txts.append(Text3D('Goal!', pos=[goal[1] -2,-goal[0] -2.7,1], c='k')) return show(path, walls, grd, txts, axes=0, zoom=1.2) ########################################################################## if __name__ == '__main__': np.random.seed(4) Z = maze(shape=(50, 70)) start, goal = (1,1), (Z.shape[0]-2, Z.shape[1]-2) S = solve(Z, start, goal) show_solution3d(S, start, goal).close()