The One with ...

思いついたことや作業メモなどを公開しています

VSCode上でのTeX設定メモ

三日後に忘れるのでメモを残しておく

前準備
  • VSCodeインストール
  • LaTeX-workshop extensionインストール
  • TeXliveインストール
  • Github copilotインストール
設定

こちらのサイトを参考にして.latexmkファイルを作成し,ルートフォルダに置く. Winの場合,C:\Users\USERNAME直下でよいらしい.

www2.yukawa.kyoto-u.ac.jp

VSCodeLaTeX-workshopのsettings.jsonを修正する. やはり上のサイトを参考にして,修正後は以下のように設定する.

        {
            "name": "latexmk",
            "command": "latexmk",
            "args": [
                "-synctex=1",
                "-interaction=nonstopmode",
                "-file-line-error",
                "-halt-on-error",
                "-pdfdvi",
                "-outdir=%OUTDIR%",
                "%DOC%"
            ],
            "env": {}
        },

変更点はエラー時の設定と,pdfdviを使うところ.

上記の設定で実行したところ,よく分からないが,TeXStudioからコンパイルするよりも格段に早くなった. TeXStudioでのビルド設定がよくないのかもしれない. (余計なDVIチェーンとか除けば早くなる?)

中間ファイルを消す

「Build LaTeX project」を押すとビルドされる。 「Build -」の中にある「Clean up auxiliary files」を押すと中間ファイルを削除することができる.

ショートカットキーの登録

TeXStudioで使っているSCを使いたいので,VSCodeで登録する

キーバインド設定のjsonファイルを次のように編集する.

// Place your key bindings in this file to override the defaults
[
    {
        "key": "ctrl+4",
        "command": "type",
        "args": { "text": "$$"},
        "when": "editorTextFocus"
    },{
        "key": "ctrl+shift+m",
        "command": "type",
        "args": { "text": "\\[  \\]"},
        "when": "editorTextFocus" 
      },{
          "key": "ctrl+e",
          "command": "type",
          "args": { "text": "\\begin{align*}\n& \\\\\n& \n\\end{align*}"},
          "when": "editorTextFocus"
    }
]

ちょっと面倒だけど,一回設定すれば後はjsonファイルを使い回しできるから,よしとしよう.

oTree: ラウンド毎のパラメータのランダム化

ラウンド毎にパラメータをランダム化する方法

最初はよく分からないので

def set_parameters(group: Group):#グループオブジェクトを引数にした関数,引数groupのタイプ(クラス)がGroupであることを指定
    rvec = C.Rvec.copy()
    random.shuffle(rvec)
    group.R = rvec[group.round_number-1]
    group.PROFIT = group.R*100-100
    nvec2=C.nvec.copy()
    random.shuffle(nvec2)
    group.n = nvec2[group.round_number-1]

のようにグループオブジェクトの関数としてWaitPageで実行させていた.

これでも一応シャッフルするが,復元抽出になってしまい,被験者が同じパラメータに暴露されることになり効率が悪い.

解決法

groupやsubsessionで定義しても怒られるので,結局,

#class の前に定義
v1=[3,5,6,4]
v2=[1,2,4,9]
random.shuffle(v1)
random.shuffle(v2)


class C(BaseConstants):
    NAME_IN_URL = 'single_rd'
    PLAYERS_PER_GROUP = None # 被験者は一人
    NUM_ROUNDS = 4
#    NUM_SUCCESS = random.randint(1,5)
    Rvec = v1#セッション毎に変わるグループ共通の利益率
    nvec = v2#random.shuffle(seed2) # N=10とおく

で解決した.被験者毎にシャッフルしたパラメータをラウンド数で呼び出して重複なく使う. oTreeは独自のルールが多すぎて,体系的に全然理解できない.

まあ動けばいいか.

補機バッテリーの残量確認方法

冬場になると車の補機バッテリーの警告灯が毎朝点滅する.

自粛要請時代に一度バッテリーが上がってしまったので,残量を確かめるために市販の車用バッテリーチェッカーを購入した.

以下その方法のメモ

