OpenFisca-coreの設計、実装調査
調査して分かった内容を適宜更新
理解がまとまったらopenfisca-japan側のドキュメントとしてコミット
openfisca/openfisca-core: OpenFisca core engine. See other repositories for countries-specific code & data.
OpenFisca Python API — OpenFisca documentation
クラス
Variable
Coding a formula — OpenFisca documentation
Introducing an input variable — OpenFisca documentation
制度を実装する場合はこのクラスを継承
クラス属性
value_type: 変数の型(内部的にはnumpyのベクトル)
float は float32
entity: 世帯、個人等の計算単位
definition_period: 計算の期間(日、月、「永遠」(=単発or不変の値))
formula メソッドに計算式を実装
def formula(person, period, parameters)
※世帯の場合は household
parameters は省略可能
person('salary', period) で他のVariableの計算結果を取得できる
名前で参照しているので衝突注意!
名前で参照している理由: https://github.com/openfisca/openfisca-core/issues/668#issuecomment-390393177
ない場合は、デフォルト値指定orユーザー入力しない限り値を決められない
命名の参考
(英語の場合なので少し勝手が違うかも)
よく使う略語以外は略さない
接頭辞を付けることで名前衝突を防ぐ
income_tax_nb_parents(日本語なら 所得税における親の数 or 所得税_親の数)
衝突回避する場合全てに接頭辞を付けること(例:housing_tax_nb_parents)
OpenFisca variables: naming guidelines — OpenFisca documentation
Entity
Entities — OpenFisca documentation
計算単位を表わす(人物、世帯)
世帯の人数
household.nb_persons()
household.nb_persons(Household.ADULT) 特定の役割の世帯員の人数
判定
person.has_role(Household.ADULT) 世帯員が特定の役割かどうか
集計
household.members('salary', period) 各世帯員に対し変数の値を取得
逆に人物から世帯の変数も参照可能 person.household('basic_income', period)
特定の役割の世帯員の変数のみ抽出 household.partner('salary', period)
Period
Periods — OpenFisca documentation
期間を定義
変数を定義した期間と異なる機関に対して値を計算できない
その値が変わらない時間を期間として定義
例:「月給」は同じ月であれば変わらないので MONTH
書式
色々あるが YYYY, YYYY-MM, YYYY-MM-DD に統一すればよさそう
変数の値の計算に使用
simulation.calculate('housing_allowance', '2019-05')
異なる期間の変数を計算したい場合: ADD, DEVIDE を使用
person('salary', period, options = [ADD]) (年額のvariableから月額のvarable salaryを呼び出す)
異なる期間の変数を参照
period.offset(n, 'year') n年前
period('2023-11-01') の形式でも書ける
(日本の制度によくある「当年12月31日」はopenfiscaではシンプルには書けなさそう)
Parameter
Parameters — OpenFisca documentation
yamlファイルで記述
.metadata.util: 単位
currency: 通貨(金額)
/1:割合
パラメータの名前(yamlファイル名)は 小文字とアンダースコアのみ使用する
values に値を記述
期間ごとに定義可能
Enum
Introducing an input variable — OpenFisca documentation
種類を表わす
Enum クラスを継承
クラス属性に定義した値がそれぞれの要素になる
Enum を継承したクラスは種類そのものを定義
Enum の 値を表わすクラスは Variable を継承、その value_type にEnumを指定
Enum の各要素の属性
name: 名前(属性名そのものが文字列で得られる)
value:値(定義するときに代入した値)
内部実装
調査対象:41.2.0
formulaはどうやって呼び出される?
Variableにformula というメソッドがあると呼び出される仕組み
formulaの初期化: __init__ でのインスタンス初期化時
属性の抽出:https://github.com/openfisca/openfisca-core/blob/41.2.0/openfisca_core/variables/variable.py#L164
formula ではじまる名前
上記属性をformulaとして設定: https://github.com/openfisca/openfisca-core/blob/41.2.0/openfisca_core/variables/variable.py#L167
https://github.com/openfisca/openfisca-core/blob/41.2.0/openfisca_core/variables/variable.py#L294
メソッド名に日付がある場合、日付情報とともに登録
formulaの取得: get_formula
https://github.com/openfisca/openfisca-core/blob/41.2.0/openfisca_core/variables/variable.py#L373
現時点で最新となるformulaを返す https://github.com/openfisca/openfisca-core/blob/41.2.0/openfisca_core/variables/variable.py#L418
例: 現在2023-11-11, 候補 formula_2023_10_01, formula_2023_11_01, formula_2023_12_01の場合 formula_2023_11_01Simulation#calculate
シミュレータ実行: Simulation#calculate
内部で Variable#get_formula呼び出し https://github.com/openfisca/openfisca-core/blob/6a6e470bcfacb39a9aa7a9d863aa36eade4edafa/openfisca_core/simulations/simulation.py#L101
https://github.com/openfisca/openfisca-core/blob/6a6e470bcfacb39a9aa7a9d863aa36eade4edafa/openfisca_core/simulations/simulation.py#L314
formulaの引数は?
上記で取得されたformulaを呼び出す
https://github.com/openfisca/openfisca-core/blob/6a6e470bcfacb39a9aa7a9d863aa36eade4edafa/openfisca_core/simulations/simulation.py#L323
引数が (person, period) の場合と (person, period, parameters) の場合は、定義の仮引数の違いで判別している
第一引数には population
https://github.com/openfisca/openfisca-core/blob/6a6e470bcfacb39a9aa7a9d863aa36eade4edafa/openfisca_core/simulations/simulation.py#L126C28-L126C28
https://github.com/openfisca/openfisca-core/blob/6a6e470bcfacb39a9aa7a9d863aa36eade4edafa/openfisca_core/simulations/simulation.py#L540
どこからきている?
Simulation のインスタンスは SimulationBuilder で作られる
https://openfisca.org/doc/simulate/run-simulation.html#application-calculate-two-households-housing-allowances
simulation_builder.build_from_entities(tax_benefit_system, TEST_CASE)
https://github.com/openfisca/openfisca-core/blob/6a6e470bcfacb39a9aa7a9d863aa36eade4edafa/openfisca_core/simulations/simulation_builder.py#L69
名前の通り、第2引数は世帯情報をテストケースのyamlとおなじdict形式で記述
populationは tax_benefit_system.instantiate_entities() で初期化
openfisca-core/openfisca_core/simulations/simulation_builder.py at 6a6e470bcfacb39a9aa7a9d863aa36eade4edafa · openfisca/openfisca-core
tax_benefit_system は CountryTaxBenefitSystem で作られる
CountryTaxBenefitSystem :各国の制度の初期化を行う
TaxBenefitSystem を継承
https://github.com/project-inclusive/OpenFisca-Japan/blob/4c1f5ef9f703191c01d4fbdb7a7fe7cb6635a43f/openfisca_japan/__init__.py#L22
(instantiate_entities の定義が見つからない…)
simulationのvariableの初期化
https://github.com/openfisca/openfisca-core/blob/6a6e470bcfacb39a9aa7a9d863aa36eade4edafa/openfisca_core/simulations/simulation_builder.py#L88C25-L88C25
PopulationはProtocol を継承している→interfaceを表わしているだけでは?
PythonでProtocolを使って静的ダック・タイピング #Python - Qiita
Goみたいに継承を明示しないタイプのインターフェース
→実体はこのメソッドを定義している具象クラスを探せばよい
ここに実装があった
https://github.com/openfisca/openfisca-core/blob/05980c4bcd44f70485a35855ec5b887abe4bba33/openfisca_core/populations/population.py#L18
variableの探索はどうやって行われる?
openfisca-core/openfisca_core/populations/population.py at 05980c4bcd44f70485a35855ec5b887abe4bba33 · openfisca/openfisca-core
https://github.com/openfisca/openfisca-core/blob/05980c4bcd44f70485a35855ec5b887abe4bba33/openfisca_core/populations/population.py#L125
ここでsimlation#calculateを呼び出す
https://github.com/openfisca/openfisca-core/blob/05980c4bcd44f70485a35855ec5b887abe4bba33/openfisca_core/populations/population.py#L135C40-L135C40
simlation#calculateがformulaを探して実行→formula内のpopulation#__call__でsimlation#calculate実行とお互いに呼び合っている
variableの形式はどこで決まる?
parameterはどうやってプログラム上に読み込まれる?
ディレクトリ配下のyamlファイルをparameterとして読み込み
https://github.com/openfisca/openfisca-core/blob/05980c4bcd44f70485a35855ec5b887abe4bba33/openfisca_core/parameters/parameter_node.py#L65
https://github.com/openfisca/openfisca-core/blob/05980c4bcd44f70485a35855ec5b887abe4bba33/openfisca_core/parameters/helpers.py#L20
load_parameter_fileでyamlファイル読み込み
ディレクトリの場合は配下を読み込むことで再帰的に取得
→ディレクトリ構造をそのままフィールド名として取得できる
formulaの引数 https://github.com/openfisca/openfisca-core/blob/6a6e470bcfacb39a9aa7a9d863aa36eade4edafa/openfisca_core/simulations/simulation.py#L319
Simulation.tax_benefit_system.get_parameters_at_instant
呼び出されるのはこれ
https://github.com/openfisca/openfisca-core/blob/05980c4bcd44f70485a35855ec5b887abe4bba33/openfisca_core/parameters/parameter.py#L209
instantで現在に適用可能なものを選択
python -m build とは?
Python のプロジェクトをパッケージングする - Python Packaging User Guide
ビルドし、配布可能な形式にする
OpenFiscaとは関係ない
テスト
Writing YAML tests — OpenFisca documentation
input に変数を指定
変数名: 値の形式だが、定義されている期間が period で指定したものと異なる場合は 期間: 値 の形式で指定
世帯員や世帯について計算する場合は入れ子にする必要がある
code:yaml
# https://openfisca.org/doc/coding-the-legislation/writing_yaml_tests.html より引用、日本語訳
- name: "IRPP - 給与収入が20,000ユーロの世帯"
period: 2012
absolute_error_margin: 0.5
input:
世帯員:
親1:
誕生年月日: 1972-01-01
公務員ボーナス: 500
# ...
家族:
- 親
- 親1
- 親2
- 子
- 子1
- 子2
世帯: # 続柄を書ける?
- 本人
- 親1
- 配偶者
- 親2
- 子
- 子1
- 子2
課税世帯:
# ...
#openfisca