workcraft/workcraft

View on GitHub
workcraft/ParityPlugin/src/org/workcraft/plugins/parity/Zielonka.java

Summary

Maintainability
A
45 mins
Test Coverage
package org.workcraft.plugins.parity;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

/**
 * Zielonka game solver class.
 * Zielonka's algorithm is then applied to a SolvableGame,to generate the
 * winning regions for players 0 and 1, as well as the winning strategies for
 * both players.
 *
 * Zielonka's Algorithm is fully detailed in the paper:
 * 'Recursive algorithm for parity games requires exponential time'
 * by Oliver Friedmann.
 */
public class Zielonka {

    /**
     * Subtract the bit vector of an attractor set from the vertex array,
     * symbolising a new subgame. Also remove the edges from the adjMatrix for a
     * provided game.
     * @param game       Copy of current SolvableGame to be mutated
     * @param attrSet    Attractor set
     */
    static SolvableGame subtractAttr(SolvableGame game, Boolean[] attrSet) {

        //remove vertices
        for (int vIter = 0; vIter < game.vertex.length; ++vIter) {
            if (game.vertex[vIter] && attrSet[vIter]) {
                game.vertex[vIter] = false;
            }
        }
        //remove edges
        for (int outer = 0; outer < game.adjMatrix.length; ++outer) {
            for (int inner = 0; inner < game.adjMatrix[outer].length; ++inner) {
                if (game.adjMatrix[outer][inner]) {
                    for (int attrIter = 0; attrIter < attrSet.length; ++attrIter) {
                        if (attrSet[attrIter] && (attrIter == outer ||
                                attrIter == inner)) {
                            game.adjMatrix[outer][inner] = false;
                        }
                    }
                }
            }
        }
        return game;
    }

    /**
     * Given two pairs of winning regions, union them together.
     * @param firstWin     First winning region
     * @param secondWin    Second winning region
     * @return             The union of the two regions
     */
    static Boolean[] unionRegions(Boolean[] firstWin, Boolean[] secondWin) {
        for (int winIter = 0; winIter < firstWin.length; ++winIter) {
            firstWin[winIter] = firstWin[winIter] || secondWin[winIter];
        }
        return firstWin;
    }

    /**
     * Given two pairs of strategies, union them together. We assert these will
     * always be disjoint, and if they are not, then something has gone wrong
     * within the code logic.
     * @param firstStrat     First strategy
     * @param secondStrat    Second strategy
     * @return               The union of the two strategies
     */
    static Integer[] unionStrategies(Integer[] firstStrat,
            Integer[] secondStrat) {
        Integer[] resultStrat = new Integer[firstStrat.length];
        for (int stratIter = 0; stratIter < firstStrat.length; ++stratIter) {
            resultStrat[stratIter] = -1;
            if (firstStrat[stratIter] != -1) {
                resultStrat[stratIter] = firstStrat[stratIter];
            } else if (secondStrat[stratIter] != -1) {
                resultStrat[stratIter] = secondStrat[stratIter];
            }
        }
        return resultStrat;
    }

    /**
     * Zielonka's recursive algorithm, as defined within Friedmann's paper.
     * This function will return the winning regions for
     * players 0 and 1, and also their strategy. These will be returned as a
     * pair.
     * @param game    Current subgame that will be divided via recursion
     * @return        A List of two pairs: One for player 0, and one for
     *                player 1. Each pair contains the winning region, followed
     *                by the strategy.
     */
    static List<Entry<Boolean[], Integer[]>> solve(SolvableGame game) {

        List<Entry<Boolean[], Integer[]>> resultPair = new ArrayList<>();
        Boolean[] emptyVertices = new Boolean[game.vertex.length];
        Integer[] emptyStrat = new Integer[game.vertex.length];
        for (int gameIter = 0; gameIter < game.vertex.length; ++gameIter) {
            emptyVertices[gameIter] = false;
            emptyStrat[gameIter] = -1;
        }
        Entry<Boolean[], Integer[]> emptyPair = new SimpleEntry<>(emptyVertices,
                emptyStrat);
        resultPair.add(emptyPair);
        resultPair.add(emptyPair);

        /**
         * BASE CASE: When amount of vertices is 0, return empty winning
         * regions and empty strategies
         */
        if (game.isArrEmpty(game.vertex)) {
            return resultPair;
        }

        // Find maximal priority p in graph
        int largestPrio = game.getHighPrio();

        /**
         * player i will be equal to p mod 2.
         * This is player who wins with maximal priority in graph.
         * Also calculate set U, which by logic will be non-empty if we are at
         * this point.
         */
        boolean maxPlayer = (largestPrio & 1) == 1;
        int maxPlayerIndex = maxPlayer ? 1 : 0;
        Boolean[] largestPrioVertex = game.getHighPrioVertices(largestPrio);

        /**
         * Calculate attractor pair for Player i. We have no idea who this will
         * be at this point, but we know it generates the attractor set A and
         * strategy Tau Union TauPrime
         */
        Attractor attrSetA = new Attractor(game.adjMatrix, game.ownedBy,
                largestPrioVertex, game.vertex, maxPlayer);

        //game G' = G \ A. Calc attractor pairs here for G'
        SolvableGame gprime = subtractAttr(new SolvableGame(game),
                attrSetA.attrSet);
        List<Entry<Boolean[], Integer[]>> gprimeResult = solve(gprime);

        /**
         * check if winning_regions[1-i] is empty.
         * if it is:
         * a) Set attractor pair for i to be g.vertices, and set its strategy to
         *    be the union of what it is in gprimeResult with i_attr_pair
         * b) Set attractor pair for 1-i to be {EMPTY_SET, EMPTY_STRAT} like in
         *    the base case
         * c) Return this attractor pair
         */
        if (gprime.isArrEmpty(gprimeResult.get(1 - maxPlayerIndex).getKey())) {
            Entry<Boolean[], Integer[]> replacement = new SimpleEntry<>(
                    game.vertex, unionStrategies(
                    gprimeResult.get(maxPlayerIndex).getValue(),
                    attrSetA.strategy));
            resultPair.set(maxPlayerIndex, replacement);
            return resultPair;
        }

        /**
         * Calculate attractor pair for Player 1-i, using the winning region of
         * 1-i in gprimeResult as the target vertices.
         */
        Attractor attrSetB = new Attractor(game.adjMatrix, game.ownedBy,
                gprimeResult.get(1 - maxPlayerIndex).getKey(), game.vertex,
                !maxPlayer);

        //Game G'' = G \ B
        SolvableGame gprimeprime = subtractAttr(new SolvableGame(game),
                attrSetB.attrSet);
        List<Entry<Boolean[], Integer[]>> gprimeprimeResult =
                solve(gprimeprime);

        // Calculate attractor pairs for i and 1-i,
        resultPair.set(maxPlayerIndex, gprimeprimeResult.get(maxPlayerIndex));
        Boolean[] winRegion = unionRegions(gprimeprimeResult.get(
                1 - maxPlayerIndex).getKey(), attrSetB.attrSet);
        Integer[] winStrat = unionStrategies(gprimeprimeResult.get(
                1 - maxPlayerIndex).getValue(), attrSetB.strategy);
        winStrat = unionStrategies(winStrat, gprimeResult.get(
                1 - maxPlayerIndex).getValue());
        Entry<Boolean[], Integer[]> replacement = new SimpleEntry<>(winRegion,
                winStrat);
        resultPair.set(1 - maxPlayerIndex, replacement);

        return resultPair;
    }
}