前準備

うちの車は補機バッテリーがトランク側に設置してあるため,エンジンルームを開けてプラグを接続することが出来ない.

トランクにはバッテリ端子をのぞける小窓があるが,奥側(プラス)側の端子が届かない(というか暗すぎて見えん). 説明書を見ると「ヒューズ交換時にはトランク内張を外せる」とあるので試してみる. 内張は差し込み式のプラスチックピンで固定してあり,これを外すには小さめのマイナスドライバーでピンの内側の円の側部をテコの要領で上に押し上げる必要がある(説明書の図が雑すぎて構造が分からず,このピンの解除にかなり手こずった.一個外せば要領が分かるので後は簡単)

内張は右側だけを外せばあとは上に押し上げるとバッテリ上部に手が届く.やれやれ.

手順
  1. まずプラス側の端子カバーを上に押し上げ,赤色のプラグを接続する(といってもカバー上部がフレームに干渉するため,斜めに突っ込んでギリギリ保持できる程度にしか接続できない).
  2. マイナス端子に黒いプラグをつなぐ.するとテスターの電源が入る.エンジンがかかっている状態だと14Vなのでバッテリーテストは実行できず,充電テストしか実行できない.
  3. バッテリー残量を知りたい場合はエンジンを切り,テストを実行する.ACC規格がわからないのでJISを選択して,搭載バッテリーの型番を選ぶ(うちの車はディーラーの記録から46B24らしい).
  4. テストの実行結果を確認,健全度と充電容量が表示されるので,写真などで結果を控えておく.
精度

テスターの精度はいまいち分からない. 最初に計ったとき16%と出てこれはやばいと思い,次に計ったら6%だったので終わったと思ったら,次は58%になり,20kmほど走らせた後計測したら85%だった. ある程度暖まらないとまともに計測でできないのか,プラス端子側のプラグ固定が不安定なせいなのか,バッテリーに対応していないのかは,よく分からない.

今後も測定を繰り返して安定性を確認するしかないかな...

MSIマザボでの起動ドライブの設定

特定のアプリケーションのエラーでwindwosが強制終了し場合に,再起動すると「起動ドライブ内にOSが見つからない」というエラーがしばしば発生する. 以下,その対処方法.

  1. MSIのロゴが出たらf11キーを連打してUFEIを起動する.
  2. 起動ドライブの優先順序が表示されるが,この状態ではOSがインストールされたドライブがない.
  3. アドバンストモード,→boot settingsへと進み,画面を下にスクロールすると,ドライブ一覧が表示され,起動順序を指定できる(このときタブをスクロールできることに気づかず,指定場所を何度も探した).
  4. OSがインストールされたドライブを一番最初に指定する(OSの入っていないドライブが勝手に最初に指定されることがエラーの原因)

起動ディスクはM.2接続のSSDだが,他にもM.2接続しているSSDがあるため,どうやらそちらを起動ディスクに設定してしまうらしい.

TeXlive更新

久しぶりにTeXLiIveをアップデートした. 3日後に忘れるのでメモを残しておく.

TeXStudioの設定

設定→コマンド→latexplatex.exe -synctex=1 -interaction=nonstopmode %.tex に変更する.この変更で日本語文書クラスのコンパイルができる.

beamerでminjisがないと怒られたら,jsclassesをtexliveshellでアップデートもしくは,そもそも入ってなければインストールしておく.

TeXStudioのショートカットキー設定

とりあえず現環境から

仮空きスペース
\vspace{2cm}
%|
\vspace{2cm}

dots:
$\cdots \cdots$

引用:
\begin{quote}%\ %\\end{quote} 

インライン数式 $...$:
$%|$

alignat:
\begin{alignat*}{3}%\ & = & \quad  &  \\ %\ & = &  &  \\ %\ & = &  &  \\ %\ &= & & %\ \end{alignat*}

複数行数式:
\begin{align*} %\ & = \\ %\  & = %\ \end{align*}

ディスプレイ数式 \[...\]:
\[ %| \]

