from otree.api import * import random import json from collections import OrderedDict doc = """ Your app description """ import math author = 'Gergely Horvath' doc = """ Linear quadratic network formation """ class C(BaseConstants): NAME_IN_URL = 'network1_5p_onesided' PLAYERS_PER_GROUP = None GROUPSIZE = 5 NUM_ROUNDS = 30 A = 10 #marginal benefit of effort B = 4 # marginal cost of effort: B/2 LA = 0.4 #spillover multiplier K = 3.9 #cost of linking MUTUAL = False NAMES = ['ID1', 'ID2', 'ID3', 'ID4', 'ID5'] CRPAYOFF = 10 EXCHANGE = 150 SHOWUP = 3.5 MAXEFFORT = 20.0 DICTATORBUDGET = 1*EXCHANGE WAITPAGE_TIMEOUT=900 FORMATION_TIMEOUT=60 FEEDBACK_TIMEOUT=60 class Subsession(BaseSubsession): pass def group_by_arrival_time_method(subsession, waiting_players): for player in waiting_players: player.nogroup = False if len(waiting_players) >= C.GROUPSIZE: return waiting_players[:C.GROUPSIZE] for player in waiting_players: if waiting_too_long(player): player.nogroup = True # make a single-player group. return [player] class Group(BaseGroup): dropout_group = models.BooleanField() network_data = models.LongStringField() def forming_network(group): nodes = [{'data': {'id': i, 'name': i}, 'group': 'nodes'} for i in C.NAMES] edges = [] for p in group.get_players(): if C.MUTUAL==False: #getattr(player, i.name): True if the player's attribute named i.name is True p.friends = json.dumps([i.name for i in p.get_others_in_group() if (getattr(p, i.name) or getattr(i, p.name))]) p.numneis=0 p.numneis_cost=0 for i in p.get_others_in_group(): if (getattr(p, i.name)) or (getattr(i, p.name)): p.numneis=p.numneis+1 if (getattr(p, i.name)): p.numneis_cost = p.numneis_cost+1 else: p.friends = json.dumps([i.name for i in p.get_others_in_group() if (getattr(p, i.name) and getattr(i, p.name))]) p.numneis=0 for i in p.get_others_in_group(): if (getattr(p, i.name) and getattr(i, p.name)): p.numneis=p.numneis+1 friends = json.loads(p.friends) edges.extend([{'data': {'id': p.name + i, 'source': p.name, 'target': i}, 'group': 'edges'} for i in friends]) elements = nodes + edges style = [{'selector': 'node', 'style': {'content': 'data(name)'}}] group.network_data = json.dumps({'elements': elements, 'style': style, }) def make_field(label): return models.IntegerField( choices=[ [1, 'Disagree strongly'], [2, 'Disagree a little'], [3, 'Neither agree, nor disagree'], [4, 'Agree a little'], [5, 'Agree strongly'], ], label=label, widget=widgets.RadioSelect, ) class Player(BasePlayer): name = models.StringField() friends = models.LongStringField() effort = models.FloatField( label="Please, choose an activity level:", min = 0.0, max = C.MAXEFFORT, ) numneis = models.IntegerField() numneis_cost = models.IntegerField() totalpayoffP1 = models.FloatField() mypayoff = models.FloatField() totalCRpayoff = models.FloatField() payoffCR1 = models.FloatField() payoffCR2 = models.FloatField() payoffCR3 = models.FloatField() CRcorrect = models.IntegerField() finalpayoff = models.FloatField() money1 = models.FloatField() money2 = models.FloatField() dropout = models.BooleanField() socpref = models.IntegerField() nogroup = models.BooleanField() #these field names should be the same as the list of names in the Constants ID1 = models.BooleanField(widget=widgets.CheckboxInput, blank=True) ID2 = models.BooleanField(widget=widgets.CheckboxInput, blank=True) ID3 = models.BooleanField(widget=widgets.CheckboxInput, blank=True) ID4 = models.BooleanField(widget=widgets.CheckboxInput, blank=True) ID5 = models.BooleanField(widget=widgets.CheckboxInput, blank=True) #survey fields BF1 = make_field('...is reserved') BF2 = make_field('...is generally trusting') BF3 = make_field('...tends to be lazy') BF4 = make_field('...is relaxed, handles stress well') BF5 = make_field('...has few artistic interests') BF6 = make_field('...is outgoing, sociable') BF7 = make_field('...tends to find fault with others') BF8 = make_field('...does a thorough job') BF9 = make_field('...get nervous easily') BF10 = make_field('...has an active imagination') gender = models.IntegerField( choices=[ [0, 'Male'], [1, 'Female'], [2, 'Prefer not to say'], ], widget=widgets.RadioSelect ) age = models.IntegerField( min=0, max=100 ) prolific = models.StringField() nationality = models.StringField() highesteduc = models.IntegerField( choices=[ [0, 'Primary school'], [1, 'Middle school'], [2, 'High school'], [3, 'Vocational school'], [4, 'Bachelor degree'], [5, 'Master degree'], [6, 'PhD degree'], ], widget=widgets.RadioSelect ) student = models.IntegerField( choices=[ [0, 'No'], [1, 'Yes'], ], widget=widgets.RadioSelect ) work = models.IntegerField( choices=[ [0, 'No'], [1, 'Yes, part time'], [2, 'Yes, full time'], ], widget=widgets.RadioSelect ) feedback = models.LongStringField() cogreflection1 = models.FloatField( min=0 ) cogreflection2 = models.IntegerField( min=0 ) cogreflection3 = models.IntegerField( min=0 ) riskaversion1 = models.IntegerField( min=0, max=100, ) invest = models.IntegerField() payoff3 = models.FloatField() def waiting_too_long(player: Player): participant = player.participant import time return time.time() - participant.wait_page_arrival > C.WAITPAGE_TIMEOUT # PAGES class Start(WaitPage): group_by_arrival_time = True template_name = 'network1/MyWaitPage.html' title_text = "Please, wait for your group to form." body_text = "Thank you for filling out the control questions! Please, wait for the other participants to finish the questions too and your group to form. This may take a while, please, be patient and pay attention to your screen as your group may be ready at any moment!" @staticmethod def is_displayed(player): return player.round_number == 1 @staticmethod def js_vars(player: Player): return dict(timeout=C.WAITPAGE_TIMEOUT) @staticmethod def after_all_players_arrive(group: Group): players = group.get_players() i = 0 for p in players: p.name = C.NAMES[i].upper() p.dropout = False i = i + 1 class NoGroup(Page): @staticmethod def is_displayed(player): return player.nogroup == True class Prolific(Page): @staticmethod def is_displayed(player): return player.round_number == 1 and player.nogroup == False timeout_seconds = 60 form_model = 'player' form_fields = ['prolific'] @staticmethod def before_next_page(player, timeout_happened): future = player.in_rounds(2, C.NUM_ROUNDS) for p in future: p.name = C.NAMES[player.id_in_group - 1].upper() p.dropout = False p.nogroup = False class ID(Page): timeout_seconds = 25 @staticmethod def is_displayed(player): return player.round_number == 1 and player.nogroup == False class NetworkFormation(Page): @staticmethod def is_displayed(player): return player.nogroup == False and player.group.dropout_group == False timeout_seconds = C.FORMATION_TIMEOUT form_model = 'player' @staticmethod def get_form_fields(player): list = [i.name for i in player.get_others_in_group()] list.append('effort') return list @staticmethod def vars_for_template(player): dropouts = [] if player.round_number>1: others=player.get_others_in_group() for p in others: prev=p.in_round(player.round_number-1) if prev.dropout==True: dropouts.append(prev.name) numdropouts = len(dropouts) dropouts = ''.join(str(e) + ', ' for e in dropouts) return dict( dropouts=dropouts, numdropouts=numdropouts, ) @staticmethod def before_next_page(player, timeout_happened): if timeout_happened: player.dropout = True player.effort = 0 player.ID1 = 0 player.ID2 = 0 player.ID3 = 0 player.ID4 = 0 player.ID5 = 0 player.group.dropout_group = True for j in range(player.round_number, C.NUM_ROUNDS + 1): g = player.group.in_round(j) g.dropout_group = True future = player.in_rounds(player.round_number, C.NUM_ROUNDS) for p in future: p.dropout = True p.effort = 0 p.ID1 = 0 p.ID2 = 0 p.ID3 = 0 p.ID4 = 0 p.ID5 = 0 class BeforeResultsWP(WaitPage): @staticmethod def is_displayed(player): return player.nogroup == False @staticmethod def after_all_players_arrive(group: Group): players = group.get_players() for p in players: #create friends list #create the network based on the friends list group.forming_network() #compute payoffs based on own and neighbors' contributions sumnei = 0 for p2 in players: if p2.name in p.friends: sumnei = sumnei + p2.effort p.mypayoff = round( C.A * p.effort - C.B / 2 * p.effort ** 2 + C.LA * p.effort * sumnei - C.K * p.numneis_cost, 2) if p.round_number == 1: p.totalpayoffP1 = round(p.mypayoff, 2) else: previous = p.in_round(p.round_number - 1) p.totalpayoffP1 = p.mypayoff + round(previous.totalpayoffP1, 2) if group.dropout_group == True and p.dropout == False: if p.totalpayoffP1 > 0: p.finalpayoff = round(p.totalpayoffP1, 2) else: p.finalpayoff = 0 p.money1 = round(p.finalpayoff / C.EXCHANGE, 2) p.money2 = round(p.money1 + C.SHOWUP, 2) class Dropout(Page): @staticmethod def is_displayed(player): return player.dropout == True and player.nogroup == False class Results(Page): @staticmethod def is_displayed(player): return player.dropout == False and player.nogroup == False timeout_seconds = C.FEEDBACK_TIMEOUT @staticmethod def vars_for_template(player): otherplayers=player.get_others_in_group() efforts = [p.effort for p in otherplayers] names = [p.name for p in otherplayers] neis= [p.friends for p in otherplayers] numneis = [len(p.friends) for p in otherplayers] myneis = player.friends linkcost = player.numneis_cost*C.K effortcost = round(player.effort*player.effort*C.B/2,2) benefit = round(player.mypayoff+linkcost+effortcost,2) dropouts = [] initiated = [] others = player.get_others_in_group() for p in others: if p.dropout == True: dropouts.append(p.name) if (getattr(player, p.name)): initiated.append(p.name) numdropouts = len(dropouts) dropouts = ''.join(str(e) + ', ' for e in dropouts) return dict( #my data myneis=myneis.replace('"', "").replace(']', "").replace('[', ""), effortcost=effortcost, linkcost=linkcost, benefit=benefit, initiated=str(initiated).replace("'", "").replace(']', "").replace('[', ""), #neighbors e0=efforts[0], e1=efforts[1], e2=efforts[2], e3=efforts[3], name0 = names[0], name1 = names[1], name2 = names[2], name3 = names[3], num0 = numneis[0], num1 = numneis[1], num2=numneis[2], num3=numneis[3], nei0 = neis[0].replace('"',"").replace(']',"").replace('[',""), nei1 = neis[1].replace('"',"").replace(']',"").replace('[',""), nei2=neis[2].replace('"', "").replace(']', "").replace('[', ""), nei3=neis[3].replace('"', "").replace(']', "").replace('[', ""), #dropouts dropouts=dropouts, numdropouts=numdropouts, ) class Dropout2(Page): @staticmethod def is_displayed(player): return player.dropout == False and player.nogroup == False and player.group.dropout_group==True @staticmethod def vars_for_template(player): if player.totalpayoffP1 < 0: text = 'Since this payoff is negative, you will receive 0 points from Part 1 of the Experiment.' else: text = '' return dict( text=text, ) class StartPart2(Page): @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS and player.group.dropout_group==False and player.nogroup == False timeout_seconds = 20 class Survey1(Page): @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS and player.group.dropout_group==False and player.nogroup == False form_model = 'player' form_fields = ['gender', 'age', 'highesteduc', 'student', 'nationality', 'work','feedback'] class Survey2(Page): @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS and player.group.dropout_group == False and player.nogroup == False form_model = 'player' form_fields = ['cogreflection1', 'cogreflection2', 'cogreflection3'] def before_next_page(player, timeout_happened): correct = 0 if player.cogreflection1 == 5 or player.cogreflection1 == 0.05: player.payoffCR1 = C.CRPAYOFF correct=correct+1 else: player.payoffCR1 = 0 if player.cogreflection2 == 5: player.payoffCR2 = C.CRPAYOFF correct = correct + 1 else: player.payoffCR2 = 0 if player.cogreflection3 == 47: player.payoffCR3 = C.CRPAYOFF correct = correct + 1 else: player.payoffCR3 = 0 player.CRcorrect = correct player.totalCRpayoff = player.payoffCR1 + player.payoffCR2 + player.payoffCR3 class Survey3(Page): @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS and player.group.dropout_group==False and player.nogroup == False form_model = 'player' form_fields = ['riskaversion1'] def before_next_page(player, timeout_happened): import random # determine payoff 2F if random.random() < 0.5: player.invest = 0 player.payoff3 = 100 - player.riskaversion1 else: player.payoff3 = 100 - player.riskaversion1 + 2.5 * player.riskaversion1 player.invest = 1 class Survey4(Page): @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS and player.group.dropout_group==False and player.nogroup == False form_model = 'player' def get_form_fields(player): list=[] for i in range(1,11,1): list.append('BF'+str(i)) return list class Survey5(Page): @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS and player.group.dropout_group==False and player.nogroup == False form_model = 'player' form_fields = ['socpref'] def before_next_page(player, timeout_happened): if player.totalpayoffP1>0: player.finalpayoff = player.totalpayoffP1+player.totalCRpayoff+player.payoff3+player.socpref else: player.finalpayoff = player.totalCRpayoff + player.payoff3 + player.socpref player.money1 = round(player.finalpayoff/C.EXCHANGE,2) player.money2 = round(player.money1+C.SHOWUP,2) class FinalResult(Page): @staticmethod def is_displayed(player): return player.round_number == C.NUM_ROUNDS and player.group.dropout_group==False and player.nogroup == False @staticmethod def vars_for_template(player): if player.totalpayoffP1<0: text='Since this payoff is negative, you will receive 0 points from Part 1 of the Experiment.' else: text='' return dict( text=text, ) page_sequence = [ Start, NoGroup, Prolific, ID, NetworkFormation, BeforeResultsWP, Dropout, Results, Dropout2, StartPart2, Survey1, Survey2, Survey3, Survey4, Survey5, FinalResult, ]