I have a code that simulates a signal game between players (see code below). The game consists of 4 players and 4 signals, who play in pairs for 3 rounds (see def main()
).
Also defined in main()
, there are some variables in the game s1
and s2
(let's call them sigmas) that affect the dynamics of the game. So in def with_b
the variable s1
affects the equation used by players 1 and 2, and the variable s2
affects the equation used by players 3 and 4.
These variables s1
and s2
remain constant throughout the game as it is now: s1=[1,0,0,0]
ands2=[0,0,0,1]
In the game, for each ronda
, each jugador
generates a list aux
that represents the signal produced. Thus, each round, each player will produce his aux
, which may be one of the following: [1,0,0,0]
o [0,1,0,0]
o [0,0,1,0]
o [0,0,0,1]
.
Goal
We create two new lists: s_inicial_1
and s_inicial_2
.
What is intended now is that, for each round and player, if his aux
coincides with the list s_inicial
( s_inicial_1
in the case of players 1 and 2, s_inicial_2
in the case of players 3 and 4), the value of his sigma s1
or s2
within the game is modified to that specific player according to some correction factors.
For example. If in a given round player 1 generates aux1=[1,0,0,0]
, since this list matches s_inicial_1=[1,0,0,0]
, the value of s1
will be multiplied by cf_pos
. This way the new updated value s1
will operate for player 1 in the def function with_b
. If instead, player 1 generates aux1=[0,0,1,0]
, since this list does not match s_inicial_1=[1,0,0,0]
, the value of s1
(for that player) will be multiplied by cf_neg
. The values of s1
or s2
of each player will be updated cumulatively round by round.
s1 = [1,0,0,0]
s_inicial_1 = [1,0,0,0]
s2 = [0,0,0,1]
s_inicial_2 = [0,0,0,1]
cf_pos = 0.9
cf_neg = 0.1
For player 1:
if aux1 == s_inicial_1:
s1[:] = [x * cf_pos for x in s1]
else:
s1[:] = [x * cf_neg for x in s1]
For player 2:
if aux2 == s_inicial_1:
s1[:] = [x * cf_pos for x in s1]
else:
s1[:] = [x * cf_neg for x in s1]
For player 3:
if aux3 == s_inicial_2:
s2[:] = [x * cf_pos for x in s2]
else:
s2[:] = [x * cf_neg for x in s2]
For player 4:
if aux4 == s_inicial_2:
s2[:] = [x * cf_pos for x in s2]
else:
s2[:] = [x * cf_neg for x in s2]
One problem I see is that in the current code aux
I am generating it by writing the csv file not in the game itself. Also, since s1
y s2
are constant variables, I don't know how I could change them dynamically (from round to round) for each player without also changing their sigma value for the rest of the players. Let's say each should evolve their sigma independently.
Thank you very much in advance.
from __future__ import division
from random import random, sample
from bisect import bisect
from collections import deque
import csv
import math
s_inicial_1 = [1,0,0,0]
s_inicial_2 = [0,0,0,1]
class Partida():
def __init__(self, jugadores, menLen, emparejamientos, senales, s1, s2, b, x, m):
self.emparejamientos = emparejamientos
self.senales = senales
self.s1 = s1
self.s2 = s2
self.b = b
self.x = x
self.m = m
self.jugadores=jugadores
self.jugadores = {nombre: Partida.Jugador(menLen)
for emparejamiento in emparejamientos[0]
for nombre in emparejamientos}
self.memoria = list()
self.entropy = float()
def generar_senales(self):
def with_b(muestra, observa, s1, s2, r, nombre):
if nombre <=2:
if not (muestra == observa == 0):
result = ((0.98) * (1.0 - self.b) * (1.0 - self.x) * muestra / r) + (
(0.98) * (1.0 - self.b) * (self.x) * observa / r) + ((0.98) * self.b * s1) + ((self.m / 8))
else:
result = ((0.98) * (1.0 - 0) * (1.0 - self.x) * muestra / r) + (
(0.98) * (1.0 - 0) * (self.x) * observa / r) + ((0.98) * 0 * s1) + ((self.m / 8))
else:
if not (muestra == observa == 0):
result = ((0.98) * (1.0 - self.b) * (1.0 - self.x) * muestra / r) + (
(0.98) * (1.0 - self.b) * (self.x) * observa / r) + ((0.98) * self.b * s2) + ((self.m / 8))
else:
result = ((0.98) * (1.0 - 0) * (1.0 - self.x) * muestra / r) + (
(0.98) * (1.0 - 0) * (self.x) * observa / r) + ((0.98) * 0 * s2) + ((self.m / 8))
return result
def choice(opciones, probs):
probAcumuladas = list()
aux = 0
for p in probs:
aux += p
probAcumuladas.append(aux)
r = random() * probAcumuladas[-1]
op = bisect(probAcumuladas, r)
return opciones[op]
yield dict(zip(self.jugadores.keys(), self.senales))
r = 1
while True:
eleccs = dict.fromkeys(self.jugadores.keys())
for nombre, inst in self.jugadores.items():
probs = [with_b(inst.mem_mostradas[op], inst.men_observadas[op], self.s1[indx],self.s2[indx], r, nombre)
for indx, op in enumerate(self.senales)]
eleccs[nombre] = choice(self.senales, probs)
r += 1
yield eleccs
def jugar(self):
gen_sens = self.generar_senales()
for n, ronda in enumerate(self.emparejamientos):
senales = next(gen_sens)
self.memoria.append(senales)
for jugador1, jugador2 in ronda:
self.jugadores[jugador1].men_observadas[senales[jugador2]] += 1
self.jugadores[jugador2].men_observadas[senales[jugador1]] += 1
self.jugadores[jugador1].mem_mostradas[senales[jugador1]] += 1
self.jugadores[jugador2].mem_mostradas[senales[jugador2]] += 1
class Jugador():
def __init__(self, menLen):
self.mem_mostradas = deque(maxlen=menLen)
self.men_observadas = deque(maxlen=menLen)
def main():
jugadores = [1, 2, 3, 4]
senales = ['S1', 'S2', 'S3', 'S4']
emparejamientos = [[(1, 2), (3, 4)],
[(1, 3), (2, 4)],
[(1, 4), (2, 3)]]
patron = 1
menLen = 2
####SIGMAS####
s1 = [1, 0, 0, 0]
s2 = [0, 0, 0, 1]
muestras = [{'b': 0.0, 'x': 0.5, 'm': 0.02}]
muestras = [d for d in muestras for _ in range(1)]
simulaciones = 10
estadisticas = {sim: {jugador: {muestra: {senal: [0 for ronda in range(1, len(emparejamientos) + 1)]
for senal in senales}
for muestra in range(len(muestras))}
for jugador in jugadores}
for sim in range(simulaciones)}
for sim in range(simulaciones):
for mu in range(len(muestras)):
juego = Partida(jugadores, menLen, emparejamientos, senales, s1,s2, muestras[mu]['b'], muestras[mu]['x'],
muestras[mu]['m'])
juego.jugar()
for n, ronda in enumerate(juego.memoria):
for jugador, senal in ronda.items():
estadisticas[sim][jugador][mu][senal][n] += 1
with open('datos.csv','w', newline='') as csvfile:
writer = csv.writer(csvfile, delimiter=';',
quotechar='"', quoting=csv.QUOTE_MINIMAL)
writer.writerow(['Sim','Muestra', 'Jugador', 'Ronda', 'Patron', 'b', 'x', 'm'] + senales + ['sumpop'])
# Escribiendo las estadisticas para cada jugador, ronda y muestra
for jugador in jugadores:
for sim in range(simulaciones):
for mu in range(len(muestras)):
for ronda in range(1, len(emparejamientos) + 1):
aux = [estadisticas[sim][jugador][mu][senal][ronda - 1] for senal in senales]
aux1 = [estadisticas[sim][1][mu][senal][ronda - 1] for senal in senales]
aux2 = [estadisticas[sim][2][mu][senal][ronda - 1] for senal in senales]
aux3 = [estadisticas[sim][3][mu][senal][ronda - 1] for senal in senales]
aux4 = [estadisticas[sim][4][mu][senal][ronda - 1] for senal in senales]
print(aux)
# Lista que contiene los sumatorios de cada tipo de senales producidas a nivel de la poblacion global en cada muestra y ronda
summation_pop = []
for i in range(len(aux1)):
summation_pop.append(
aux1[i] + aux2[i] + aux3[i] + aux4[i])
writer.writerow([sim +1, mu + 1, jugador, ronda, patron, muestras[mu]['b'], muestras[mu]['x'],
muestras[mu]['m']] + aux + [summation_pop])
if __name__ == '__main__':
main()
Code edition (06/27/2019, 21:19 gtm +00)
- Code editing. The variable is simplified
muestras
to make data easier to read. The values{'b': 0.0, 'x': 0.5, 'm': 0.02}
allow more variation in the outcome of aux, since the equation implemented in thedef with_b
model drifts. - For tests: yes
'b': 1.0
ands1 = [1,0,0,0]
there is a high probability that players 1 and 2 produceaux = [1,0,0,0]
. If'b': 0.0
, there is more probability that the players will produce a different aux.
final update
- The answer is answered satisfactorily and the reward assigned. On this additional note I would like to add that the original code I shared here did not include an additional functionality that I had implemented in my old code: the ability to limit the memory size of agents to a given number of rounds using the variable
memLen
. The implementation requires loading a libraryfrom collections import deque
. I have edited the code provided in the question. Having changed so many things, I was wondering what would be the best way to implement similar functionality in the current code in the answer, whether in the Game class or the Player class.
Update
I delete my previous "answer", which was basically an extended comment, and replace it with the following, which is still not a definitive answer but it's getting close.
The first thing I have done has been to "refactor" your code, to separate into two classes
Jugador
andPartida
what was previously in one. The changes I have made are the following:Jugador
is now outside the classPartida
, and is more complex as it not only stores what the player has seen, but also their "sigma" and the parameters needed to assign probabilities to their options. That is, the functionwith_b
, which was previously "generic" and received many things as parameters, is now part of the player's behavior, so instead of receiving these things as parameters, it takes them from the player's attributes. This will allow in the future (not yet) for each player to modify their sigma.choice()
, which is general purpose and not game or player dependent, has been removed and left as a generic function, rather than as an inner function within another, which is less efficient (by causing redefinition of this inner function every time the outer one is executed)Partida
, in its initialization, creates the players and passes each their parameters (including their sigmas). To do this, it requires, instead of the parameterss1
ands2
from before, a dictionary whose keys are the names of the players and the values are the corresponding sigmas.generar_senales()
of thePartida
has been simplified quite a bit now, calling the methodwith_b
of each player.Partida
at startup.Incidentally I've passed all the code through the "black" autoformatter to make it consistent on things like spaces around assignments, breaking long lines properly, etc.
In the end, I changed so many things that I wasn't sure if it would still work correctly, so I did the following:
random.seed(1)
to force the result to always be the same..csv
one they generate.They output identical .csv's, so I'm reasonably sure my code behaves like yours did.
A partir de aquí vendría la parte que modifica el sigma de cada jugador. Esa parte no la he entendido, por lo que no la he implementado, pero debería ser mucho más simple ahora, ya que cada jugador tiene su propio
self.sigma
independiente. Dime en qué necesitarías ayuda para implementar esto.Actualización (2019-06-29)
Tratando de implementar la modificación del
self.sigma
según la elección de cada jugador, he tenido que hacer nuevas refactorizaciones:Jugador.choose(r)
que recibe como parámetro la ronda, y esta función es llamada desde el generadorPartida.generar_senales()
para cada jugador. Esto simplifica bastante la funcióngenerar_senales()
y permite que cada jugador sea "consciente" de qué elección ha hecho, lo que le permite generar suaux
y compararlo con loss_inicial
para así actualizar su sigma.s_inicial_1
,s_inicial_2
he visto preferible tener una solas_inicial
que sería un diccionario cuyas claves son los números de jugador y los valores las listas[1,0,0,0]
con las que tiene que comparar suaux
.Gracias a esta separación, cada jugador puede ahora actualizar su sigma tras cada elección de jugada. Este es el nuevo código, en el que he marcado con comentarios las partes modificadas:
Lo he ejecutado y, a pesar de que los sigmas se van modificando (he podido verificarlo con un depurador), el csv final sigue saliendo el mismo. Entiendo que debido a que sólo se juegan tres rondas (y tras ellas los sigma se reinician pues los jugadores se crean de nuevo), el efecto acumulativo de ir aplicando esas sigmas no llega a afectar a las probabilidades y por eso sigue saliendo el mismo resultado que cuando los sigma no se modificaban.
Aunque es posible también que, ya que sigo sin entender la mecánica del juego y me limito a refactorizar sin entender el objetivo último, haya implementado mal la parte que modifica los sigma. Tú me dirás si está correcto así.