i want simulate discrete markov chain given transition matrix input , histogram showing frequencies of visiting states. here : markov chain simulation
i inspired work :
http://setosa.io/markov/index.html#%7b%22tm%22%3a%5b%5b0.5%2c0.5%5d%2c%5b0.5%2c0.5%5d%5d%7d
here html/javascript code:
<!doctype html> <html> <head> <title>markov chains</title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1"> <link href="style.css" type="text/css" rel="stylesheet"> <script src="vector.js"></script> <script src="d3.min.js"></script> <script src="angular.min.js" charset="utf-8"></script> <style> body { background-color: #222; color: white; } .controls { position: absolute; top: 100px; right: 10px; } .st-diagram { pointer-events: none; position: absolute; left: 0; width: 100%; z-index: 1; } .st-diagram .nodes { pointer-events: all; } .matrixinput { display: block; height: 100%; width: 40%; top: 50px; right: 50px; position: absolute; } .matrixinput textarea{ border: none; background-color: transparent; color: red; width: 100%; height: 100%; font-size: 20px; outline: none; } .matrixinput textarea.valid { color: white; } .matrix table { width: 100%; height: 100%; text-align: center; table-layout: fixed; } .matrix table td { width: 33.33%; } .matrix table td input { pointer-events: all; width: 80%; } </style> </head> <body> <div class="title-area"> <h1 <center> markov chains </center> </h2> <title rows="4" cols="50"> </title> <body ng-app="myapp" ng-controller="mainctrl"> <st-diagram center="diagramcenter" states="states" transition-matrix="transitionmatrix" duration="duration" state="state" selected-transition="selected.transition"></st-diagram> <div class="sequence" step-1="70%" step-2="60%"></div> <div class="matrixinput"> <p> matrice de transition </p> <textarea ng-class="{ 'valid' : validtransitionmatrix }" ng-model="transitionmatrixjson">{{transitionmatrix | json}}</textarea> </div> <div class="controls"> <input class="speedrange" type="range" ng-model="speedrange" min="1" max="10" step="1"> <label> vitesse </label> </div> <script> var myapp = angular.module('myapp', []); myapp.controller('mainctrl', function($scope, utils, $window) { angular.element($window).on('resize', function() { $scope.$apply(); }); $scope.diagramcenter = [0.8, 0.5]; $scope.isselectedtransition = function(i, j) { return !!$scope.selectedtransition; if (!$scope.selectedtransition) return false; return $scope.selectedtransition[0] === && $scope.selectedtransition[1] === j; }; $scope.speedrange = 2; $scope.$watch('speedrange', function(speed) { $scope.duration = 2000 / +speed; }); $scope.updatetransitionmatrix = function(matrix) { var prev = $scope.transitionmatrix; $scope.transitionmatrix = matrix; if($scope.states && matrix.length === prev.length) return; $scope.states = matrix.map(function(d, i) { return { label: string.fromcharcode(65 + i), index: }; }); utils.sethash({ tm: matrix }); }; var hash = utils.gethash(); if(hash && hash.tm) $scope.updatetransitionmatrix(hash.tm); else $scope.updatetransitionmatrix([[0.3,0.3,0.4], [0.3,0.5,0.2],[0.4,0.4,0.2]]); $scope.transitionmatrixjson = json.stringify($scope.transitionmatrix) .replace(',[', ',\n['); $scope.$watch('transitionmatrixjson', function(str) { var valid = false; try{ var matrix = json.parse(str); valid = matrix[0].length === matrix.length; var sum = matrix.reduce(function(c, row) { return c + row.reduce(function(p, c){ return p + c; }, 0); }, 0); var r = sum / matrix.length; valid = valid && r < (1 + 1e-9) && r > (1 - 1e-9); if (valid) { $scope.updatetransitionmatrix(matrix); } }catch(e) {} $scope.validtransitionmatrix = valid; }); $scope.state = { current: null }; $scope.selected = { transition: null }; <!-- var labels = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']; var labels = ['a','b','c']; var color = d3.scale.category10(); $scope.states = labels.map(function(label, i) { return {text: label, label: label, index: i, color: color(label) }; }); }); </script> <script src="common.js" charset="utf-8"></script> </html>
and common.js
myapp.factory('utils', function() { var utils = {}; utils.gethash = function() { var hash = window.location.hash; if(!hash) return; hash = hash.slice(1); // remove '#' try { return json.parse(decodeuricomponent(hash)); }catch(e) {}; }; utils.sethash = function(obj) { window.location.hash = encodeuricomponent(json.stringify(obj)); }; utils.sample = function(probs) { var t = 0; var r = math.random(); for(var = 0; < probs.length; i++) { t = t + probs[i]; if (r <= t) return i; } throw new error('invalid distribution'); }; utils.normalizetransitionmatrix = function(matrix, idx1, idx2) { // next states state transition to. var states = matrix[idx1]; // convert numbers. states.foreach(function(d, i){ states[i] = +d; }); // need re-normalize transitions each state // add one. // `val` - selected next state value. var val = states[idx2]; if(val === 1) return states.foreach(function(d, i) { if(i === idx2) return; states[i] = 0; }); // `r` - remaining state probability. var r = states.reduce(function(total, state, i){ return total + (i === idx2 ? 0 : state); }, 0); if(r === 0) r = states.length - 1, states.foreach(function(d, i) { if(i === idx2) return; states[i] = 1; }); // normalize remaining states , multiply remaining // probability, `( 1 - val)`. states.foreach(function(d, i) { if(i === idx2) return; states[i] = states[i] / r * (1 - val); }); }; return utils; }); myapp.directive('stdiagram', function($compile) { function link(scope, el, attr) { el = d3.select(el[0]); calcresize(); var svg = el.select('svg'); var aligng = svg.append('g'); var centerg = aligng.append('g'); var color = d3.scale.category10(); var links = centerg.append('g').attr('class', 'links').selectall('paths'); var nodes = centerg.append('g').attr('class', 'nodes').selectall('g'); var markers = svg.append('defs').selectall('.linkmarker'); var currentstateg = centerg.append('g').attr('class', 'currentstate') .attr('transform', 'translate(' + [w / 2, h / 2] + ')') .style('opacity', 0); var w, h, r = 20; var linkelements = {}; var force = d3.layout.force() .linkdistance(function(d){ return w / 16 + (1 - d.value) * 200 * w / 1200 }) .charge(-4000); currentstateg .append('circle') .attr('r', 10); function calcresize() { return w = el.node().clientwidth, h = el.node().clientheight, w + h; } scope.$watch(calcresize, resize); scope.$watch('center', resize, true); scope.$watch('states', update, true); scope.$watch('transitionmatrix', update, true); scope.$watch('selectedtransition', update); function resize() { force.size([w, h]); svg.attr({width: w, height: h}); var center = scope.center; var cx = (center && angular.isdefined(center[0])) ? center[0] : 0.5; var cy = (center && angular.isdefined(center[1])) ? center[1] : 0.5; aligng.attr('transform', 'translate(' + [ w * cx, h * cy ] + ')'); centerg.attr('transform', 'translate(' + [ - w / 2, - h / 2] + ')'); } function update() { var linksdata = []; var enter; scope.transitionmatrix.foreach(function(transitions, idx1) { // idx1 - index of state // transitions - array of next state probabilities // each index in array coorisponds state in `scope.states`. transitions.foreach(function(prob, idx2) { if(prob === 0) return; linksdata.push({ source: scope.states[idx1], target: scope.states[idx2], value: +prob }); }); }); nodes = nodes.data(scope.states); enter = nodes.enter().append('g') .attr('class', 'node') .style('fill', function(d){ return color(d.index); }) .call(force.drag); enter.append('circle') .attr('r', r); enter.append('text') .attr('transform', 'translate(' + [0, 5] + ')') nodes.exit().remove(); var linkkey = function(d) { return (d.source && d.source.index) + ':' + (d.target && d.target.index); }; links = links.data(linksdata, linkkey) links.enter().append('path') .attr('marker-end', function(d) { if(!d.source || !d.target) debugger; return 'url(#linkmarker-' + d.source.index + '-' + d.target.index + ')'; }).classed('link', true) .style('stroke', function(d){ return color(d.source.index); }) links.exit().remove(); links.each(function(d, i) { linkelements[d.source.index + ':' +d.target.index] = this; var active = false, inactive = false; if (scope.selectedtransition) { active = scope.selectedtransition[0] === d.source.index && scope.selectedtransition[1] === d.target.index; inactive = !active; } d3.select(this) .classed('active', active) .classed('inactive', inactive); }); markers = markers.data(linksdata, linkkey); markers.enter().append('marker') .attr('class', 'linkmarker') .attr('id', function(d) { return 'linkmarker-' + d.source.index + '-' + d.target.index }) .attr('orient', 'auto') .attr({markerwidth: 2, markerheight: 4}) .attr({refx: 0, refy: 2}) .append('path') .attr('d', 'm0,0 v4 l2,2 z') .style('fill', function(d){ return color(d.source.index); }); markers.exit().remove(); force.nodes(scope.states) .links(linksdata) .start(); } force.on('tick', function() { var _r = r; links .style('stroke-width', function(d) { return math.sqrt(100 * d.value || 2); }) .attr('d', function(d) { var r = _r; var p1 = vector(d.source.x, d.source.y); var p2 = vector(d.target.x, d.target.y); var dir = p2.sub(p1); var u = dir.unit(); if(d.source !== d.target) { r *= 2; var right = dir.rot(math.pi /2).unit().scale(50); var m = p1.add(u.scale(dir.len() / 2)).add(right); u = p2.sub(m); l = u.len(); u = u.unit(); p2 = m.add(u.scale(l - r)); u = p1.sub(m); l = u.len(); u = u.unit(); p1 = m.add(u.scale(l - r)); return 'm' + p1.array() + 's' + m.array() + ' ' + p2.array(); }else{ var s = 50, rot = math.pi / 8; r = r * 1.5; p1 = p1.add(vector(1, -1).unit().scale(r - 10)) p2 = p2.add(vector(1, 1).unit().scale(r)) var c1 = p1.add(vector(1, 0).rot(-rot).unit().scale(s)); var c2 = p2.add(vector(1, 0).rot(rot).unit().scale(s - 10)); return 'm' + p1.array() + ' c' + c1.array() + ' ' + c2.array() + ' ' + p2.array(); } }); nodes.attr('transform', function(d) { return 'translate(' + [d.x, d.y] + ')'; }).select('text').text(function(d){ return d.label; }) }); var currentstate = 0; function loop() { var = currentstate; var nextstates = scope.transitionmatrix[i]; var nextstate = -1; var rand = math.random(); var total = 0; for(var j = 0; j < nextstates.length; j++) { total += nextstates[j]; if(rand < total) { nextstate = j; break; } } var cur = scope.states[currentstate]; var next = scope.states[nextstate]; var path = linkelements[cur.index + ':' + next.index]; scope.$apply(function() { scope.$emit('statechange', next); }); currentstateg .transition().duration(+scope.duration * 0.25) .style('opacity', 1) .ease('cubic-in') .attrtween('transform', function() { var m = d3.transform(d3.select(this).attr('transform')); var start = vector.apply(null, m.translate); var scale = m.scale; var s = d3.interpolatearray(scale, [1, 1]); return function(t) { var end = path.getpointatlength(0); end = vector(end.x, end.y); var p = start.add(end.sub(start).scale(t)); return 'translate(' + p.array() + ') scale(' + s(t) + ')'; }; }) .transition().duration(+scope.duration * 0.5) .ease('linear') .attrtween('transform', function() { var l = path.gettotallength(); return function(t) { var p = path.getpointatlength(t * l); return 'translate(' + [p.x, p.y] + ') scale(1)'; }; }) .transition().duration(+scope.duration * 0.25) .ease('bounce-in') .attrtween('transform', function() { var m = d3.transform(d3.select(this).attr('transform')); var translation = vector.apply(null, m.translate); var scale = m.scale; var s = d3.interpolatearray(scale, [2, 2]); return function(t) { var end = vector(next.x, next.y); var p = translation.add(end.sub(translation).scale(t)); return 'translate(' + p.array() + ') scale(' + s(t) + ')'; }; }) .each('end', function() { loop(); }) currentstate = nextstate; } settimeout(loop, +scope.duration); } return { link: link, restrict: 'e', replace: true, scope: { states: '=', center: '=', transitionmatrix: '=', duration: '=', selectedtransition: '=', state: '=?' }, template: '' + '<div class="st-diagram">' + '<svg>' + '</svg>' + '</div>' }; }); myapp.directive('cell', function() { function link(scope, el, attr) { scope.$parent.numcells++; scope.$on('destroy', function() { scope.$parent.numcells--; }); scope.$parent.$watch('width + height', function(){ scope.width = scope.$parent.width / scope.$parent.numcells; scope.height = scope.$parent.height; }); } return { link: link, restrict: 'e', replace: true, transclude: true, template: '<div class="cell"' + "ng-style=\"{width: width + 'px', height: height + 'px'}\"" + 'ng-transclude>' + '</div>' }; }); myapp.directive('row', function() { function prelink(scope, el, attr) { scope.numcells = 0; } function postlink(scope, el, attr) { scope.$parent.numrows++; scope.$on('destroy', function() { scope.$parent.numrows--; }); scope.$parent.$watch('width + height', function(){ scope.width = scope.$parent.width; scope.height = scope.$parent.height / scope.$parent.numrows; }); } return { link: { pre: prelink, post: postlink }, restrict: 'e', replace: true, transclude: true, template: '<div class="row"' + "ng-style=\"{width: width + 'px', height: height + 'px'}\"" + 'ng-transclude>' + '</div>' }; }); myapp.directive('grid', function() { function prelink(scope, el, attr, controllers) { scope.numrows = 0; } function postlink(scope, el, attr, controllers) { var sel = d3.select(el[0]); var w, h; scope.name = 'victor'; scope.numberofrows = 0; scope.$watch(function() { return w = sel.node().clientwidth, h = sel.node().clientheight, w + h; }, function() { scope.width = w, scope.height = h; }); } return { link: { pre: prelink, post: postlink }, restrict: 'e', transclude: true, replace: true, template: '' + '<div style="overflow:hidden" class="grid">' + '<div ng-transclude></div>' + '</div>' }; }); myapp.directive('sequence', function() { function link(scope, el, attr) { var sel = d3.select(el[0]); var w, h; scope.$watch(function() { return w = sel.node().clientwidth, h = sel.node().clientheight, w + h; }, function() { scope.width = w, scope.height = h; sel.style('line-height', h + 'px'); }); scope.$on('statechange', function(e, state) { sel.append('span').text(state.text).style('position', 'absolute') .style('color', state.color) .style('opacity', 0) .transition() .duration(2000) .ease('cubic-in') .style('opacity', 1) .styletween('left', function() { return d3.interpolate(attr.step1 || '90%', attr.step2 || '80%');}) .transition() .duration(15000) .ease('linear') .styletween('left', function() { return d3.interpolate(attr.step2 || '80%', '10%'); }) .remove(); }); } return { link: link, restrict: 'c' }; });
Comments
Post a Comment