org.ggp.base.util.statemachine.StateMachine Maven / Gradle / Ivy
Show all versions of alloy-ggp-base Show documentation
package org.ggp.base.util.statemachine;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.ggp.base.util.GoalTuplePool;
import org.ggp.base.util.GoalTuplePool.GoalTuplePoolNode;
import org.ggp.base.util.gdl.grammar.Gdl;
import org.ggp.base.util.gdl.grammar.GdlConstant;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.propnet.architecture.Component;
import org.ggp.base.util.propnet.architecture.PropNet;
import org.ggp.base.util.statemachine.exceptions.GoalDefinitionException;
import org.ggp.base.util.statemachine.exceptions.MoveDefinitionException;
import org.ggp.base.util.statemachine.exceptions.TransitionDefinitionException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
/**
* Provides the base class for all state machine implementations.
*/
public abstract class StateMachine
{
// ============================================
// Stubs for implementations
// ============================================
// The following methods are required for a valid
// state machine implementation.
/**
* Initializes the StateMachine to describe the given game rules.
*
* This method should only be called once, and it should be called before any
* other methods on the StateMachine.
*/
public abstract void initialize(List description);
/**
* Returns the goal value for the given role in the given state. Goal values
* are always between 0 and 100.
*
* @throws GoalDefinitionException if there is no goal value or more than one
* goal value for the given role in the given state. If this occurs when this
* is called on a terminal state, this indicates an error in either the game
* description or the StateMachine implementation.
*/
public abstract int getGoal(MachineState state, Role role) throws GoalDefinitionException;
/**
* Returns true if and only if the given state is a terminal state (i.e. the
* game is over).
*/
public abstract boolean isTerminal(MachineState state);
/**
* Returns a list of the roles in the game, in the same order as they
* were defined in the game description.
*
* The result will be the same as calling {@link Role#computeRoles(List)}
* on the game rules used to initialize this state machine.
*/
public abstract List getRoles();
/**
* Returns the initial state of the game.
*/
public abstract MachineState getInitialState();
/**
* Returns a list containing every move that is legal for the given role in the
* given state.
*
* @throws MoveDefinitionException if the role has no legal moves. This indicates
* an error in either the game description or the StateMachine implementation.
*/
// TODO: There are philosophical reasons for this to return Set rather than List.
public abstract List getLegalMoves(MachineState state, Role role) throws MoveDefinitionException;
/**
* Returns the next state of the game given the current state and a joint move
* list containing one move per role.
*
* @param moves A list containing one move per role. The moves should be
* listed in the same order as roles are listed by {@link #getRoles()}.
* @throws TransitionDefinitionException indicates an error in either the
* game description or the StateMachine implementation.
*/
public abstract MachineState getNextState(MachineState state, List moves) throws TransitionDefinitionException;
// The following methods are included in the abstract StateMachine base so
// implementations which use alternative Role/Move/State representations
// can look up/compute what some Gdl corresponds to in their representation.
// They are implemented for convenience, using the default ways of generating
// these objects, but they can be overridden to support machine-specific objects.
public MachineState getMachineStateFromSentenceList(Set sentenceList) {
return new MachineState(sentenceList);
}
public Role getRoleFromConstant(GdlConstant constant) {
return new Role(constant);
}
public Move getMoveFromTerm(GdlTerm term) {
return new Move(term);
}
/*
* Should return a copy of the state machine that can be used in a synchronized
* fashion in conjunction with this state machine. Assume that initialize() has
* already been called on this machine, and return a machine as if initialize()
* has already been called on it.
*
* If the state machine doesn't depend on internal storage (e.g.
* ProverStateMachine), it may return itself.
*/
public abstract StateMachine getSynchronizedCopy();
// ============================================
// Stubs for advanced methods
// ============================================
//
// The following methods have functioning stubs,
// which can be overridden with full-fledged versions
// as needed by state machines. Clients should assume
// the contracts for these methods hold, regardless
// of the state machine implementation they pick.
/** Override this to perform some extra work (like trimming a cache) once per move.
*
* CONTRACT: Should be called once per move.
*/
public void doPerMoveWork() {}
/** Override this to provide memory-saving destructive-next-state functionality.
*
* CONTRACT: After calling this method, "state" should not be accessed.
*/
public MachineState getNextStateDestructively(MachineState state, List moves) throws TransitionDefinitionException {
return getNextState(state, moves);
}
/** Override this to allow the state machine to be conditioned on a particular current state.
* This means that the state machine will only handle portions of the game tree at and below
* the given state; it no longer needs to properly handle earlier portions of the game tree.
* This constraint can be used to optimize certain state machine implementations.
*
* CONTRACT: After calling this method, the state machine never deals with a state that
* is not "theState" or one of its descendants in the game tree.
*/
public void updateRoot(MachineState theState) {
;
}
// ============================================
// Implementations of convenience methods
// ============================================
public String getName() {
return this.getClass().getSimpleName();
}
/**
* Returns a list containing every joint move possible in the given state.
* A joint move consists of one move for each role, with the moves in the
* same ordering that their roles have in {@link #getRoles()}.
*
* The list of possible joint moves is the Cartesian product of the lists
* of legal moves available for each player.
*
* If only one player has more than one legal move, then the number of
* joint moves returned will equal the number of possible moves for that
* player.
*/
public List> getLegalJointMoves(MachineState state) throws MoveDefinitionException
{
List> legals = new ArrayList>();
for (Role role : getRoles()) {
legals.add(getLegalMoves(state, role));
}
List> crossProduct = new ArrayList>();
crossProductLegalMoves(legals, crossProduct, new ArrayDeque());
return crossProduct;
}
/**
* Returns a list of every joint move possible in the given state in which
* the given role makes the given move. This will be a subset of the list
* of joint moves given by {@link #getLegalJointMoves(MachineState)}.
*/
public List> getLegalJointMoves(MachineState state, Role role, Move move) throws MoveDefinitionException
{
List> legals = new ArrayList>();
for (Role r : getRoles()) {
if (r.equals(role)) {
List m = new ArrayList();
m.add(move);
legals.add(m);
} else {
legals.add(getLegalMoves(state, r));
}
}
List> crossProduct = new ArrayList>();
crossProductLegalMoves(legals, crossProduct, new ArrayDeque());
return crossProduct;
}
public List> getLegalMovesByRole(MachineState state) throws MoveDefinitionException {
List> legalMovesByRole = Lists.newArrayListWithCapacity(getRoles().size());
for (Role role : getRoles()) {
legalMovesByRole.add(getLegalMoves(state, role));
}
return legalMovesByRole;
}
/**
* Returns a list containing every possible next state of the game after
* the given state. The list will contain one entry for every possible
* joint move that could be played; as such, a single machine state could
* be included multiple times.
*/
public List getNextStates(MachineState state) throws MoveDefinitionException, TransitionDefinitionException
{
List nextStates = new ArrayList();
for (List move : getLegalJointMoves(state)) {
nextStates.add(getNextState(state, move));
}
return nextStates;
}
/**
* Returns a map from each move that is legal for the given role in
* the given state to the list of possible resulting states if that
* move is chosen.
*
* If the given role is the only role with more than one legal move,
* then each list of states in the map will only contain one state.
*/
public Map> getNextStates(MachineState state, Role role) throws MoveDefinitionException, TransitionDefinitionException
{
Map> nextStates = new HashMap>();
Map roleIndices = getRoleIndices();
for (List moves : getLegalJointMoves(state)) {
Move move = moves.get(roleIndices.get(role));
if (!nextStates.containsKey(move)) {
nextStates.put(move, new ArrayList());
}
nextStates.get(move).add(getNextState(state, moves));
}
return nextStates;
}
protected void crossProductLegalMoves(List> legals, List> crossProduct, Deque partial)
{
if (partial.size() == legals.size()) {
crossProduct.add(new ArrayList(partial));
} else {
for (Move move : legals.get(partial.size())) {
partial.addLast(move);
crossProductLegalMoves(legals, crossProduct, partial);
partial.removeLast();
}
}
}
private Map roleIndices = null;
/**
* Returns a mapping from a role to the index of that role, as in
* the list returned by {@link #getRoles()}. This may be a faster
* way to check the index of a role than calling {@link List#indexOf(Object)}
* on that list.
*/
public Map getRoleIndices()
{
if (roleIndices == null) {
ImmutableMap.Builder roleIndicesBuilder = ImmutableMap.builder();
List roles = getRoles();
for (int i = 0; i < roles.size(); i++) {
roleIndicesBuilder.put(roles.get(i), i);
}
roleIndices = roleIndicesBuilder.build();
}
return roleIndices;
}
/**
* Returns the goal values for each role in the given state. The goal values
* are listed in the same order the roles are listed in the game rules, which
* is the same order in which they're returned by {@link #getRoles()}.
*
* @throws GoalDefinitionException if there is no goal value or more than one
* goal value for any one role in the given state. If this occurs when this
* is called on a terminal state, this indicates an error in either the game
* description or the StateMachine implementation.
*/
public ImmutableList getGoals(MachineState state) throws GoalDefinitionException {
GoalTuplePoolNode curNode = GoalTuplePool.getInitialNode();
for (Role r : getRoles()) {
curNode = curNode.get(getGoal(state, r));
}
return curNode.getList();
}
/**
* Returns a random joint move from among all the possible joint moves in
* the given state.
*/
public List getRandomJointMove(MachineState state) throws MoveDefinitionException
{
List random = new ArrayList(getRoles().size());
for (Role role : getRoles()) {
random.add(getRandomMove(state, role));
}
return random;
}
/**
* Returns a random joint move from among all the possible joint moves in
* the given state in which the given role makes the given move.
*/
public List getRandomJointMove(MachineState state, Role role, Move move) throws MoveDefinitionException
{
List random = new ArrayList(getRoles().size());
for (Role r : getRoles()) {
if (r.equals(role)) {
random.add(move);
} else {
random.add(getRandomMove(state, r));
}
}
return random;
}
public List getRandomJointMoveWithAnyGebMoves(MachineState state) throws MoveDefinitionException {
Map gebMoves = getGebMoves(state);
return getRandomJointMoveWithMoves(state, gebMoves);
}
public List getRandomJointMoveWithMoves(MachineState state,
Map specifiedMoves) throws MoveDefinitionException {
List moves = new ArrayList(getRoles().size());
for (Role r : getRoles()) {
Move suggestedMove = specifiedMoves.get(r);
if (suggestedMove != null) {
moves.add(suggestedMove);
} else {
moves.add(getRandomMove(state, r));
}
}
return moves;
}
/**
* Returns a random move from among the possible legal moves for the
* given role in the given state.
*/
public Move getRandomMove(MachineState state, Role role) throws MoveDefinitionException
{
List legals = getLegalMoves(state, role);
return legals.get(new Random().nextInt(legals.size()));
}
/**
* Returns a state chosen at random from the possible next states of the
* game.
*
* The distribution among states is based on the possible joint moves.
* This is not necessarily uniform among the possible states themselves,
* as multiple joint moves may result in the same state.
*/
public MachineState getRandomNextState(MachineState state) throws MoveDefinitionException, TransitionDefinitionException
{
List random = getRandomJointMove(state);
return getNextState(state, random);
}
/**
* Returns a random next state of the game from the possible next states
* resulting from the given role playing the given move.
*
* The distribution among states is based on the possible joint moves.
* This is not necessarily uniform among the possible states themselves,
* as multiple joint moves may result in the same state.
*
* If the given role is the only role with more than one legal move, then
* there is only one possible next state for this method to return.
*/
public MachineState getRandomNextState(MachineState state, Role role, Move move) throws MoveDefinitionException, TransitionDefinitionException
{
List random = getRandomJointMove(state, role, move);
return getNextState(state, random);
}
/**
* Returns goals from a terminal state derived from repeatedly making random joint moves
* until reaching the end of the game.
*
* @param theDepth an integer array, the 0th element of which will be set to
* the number of state changes that were made to reach a terminal state.
*/
public ImmutableList performDepthCharge(MachineState state, final int[] theDepth) throws TransitionDefinitionException, MoveDefinitionException, GoalDefinitionException {
int nDepth = 0;
while(!isTerminal(state)) {
nDepth++;
state = getNextStateDestructively(state, getRandomJointMove(state));
}
if(theDepth != null)
theDepth[0] = nDepth;
return getGoals(state);
}
// public DepthChargeBatchResult performDepthChargeBatch2(MachineState state) throws TransitionDefinitionException, MoveDefinitionException, GoalDefinitionException {
// int[] theDepth = new int[1];
// ImmutableList goals = performDepthCharge(state, theDepth);
// return new FlatDepthChargeResult(goals, 1);
// }
public abstract Map getGebMoves(MachineState state);
public abstract MachineState translateState(MachineState state);
public abstract boolean isNative(MachineState state);
public void getAverageDiscountedScoresFromRepeatedDepthCharges(final MachineState state, final double[] avgScores, final double[] avgDepth, final double discountFactor, final int repetitions) throws TransitionDefinitionException, MoveDefinitionException, GoalDefinitionException {
avgDepth[0] = 0;
for (int j = 0; j < avgScores.length; j++) {
avgScores[j] = 0;
}
final int[] depth = new int[1];
for (int i = 0; i < repetitions; i++) {
MachineState stateForCharge = state.getCopy();
List goalValues = performDepthCharge(stateForCharge, depth);
avgDepth[0] += depth[0];
final double accumulatedDiscountFactor = Math.pow(discountFactor, depth[0]);
for (int j = 0; j < avgScores.length; j++) {
avgScores[j] += goalValues.get(j) * accumulatedDiscountFactor;
}
}
avgDepth[0] /= repetitions;
for (int j = 0; j < avgScores.length; j++) {
avgScores[j] /= repetitions;
}
}
public abstract boolean isPropNetBased();
public abstract PropNet getPropNet();
public abstract boolean getComponentValue(MachineState state, Component component);
public abstract int getComponentTrueInputsCount(MachineState state, Component component);
}