Modeling Sequential Actions with Multiple Agents
We’re interested in modeling data generated by multiple users, so let’s extend our sequential planning setup to a setup where multiple users contribute actions to a partially shared goal. We won’t model agents thinking about each other (for now).
///fold:
var call = function(f, a, b, c, d) {
return f(a, b, c, d);
}
var moveFunctions = {
push: function(state, s) {
return state.concat([s]);
},
pop: function(state) {
return state.slice(1);
},
swap: function(state, i) {
var a = state[i];
var b = state[i + 1];
return state.slice(0, i).concat([b, a]).concat(state.slice(i + 2));
}
};
var randomSymbol = function() {
return uniformDraw(['a', 'b', 'c', 'd', 'e']);
};
var samplePush = function(state) {
return {
name: 'push',
data: randomSymbol()
}
};
var samplePop = function(state) {
return {
name: 'pop',
data: null
}
};
var sampleSwap = function(state) {
return {
name: 'swap',
data: randomInteger(state.length - 1)
}
};
var availableMoves = function(state) {
if (state.length >= 2) {
return [samplePush, samplePop, sampleSwap];
} else if (state.length >= 1) {
return [samplePush, samplePop];
} else {
return [samplePush];
}
};
var sampleMove = function(state) {
var sampler = uniformDraw(availableMoves(state));
return sampler(state);
};
var applyMove = function(state, move) {
var moveFunc = moveFunctions[move.name];
return moveFunc(state, move.data);
};
var applyMoves = function(state, moves) {
if (moves.length === 0) {
return state;
} else {
return applyMoves(applyMove(state, moves[0]), moves.slice(1));
}
};
var sampleMoves = function(state, n) {
if (n === 0) {
return [];
} else {
var move = sampleMove(state);
var newState = applyMove(state, move);
return [move].concat(sampleMoves(newState, n - 1));
}
};
var showMove = function(state, move, prefix) {
var nextState = applyMove(state, move);
print(
(prefix || "") +
JSON.stringify(state) +
" --{" + move.name +
((move.data !== null) ? " " + move.data : "") + "}--> " +
JSON.stringify(nextState));
}
var showMoves = function(state, moves) {
if (moves.length === 0) {
return;
} else {
var move = moves[0];
showMove(state, move);
var nextState = applyMove(state, move);
return showMoves(nextState, moves.slice(1));
}
};
wpEditor.put('sampleMoves', sampleMoves);
wpEditor.put('applyMoves', applyMoves);
wpEditor.put('showMoves', showMoves);
///
var targetState = ['a', 'b', 'c', 'd'];
var agents = [
{
name: 'random agent',
move: function(state){
return sampleMove(state);
},
probability: .3
},
{
name: 'goal-directed agent',
move: function(state) {
var n = Math.abs(targetState.length) * 2;
var moves = Infer({method: 'rejection'}, function() {
var i = randomInteger(n * 2);
var moves = sampleMoves(state, i);
var finalState = applyMoves(state, moves);
condition(_.isEqual(finalState, targetState));
return moves;
}).support()[0];
return moves[0];
},
probability: .7
}
];
var sampleAgent = function() {
// var ps = _.map(agents, 'probability');
// return agents[discrete(ps)]; -- `discrete` is currently broken
return uniformDraw(agents);
};
var generateMoves = function(state, n) {
if (n === 0) {
return [];
} else {
var agent = sampleAgent();
print(agent.name + " acting");
var move = call(agent.move, state);
var nextState = applyMove(state, move)
var datum = {
agent: agent,
move: move
};
return [datum].concat(generateMoves(nextState, n - 1));
}
};
var showTrace = function(state, trace) {
if (trace.length === 0) {
return;
} else {
var t = trace[0];
showMove(state, t.move, t.agent.name + ": ");
var nextState = applyMove(state, t.move);
return showTrace(nextState, trace.slice(1));
}
};
var trace = generateMoves([], 20);
showTrace([], trace);
Our goal-directed agent isn’t very goal directed yet, though—it doesn’t attempt to minimize the number of moves, which results in a lot of random moves, even from the goal-directed agent.