Dallinger/Dallinger

View on GitHub
demos/dlgr/demos/rogers/experiment.py

Summary

Maintainability
A
30 mins
Test Coverage
"""Replicate Rogers' paradox by simulating evolution with people."""

import random

import six

from dallinger.config import get_config
from dallinger.experiment import Experiment
from dallinger.information import Meme
from dallinger.models import Node, Participant
from dallinger.networks import DiscreteGenerational


def extra_parameters():
    config = get_config()
    types = {
        "experiment_repeats": int,
        "practice_repeats": int,
        "catch_repeats": int,
        "practice_difficulty": float,
        "catch_difficulty": float,
        "difficulties": six.text_type,  # comma separated floats
        "min_acceptable_performance": float,
        "generations": int,
        "generation_size": int,
        "bonus_payment": float,
    }

    for key in types:
        config.register(key, types[key])


class RogersExperiment(Experiment):
    """The experiment class."""

    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(RogersExperiment, self).__init__(session)
        from . import models

        self.models = models
        self.known_classes["LearningGene"] = self.models.LearningGene

        if session and not self.networks():
            self.setup()

    def configure(self):
        config = get_config()
        self.experiment_repeats = config.get("experiment_repeats")
        self.practice_repeats = config.get("practice_repeats")
        self.catch_repeats = config.get(
            "catch_repeats", 0
        )  # a subset of experiment repeats
        self.practice_difficulty = config.get("practice_difficulty")

        self.difficulties = [
            float(f.strip()) for f in config.get("difficulties").split(",")
        ] * self.experiment_repeats

        self.catch_difficulty = config.get("catch_difficulty")
        self.min_acceptable_performance = config.get("min_acceptable_performance")
        self.generation_size = config.get("generation_size")
        self.generations = config.get("generations")
        self.bonus_payment = config.get("bonus_payment")
        self.initial_recruitment_size = self.generation_size

    @property
    def public_properties(self):
        return {
            "practice_repeats": self.practice_repeats,
            "experiment_repeats": self.experiment_repeats,
        }

    def setup(self):
        """First time setup."""
        super(RogersExperiment, self).setup()

        for net in random.sample(self.networks(role="experiment"), self.catch_repeats):
            net.role = "catch"

        for net in self.networks():
            source = self.models.RogersSource(network=net)
            source.create_information()
            net.max_size = net.max_size + 1  # make room for environment node.
            env = self.models.RogersEnvironment(network=net)
            env.proportion = self.color_proportion_for_network(net)
            env.create_information()
        self.session.commit()

    def color_proportion_for_network(self, net):
        if net.role == "practice":
            return self.practice_difficulty
        if net.role == "catch":
            return self.catch_difficulty
        if net.role == "experiment":
            return self.difficulties[self.networks(role="experiment").index(net)]

    def create_network(self):
        """Create a new network."""
        return DiscreteGenerational(
            generations=self.generations,
            generation_size=self.generation_size,
            initial_source=True,
        )

    def create_node(self, network, participant):
        """Make a new node for participants."""
        return self.models.RogersAgent(network=network, participant=participant)

    def info_post_request(self, node, info):
        """Run whenever an info is created."""
        node.calculate_fitness()

    def submission_successful(self, participant):
        """Run when a participant submits successfully."""
        num_approved = len(Participant.query.filter_by(status="approved").all())
        current_generation = participant.nodes()[0].generation
        if (
            num_approved % self.generation_size == 0
            and (current_generation % 10 + 1) == 0  # noqa
        ):
            for e in self.models.RogersEnvironment.query.all():
                e.step()

    def recruit(self):
        """Recruit participants if necessary."""
        num_approved = len(Participant.query.filter_by(status="approved").all())
        end_of_generation = num_approved % self.generation_size == 0
        complete = num_approved >= (self.generations * self.generation_size)
        if complete:
            self.log("All networks full: closing recruitment", "-----")
            self.recruiter.close_recruitment()
        elif end_of_generation:
            self.log("generation finished, recruiting another")
            self.recruiter.recruit(n=self.generation_size)

    def bonus(self, participant):
        """Calculate a participants bonus."""
        scores = [
            n.score for n in participant.nodes() if n.network.role == "experiment"
        ]
        average = float(sum(scores)) / float(len(scores))

        bonus = round(max(0.0, ((average - 0.5) * 2)) * self.bonus_payment, 2)
        return bonus

    def attention_check(self, participant=None):
        """Check a participant paid attention."""
        if self.catch_repeats == 0:
            return True

        scores = [n.score for n in participant.nodes() if n.network.role == "catch"]
        avg = float(sum(scores)) / float(len(scores))
        return avg >= self.min_acceptable_performance

    def data_check(self, participant):
        """Check a participants data."""
        nodes = Node.query.filter_by(participant_id=participant.id).all()

        if len(nodes) != self.experiment_repeats + self.practice_repeats:
            print(
                "Error: Participant has {} nodes. Data check failed".format(len(nodes))
            )
            return False

        nets = [n.network_id for n in nodes]
        if len(nets) != len(set(nets)):
            print(
                "Error: Participant participated in the same network \
                   multiple times. Data check failed"
            )
            return False

        if None in [n.fitness for n in nodes]:
            print(
                "Error: some of participants nodes are missing a fitness. \
                   Data check failed."
            )
            return False

        if None in [n.score for n in nodes]:
            print(
                "Error: some of participants nodes are missing a score. \
                   Data check failed"
            )
            return False
        return True

    def add_node_to_network(self, node, network):
        """Add participant's node to a network."""
        network.add_node(node)
        node.receive()

        environment = network.nodes(type=self.models.RogersEnvironment)[0]
        environment.connect(whom=node)

        gene = node.infos(type=self.models.LearningGene)[0].contents
        if gene == "social":
            agent_model = self.models.RogersAgent
            prev_agents = agent_model.query.filter_by(
                failed=False, network_id=network.id, generation=node.generation - 1
            ).all()
            parent = random.choice(prev_agents)
            parent.connect(whom=node)
            parent.transmit(what=Meme, to_whom=node)
        elif gene == "asocial":
            environment.transmit(to_whom=node)
        else:
            raise ValueError(
                "{} has invalid learning gene value of {}".format(node, gene)
            )
        node.receive()