/*
* JASA Java Auction Simulator API
* Copyright (C) 2013 Steve Phelps
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package net.sourceforge.jabm.gametheory;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import net.sourceforge.jabm.report.AggregatePayoffMap;
import net.sourceforge.jabm.report.DataWriter;
import net.sourceforge.jabm.report.PayoffMap;
import net.sourceforge.jabm.strategy.Strategy;
import net.sourceforge.jabm.util.BaseNIterator;
import net.sourceforge.jabm.util.MathUtil;
import net.sourceforge.jabm.util.Partitioner;
import org.apache.commons.math3.stat.descriptive.StatisticalSummary;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.apache.log4j.Logger;
import cern.jet.math.Arithmetic;
/**
* @author Steve Phelps
* @version $Revision: 325 $
*/
public class CompressedPayoffMatrix implements Serializable {
protected HashMap<Entry,PayoffMap> matrix;
protected int numRoles;
protected int numStrategiesPerRole[];
protected int numPlayersPerRole[];
protected int numPlayers;
protected int numStrategies;
protected int numEntries;
static Logger logger = Logger.getLogger(CompressedPayoffMatrix.class);
public CompressedPayoffMatrix(int numRoles, int[] numStrategiesPerRole,
int[] numPlayersPerRole, List<Strategy> strategies) {
numEntries = 0;
this.numRoles = numRoles;
this.numPlayersPerRole = numPlayersPerRole;
this.numStrategiesPerRole = numStrategiesPerRole;
for (int i = 0; i < numRoles; i++) {
numPlayers += numPlayersPerRole[i];
numStrategies += numStrategiesPerRole[i];
}
matrix = new HashMap<Entry,PayoffMap>();
OrderedEntryIterator i = new OrderedEntryIterator();
while (i.hasNext()) {
Entry e = i.next();
matrix.put(e, new AggregatePayoffMap(strategies));
}
}
public CompressedPayoffMatrix(List<Strategy> strategies, int numPlayers) {
this(1, new int[] { strategies.size() }, new int[] { numPlayers },
strategies);
}
public int size() {
return matrix.keySet().size();
}
public PayoffMap getCompressedPayoffs(Entry entry) {
return matrix.get(entry);
}
// public void updateWithPayoffs(Entry entry, PayoffMap compressedPayoffs) {
// //TODO
// }
//
// public void reset() {
// Iterator<Entry> i = compressedEntryIterator();
// while (i.hasNext()) {
// Entry e = i.next();
// PayoffMap p = getCompressedPayoffs(e);
// p.reset();
// }
// }
public Iterator<Entry> compressedEntryIterator() {
return matrix.keySet().iterator();
}
public Iterator<Entry> orderedEntryIterator() {
return new OrderedEntryIterator();
}
public Iterator<FullEntry> fullEntryIterator() {
final CompressedPayoffMatrix matrix = this;
return new Iterator<FullEntry>() {
BaseNIterator b = new BaseNIterator(numStrategies, numPlayers);
public boolean hasNext() {
return b.hasNext();
}
public FullEntry next() {
return new FullEntry(b.next(), matrix);
}
public void remove() {
}
};
}
public double[] getFullPayoffs(FullEntry entry) {
Entry compressedEntry = entry.compress();
PayoffMap p = getCompressedPayoffs(compressedEntry);
double[] fullPayoffs = new double[numPlayers];
for (int i = 0; i < numPlayers; i++) {
fullPayoffs[i] = p.getMeanPayoff(entry.getStrategy(i));
}
return fullPayoffs;
}
public double payoff(double[] mixedStrategy) {
double payoff = 0;
for (int s = 0; s < mixedStrategy.length; s++) {
payoff += mixedStrategy[s] * payoff(s, mixedStrategy);
}
return payoff;
}
public double payoff(int strategy, double[] mixedStrategy) {
if (mixedStrategy[strategy] == 0) {
return 0;
}
assert MathUtil.approxEqual(MathUtil.sum(mixedStrategy), 1, 1E-10);
@SuppressWarnings("all")
double totalProbability = 0;
double payoff = 0;
Iterator<Entry> entries = compressedEntryIterator();
iterating: while (entries.hasNext()) {
Entry entry = entries.next();
// double[] payoffs = getCompressedPayoffs(entry).getPayoffs();
PayoffMap p = getCompressedPayoffs(entry);
if (entry.getNumAgents(strategy) == 0) {
continue iterating;
}
entry = entry.removeSingleAgent(strategy);
double probability = 1;
for (int s = 0; s < numStrategies; s++) {
probability *= Math.pow(mixedStrategy[s], entry.getNumAgents(s));
}
probability *= entry.permutations();
assert probability <= 1 && probability >= 0;
totalProbability += probability;
double expectedPayoffToStrategy = p.getMeanPayoff(strategy);
if (Double.isNaN(expectedPayoffToStrategy)) {
expectedPayoffToStrategy = 0;
}
payoff += probability * expectedPayoffToStrategy;
}
return payoff;
}
public static CompressedPayoffMatrix readFromFile(String fileName) {
CompressedPayoffMatrix result = null;
try {
ObjectInputStream ois;
ois = new ObjectInputStream(new FileInputStream(fileName));
result = (CompressedPayoffMatrix) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
public void export(DataWriter out) {
Iterator<Entry> entries = orderedEntryIterator();
while (entries.hasNext()) {
Entry entry = entries.next();
for (int s = 0; s < numStrategies; s++) {
out.newData(entry.getNumAgents(s));
}
PayoffMap payoffs = getCompressedPayoffs(entry);
for (int i = 0; i < payoffs.size(); i++) {
StatisticalSummary payoffStats = payoffs
.getPayoffDistribution(i);
out.newData(payoffStats.getMean());
out.newData(payoffStats.getStandardDeviation());
out.newData(payoffStats.getN());
}
}
}
public void exportToGambit(PrintWriter nfgOut) {
exportToGambit(nfgOut, "JASA NFG");
}
public void exportToGambit(PrintWriter nfgOut, String title) {
nfgOut.print("NFG 1 R \"" + title + "\" { ");
for (int i = 0; i < numPlayers; i++) {
nfgOut.print("\"Player" + (i + 1) + "\" ");
}
nfgOut.println("}");
nfgOut.println();
nfgOut.print("{ ");
for (int i = 0; i < numPlayers; i++) {
nfgOut.print("{ ");
for (int j = 0; j < numStrategies; j++) {
nfgOut.print("\"Strategy" + j + "\" ");
}
nfgOut.println("}");
}
nfgOut.println("}");
nfgOut.println("\"\"");
nfgOut.println();
nfgOut.println("{");
int numEntries = 0;
Iterator<FullEntry> entries = fullEntryIterator();
while (entries.hasNext()) {
nfgOut.print("{ \"");
FullEntry fullEntry = entries.next();
for (int i = numPlayers - 1; i >= 0; i--) {
nfgOut.print(fullEntry.getStrategy(i) + 1);
}
nfgOut.print("\" ");
double[] outcome = getFullPayoffs(fullEntry);
for (int i = 0; i < outcome.length; i++) {
double payoff = outcome[i];
if (Double.isNaN(payoff)) {
nfgOut.print("0");
} else {
nfgOut.print(payoff);
}
if (i < outcome.length - 1) {
nfgOut.print(",");
}
nfgOut.print(" ");
}
nfgOut.println("}");
numEntries++;
}
nfgOut.println("}");
for (int i = 1; i <= numEntries; i++) {
nfgOut.print(i);
if (i < numEntries) {
nfgOut.print(" ");
}
}
nfgOut.flush();
}
public int getNumStrategies() {
return numStrategies;
}
public int getNumPlayers() {
return numPlayers;
}
public int getNumPlayersPerRole(int role) {
return numPlayersPerRole[role];
}
public int getNumStrategiesPerRole(int role) {
return numStrategiesPerRole[role];
}
public int getNumEntries() {
return numEntries;
}
class OrderedEntryIterator implements Iterator<Entry>, Serializable {
private Partitioner[] p;
ArrayList<int[]> state;
public OrderedEntryIterator() {
p = new Partitioner[numRoles];
state = new ArrayList<int[]>(numRoles);
for (int i = 0; i < numRoles; i++) {
p[i] = new Partitioner(numPlayersPerRole[i], numStrategiesPerRole[i]);
state.add(i, null);
}
}
public boolean hasNext() {
for (int i = 0; i < numRoles; i++) {
if (p[i].hasNext()) {
return true;
}
}
return false;
}
public Entry next() {
for (int i = 0; i < numRoles - 1; i++) {
if (state.get(i) == null) {
state.set(i, p[i].next());
}
}
nextState(numRoles - 1);
int[] entry = new int[numStrategies];
int strategy = 0;
for (int i = 0; i < numRoles; i++) {
for (int s = 0; s < numStrategiesPerRole[i]; s++) {
entry[strategy++] = state.get(i)[s];
}
}
return new Entry(entry);
}
public void remove() {
}
protected void nextState(int role) {
if (p[role].hasNext()) {
state.set(role, p[role].next());
} else {
p[role] = new Partitioner(numPlayersPerRole[role],
numStrategiesPerRole[role]);
state.set(role, p[role].next());
if (role > 0) {
nextState(role - 1);
}
}
}
}
public static class Entry implements Cloneable, Serializable {
protected int[] numAgentsPerStrategy;
public Entry(int[] numAgentsPerStrategy) {
this.numAgentsPerStrategy = numAgentsPerStrategy;
}
public int getNumAgents(int strategy) {
return numAgentsPerStrategy[strategy];
}
public Object clone() throws CloneNotSupportedException {
Entry newEntry = (Entry) super.clone();
newEntry.numAgentsPerStrategy = this.numAgentsPerStrategy.clone();
return newEntry;
}
public Entry removeSingleAgent(int strategy) {
try {
Entry entry = (Entry) clone();
if (numAgentsPerStrategy[strategy] > 0) {
entry.numAgentsPerStrategy[strategy]--;
}
return entry;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public String toString() {
StringBuilder result = new StringBuilder("");
int numStrategies = numAgentsPerStrategy.length;
for (int i = 0; i < numStrategies - 1; i++) {
result.append(numAgentsPerStrategy[i]).append("/");
}
result.append(numAgentsPerStrategy[numStrategies - 1]);
return result.toString();
}
public long permutations() {
int numAgents = 0;
for (int i = 0; i < numAgentsPerStrategy.length; i++) {
numAgents += numAgentsPerStrategy[i];
}
long r = 1;
for (int i = 0; i < numAgentsPerStrategy.length; i++) {
r *= Arithmetic.factorial(numAgentsPerStrategy[i]);
}
return ((long) Arithmetic.factorial(numAgents)) / r;
}
public boolean equals(Object other) {
for (int i = 0; i < numAgentsPerStrategy.length; i++) {
if (this.numAgentsPerStrategy[i] != ((Entry) other).numAgentsPerStrategy[i]) {
return false;
}
}
return true;
}
public int hashCode() {
int radix = 0;
for (int i = 0; i < numAgentsPerStrategy.length; i++) {
radix += numAgentsPerStrategy[i];
}
int hash = 0;
int base = 1;
for (int i = 0; i < numAgentsPerStrategy.length; i++) {
hash += numAgentsPerStrategy[i] * base;
base *= radix;
}
return hash;
}
}
public static class FullEntry implements Serializable {
protected int[] pureStrategyProfile;
protected CompressedPayoffMatrix matrix;
public FullEntry(int[] pureStrategyProfile, CompressedPayoffMatrix matrix) {
this.pureStrategyProfile = pureStrategyProfile;
this.matrix = matrix;
}
public int getStrategy(int player) {
return pureStrategyProfile[player];
}
public Entry compress() {
int[] numAgentsPerStrategy = new int[matrix.getNumStrategies()];
for (int i = 0; i < matrix.getNumPlayers(); i++) {
numAgentsPerStrategy[getStrategy(i)]++;
}
return new Entry(numAgentsPerStrategy);
}
}
}