Imitation chains¶
An imitation chain experiment organizes participants into chains, where each chain starts with a particular stimulus, and participants recursively imitate the previous participant’s imitation of that stimulus.
This demo gives a particularly basic example where participants are presented with a number and are told to remember and then reproduce it.
Source: demos/imitation_chain
# pylint: disable=unused-import,abstract-method,unused-argument
import random
import re
from statistics import mean
import psynet.experiment
from psynet.bot import Bot
from psynet.consent import NoConsent
from psynet.modular_page import ModularPage, Prompt, TextControl
from psynet.page import InfoPage, SuccessfulEndPage
from psynet.timeline import FailedValidation, Timeline
from psynet.trial.imitation_chain import (
ImitationChainNetwork,
ImitationChainNode,
ImitationChainTrial,
ImitationChainTrialMaker,
)
from psynet.utils import get_logger
logger = get_logger()
class FixedDigitInputPage(ModularPage):
def __init__(self, label: str, prompt: str, time_estimate: float, bot_response):
self.n_digits = 7
super().__init__(
label,
Prompt(prompt),
control=TextControl(
block_copy_paste=True,
bot_response=bot_response,
),
time_estimate=time_estimate,
)
def format_answer(self, raw_answer, **kwargs):
try:
pattern = re.compile("^[0-9]*$")
assert len(raw_answer) == self.n_digits
assert pattern.match(raw_answer)
return int(raw_answer)
except (ValueError, AssertionError):
return "INVALID_RESPONSE"
def validate(self, response, **kwargs):
if response.answer == "INVALID_RESPONSE":
return FailedValidation("Please enter a 7-digit number.")
return None
class CustomTrial(ImitationChainTrial):
time_estimate = 2 + 3
def show_trial(self, experiment, participant):
page_1 = InfoPage(
f"Try to remember this 7-digit number: {self.definition['number']:07d}",
time_estimate=2,
)
page_2 = FixedDigitInputPage(
"number",
"What was the number?",
time_estimate=3,
bot_response=lambda: self.definition["number"],
)
return [page_1, page_2]
class CustomNetwork(ImitationChainNetwork):
pass
class CustomNode(ImitationChainNode):
def create_initial_seed(self, experiment, participant):
return {"number": random.randint(0, 9999999)}
def summarize_trials(self, trials: list, experiment, participant):
return {"number": round(mean([trial.answer for trial in trials]))}
class CustomTrialMaker(ImitationChainTrialMaker):
response_timeout_sec = 60
check_timeout_interval_sec = 30
class Exp(psynet.experiment.Experiment):
label = "Imitation chain demo"
initial_recruitment_size = 1
timeline = Timeline(
NoConsent(),
CustomTrialMaker(
id_="imitation_chain",
network_class=CustomNetwork,
trial_class=CustomTrial,
node_class=CustomNode,
chain_type="within",
max_nodes_per_chain=5,
max_trials_per_participant=5,
expected_trials_per_participant=5,
chains_per_participant=1,
chains_per_experiment=None,
trials_per_node=1,
balance_across_chains=True,
check_performance_at_end=False,
check_performance_every_trial=False,
recruit_mode="n_participants",
target_n_participants=10,
),
InfoPage("You finished the experiment!", time_estimate=0),
SuccessfulEndPage(),
)
def test_check_bot(self, bot: Bot, **kwargs):
assert len(bot.alive_trials) == 5