\begin{verbatim:
\begin{verbatim}%\ %\ \end{verbatim}

などをコピペして登録.こういう設定,エクスポートできそうな気もしますが,頻繁にやることでもないので手作業で設定します.

ビルド&表示は txs:///dvi-pdf-chain

とりあえずこれで,コンパイルはOK.

ラウンド毎に異なるパラメータを設定する方法

前回の記事でワンショットの剥奪ゲームのトリートメントを作成しました. 同じ被験者にゲームの設定を変えつつ複数回繰り返す方法を考えてみましたが3日後には忘れそうなので,例によって メモを残しておきます.

グループに共通の値を設定する

結論から言うと,次のコードで一応の目的は達せられます.

class C(BaseConstants):
    NAME_IN_URL = 'relative_deprivation'
    PLAYERS_PER_GROUP = 5
    NUM_ROUNDS = 2
    Rvec = [3,5]#セッション毎に変わるグループ共通の利益率
    nvec = [2,4]#セッション毎に変わるグループ共通の成功者数

class Subsession(BaseSubsession):
    pass

class Group(BaseGroup):
    x = models.IntegerField()
    R = models.IntegerField()#セッション毎に変わるグループ共通の利益率
    PROFIT = models.IntegerField()
    n = models.IntegerField()

def set_parameters(group: Group):#グループオブジェクトを引数にした関数,引数groupのタイプ(クラス)がGroupであることを指定
    group.R = C.Rvec[group.round_number-1]
    group.PROFIT = group.R*100-100
    group.n = C.nvec[group.round_number-1]

class MyWaitPage(WaitPage):
    after_all_players_arrive = set_parameters

このような面倒なコードになる理由は

ズバリ

Conventionally, group-level treatments are assigned in creating_session:

for g in subsession.get_groups():
     g.treatment = random.choice([True, False])

However, this doesn't work when using group_by_arrival_time, because groups are not determined until players arrive at the wait page. (All players are in the same group initially.) Instead, you need to assign treatments in after_all_players_arrive.

だからです.

この実験では行為者の選択前に所与の条件(グループ固定の属性である利益率と成功人数)を、表示する必要があります.条件表示の前にcreating_sessionを使ってグループオブジェクトの属性に値を渡すとエラーがでるというわけです.そこで条件表示の前にafter_all_players_arrive = set_parametersを使ってグループ属性を定義します. (sessionレベルの変数の定義しようと試行錯誤しましたが,うまくいきませんでした).

結局,

  • 定数クラスで水準のベクトルを準備する
  • set_parameters関数を定義し,group属性のround_numberをつかって,ラウンドに対応したパラメータを取得
  • 最初の入力フォーム表示前にwaitpageを追加してafter_all_players_arrive = set_parametersでパラメータをグループの属性に代入

という手順でラウンド毎のパラメータ設定を実行しました.やれやれ.

多少面倒ですが,otreeは「手軽にオブジェクトの属性やメソッドを定義できるPythonの特徴」を生かした,便利なパッケージと言えるでしょう.

相対的剥奪のオンライン実験用コード

はじめに

とある原稿を書いているうちに,昔作ったztreeトリートメントをアップデートしたい気持ちがふつふつと高まってきたので,otreeで新たに実験用コードを作成しました.以下忘れた時用のメモです.

otree準備

  • アナコンダの最新版をインストール
  • pip install -U otreeでotreeをインストールする

こちらのサイトがたいへん参考になりました.

akrgt.gitbook.io

作業用ディレクトリの設定

異なるマシンで作業ができるようにdropbox上にフォルダを作ります.

たとえばC:\Users\Dropboxにドロップボックスのローカルアドレスがあれば

otree startproject C:\Users\Dropbox\oTree

という感じでホームディレクトリをつくってあげるとよいでしょう.

はじめてディレクトリをつくるとサンプルコードもdlするかと聞かれるので,これはdlしておきましょう.

今回作ったようなトリートメントはcournotゲームをベースにして作ると簡単でした.

サンプルを探せば自分がやりたい実験に似たコードがあるはずなので,まずはそれをベースに少しずつ修正を加えると作業が楽でしょう (フルスクラッチでいきなり書くのは難しそう)

以降はマシンを変える度にdb.sqlite3を消してから,テスト用のローカルサーバーを otree devserverでたてればOK.

すでに作成済みのトリートメントの実行

アナコンダのパワーシェルでトリートメントを含むディレクトリ移動する. たとえばdropbox上に保存した場合,研究室等のマシンから cd C:\Users\hogehoge\Dropbox\oTree などと入力し,ディレクトリを移動する.移動したディレクトリにはsettings.pyがあり,この中に目的のトリートメントを記入しておく. (新しく作った場合はあとでここで記入する)

移動したらテスト用のローカルサーバーを otree devserverでたてる.

ブラウザでhttp://localhost:8000/にアクセス

settingに登録したトリートメントが表示されるので実行(登録してないとメニューに出てこない) .各クライアントを別ウィンドウで開くためのリンクは,参加人数で設定した分だけ表示される.

アプリの準備

サンプルコードにcournotゲームのフォルダがあるのでコピーしてリネームしましょう. 相対剥奪なので,relative_deprivationという名前にします.

このフォルダには主に

  • __init__.py
  • Contribute.html
  • Results.html

の三つのファイルがあります.

この中身を適当に修正して実験コードを作成します.一つ上の階層

C:\Users\Dropbox\oTreeには

  • settings.py

というファイルがあります.この中の

SESSION_CONFIGS = [
    dict(
        name='guess_two_thirds',
        display_name="Guess 2/3 of the Average",
        app_sequence=['guess_two_thirds', 'payment_info'],
        num_demo_participants=3,
    ),
    dict(
        name='survey', app_sequence=['survey', 'payment_info'], num_demo_participants=1
    )]

に自分が作成したrelative_deprivation'を登録します.guess_two_thirdssurveyはデフォルトのdemoです.

結果は次のようなものになるでしょう.

SESSION_CONFIGS = [
    dict(
        name='guess_two_thirds',
        display_name="Guess 2/3 of the Average",
        app_sequence=['guess_two_thirds', 'payment_info'],
        num_demo_participants=3,
    ),
    dict(
        name='survey', app_sequence=['survey', 'payment_info'], num_demo_participants=1
    ),
    dict(
        name='relative_deprivation', app_sequence=['relative_deprivation', 'payment_info'], num_demo_participants=5
    )
]

settings.pyを保存したらotree devserverでテスト用サーバを立ち上げます.demoの中に自分が登録した実験(relative_deprivation)があれば成功です.

コードの中身

アプリを構成するコードの中身はこんな感じです

__init__.py

クラスの定義や計算用関数を定義するpythonコードです.

pythonになじみのある人は,なんとなく何をやっているか分かると思います.詳細は後ほど解説します.

from otree.api import *
import random


class C(BaseConstants):
    NAME_IN_URL = 'relative_deprivation'
    PLAYERS_PER_GROUP = 5
    NUM_ROUNDS = 1
    RETURN_RATE = random.randint(3,5)
    NUM_SUCCESS = random.randint(1,5)
    PROFIT = RETURN_RATE*100-100 #COST=100


class Subsession(BaseSubsession):
    pass


class Group(BaseGroup):
    x = models.IntegerField()

class Player(BasePlayer):
    action = models.IntegerField(
        choices=[[0, 'no'],[1,'yes (invest 100)']],
        label="Do you invest? ",initial=(0)
    )
    outcome = models.IntegerField(
        choices=[[5, 'very satisfied'],[4, 'satisfied'],[3, 'neither'],[2, 'dissatisfied'], [1, 'very dissatisfied']],
        label='How do you feel about the result?',
        widget=widgets.RadioSelect,
    )
    profit = models.IntegerField()


# FUNCTIONS
def set_profit(group: Group):#グループオブジェクトを引数にした関数
    players = group.get_players()#playerを取得
    group.x = sum([p.action for p in players])#投資者数の総数xを計算
    xids=[]#投資者のplayeridを格納するリスト
    for p in players:
        if p.action==1:
            xids.append(p.id_in_group)#投資者のplayeridを全てリストに追加
    #投資者数が多い場合の抽選
    if C.NUM_SUCCESS < group.x:
        win=random.sample(xids,C.NUM_SUCCESS)#NUM_SUCCESSだけxidsから勝者を選択
    for p in players:
        if p.action == 0:
            p.profit=0 # 非投資者の利得を計算
        else:
            if C.NUM_SUCCESS >= group.x:
                p.profit= 100*(C.RETURN_RATE*p.action-p.action)
            else:
                if p.id_in_group in win:
                    p.profit= 100*(C.RETURN_RATE*p.action-p.action)
                else:
                    p.profit= -100

# PAGES
class Contribute(Page):
    form_model = 'player'
    form_fields = ['action']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_profit


class Results(Page):
    form_model = 'player'
    form_fields = ['outcome']


page_sequence = [Contribute, ResultsWaitPage, Results]

Contribute.html

つぎは入力画面のhtmlです.

htmlタグ直書き世代の自分にとっては懐かしいコードです(わしらの若い頃は,これを手打ちして「ホームページ」をつくっておったのじゃよ).

ちなみに{{C.PROFIT}}のように変数名C.PROFITを囲むと,pythonで定義した変数をhtml上に表示させることができます.

{{ extends "global/Page.html" }}
{{ block title }}Contribute{{ endblock }}
{{ block content }}

    <p>
        You can choose "invest" or "not invest".
    </p>

    <p>
        {{C.NUM_SUCCESS}} players among those who invest will get a profit.
    </p>

 <p>
     You will get 
 <ul>
     <strong>{{C.PROFIT}}, when you win.</strong>
 </ul>
 <ul>
     <strong>- 100 , when you lose.</strong>
 </ul>
 <ul>
     <strong>0 , when you do not invest.</strong>
 </ul>


    {{ formfields }}

    {{ next_button }}

{{ endblock }}

Results.html

最後は結果表示と結果に対する満足度を聞くフォームを表示するhtmlです.

{{ block title }}Results{{ endblock }}
{{ block content }}

    <p>
       Your profit is {{ player.profit }}.
    </p>

    <p>
        Please answer the following questions.
    </p>

    {{ formfields }}


    {{ next_button }}

{{ endblock }}

実験の概要

相対的剥奪実験の具体例は以下の通りです.

  • 被験者は100円投資or投資しないを選択できます
  • 投資をえらんだ人は成功すれば300円もらえますが,失敗すると何ももらえません.
  • 成功人数は2名です.投資した人の数が2名を超えると,成功者の2名以外は100円失います

セッションによって利益率Rと成功者数nが変化します.一般的には

  • 被験者は100円投資or投資しないを選択できます
  • 投資をえらんだ人は成功すれば100*R円もらえますが,失敗すると何ももらえません.
  • 成功人数はn名です.投資した人の数がn名を超えると,成功者のn名以外は100円失います

というル-ルです

実験の流れ

直感的には実験は次のような順番で進行します.

  1. 実験用URLにアクセスしたプレイヤーにContribute.htmlが表示され,プレイヤーは行動(投資するか否か)を選択します
  2. 全プレイヤーが選択を終えると __init__.pyで定義した関数(set_profit)がプレイヤーの利得を計算します
  3. Result.htmlが表示され,各プレイヤーが受け取った利得がその中に表示されます.
  4. Result.htmlの中に満足度を入力するフォームが表示されます.プレイヤーが満足度を選択すると実験は終了です
  5. 結果が自動でデータベースに記録されます.

コードの概要

otreeでは基本変数と関数の定義を全て,__init__.py内に記述します.htmlはプレイヤーからの入力の受け取りや,計算結果の表示を担当します.

otreeでは実験用に基本的なクラスである

  • class C(BaseConstants): グローバル変数を定義するクラス
  • class Subsession(BaseSubsession): セッションを定義するクラス
  • class Group(BaseGroup): グループ属性を定義するクラス
  • class Player(BasePlayer): プレイヤー属性を定義するクラス

などがあらかじめ定義されています.

それぞれ簡単に内容を確認します.

class C(BaseConstants):
    NAME_IN_URL = 'relative_deprivation' #実験の名称です
    PLAYERS_PER_GROUP = 5 #被験者数です
    NUM_ROUNDS = 1 #ラウンド数です
    RETURN_RATE = random.randint(3,5) #利益率です
    NUM_SUCCESS = random.randint(1,5) #成功者数です
    PROFIT = RETURN_RATE*100-100 #COST=100 #成功時の利得です

以上がグローバル変数です.ラウンドごとに水準を変化させる場合は, 利益率などをsessionクラスで定義します.

class Group(BaseGroup):
    x = models.IntegerField()

グループの属性です.被験者集団全体で決まる値をここで属性として定義します. 属性xは投資者数を記録します.この値は被験者の行動によって変化するので,グローバル変数としては定義できないことに注意します.

class Player(BasePlayer):
    action = models.IntegerField(
        choices=[[0, 'no'],[1,'yes (invest 100)']],
        label="Do you invest? ",initial=(0)
    )
    outcome = models.IntegerField(
        choices=[[5, 'very satisfied'],[4, 'satisfied'],[3, 'neither'],[2, 'dissatisfied'], [1, 'very dissatisfied']],
        label='How do you feel about the result?',
        widget=widgets.RadioSelect,
    )
    profit = models.IntegerField()

プレイヤーのクラスです.ここでは

  • action #行動
  • outcome #結果への評価(満足度)
  • profit #受け取る利得

の3属性を定義します.idのような基本属性はもともと定義されているので,呼び出すだけで使えます.

def set_profit(group: Group):#グループオブジェクトを引数にした関数
    players = group.get_players()#playerを取得
    group.x = sum([p.action for p in players])#投資者数の総数xを計算
    xids=[]#投資者のplayeridを格納するリスト
    for p in players:
        if p.action==1:
            xids.append(p.id_in_group)#投資者のplayeridを全てリストに追加
    #投資者数が多い場合の抽選
    if C.NUM_SUCCESS < group.x:
        win=random.sample(xids,C.NUM_SUCCESS)#NUM_SUCCESSだけxidsから勝者を選択
    for p in players:
        if p.action == 0:
            p.profit=0 # 非投資者の利得を計算
        else:
            if C.NUM_SUCCESS > group.x:
                p.profit= 100*(C.RETURN_RATE*p.action-p.action)
            else:
                if p.id_in_group in win:
                    p.profit= 100*(C.RETURN_RATE*p.action-p.action)
                else:
                    p.profit= -100

利得計算関数です.本実験の肝の部分です.投資者数xnで設定した成功者数を下回る場合は,投資者全員が成功し, n<xの場合はランダムに成功者が決まるようにrandom.sample(xids,C.NUM_SUCCESS)で計算しています. そのため,投資選択者のidリストをxids.append(p.id_in_group)で作ります.このときotreeのプレイヤーのデフォルト属性であるid_in_group を利用します.

このようにotreeにはクラス毎によく利用する属性が準備されています.

# PAGES
class Contribute(Page):
    form_model = 'player'
    form_fields = ['action']


class ResultsWaitPage(WaitPage):
    after_all_players_arrive = set_profit


class Results(Page):
    form_model = 'player'
    form_fields = ['outcome']


page_sequence = [Contribute, ResultsWaitPage, Results]

htmlのクラスです.フォームからの入力などを定義します. 入力の受け取りにはプレイヤークラスで定義した属性をform_fieldsで指定します. 回答選択肢などは,プレイヤークラスの方で定義しています.




まとめ

otreeで相対的剥奪オンライン実験用コードを作成しました.

ポイントは

  • クールノーゲームのようにベースとなる実験を見つけ,それを上書き修正する
  • クラス毎に定義すべき属性を理解する

です.ztreeに比べるとかなりコードが書きやすくなった印象です. pythonの基本とhtmlのタグを知っていれば,簡単な実験なら2~3時間くらいで書けると思います.