oTreeの公共財ゲームで、分配ポイントをランダムにする方法と最大値・最小値を動的に定義する方法

oTreeの公共財ゲームで、分配ポイントをランダムにする方法と最大値・最小値を動的に定義する方法

2022年3月28日

oTreeで公共財ゲームを実装する際に、分配ポイントをランダムにする方法をまとめておきます。また、固定値ではなくフォームの最大値・最小値を動的に設定する方法もまとめます。

例えば、公共財ゲームで、初期の分配ポイントを固定20点から、変動型の20~30点の範囲にしたいときに、どのようにコードを書けばいいかまとめておきます(自分の備忘録です)

oTreeに付属のpublic_goods_simpleをベースに作成しています。oTree 5.7.2での動作を確認しています。

randomの使用は禁止されている

import random
class C(BaseConstants):
    #20~30点の範囲でランダムに配分する
    ENDOWMENT = random.randint(20, 30)

oTreeでは、関数以外の部分でrandomを使用することを禁止されています。つまり、上記の様なコードを書いて、class C(BaseConstants):の中で、ランダムを使用することはできません。

公共財ゲームでランダムに分配ポイントを決定する

import random
class Group(BaseGroup):
    total_contribution = models.CurrencyField()
    individual_share = models.CurrencyField()
    P_ENDOWMENT = models.CurrencyField(initial=cu(0))

# 労働によって還元されるポイントの設定(今回はランダムに設定)
def set_Endowment(group: Group):
    group.P_ENDOWMENT = group.P_ENDOWMENT + random.randint(20,30)

#参加者が集合したら、利得を設定するWaitPageを用意する
class PlayerWaitPage(WaitPage):
    after_all_players_arrive = set_Endowment

分配するポイントをGroupクラスに”P_ENDOWMENT”として定義します。Cクラスの関数は動的に定義することができないので、Groupクラスに定義しています。

そして、動的に変動させるために、set_Endowment関数を定義します。その後、ゲーム開始前に、分配ポイントを決定するために、PlayerWaitPageを新設し、after_all_players_arriveで参加者が集合したら実行します。

あとは、”Contribution.html”の”C.ENDOWMENT”を”group.P_ENDOWMENT”にしておきましょう。

フォームの最大値・最小値を動的に決定する

# フォームに最大値の制約をかける
def contribution_max(player):
    return player.group.PENDOWMENT

oTree公式ページの動的なフォームの検証で、フォーム内に設定するminやmaxを動的に定義する方法が書かれています。

定義の方法は簡単で”{フォーム名}_max”や”{フォーム名}_min”で定義するだけです。特にどこかで実行する必要はなく、書くだけでOK (なぜこれでうまくいくのかは謎ですが)。

oTreeが5.7になり、selfが廃止され、ファイルもシンプルになり、非常に書きやすくなりました。

フルコード

from otree.api import *
import random


class C(BaseConstants):
    NAME_IN_URL = 'public_goods_simple_article'
    PLAYERS_PER_GROUP = 3
    NUM_ROUNDS = 1
    ENDOWMENT = cu(100)
    MULTIPLIER = 1.8


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    total_contribution = models.CurrencyField()
    individual_share = models.CurrencyField()
    P_ENDOWMENT = models.CurrencyField(initial=cu(0))

class Player(BasePlayer):
    contribution = models.CurrencyField(
        min=0, label="How much will you contribute?"
    )


# FUNCTIONS
def set_payoffs(group: Group):
    players = group.get_players()
    contributions = [p.contribution for p in players]
    group.total_contribution = sum(contributions)
    group.individual_share = (
        group.total_contribution * C.MULTIPLIER / C.PLAYERS_PER_GROUP
    )
    for p in players:
        p.payoff = C.ENDOWMENT - p.contribution + group.individual_share

# 労働によって還元されるポイントの設定(今回はランダムに設定)
def set_Endowment(group: Group):
    group.P_ENDOWMENT = group.P_ENDOWMENT + random.randint(20,30)

# フォームの最大値を動的に設定する。
def contribution_max(player):
    return player.group.P_ENDOWMENT

# PAGES
class PlayerWaitPage(WaitPage):
    after_all_players_arrive = set_Endowment

class Contribute(Page):
    set_Endowment
    form_model = 'player'
    form_fields = ['contribution']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_payoffs


class Results(Page):
    pass


page_sequence = [PlayerWaitPage, Contribute, ResultsWaitPage, Results]