demos/dlgr/demos/mcmcp/experiment.py
"""Monte Carlo Markov Chains with people."""
import random
import time
from operator import attrgetter
from flask import Response
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from dallinger.bots import BotBase
from dallinger.experiment import Experiment, experiment_route
from dallinger.networks import Chain
class MCMCP(Experiment):
"""Define the structure of the experiment."""
def __init__(self, session=None):
"""Call the same function in the super (see experiments.py in dallinger).
The models module is imported here because it must be imported at
runtime.
A few properties are then overwritten.
Finally, setup() is called.
"""
super(MCMCP, self).__init__(session)
from . import models
self.models = models
self.experiment_repeats = 1
self.trials_per_participant = 10
if session:
self.setup()
def create_node(self, network, participant):
"""Create a node for a participant."""
return self.models.MCMCPAgent(network=network, participant=participant)
def setup(self):
"""Setup the networks."""
if not self.networks():
super(MCMCP, self).setup()
for net in self.networks():
self.models.AnimalSource(network=net)
self.session.commit()
def create_network(self):
"""Create a new network."""
return Chain(max_size=10)
def get_network_for_participant(self, participant):
if len(participant.nodes(failed="all")) < self.trials_per_participant:
return random.choice(self.networks())
else:
return None
def add_node_to_network(self, node, network):
"""When a node is created it is added to the chain (see Chain in networks.py)
and it receives any transmissions."""
network.add_node(node)
parent = node.neighbors(direction="from")[0]
parent.transmit()
node.receive()
def data_check(self, participant):
"""Make sure each trial contains exactly one chosen info."""
infos = participant.infos()
return len([info for info in infos if info.chosen]) * 2 == len(infos)
@experiment_route("/choice/<int:node_id>/<int:choice>", methods=["POST"])
@classmethod
def choice(cls, node_id, choice):
from dallinger import db
from .models import Agent
try:
exp = MCMCP(db.session)
node = Agent.query.get(node_id)
infos = node.infos()
if choice == 0:
info = max(infos, key=attrgetter("id"))
elif choice == 1:
info = min(infos, key=attrgetter("id"))
else:
raise ValueError("Choice must be 1 or 0")
info.chosen = True
exp.save()
return Response(status=200, mimetype="application/json")
except Exception:
return Response(status=403, mimetype="application/json")
class Bot(BotBase):
"""Bot tasks for experiment participation"""
def participate(self):
"""Finish reading and send text"""
try:
while True:
left = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.ID, "left_button"))
)
right = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.ID, "right_button"))
)
random.choice((left, right)).click()
time.sleep(1.0)
except TimeoutException:
return False