我想模拟具有给定转移矩阵输入的离散马尔可夫链,并获得显示访问所有状态的频率的直方图。 这就是我得到的:markov chain simulation
我受到这项工作的启发:
http://setosa.io/markov/index.html#%7B%22tm%22%3A%5B%5B0.5%2C0.5%5D%2C%5B0.5%2C0.5%5D%5D%7D
这是html / javascript代码:
<!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] === i
&& $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: i };
});
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>
&#13;
和common.js
myApp.factory('utils', function() {
var utils = {};
utils.getHash = function() {
var hash = window.location.hash;
if(!hash) return;
hash = hash.slice(1); // remove the '#'
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 i = 0; i < probs.length; i++) {
t = t + probs[i];
if (r <= t) return i;
}
throw new Error('invalid distribution');
};
utils.normalizeTransitionMatrix = function(matrix, idx1, idx2) {
// The next states this state will transition to.
var states = matrix[idx1];
// Convert to numbers.
states.forEach(function(d, i){ states[i] = +d; });
// We need to re-normalize the transitions for each state so that they
// add to one.
// `val` - The 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` - The 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 the remaining states and then multiply by the 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 - the index of the currently state
// transitions - an array of the next state probabilities were
// each index in the array coorisponds to a 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 i = 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'
};
});