From eeae0f0544a75ef62a8a1eebb1e262291d564fa0 Mon Sep 17 00:00:00 2001 From: MaxBlesch Date: Mon, 9 Feb 2026 14:04:06 +0100 Subject: [PATCH 1/2] Initial states as input --- soepy/simulate/initial_states.py | 175 ++++++------------ soepy/simulate/simulate_auxiliary.py | 48 +++-- soepy/simulate/simulate_python.py | 56 +++--- soepy/test/resources/initial_states.py | 133 +++++++++++++ .../test/resources/regression_vault.soepy.pkl | Bin 953629 -> 953629 bytes soepy/test/resources/update_vault.py | 41 +++- soepy/test/test_childless.py | 17 +- soepy/test/test_expectation.py | 7 + soepy/test/test_get_simulate.py | 8 + soepy/test/test_quadrature.py | 17 +- soepy/test/test_regression.py | 124 ++++++++++++- soepy/test/test_single_woman.py | 19 +- soepy/test/test_unit.py | 21 ++- soepy/test/test_validation_childless.py | 17 +- ...t_value_function_simulation_consistency.py | 6 + 15 files changed, 486 insertions(+), 203 deletions(-) create mode 100644 soepy/test/resources/initial_states.py diff --git a/soepy/simulate/initial_states.py b/soepy/simulate/initial_states.py index 305f17e..066ee4b 100644 --- a/soepy/simulate/initial_states.py +++ b/soepy/simulate/initial_states.py @@ -1,123 +1,58 @@ import numpy as np import pandas as pd -from soepy.shared.experience_stock import exp_years_to_stock -from soepy.shared.experience_stock import get_pt_increment -from soepy.shared.experience_stock import stock_to_exp_years -from soepy.shared.numerical_integration import draw_zero_one_distributed_shocks - - -def prepare_simulation_data( - model_params, - model_spec, - prob_educ_level, - prob_child_age, - prob_partner_present, - prob_exp_pt, - prob_exp_ft, - biased_exp, -): - """Draw initial conditions and precompute grid objects for simulation.""" - - initial_educ_level = np.random.choice( - model_spec.num_educ_levels, model_spec.num_agents_sim, p=prob_educ_level - ) - - initial_period = np.asarray(model_spec.educ_years)[initial_educ_level] - - initial_child_age = np.full(model_spec.num_agents_sim, np.nan) - initial_partner = np.full(model_spec.num_agents_sim, np.nan) - initial_exp_pt = np.full(model_spec.num_agents_sim, np.nan) - initial_exp_ft = np.full(model_spec.num_agents_sim, np.nan) - - for educ_level in range(model_spec.num_educ_levels): - mask = initial_educ_level == educ_level - - initial_child_age[mask] = np.random.choice( - list(range(-1, model_spec.child_age_init_max + 1)), - mask.sum(), - p=prob_child_age[educ_level], - ) - - initial_partner[mask] = np.random.binomial( - size=mask.sum(), - n=1, - p=prob_partner_present[educ_level], - ) - initial_exp_pt[mask] = np.random.choice( - list(range(0, model_spec.init_exp_max + 1)), - mask.sum(), - p=prob_exp_pt[educ_level], - ) - - initial_exp_ft[mask] = np.random.choice( - list(range(0, model_spec.init_exp_max + 1)), - mask.sum(), - p=prob_exp_ft[educ_level], - ) - - # Combine stocks - pt_increment = get_pt_increment( - model_params=model_params, - educ_level=initial_educ_level, - child_age=initial_child_age, - biased_exp=biased_exp, - ) - - total_years = initial_exp_pt * pt_increment + initial_exp_ft - - initial_exp_stock = exp_years_to_stock( - exp_years=total_years, - period=0, - init_exp_max=model_spec.init_exp_max, - pt_increment=pt_increment, - ) - - lagged_choice = lagged_choice_initial(initial_exp_years=total_years) - - unobserved_type = np.random.choice( - np.arange(model_spec.num_types), - model_spec.num_agents_sim, - p=model_params.type_shares, - ) - - draws_sim = draw_zero_one_distributed_shocks( - model_spec.seed_sim, model_spec.num_periods, model_spec.num_agents_sim - ) - draws_sim = draws_sim * float(model_params.shock_sd) - - # prob_exp_pt/prob_exp_ft are passed in from simulate.py and originate from the - # same legacy share files as prob_exp_years. - initial_states = pd.DataFrame( - { - "Identifier": np.arange(model_spec.num_agents_sim, dtype=int), - "Period": initial_period.astype(int), - "Education_Level": initial_educ_level.astype(int), - "Lagged_Choice": lagged_choice.astype(int), - "Experience_Part_Time": initial_exp_pt.astype(int), - "Experience_Full_Time": initial_exp_ft.astype(int), - "Experience_Stock": initial_exp_stock.astype(float), - "Type": unobserved_type.astype(int), - "Age_Youngest_Child": initial_child_age.astype(int), - "Partner_Indicator": initial_partner.astype(int), - } - ) - - return initial_states, draws_sim - - -def lagged_choice_initial(initial_exp_years): - """Determine initial lagged choice from total experience. - - Rule (per project decision): - - lagged_choice = 2 (full-time) if initial experience years > 1 - - otherwise lagged_choice = 0 - """ - - lagged_choice = np.zeros_like(initial_exp_years, dtype=int) - lagged_choice[initial_exp_years > 1] = 2 - # Check if initial years is a float if so, assign 1 part-time - int_exp = initial_exp_years.astype(int) - is_float = np.abs(initial_exp_years - int_exp) > 1e-8 - lagged_choice[is_float] = 1 - return lagged_choice +from soepy.shared.constants_and_indices import NUM_CHOICES + + +def validate_initial_states(initial_states, model_spec): + required_columns = [ + "Identifier", + "Period", + "Education_Level", + "Lagged_Choice", + "Experience_Part_Time", + "Experience_Full_Time", + "Type", + "Age_Youngest_Child", + "Partner_Indicator", + ] + missing = [col for col in required_columns if col not in initial_states.columns] + if missing: + raise ValueError(f"Initial states missing columns: {missing}") + + if initial_states[required_columns].isna().any().any(): + raise ValueError("Initial states contain missing values.") + + identifiers = initial_states["Identifier"].to_numpy() + unique_ids = np.unique(identifiers) + if len(unique_ids) != len(identifiers): + raise ValueError("Initial state identifiers must be unique.") + + if not np.issubdtype(unique_ids.dtype, np.integer): + raise ValueError("Initial state identifiers must be integers.") + + expected_ids = np.arange(len(unique_ids), dtype=unique_ids.dtype) + if not np.array_equal(np.sort(unique_ids), expected_ids): + raise ValueError("Initial state identifiers must be consecutive from 0.") + + # if (initial_states["Period"] < 0).any() or ( + # initial_states["Period"] >= model_spec.num_periods + # ).any(): + # raise ValueError("Initial state periods out of bounds.") + + if (initial_states["Education_Level"] < 0).any() or ( + initial_states["Education_Level"] >= model_spec.num_educ_levels + ).any(): + raise ValueError("Initial state education levels out of bounds.") + + if (initial_states["Type"] < 0).any() or ( + initial_states["Type"] >= model_spec.num_types + ).any(): + raise ValueError("Initial state types out of bounds.") + + if (initial_states["Lagged_Choice"] < 0).any() or ( + initial_states["Lagged_Choice"] >= NUM_CHOICES + ).any(): + raise ValueError("Initial state lagged choices out of bounds.") + + return initial_states diff --git a/soepy/simulate/simulate_auxiliary.py b/soepy/simulate/simulate_auxiliary.py index 3ddada2..e9629a4 100644 --- a/soepy/simulate/simulate_auxiliary.py +++ b/soepy/simulate/simulate_auxiliary.py @@ -3,10 +3,12 @@ from soepy.shared.constants_and_indices import HOURS from soepy.shared.constants_and_indices import NUM_CHOICES +from soepy.shared.experience_stock import exp_years_to_stock from soepy.shared.experience_stock import get_pt_increment from soepy.shared.experience_stock import next_stock from soepy.shared.non_employment import calc_erziehungsgeld from soepy.shared.non_employment import calculate_non_employment_consumption_resources +from soepy.shared.numerical_integration import draw_zero_one_distributed_shocks from soepy.shared.wages import calculate_log_wage from soepy.simulate.constants_sim import DATA_FORMATS_SIM from soepy.simulate.constants_sim import DATA_FORMATS_SPARSE @@ -14,8 +16,7 @@ from soepy.simulate.constants_sim import LABELS_DATA_SPARSE from soepy.simulate.constants_sim import STATE_LABELS_SIM from soepy.simulate.income_sim import calculate_employment_consumption_resources -from soepy.simulate.initial_states import prepare_simulation_data - +from soepy.simulate.initial_states import validate_initial_states def pyth_simulate( model_params, @@ -26,33 +27,47 @@ def pyth_simulate( covariates, non_consumption_utilities, child_age_update_rule, - prob_educ_level, - prob_child_age, - prob_partner_present, - prob_exp_pt, - prob_exp_ft, prob_child, prob_partner, + *, biased_exp, + initial_states, data_sparse=False, ): """Simulate agent histories under the continuous-experience model.""" np.random.seed(model_spec.seed_sim) + initial_states = validate_initial_states(initial_states, model_spec) + num_agents_sim = initial_states["Identifier"].nunique() + emaxs = np.asarray(emaxs) non_consumption_utilities = np.asarray(non_consumption_utilities) - initial_states, draws_sim = prepare_simulation_data( + pt_increment = get_pt_increment( model_params=model_params, - model_spec=model_spec, - prob_educ_level=prob_educ_level, - prob_child_age=prob_child_age, - prob_partner_present=prob_partner_present, - prob_exp_pt=prob_exp_pt, - prob_exp_ft=prob_exp_ft, - biased_exp=biased_exp, + educ_level=initial_states["Education_Level"].to_numpy(), + child_age=initial_states["Age_Youngest_Child"].to_numpy(), + biased_exp=False, + ) + total_years = ( + initial_states["Experience_Part_Time"].to_numpy() * pt_increment + + initial_states["Experience_Full_Time"].to_numpy() + ) + initial_states = initial_states.copy() + initial_states["Experience_Stock"] = exp_years_to_stock( + exp_years=total_years, + period=initial_states["Period"].to_numpy(), + init_exp_max=model_spec.init_exp_max, + pt_increment=pt_increment, + ).astype(float) + + draws_sim = draw_zero_one_distributed_shocks( + model_spec.seed_sim, + model_spec.num_periods, + num_agents_sim, ) + draws_sim = draws_sim * float(model_params.shock_sd) data_list = simulate_agents_over_periods( model_spec=model_spec, @@ -161,7 +176,8 @@ def simulate_agents_over_periods( ) ) - wages = np.exp(log_wage_agents + draws_sim[period, : len(current_states)]) + identifiers = current_states.iloc[:, state_col["Identifier"]].to_numpy() + wages = np.exp(log_wage_agents + draws_sim[period, identifiers]) wages = wages * float(model_spec.elasticity_scale) female_income = wages[:, None] * HOURS[None, 1:] diff --git a/soepy/simulate/simulate_python.py b/soepy/simulate/simulate_python.py index d2b6480..c6bdb62 100644 --- a/soepy/simulate/simulate_python.py +++ b/soepy/simulate/simulate_python.py @@ -1,21 +1,20 @@ from functools import partial -from soepy.exogenous_processes.children import gen_prob_child_init_age_vector from soepy.exogenous_processes.children import gen_prob_child_vector -from soepy.exogenous_processes.education import gen_prob_educ_level_vector -from soepy.exogenous_processes.experience import gen_prob_init_exp_component_vector from soepy.exogenous_processes.partner import gen_prob_partner -from soepy.exogenous_processes.partner import gen_prob_partner_present_vector from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init from soepy.simulate.simulate_auxiliary import pyth_simulate from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import get_solve_function +from soepy.simulate.initial_states import validate_initial_states def simulate( model_params_init_file_name, model_spec_init_file_name, + *, + initial_states, biased_exp=True, data_sparse=False, ): @@ -28,7 +27,11 @@ def simulate( data_sparse=data_sparse, ) - return simulate_func(model_params_init_file_name, model_spec_init_file_name) + return simulate_func( + model_params_init_file_name, + model_spec_init_file_name, + initial_states=initial_states, + ) def get_simulate_func( @@ -42,19 +45,6 @@ def get_simulate_func( model_params_df, model_params = read_model_params_init(model_params_init_file_name) model_spec = read_model_spec_init(model_spec_init_file_name, model_params_df) - prob_educ_level = gen_prob_educ_level_vector(model_spec) - prob_child_age = gen_prob_child_init_age_vector(model_spec) - prob_partner_present = gen_prob_partner_present_vector(model_spec) - - prob_exp_pt = gen_prob_init_exp_component_vector( - model_spec, - model_spec.pt_exp_shares_file_name, - ) - prob_exp_ft = gen_prob_init_exp_component_vector( - model_spec, - model_spec.ft_exp_shares_file_name, - ) - prob_child = gen_prob_child_vector(model_spec) prob_partner = gen_prob_partner(model_spec) @@ -76,8 +66,12 @@ def get_simulate_func( biased_exp=biased_exp, ) + def simulate_func( - model_params_init_file_name_inner, model_spec_init_file_name_inner + model_params_init_file_name_inner, + model_spec_init_file_name_inner, + *, + initial_states, ): model_params_df_inner, model_params_inner = read_model_params_init( model_params_init_file_name_inner @@ -89,6 +83,9 @@ def simulate_func( non_consumption_utilities, emaxs = solve_func(model_params_inner) + initial_states_validated = validate_initial_states( + initial_states, model_spec_inner + ) df = pyth_simulate( model_params=model_params_inner, model_spec=model_spec_inner, @@ -98,14 +95,10 @@ def simulate_func( covariates=covariates, non_consumption_utilities=non_consumption_utilities, child_age_update_rule=child_age_update_rule, - prob_educ_level=prob_educ_level, - prob_child_age=prob_child_age, - prob_partner_present=prob_partner_present, - prob_exp_pt=prob_exp_pt, - prob_exp_ft=prob_exp_ft, prob_child=prob_child, prob_partner=prob_partner, biased_exp=False, + initial_states=initial_states_validated, data_sparse=data_sparse, ).set_index(["Identifier", "Period"]) @@ -120,17 +113,13 @@ def partiable_simulate( indexer, covariates, child_age_update_rule, - prob_educ_level, - prob_child_age, - prob_partner_present, - prob_exp_years, - prob_exp_pt, - prob_exp_ft, prob_child, prob_partner, data_sparse, model_params_init_file_name, model_spec_init_file_name, + *, + initial_states, ): # Read in model specification from yaml file model_params_df, model_params = read_model_params_init(model_params_init_file_name) @@ -141,6 +130,7 @@ def partiable_simulate( non_consumption_utilities, emaxs = solve_func(model_params) # Simulate agents experiences according to parameters in the model specification + initial_states_validated = validate_initial_states(initial_states, model_spec) df = pyth_simulate( model_params, model_spec, @@ -150,14 +140,10 @@ def partiable_simulate( covariates, non_consumption_utilities, child_age_update_rule, - prob_educ_level, - prob_child_age, - prob_partner_present=prob_partner_present, - prob_exp_pt=prob_exp_pt, - prob_exp_ft=prob_exp_ft, prob_child=prob_child, prob_partner=prob_partner, biased_exp=False, + initial_states=initial_states_validated, data_sparse=data_sparse, ).set_index(["Identifier", "Period"]) diff --git a/soepy/test/resources/initial_states.py b/soepy/test/resources/initial_states.py new file mode 100644 index 0000000..42d58ae --- /dev/null +++ b/soepy/test/resources/initial_states.py @@ -0,0 +1,133 @@ +import numpy as np +import pandas as pd + +from soepy.exogenous_processes.children import gen_prob_child_init_age_vector +from soepy.exogenous_processes.education import gen_prob_educ_level_vector +from soepy.exogenous_processes.experience import gen_prob_init_exp_component_vector +from soepy.exogenous_processes.partner import gen_prob_partner_present_vector +from soepy.pre_processing.model_processing import read_model_params_init +from soepy.pre_processing.model_processing import read_model_spec_init +from soepy.shared.experience_stock import get_pt_increment + + +def _lagged_choice_initial(initial_exp_years): + lagged_choice = np.zeros_like(initial_exp_years, dtype=int) + lagged_choice[initial_exp_years > 1] = 2 + int_exp = initial_exp_years.astype(int) + is_float = np.abs(initial_exp_years - int_exp) > 1e-8 + lagged_choice[is_float] = 1 + return lagged_choice + + +def create_initial_states_from_probs( + model_params, + model_spec, + *, + prob_educ_level, + prob_child_age, + prob_partner_present, + prob_exp_pt, + prob_exp_ft, +): + np.random.seed(model_spec.seed_sim) + + initial_educ_level = np.random.choice( + model_spec.num_educ_levels, + model_spec.num_agents_sim, + p=prob_educ_level, + ) + + initial_period = np.asarray(model_spec.educ_years)[initial_educ_level] + + initial_child_age = np.full(model_spec.num_agents_sim, np.nan) + initial_partner = np.full(model_spec.num_agents_sim, np.nan) + initial_exp_pt = np.full(model_spec.num_agents_sim, np.nan) + initial_exp_ft = np.full(model_spec.num_agents_sim, np.nan) + + for educ_level in range(model_spec.num_educ_levels): + mask = initial_educ_level == educ_level + + initial_child_age[mask] = np.random.choice( + list(range(-1, model_spec.child_age_init_max + 1)), + mask.sum(), + p=prob_child_age[educ_level], + ) + + initial_partner[mask] = np.random.binomial( + size=mask.sum(), + n=1, + p=prob_partner_present[educ_level], + ) + initial_exp_pt[mask] = np.random.choice( + list(range(0, model_spec.init_exp_max + 1)), + mask.sum(), + p=prob_exp_pt[educ_level], + ) + + initial_exp_ft[mask] = np.random.choice( + list(range(0, model_spec.init_exp_max + 1)), + mask.sum(), + p=prob_exp_ft[educ_level], + ) + + pt_increment = get_pt_increment( + model_params=model_params, + educ_level=initial_educ_level, + child_age=initial_child_age, + biased_exp=False, + ) + total_years = initial_exp_pt * pt_increment + initial_exp_ft + lagged_choice = _lagged_choice_initial(total_years) + + unobserved_type = np.random.choice( + np.arange(model_spec.num_types), + model_spec.num_agents_sim, + p=model_params.type_shares, + ) + + initial_states = pd.DataFrame( + { + "Identifier": np.arange(model_spec.num_agents_sim, dtype=int), + "Period": initial_period.astype(int), + "Education_Level": initial_educ_level.astype(int), + "Lagged_Choice": lagged_choice.astype(int), + "Experience_Part_Time": initial_exp_pt.astype(int), + "Experience_Full_Time": initial_exp_ft.astype(int), + "Type": unobserved_type.astype(int), + "Age_Youngest_Child": initial_child_age.astype(int), + "Partner_Indicator": initial_partner.astype(int), + } + ) + + return initial_states + + +def create_initial_states( + *, + model_params_init_file_name, + model_spec_init_file_name, +): + model_params_df, model_params = read_model_params_init(model_params_init_file_name) + model_spec = read_model_spec_init(model_spec_init_file_name, model_params_df) + + prob_educ_level = gen_prob_educ_level_vector(model_spec) + prob_child_age = gen_prob_child_init_age_vector(model_spec) + prob_partner_present = gen_prob_partner_present_vector(model_spec) + prob_exp_pt = gen_prob_init_exp_component_vector( + model_spec, + model_spec.pt_exp_shares_file_name, + ) + prob_exp_ft = gen_prob_init_exp_component_vector( + model_spec, + model_spec.ft_exp_shares_file_name, + ) + + return create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) diff --git a/soepy/test/resources/regression_vault.soepy.pkl b/soepy/test/resources/regression_vault.soepy.pkl index 0988d0f8e0d8eb83f4d7e990dbd9aa0c6707a04b..e4ded74d0df08b7f5e61280709f9d8f611f4a106 100644 GIT binary patch delta 31021 zcmcG%c|29$+y8%znNpF`AV(!CB7`UwnaMoQ^O%ewlw&ADG9PoM45dLTnlGB9d7evY z5Dl73(|7H&H|jp``~H1Dzx(|?f3zOgy6m-|>$TUs*4|Z?X;hYJ?9r1daF9}*G*Le; z+%Gc7Z?%t~hfQ>tPe|JasX`7Bz5+xeen!P)IJBYQ^}Lx$^l8d*WJE~`+AeXdEY;y6 znz^y-s&lT44)OOi*Fu!2@_owP3tn(QN7`~ANDY3k;F%B};|thZ9e;BzS~ta&85Lv% z^RoC3ow}|HukyaBjq|l4{4|#@ThQU2D8YHJoj_RjXFp(n+J?g(kuyD@FuEZ6P)OV2 zxwV}C3g5+$Sx21KsKJz>3w zVw7)Crbx12uBmuZFYj+xL}ZG_NI{Opo@e>%PoPDoG-_UdpG1n~xd|Cs=_S5C=mjdD zX86tMP=+0wbgjf?SunKO*|F%U{LCv6D$X#qw%xm1K^baGg#_f^x&uxX8OCR#cFwep zigziUBmNsf_be*0T^P;WC&LlamSR-K`JdCcE@-WI^+p*^-hI@2XAwIM=V!J3j$O(S z&!N9{rKkrR&1Ai->Bg4I5YTha@=uysW{)Kc>cYg6sw<2G5zYgR)|-WH_EtVJe{~W) z4Y{UqM?r$rCU65%r@g)9E94FD^E%g`pQ8g(GWz~zcl-cf#pv;kK?d>sl09pzAy=)) zM_>>E*Y*zwLq9taezf3nDw_HrV1e^_w^4sEZ}XG&4c;RKi6azC^B==B@U#jqmC*ry zQP(cvyX-J~1oJyiz^1n-NO2{^OCT;wg7z2r55&8rIfzKy|DhVPb4>h(gH9cvOq zhRBl)Gt$YB9tSky7rqAJe;IRoZHbUZQ)HmY8^5loGpIv>FY1~&tY(rL@}(fzt|;x^ z9tFr*B=K}LFBeEoQMu~L?V_QSc+Cy z=V(d9cO%g?k|#DWIECHv>IO|u4`rS*CCk~s9`ggBRiHsZPFj6j8w5M^3^izDg zwFqBEqwS&iPOYW*_LQQy!wsL0tYhbMR+Q>`gZWG^3gs2P(S>z44lG%gI>wjbB5SC! zK@09*eDQPEC0)3FOht~{d5mu}my%!b7c=nxw)(Aok}d>Q?s^eAf#7S4KIBU;=)EhR zgl4v`CCTFQQ3KECl$DK-(fA+Nj~$koPYRWok1pK4J|mUW2K+OBn$^`#g=#xBu85Vk zfb-*NbHEoZRNT5V!F?{gsJrsLCT%LT>>59OwrLUJuYUeH7YP>}D6FnF9`)~1x+D z*A0}^MiL)p@*HaA2?r?l-X(2Bwi|yY{vnyS_-aWQbVa}Za8xL%AmY&K_D@_KF_I$c zTM$7)i!LEU6vdR1;gCq;u#6%j&NGp_)RwQV{I8K_-U63sA9?bDLka47vtv(l*&0}I zil(~cq$516GYf7x7!NqPURq`*s2|@Ed?Il7dgUGhm0HCd&U?Am0ekkg?kroiF9#7HfUeCNH6={9& zP~5uS8HOuddIXkvf>zMy<}X>ngdc8d)u7J*uF1{ zW4)9Ju8lZ1G@3~b8HN`!&5E#op3|e&lQC(fXzt0^M^X7xbSTO4(Z!QgDa_c4jxCtx zvvclRn8gUK+`KCi`1F$7s~4vMP7is>6d*|zGlTh+32?*JV&(SSNKlq*f7#--f$%fx zTFTM>uS>3d`rod*w!19^V8Py#nHLg)hJX1{nJ7bP zuEF|yCpuR=Q#~-LBm^$)%&8%4>;~E5dNd`ONkV3p)i2O@;&et$wM?7g^F>7dT6g<2iXT65u2N#Vrw?)Z} zAx^)&sGC46dRe{el%yZ591Iow6S1zT_YHx#)<9|Detk#uBm0~w*Z1JM>rJrjQL1G%W09;p-Gj`*)VbvV=M0KRiM zHfr3k2gO0RC=6&XCy-P%LKoJW_oO@$#zU`GxuhFriY>Kjxi1xV9xI z*1sAWY;Ycxffy&(`^h=%JK=sc3TC`i*ymAY?6UiM26Kd%w?7Y-P;S) zXm=aPHMP>;K?P}|554x4Ft%t9oc&ZT@LHouQk?COWkMYB@Xt{NIbr(6&ZVULtT z4J$%GL?Qm5TAWeDyb;9yEHu3|pnU#rc0`8iDs3~2Xk61^+l&!;LeoCm{eft_-SH-Y z*q`=ZRw~;N%qE1x4f8R?85H6nqwVbt7;&QP--tJDId@2j)Kk6Q=beq#F|*hYGZTYfWLd!4+B zUM@%akWreJ>gC6pAJbP@7=LxDOY(%)`u@heHNZ&iD$P~mq&Rc2=8Gt6wdJ}%1F~XEfA1)c?FN5(9 zEz*_l>%*z%4<^lT8Y8^7)&5AMxGAX7LI>xq)`un&qlDenoSUVXZe5&sky4dG z4P+kHpzn({w8i^xAOWwBq0g&VkfjdCbFbKCXM5cnu4X-w<)ztzjq#;z@tms&!RSZl ztVbI68*Ao2*8^{Uqiq2fZQ!f2s@C-ySHchV*Xod_-b8U;$)HhxQJd!rr5O<;f;pVv zEaUwDfwODtBY~jY_rgPE!>^N$ZmpNY;wt+Uehw8;W>^K==(oeLn*}&t|^;s4( zM{z!!=G;V2qLmMk z#Vv7u&1Owf#-^?4PRW@q@3^Dj={9a7!BRI^dt2<_K|HI#*LkL{-GnY&J~D8_+aI{o zGusxb3b=vp&v~D3xJ3|uKwmFVgJRux%5bf{(nKQtQt@w&_P?9-Eh)Y~VOvw@9tkP(c zOHg{5B%NW)d^~|`I@1F?)rMB>HxA_rho&l-$u4Pr;9lb$5OaxaR(6KeqJmXCJ)ZuF zqyA*eJ0Da^Q&NU1$3(7el1YjQBz!g1QzIwRMwG$~3&2^!kD9dzz)<3LM=ZN!BN{L$^Z3cT8|CT7 ziZjxtlLcl^Hj+0_donl88+@K*CI`t{L6wF>&is}jz}GSQgrbpi?6T-n2D>|dSco22 zX!Oex2CM3x5fQ{6#CYYP1$|lz7)5Vbe?a(a?Ccg}n=CME7IWbOL#MEF|F5v=M!(z; zPMt=}-H4hKWy`68g>Q(ZbaXo}VH zM=xHV>f~PfJnf`>6sWwDbnnMD;Xm@E0d+Zx)b4Z{*EQllnMT`A*m#(PS6JS13gSr% z@wYUcKhYQ;_ETuLtI&|?wM)z1vZKW=ns@aYMr*MXth;C$1xh6%$C8$gq3z2i(Waf8 z=Kf4I3e@vl|3dR{CDFFmS8OkpjDptf!_6cbx%d;}%#|(4{JoY-T$C}J-=D-(EJgR{ zO7lCpGiS$hDXFP9x^%@}aE7NTWo5Rt&R`{9q_+7I`S1>OR3Mk^M+-bm0-&pPMX_Fu zGZ?HDdg6E|f>qDH#p4ns5|>LRoI&j!R{Ju)ujP_R^0*m5IxI`kk%UQNT!Diq(TXQ= zYpDwPaBee^=7Qolv5CPTb&9jEBEbb##A_d%6czD@IgYWOcuA$s-;?LWLl}W-?rRcq#&$EUzb}-fb){>k63piR6 z*EoKSH$g~GsE9|sPg=JYSnEKruk@j-6D?qMw#tonHLR{1A}5xhr&`yW?ppJ9E!nWV z>n!#=+q7*bY+^oob1N4yAEoGGM(9=$ba-q7T2x&%D?#T5=SK1;vEwi)Pa?2Di1v7w}A6~AB?tCj^W*^y~e`JJAc}5`x|qZ z+`NavW8T<`*AMU4aplnkHd1KxP0XZ|7!9@whm*>!Dnv%LH=CDDzl)r&sL7_kqm<)g zqKUebrikBN0fl^KyHZ2sVWYtL>lZ6b2<58BB$xztN_h<-)W0K&oQU83^ZzMhi@4 zX7}S_XVF2e$n7gbNxKicJ|%w&H6*?kdE+;Osu+c6vqH|3RNhc9mwTtXVU;Dw3*ITT zd&lZyt6LAoBk%8q!a=pJaQRA);kfCRFmqmz@(Te!z_&qmOE#hUlv_;koc*06eqQly z7YG{{J91?`^CeMtJt;)mUx@etk8h(GTguS(tve**W!V|uO!+oYg&7@B@K|h*vIHgR z-=dG-k1;k#PSZ^z7@hr(c6wMsPTqvAlV!&k_uAcKF$QZK=5?E8i5HIBDMr>_cKf{7 zTo36Y9c2h-ibi|fN`|+yn6rerG2*Hfv1H7$N@V@{#zm!voI0~JEz-X|RU>6AtU&Me z&V_B?pa=674@?$ql!nhd96KkN8Us!bO}G+=dM?_p;e}c7q*7|T{uF7L*0t~P_hCiC zUwuHa3@v+*cWBx>HBhsQ%NZ1uhNlOUg}chp$VhhP1a2(ize2c|Q|w6;Z5~8EYx3xH zXD9qzdGgbg9%=Aad>5c$vY3tVS4ZRXJ%-Y7t-`C}b<-$ehY0s=DM7}BlL%)CFTIvA zwbi*LlCE)3(-rZZDB10R?e7om1Fj>B8 zccwxS;AD{grBYP?;=uV!hho5vb|^|dCk@h)d1caivsixlEiK6T$>I0j6E=+c$Hldk zYM;#?ZJTSB5aMxfM>s*+|BCSKo;dly$7%43E6{tp8atulYku*@6KOEs?6lBY!E81{ z1IN2>MJv*P+vD|v>leoemoMeUy~nXygaPqjppgvuikSmWxByn4Uy(}2gg2s&g~Ps; zQ-+YbqEqjUVr{Yj8q}jR(JxkW%!mg4+WAM*JOe=0QeM!dc0J(qdcUgWp~%KnpM&rE zz@j~3%AE=6*sCV2c0Deu)+LRVPhL~An%R*d`zw_WjnTT_+L?mBnH$@UDV%>o<0*ZW4ax^KK{g%@Ygr{ep$z5Rw z*$Hh*DXL+BGtIE~TZhEG#lBYWbcNW^2iH%C+d=&#%NKXo1QLF9_*WJRop^Iv!1Ax1 zBmUZ=XR`=W%3osQs+gg@>}M zVP<`pLDh6`_@sD1?!h_Mq!y`gZa{P5#)n1*xI@&dq_e7nUT{CnCM8xQoF#FJVgsE=hboJVK9wC`;6yi6q<0-6*Y=1u^1V@edFI5 z_}`Ox*`*{l33!8;N99lXlVfCemaScV)!!Xv$~h^ty!3);RqHn7Egd84SzF5@bJ0Kh z$>OXR6m8xYPm&cfW$L(t+vOX%`z1w23f}nwgo_2q2%3CBau?4B7X#}K0fZtGcM#E^Ljr_1xh!T)t+scFC%qP+7?S2c`yvdZJb z(8h4!+gIFvbBI7}$B5IVL|U2<&JK++;}k2TeNACHiSXb;=(@9rjEUKegj3~X_iXu$ zD)K+Xs18saa@8*M%do_6{HirDyYy-GmF!4J&B#i8=*5~YdR{c&ic&sK*8L^s2cCip z=XouP1o!8!8uJTT3pX@xsWv2Z?So=6rn@u6*yfFVBxEMsd*s}nJW>c9M0v1&{|aHH zrroR1-w~jk?)lSChMjQbLOD~Jwg@=&gMWSFB6h;(pElhPFOGnTgF6~><3N;lp}Vf9(UW z!2mm9k9=6kW=vSn)&15bAPVG$MP}KNd+{v7i9h*Rgt|+W=dY8Ff-i2dyZuT^YE9?GY94 zCw;kf#$%{6CRjD=`J)_r+OU+fAnr2Fx$dt?N2 z_t8w87U-*dSs0bZ$|rj4(tkuu?H!m6B=IH$`C8i zcV_)7XTaC@+VO8h!do@?z4k7FN2+H(?HQ*G?++V&*t}x};rHA>sR6lWOit@7v9cWf zgM6*KWoHgCTbp^p+S3|%$~gZWz6HX`O9$MP!LzocF8>2NpJ8Ivq_UODU@5zM{pWN4 zz~}uo(|B-(GCa2TxF37G9rK-}i#AcCuFJIuXNdJ<=JxQ+NP?xG8Hi z-XMtBlP&M8N@|B!m0W&3i8LGvg_~B-OXa>gK+UJ?yF{GGq5Y0YiOACH@RSprcEC`I zlb;&l0KYc=F5b$^n!(c>hsx20QK;vNN8LIkUXV&;geDy?OU1 zocX#S&8}H#oeF%sSyDyVBb~~WcSJ?*rs@J8g~*pmAW9^iJWUbeP125^v-{B14mLuS zfcl$9OATP4#MOjxQU!#kF56KgZwa`7um~$^IrGyn;iqLXyWAU8;D>OSWrq70q1;)L z@QJQ!)V6pPc(reYkm^+Se&ks%#k@X^S3C(OE>9sPtFK47r=G6>3n+dl1=(hNe>kCC6-w8o8f1AG!1V^-!h|pu zFiqUF3aRU8P1$a0HtKhNcCs=1g(EQ-T(gfaq+Mw$FWzJP&v}gIM%AydGK9>qaM4p1 z>^$L%+e?4VG=yVoFWKbvu=AXbEETN6MIrx+t?5rrr;)MM#p%3@=uB=+-l<8wd^P^0 zJbW9`-Xj&QdLM3~V^@tr<|j@h*=Vtl@G}xZ8A$w+glO^XxugDl=M>$llNOEO{M6*d z8)W}Ej>waHyIMYq!HS1FEhh`G?o(Pk7_aj&sC<<8iIK7?|GT7(X|5 z6sJ_1_obA`U23p2=@`P|iDzD|y@>m(Rob-Y)OnrA z9mqg1OlV1^BbfTmwhwK#g-5aem*3!#2~LM`)GP}P2rY%)CGGMuIKGi#1Op zY{Jibs~(+r_9#_Wx?t3Q{!1^*9;t$5-n@9RdEZflFQC!9n@BWBQ)hKlcx|@@y+X>v z%iGw|xO4Tp_EtCoO!d>cmthMtWj>#q@N^7qDlds<^uT4X(%Tlyk2d~=wwFSSohR|+ zk(({d|NJ+asy{D2b635QMEgOt4&cdrw~4b37BfA;Rvph46uGfdk0^KinS3w(^D@v-RcIDr_4&>$hLwE#KV`H763S_Aod^zWoUS%-FsvL%Lg5vKQ*!fl{FrG zAJg0kKYi7zY-Dd(@S~vPR_Ah;v{$#VzZ*4;FT^&U_oSuszUBOP=<>xtFL##}G%4t8 zsVicqdz&H=Vj~l zXtaz>bW~xIDjK|kE?aMZsxu@?iiP8!UpPAb)l_?so6@!}E^IO|LJW-;sk63JJPpc2 zkkW_oyY}a4K#70D*6Wu<;rW}{KXcw{0Zx_iz%m_aRh^sLIMZ^}zwkkBoP>%MuAAhZ z)#?_a>H3dx@^A26JDxZhy5gIeFaH7Ow&UfNz3P*3>+Jbq2d_384joGUcy5b=f1o}` z9*M)WQ{W6~7u>_UMpA@9jdZ!02xkP3tx}j}mx%HeCK^q~;1@syjz^XpeRT`z{v61C zFrM1Yz^C6YAJa^}WoZUCmOeB}>y?3`g?XI}VRHH8+^txoA$@26oiEBzGpyz^!&3em*b`JuOe5NYbdGSj~z0_bWxRE5Jtz@qK zgf*--%7`OlJ{F<%PG@po3tvW|eb>|N?@&j<;)>CM+kNv&L)_qgQ;lesoGWz9GH8mG z4gnfoKH0D?1^Hd_h}Yd?4#GbIH*=kJfdj9k2KK#W^ZNS{D!+vc8bt z?E;ER&->+14j$Y2#5>10Fr zCt-NOmXD1o<<}kw~$Ki5#b$TipV^oDs_qb`* zX?3EF2h>6@m+O*M7b?+nzVxqa+(Y4G(%Q}Gjy@pAxYD%bCu^yZG0`CjY0g7HV{dUg&7aLfMxDY+J5G@Vk34JMjvvb5htj?cL*dycJD>KJo5%*AtB@|-Q z77~%3tw|wzR-wX4>g{v2*%7xGiEdWFh$iQAN({|>VAW46xkX?8K>R%ERo6IY=qpP& zYOvG?UJlHuJ)Jy;C}s0`6@ipvzhKi_1s{-=?#=rXF_b=zM@poF8t!&%CN<00$jsKk zDpp^Kpw81>&PJPst9&zxac}JT`$20s^^T!pCVDt=+)wSLM$k~8mTzUC0m3wt-M5cb zS>UbrT4c_!NHA|faCF@v=d7lsp!7?Adwr=X(D0ATCmpHWOHxqDuzzRt<->9 zstPF={~*jOQD`XGfq-c?#3wyV12~e5p8e_>Bg`!b*Op7SfH|QP40&{yg68I;jjQI4 z63#OicU!Vx)q6qW<*)Igy}U(=Y=vT!k=@3_by zV{R9t7Mr$ZC)eIXuhV{8=&n>CD-*pK`9G?BE&n(i8bVI;)SKACX~(mrcS6vVrKu+uFBd zd!#Z=%_hphzk(Qbkn>HTv@JZzkaS4%VMk1Kw69kFZUfr!YMVcZFhDh?)$h=N>{untLdzA|o9QPrtl}adIaLJ@;efkz(qlXU{xj zQ1gMSI58Y9XKp&}cq$0C_P^Nl@m30;{`Fr|GiSKFjjHJ0N~l;uA)^0+2!oCkVqiMjd9V2RfgS9K z9*4gO^k76m2Q{_(7lXj^@+SL^`(ubrwar@RG2-%fs@GlGgW#<0q{<0L#}J2ZxTFz? z@(~Y2_=|(UT0)%pC*ttFapJg1qDInYdlAkX{%vN;-6Wc%h)bIgDr6;RZ^CJu>`XaEnm!2!&|z7otScx$6{R zVg+(9bNlGFi=A+p_=sAMqT%ln&AvG*Z;sWez&7)^piSdwbS8n zQQb$BsCsy(Hy?FWPO%h%Li`CSgLeO-#mbbaUz+}#qvhgXrOvBcNaqP=DU0;##duwYtvhYR-Kd-t?;Yj5=EC@A44R#8-u zLbU%IaYj;aiY>+1TZf!BWm%j21LO1ynX3p!iG3%}i7EMj;``NWH~$)A3|FPqs>azv z-?C|5_e6XkHXtNq26K$@lPcY~9y6}xUM1}L+#9|hG>shSBN+F$@J*wSzKHExLkc*N z!F)vL!~4`0yOPP6+ZCv6pT;hE;rGZTtjO)PJ=G*K%1}^QwB{-^cj(~`kj@j(0a2x} zw-vdpGmB3h8N%r|+>6#lBN$RS9ti5JO*)W1QjT_qlU;X~(q;;oaHBKH*FRGPmi0&1ej9^U8b{Vr&)P9UBP*=`(}= zLR>4&cSBMH6_AL9)QERz9WziLt5~g%M_DxDcfJB`^0B%v@%cT{Tdt?^v32a7SnJ zyT%At-TP!b{g*Lh4%Q1~^Q%Exy~z2Ge4~V%CVWRFMLelCJ3JoYoD*MtVBQJB8uo6Q z9Yw}uY(e7-cjjM_>p|C|Ufyn15hFWi*-hw~e51nC4Gyr>P3*}}FnQ}GO1hQjlV5mxVc2~GWuE?Gcn|vKKaNnN`{J>@}i<>7(RMaJk&e{ zB<3azUP@0QZr>|+Wg~hJoo29RofilzCNzpZ3Wkj-N;lfm#_%|uY7e;kr^6KWw7CMu zf?9wdra7`uQJHX|QBHuRQ>+HM517tq!S%vAun zof;mpS{8uQ>0OW&k2+Rd74_Fqgzb-a^{4AB0-CqrrP=(;2|s)c%|R(YKX!|^FN3ea zDL=m)mk0YtFY3nE>y6A;WbP7={$C-KL~`eTdn!OON4gfDDLdgC{@kL|-xtC8nXX|M z9oPv~9ZC<{K9`4_{l({GT1E*^Jm52tWQzUd6Bk)(>xXc@Xtb{x$a2Py$H`_)wV$}` zZ#v$sy&?laX3p1NmGKJDJK^N2@9zu%|0TM9CLJwfau`f^bs6;sXeYUT&UI&%ec@kx zcs2RINxtYBzmn=m>d4znuE z&s?zuVHz5(Kb7e@n;-Wj`}U?FB7!ztgG^F5f38q^fZha~mn=KHoP74xH7GZ&S?1D1 zU*J7?&{Ij>1PUWIJ`*is4F_mKV#z2~sDDXX^-6d%ZK1yUV`JE@@M8A!DXd1wlZU$! z70)PosN5smm89P*I0gGP-u!$-*dqf&_c{Fd;ZLLx<^DpHNJ1U=@u`U{BYue_z7<)d1HbpBKmFVJ7}=-De!2E3 z&KEkQh?8qk#-^KTbDSLEn6XWB*L7W>MXX=wS?CEkIZv-?Yf$43v5wH6k2=vUi3*ca zT~J+f76=>r(W#tEJU>&q1axn7^9DvWdgUtRo4Vj0qLHDa1n*q9+Rx zx1K(x6yuW`q}8%C*l#{Nqxg@;SvwpZLEW=zrQk(f_>}YRP+tBR<4_&RIA0}HVCQ!H z5u7bo{$h-$7&|w~7N>8~1>?aHM)I_|^h*Bkbdev_gPygM6zzN-Q)4wQi^!cng^XEN zgC-#=-Lc;z)!!j`+WuaoR zYkSUbecrjnuRlSy*dC_12puA9?C)LXT8oPL`()-D`*tltM&Hg9+Jl+yE0#S{7S+QD z74g^)UqGWx{4a=}=6orLaM09iko%e6QzFFK5uG*S68tfuwQ0q2(E)o1{vx(_`BZt-kL1VeplO8&Cx}0aK&^v@ z=P;)g5-P58iXc7JMd)I+#;nQQcq>z4+ozs9Y8^DN42c@pytx<<2ZbBX9dfUV1-o4DI1raVdQiv5rXnLXEnjB7c#Ae~T;OiLil4jYv{vEOK)ItB4 z*6lIG&mYBDh)cd7>8+`cg=asfWc-O}J9U_an8EE^&&-O2x4*^O{zPmm;UA-k{7>Ck}`^Z@%@FJA5HTw;e1a^-2%{HVpox%2q zWYvfG@s7oR&Lb=EKFd4D0#?mE7wYfG&a>`yKR=h31&CzbNmjYY&hu(U|CM4{3-H{x zcgLSR%Z~9c$ICKX5Y7ON_Jf*lUS=>Ao)D$XrDhfBnVF9YUyIo_W!yx3=8aWLhvw+8 zu29TJx$18ZUVZNZh0acdv%L2&2YS`R zb&Zl{grBB&HV5%ebD8JaeX$dZ&SSY_IlS+bQ6}sW$?_f%rR-lpTyN~_8!Vv(TA3o> z9^-esF~MJmOxc;oB`0Zt-qxjaG!5Aiqk>M_9Xq-lqW3HOg(%<0Z%I$Gq)z1QNklkd z{4FDDcH~H@+q(u>@k8MVhrN}ttBDZz(gi58XX=ELU+*DJkNIj&=co-d_01@?rus}$ zUkKdsza*z|L=%#mc(w@54+G+|UF+5(*^pYv8~sk;b}JxY;bjF)czS>7+<^J4ww!(` zY7@Hl>4WgMA}{bMW*S>dX~Om8DnGT)`mzQiMce}TEyw?d)H0hM@_b3qgh1Q-pJS!i zsjoWiB=urGfrU~@1Jv|EwgC}Z&@Shb z;r(r7NQ)%Hi8(~bc$H0ciz3_5>&R;#R$TvuUS-+zFy14w9H&*I+z(SHWC%pSr!0XE z-dlk%LFG+a+5owBccVBDy}!1>QTe+kylsx#xnOc)AjmHM@NtGxG!Qo^nplcV`1nfJ zKlKJlWh4~#~1Wv1mXiqvV*efW7M`suISog z>eMYSPCKaw!l2l+KvUj!OkG76@uUE%sW32`>0~Z|8wRQ97(!SYO{f5cC(s^AR`#Lp z!r;8(`%+ZHkb^igza9Rq;RT0|gl+ut!xT<8iC&Yw?hp7XM)dY%cJWn*dsM_zp{ zb-rc_jQDA}-&VO3{^}cjnJBR4UVUXyN@q&eZPSZBrcfQ9$h8&QBMpOt1=ds~kV3<+ zK;jE&v^W+b9#FoMy^XgZ)^&@ZF4zELh9AA57q)wbdj0G z2&YJ+jV$qaW-}K>VHIJ~l}ysJuL8w+l?CqddWTX2o%I;)BTGC?q&~1Qb>bpp@K2E! z;##TzzDIf2#p74RaC+5wN0ZRS`Se=;@rqDnd^xG6e-Y#is2+SUPMhU_Kb()m<-Fo( zxtj3OVddF7%8Ow7k))dPY5F5mGP!6%M7&3V)5ClDd1Q!1WDzoMKA;Hk8>H5j+qe(@ z1L286r)eN>46|~}vg#Hqz{&c8;!yW7LSfh5%i}Pip#7Rb?jMWb?l4b~&ZIHI6~po7 z@3&~ekFOf9kH{{91f2l6zA2-G-LV2^=prMhM^c(d!lpE4a|zav;W%X@8G}F0HS}(B zzjn!Ebfn{%)j+&DSrTVgqeH`AH0WlYP!o0f<8f~rIQ#X!Sk0Lrpy6J7&BAPyvqJgP z$}?6l>3QzwdlPJ+CrVABLe-n_SI?EML8>`x>LX$~yIMn+45@swhI#yF8VDPgMCo$D zkoLw>LPSSu&D)hiL>mZ1i6ow76e0f5!AFCL75B^739G*u#q^kXg3W^9#v2|sV7)R# z@8QWY!u3nXa~hnof`Vezv&Wa(!0Vo62gFRq2-oh2W)Ut}eqVIKkTv9FoT3N|ne)m8 zgWFeB3MkM;eW?bgyB^`Zv3|_WM+Igip=CByZ}o$T;N+jI!0b ztZo+V3oSsUN@f+WS9^^59sG6|zjX~7DT&HM%Lx=2Pm$K7;PRp z!L;ZUn7Q)I$~86ktkAja$e9nvV6-Of?zw?s?rO_gw%FLhGUI{y5t?H#G-r49K@7tc zqoo`oV+)M|bLZ1^f>=G{yei32OZ)F6yy89hzp5#Bv>!%FvRAu z#u)fp1bIkvW4>&MeUZ40fz&sePY zQJg+xf0%WtlsSgjEGY2Yp}`$eAHM704aMJrQC`1)122Kd`Wj8wbAf$QBI}z;vhq$c zRKz;e?lx)J`~sR_-fXT{s3iD~q% z5r6l44+df5YRh^pI)`5ni}IjP+n4yz5(Gpv3|^8Il&o8)zwqX5z9vD=zWsA%yEr6RR6|F zOI1jcln?Jgsa#go%rxs5M=UtCXOKQ@3157z_KVD5N6fZ5akaGD z5^P@Xm?ki33~|6#Fq^e6z|4pi6c$BmE>L5t^OY#c_9s_nCbK45P)Y40^qs9)Vhz=) z$Im^PU=Mkr`_|OUuzDwy7*dV2V)nOjB?pZ9cW^AL9W)4MwY70;1o5|u@!rG#c#r8! zhd4p;(Hq??znj(=WDf;vje=s$*l(nr_U6Ey>GsfzG@TY2lQ){YR?t^UWSClaG(Ule z2T427z+6;?O9Z1;i5fsE?LmR^ugmQwaOm*s?mIU3jtyC(cI`qLA(v8RR(r#6giW+0 z!yU}3ix0UNvh+rc_@vV810L(YkF$o5w@WXG{Bnbz*tBNwdf8VX)<%miT3DUA*?avnfddPJKVp;XS2nPov_>V#?GD1?y&09 zb@2;ota|qsVSe$hj{9@n;pq&$Zy7H}3FWp5%F~%%#|5V)rQ}mp2!E6^2zQWSZ)WRX?)%s^|~;GLN_@dW3_$|bu$jFw)W5Uw6}zF{=NA< zCAyH(w(b7jE37VvrstH36J=e;gLtTQd>rZKDlVuE*qPH#F-@c5;E!r&C z)qTrWfL_$0E>ry7I-CxgSDS=Z++5UL`fhR8!IzIy?|jjP?5Q>^8~f?2+KbSAbcBi)~0AFndfBvte*LyZsB ze=IFG0RMb_kvoG3LLWA}jCZyH8h#qy$JU{5N{1c3#4dsR&tChhA4K3fbnD$-^0OwM zx0JV{?Ly2)`{yQfo%=QT!L|*-c>YqBJ)&Z)pb$Rf{=Y(-djG__?_Uw1M;rH;d9tJ3 zS~8ut@G*iQE>XX!=S&FnztHx-<8kIZjNr?EK@*)Igg?)Vmw)jEG@1pqzZzZ4G+B(* zqg#GYB|T5KB5jR1JDcmSB42(7y@yOAQoV=OC^BvL4Y|Xi(2{aL@^_;P99!(J7+1+! zzG8R`C7@GJ3{H5I`Hs$3JYJnGmH-@k6M$i-)QhfgbB|0l$?E~W1?H@U#P^~?W4 ze7ZykZv|D=l3J~9C&OL_bCL^D2A52w${_xlT=8!$p~aJjk4`)(4}RO z1*aFS0gf3qb(#6Da3j2LP1;Zb(D3l|?6`GE^H_5NUxjmLPtM5;4a;4jxZPH14z{tM zk=oyYXzSC~k|0+I++weKE~ zZIFhPx!qd3=j#$m(e4MVMO`1=BD8C*NBw+CyPmtz?N|da=Ncj0hyG7DQ5sD@?D_ea zL)WBXnaF_(d6j=kfBF@}7HQCm|6(U1MW+9`Ug(-6^U6sf{D0eA{^pGcCfemxv~ZWd z{n2d~(dO*p;p;!C^Ei8w5flgee7Uw1+J4k0^mvKF-utqr<``=MahX(50QzoG75C0j z3LFm^9G0IV3I`@mSe-&&K=>K7dL(YRe>2hPO$8$C96C|p+L?>qFI+6XYV(saf>A}?lfjAycxv_I4gwvI$u29 zAbydxhzMxCVN8JmTungoL@XIIu@EU=d3H`fqaV%K@TEG>gnFkE$4~p2Y2ECn4e$2u z|LGf`0}|L$C0 zyyRyvPG#*574Tndcd)!}3~^6Ur`({=Lb#>Lk<}Hc0-v=vq;2wHEfKO1-6_O|)Ubry zK4qA!{WoI$8=)Aw$h9p86t?Rqb$U$otm<7p(&u!_eWi&6VP#`O7x&hVuxLBA0%YS}n0 z%ebYnitxkFkF}_4?;As-gS0N|C+|CB@jBCLyJl0u9#K(g;&`m$2qK+AM4L%OM&`~j z#H$4xUiEXcBPM_9iX{+(#p{*!0%tIK?bbgt-V5rQB>O9a z|4(ga9@oUR#qqH&ST$jjO+=QDphbKS1w=*oT$YrgvFxHqVnhYY5&?l|0TT#PQ0j9l zL9kF3Spxb>h(xUDA#te%F(59WwYbnq6{{=)zN+sg8AKXC{o{Rn`DZ?7&g9{u1+6b2R)#DIbbSXIOMSP8LoA9(la*hFyEs)z`6(O#l{GsUw z(Mlhyjx^s3NP4}sq^*<8NnddYCBN|qwp)l}x7LSq>^6G9jm-=TlPi9J%j3o0;-NJO zarK>rn$lo8Q+xG758zZJo>th{&uU8-pf+8<>pIr)Z~aSW2Po}P6xRbbWRM1F~mF%72>X+ zmIS??>j6h*j7Q`=_EjQHNt(7IewqiYf95|9F=8pj08hh8SAh(ZsKk0f{#vY}nb?A< z^57?$=`%KmU3n!%@_t5k>k~2~I8GCPZ)x_yMpzEa=aM7y#U`*}#h~QoP6Pz*Yen=J z6u)-PgylWvK&ve8%i+$3UiV&NRm(z!pQv-sK<(wf$H^@m;Perby;0b|l^<;6qvtew z3Ouq-A>s!iJms_5r%h%8M^AK_e3_(9*gNUm(4i<3ILj3II4t^y8liZ${vGeE*A4lk2A%0cn#03i0anS(C@T%+ z@IP&Ca!G46?D}`rckkhL_GNy#P3{35p#t|r^)l-{r4aS6=g}gF=AO=R>Q4SHrV7HG+jkxzC|>g z=&3|(>obY!^JGKmh}Q4#BIZ@A5R1!>tqGjUhGvbc;}BDX6r9iLa}^?yDMq+$1aZDd zAZ)@aqEDuiDi&0tyM*b}3yuTmPBCjZw2-_Lcyb(tuM^$$F=fCXdD&Hy_cNh)?mfFh zYoY;{gKCp=ku0!RlF+pgxQ>f*_?wupziZd&-p^EVyhWjv=xM#-Z-T~WP~OxuH!Hv>DYKg4>^TlCZG*a z40Vx|!biC2cqw=uiruDp247*CpHUV&+D6f(Z}4*8*EWq0zRvaoc4V{Pia{3OBY8y+ zvr%7Iqp-O$5c^}b=3syM+F+CDA2umRu_ezbcv$y$<Bp zvl6i}BCsAOz=O%OkMF*VXzHv&bm?=`j@paUG&g9Hh!TugXCW9|K{e#ELMqddqE!Sc zL#uimvLb6=AbFEj%(*1Gt8%}%6jh1EM2L0@Flgq?k>}wcnl=9c;Keg~;7-+GC}vqPvYIel7wVY+lu(+lnIIfqPB|^xa+`~g0+40}7MBvfyVo{6ydz%_iFsb(BrZUL9?-#ZxG9rXHP%-#~?%!h09RSGJ1Zd^H?A zyB~*L9Z;YspOo!&rQ$48qiMT^l}IUpR;D0k3)O^cT(wWN1HaA4*5Chd^u^n#>&Lnn z%M%M!S`j#%S?|1CHQz^oTXE~R9l11^*{|=j!+#s#g1V^x;h@zUtRn_EUXYe=zU8AJ z8l;WLtlYbm0XrfmpaA*ETW!;H#f@4QFVL_1)8INcHc(-|jkC6eD!SH(K-@ah-_50n z2Kw;rZd%}b5OYduBY8A1@AmeaHl&VNak3@l*g6_)NJ<)y$PS~rm~%@>^_Gy&8{11& z;$bC}pQQ(oa%)?x*dmJ&Qe6EiWVCF<#Z*&G*L@=fjkPIczKogGsMT!aHk-~A5bdaw zdi{$7F$KSL?sD1#`1^1|FAvG)_tdY>`vSabkmZ7>G!8tnje|=EQWXaf-Yzu=v{#?{21pl(!p&+N~NoB$zCu=EQUfU%tM3q@{@O; zyMe?KTW;HB&eg_$K%qF~ph1&6o_W_@!Dp%dh4<^#!R~UUno_6-U~fr8=N7~z8X{;A zt`85;vQw8k)ZndB;lm(5Gr~`?|FQ#F_6>CvK3@mt9xC^r$9~7AG1Re#qGG-5SQqxyo4Py}~MWSpNRa&EW)A&SRfi ztlo61f$jAR*Y8hf;e0)JmjnmRBHtrBay6kGB_0(mroAxRd=4pISW^1tyC6e3mlP!Z zI`sRildhopX_n`#hDDG(XLH;1c_a}z$xZR-&VPmu7YgYr})aqZu5$b}U3UxU!`dzd;ZropWn?DGO0iz+nkdUjjuiEGOlC;ho6``aA6QhyqkmkGo0yw;>)153b}aVtuEx!HE9@SDB&j2cy4LyLX>xVuT?QexHBvo`op%(On6SZed?U zA7q;##!IQlMQ^=YEVN4K;_MSdv$KDTsQo><7)S`RKYG_o|x~mCdPd)~-0|Dy zw~NJujs4JjJqul~+2LY%En&p}>SiWsk19$w&zgrxL-&t<oMiJ^L{XMKByPQ{o$x^bRaN|X1X0|$NW zLZr9AJbPO4hkZeympv#~u9N^c4sxBFjp~K(ynC{ID>(Mp<@BWofq9@>8clf{;ir|? z6rhQ*np|=VQ<`@rEAswI3xa`T>*))zJ*Jd4&Yj?1ZD{w@ z+aJ`9JoM{U?aa>Ktf*o`ksD($s&3OSDe>GOuzeI4y>sp;>aHDMwm2wlh2*--xcxCf zAe+|xwR6rWDj#p*@O+HcQvFoXFC++VPf(Sz44dV@YUUz_f<$&67-Q3X1D+19zrBoG2txkv6j><6AMr-V;kj0Bu@ zN>*?xdJvh~Cw=E&v+{;T+Pyh`uzn`5r7X6w8|LL!pgf7tXR4QeH|u%G?GMHNmchsG z2>WkUXPqHb{ZCLe&SfpSSmOuYC*3Y4UtmR*zIjz6yVwu%OH#*0U1YqtFn)(0OnLTb zT-17)P)Z#c$hB1>LJJCwtoyw+DZzB#$3z$9k%#6#vXhCPaSL5qdtJ+Z6|;*9&qK)> zx%ZzJghGu$e9Fd!77%fD=J$Sqt$@Q&KZ}GQ`p0XD%dT5NZ+`d#)idS*l<2Jo^4$qP z=2(qevqazRdcvQup{TSG+oPfu)K7jgp^xo9p}eiJF(up40+uN?*vQRd zr99BaZE#Y{0zh=z4TCOL%41hn=gqup4$pt=JoFbO9aFm1^Y=|yClRB;?DH926NQqO zjBs{oA9a$5bo57nF+8ETq$Bc|Avs8Ii}trKkItf!kCxSn1EhS3?txYZyNw-PHw#J7wFOBrx_6!yWB-3XvV8g{%{{X5N zMA?(*KajneQDc#iDMMD<{ir&7z@)0l25dLDui(6C4VK;9Pq)qY2CN!YYf1rP@$#Tm{)t zr`+o2$YX&;8yTdOjmqH?i{o1Ka(%u6od{cK->{V7$HmhqelxhPpCo|fe#B9Lh zdPMZ+#iOJZLwlVntn|QOIQaeqJQGsA-`+d)W|S0pKIgyNWeL9pyQcekTLVp||8^%X z2V*OmqY~Q%s;CdMGzu{1O(mfu?Yf`A^fn7NVt^%(j+z@e#QS?%P@ZH{zKsTRVmF+P zJ{jwHG&JHT=B(*^jVCG3jL2wZx1N^iRe4GsR<~%8jCOr=yec zVhh_IvZ4-KEBM~Vs0LqGl>1=R)sksxnqyE+RzE&Wpn9+G{cRny8Z3NN9&zc7qEg;7 zP&G~r9LvS1n_+y^PD{1}lA<-tUY6|;sV0T8ok*!awZrI25u~aj`KX&m(!prg3*^hg zt$(#;F{7B`-^Xomok7tG0MSZrPO82uNM+QlxA0+1L!71J5ZmQgF}Gz_a9vX8t)rSN z#N6$?FW5^CTPW{&>BxS+W?)F27w8X6IX$%46}Y@U4i4kiWQ?i~N^BnW`C)AFZ2t|a z{2hyp9priu zX!< zJ1-WspynZ-*m!=OXmDthS~v2Sjiam)oQ?m^wsg}f@SACK#r6e*y0wIDyCerKvNeHj z)DZ>XtVj*+)l*nI^^G1vXW6$@$dCGzsjg~xPi{>9V^7YS%7wC_Lhp@euRG1 zGjpO*`qd+6X1&ve8KnUggO@E}Q-9&FMP&|zA1d0*(czw}?FSaGAMtAobOpx91db`H z_q&lP4Im^)+v$)*XypITX&cC%M$i`=jP z{)&6)!Llq&A1|_{P(^L?5Y7vQ@~euGnv#=1mpi-S=ov2<&1W$ZFXYIjSDinIA}Vh@IE&f?N>+a00y>Gfb4<1K*Wxx{ZtL18!A<%_)SoApEmw>wU7 zgs!TuohPu3{nTjQQnW4L>ri4)>xf^iagA&ew#QU-viZz;Ni?69Ow@1xKt*PwsQks~ z-F7w0QXI<@#}LE^}Ly`Mwpi)^k{P^)Qv%X zewcw;9sF5+K+qBNqv8UxBdFK^**X-zz zr?89Co%z{C)LrK6xL-LT0IG}UPP0$&gqt?Yz8<>HXp0W|l_5)B?TK1$kt2RriLj~C z^fmLSR~K1b4#@0R&B z&arM#WN6~;H_r^ByXjJ%`_==FKzqs`g1j}YQu#P{H@_GCS!(dh6z=5A@#n`j;SY7q zK$H0=y4Zg18}au?&-Nzlzl+c2h3vTaC^Jz-{(Ps<$!&}A2 zMHO7kUPYai!z}5(%tZs7Vf=%%1QQYciWU1TEGu;nO7hH}mlt*gZICfCx#P8(A*)+C zns-5bt!`8Zs3nSB`E3~t-|Vd@oXg{Yh|!{*fqG8`>@@g%skyao)4Jmd!N4(SE?|gl z!r%C$3O(U}(m(eGSIZy2s&d)SJ$|xqPtA4xYv3=UbN9LC#XArSonvzTdirLX;*_6<=lI4K1}Rb9GDY!1?T^ zj34&{$-4J7f+$`IwSorJHG8!j7*|q#0-Ap53a9 zZ>*b+CfYa7KR(G9GG2T;CK6!}=C^dp6a9k$2cWcNrlAF|e9C36*}*+^PR~+Hdw83+ zL;Q;*ISQc>bkmgga_|^=L$URt#i!5Of!M;0xfgrKw)l*3{jO;CY^rDlvke+zuAmT4 z!8PhP=T`cXJ?wY}f*U99lwi}^r|l^7t4QCmZ;KeDI0rH>eq@PVwt^7xP9q6#6X2We zdH&#LH^2d?TMHvlorvTbx5e7xpb0ig+g96Mm#q@sMMRvE; z=Otkj^6$Cn=vKL*2~mI`u`N7v9470s;MN%>R865OK)=`++?kiz*GY&< zGb>haG{TvrP;6s~lr}Xhnob=cyBLprBpL14vEb7=R2BJV>RZoQ%$k*ht}Yl_o5<}C z3oMP_mTfSFHEgSXI(!HO9HFH4WCUvbTKe#uu_H_>o6jL4iU*i6^t@Rvo`fG+Elxpi z6=Wkes+BkY@u#*nKPT+b^2(UtrS=nT0F{Z__77B~vU3F0B_FkiR5-?cU`5U7u-(!~ zq9(*ZuqoUXkBu7oIEq^MCiE+TI^XEbnoeg^;I7ORTpl%wTCDa5wYhA)xGDTR*fTDw z>>KtTYD`5w!g-`n?$;PsjN%3LW%2|w5x(F@t;HBM*W7C#OHA(vV zOr4>1P9a(%squY6f-cw`Sis#;Ap;@DtK(kk8vq4sIptP3axU|DP%kC{+w7-bT2MGm z2I>w!`>{cAA@K(#b;@|&Slg75*{=>|!tI)RyfX0e&CAjfI${iPrf}f7NDY&^i%E^= z%lN~TLreY-YWX7cp)4nLW;`o3_rc_ls5o8t)+QY?^A~=ouwvB|`%!9HMT!TGGD_Ve%Vr;hso7kkqQ0+^f%``Hb52$;s2iqobV~|R1kRI~lmQYS z=C9mAFHq&cE$!rk$X-O~_6p1Q=+MmO;=6gw_1|j;(cM}6b9gFKAYyJx{LS+Tus_{l z5r^C^z}ZEsZth0k&DpG|heKgT)xfo;qy(TA=kANWlScX#cGRHeBsty7?Au2C3xDl# zeF#}&W+se2p>#18R+O@zB&6~FgOnzz_J@@BAd=PW*V%8*O8TWbeG@@CdB^X<#O?%` zexx_y-NI4Q8NG|myfEp9MR)b`PbWZrfy+JP-=n1Ob~}jwz@(0j)LYKG65!dDiShv# zSxJ{`a^T+Uco|7bxx!qX8s0_NH0!_=dbA@4G2(RCixySx=5pOIh%}Vf_1?+VViZxm z3Zz$jTt{0d1b#Ft1)jC>2KQZh#d~w30Y~T^R^N#Rms~TSHN^v3f8WS8QS=54US3zj z@DRdJp*$->y+><|cPmvjM=5tdST@fa;#xN3=VSZt=HR6rA?oKcM0}2dn5ejY#}=G< zJc3%h7n!G6tzI3o8p z;UQnvb+x2XRJCbVBG837f_P_9Yq5?M+&fjgTyF6uz?r34 zdqyE;5B{aEt%9153h7N25wQYuPanfUY-2w<{VNSk)|9BW|1dn_7ucnEkg)$&)jNhP zs{aM*b<15^k;YbFTlKc@jyF5Gs`uAv8Y|F9ZN+{zpaQ1j5CSJ_BG zZX+0n|6a{2>IPqP7s)KY76v#9Qf1qQWFlz}<};i?>}dSC2d`X##<@b4LpFf$)8cH4 zkob}a!!GaL&9(#e$G2dA!ot*`yVxF;wO+BD;|1F+ZYJh%8N#3a^Fvb@gNctYSbbVL z$?+H~rmlqT1PzQ?!zQ&jhQ|$36O=-Y&W~c&wQ*_sVobvq>$l4Hy8`{^hwqv)qnImR zMw6Iw{keCdT3tb{Hf-ZRG3gl7yEOT$V2!78vpA=+%}cQe3&N}li}A$he_ z_mx(wAURUJac1{I(6AEBET6xI@Kd_Aa#8VV<I{V27v9TT>|zK%x%(?>XFpraGz8*3gBAVL0)WT-YS!$Rv@xR6 z72e~of9MS%st6OYm6?i=sNg?1f;w1+>Xmb)9L7OS3^b8a!l z_J}UO&CvVe(%S&MUCw4Aky_wNPKjM^tlfhShuGvw%Xos_tw4+Eia~Hh``D}(1zP|= zf#N243?=yPOMl=T0tXvs$CfDs!AaUVmG#GC#|X+;k`&^^dj2P*mC15BkwIN_d}V4gJ*S#E_U!f$)C`8a$Fsa$uub>{1##RNhY}uc6l>ww z^D|((dNAbgzm!baqb+Fx-QOC|2bX9Nq6Ykh$~c`;@`>*cDpw_X?6gQnmCA~`Y2BB% zbc`CSYV9${I~WGe*3D~C9!1qadq~vh>`q3?GQp6Q#N#y%jU2vylTr43^H%9{`D+$`f!;{lB)Hn_)O8$X6rRVDSVl6vuA zb6wwdi+v#;kTW4igRsYmSsH8btl+Bjb_AirSa` z@Z41AP&l=?`{nV+9`JE@Wx1gKDC)pR5fZifOdbET77v)pm*X)ORsUT#iCX&U%P~<; z4=_5HK0fM67fyVNjxm|SM@q@o2``O=Ae`xv3qLo}wP|GiKc0o&COT}8-S!>@mB{Q? ztzh<2+}SAGfBu7e`1qSl2da2~$^h1+-pS?FCl@bh0%EbaFi*bNx^E@?NPg9`rAHrL zm`f$z%CiFe1nR8QyU>ai6W2|DWDXZ?>b9P%)CVd5g`X6Xo&K)>t78dK@%d}~Iouxo zgA^$YkB}b8LWRoHTAswSlIDQ-_wydMpx>?c)ON8U+}!uQC)jh8^tf=r=R24*v8*s6 z;iCcUIHk_kKg$~M^GMQPqW#TMnAAe%jK|CO`Vcp>##p9YZ$o4%J-U)q z4;RW0c@u6}t$c;z?8;XcKG$Vb^?-fItYzRse~2|mhj>jtw{#I0_$Rx{y|N>o*=xm< zhz>n)PMfiRF;p)|+a}Ao2pWWf6qi=5B>XNX1glV;P@wHqBhwMTm+Wn0_v4mh)T5u- z$LU_5P{m1{8~=AuU-wg;DR%g9yf9a-{CifY2NL4ne;F)-+~O1~nTxDY8>cwuEStFq z1b!})y)C{fnF_ z;Jichi(sS^?Y`saXBCh;vSr!}nkxK`<=V?Rn=BJxf)B6T=qMUJJU7(5%yyma{Vl5S zeUh6oVUM<+jFqVw?}>U&Wuh80QGH0%-j>o4RNN9+?iM;VVI3=~S4-hH2f7}Fhol;n zW7LqlBDc|kQPhj2o*cRuwR(Pl#^f?pxZ7E#?kYcu`rwBGiE4Q$Sgbu%6<+7Nj*EJ9 zh%;J}gQioLNSB|%MGTWFD$wt*3PufP?-0#ie9L5Hz|ar>UcB?yXW8}kpr*0(O6q+* zn0NEF@`^?7q<#^Dafq7C-5$J04fv~1_^vk91N6i1)N(fxnj&IUj1ITIKi(>5JmMEP zFGam@({^k{k2=onUaum|MQq*vJ1E}CAk4%l-B~Q4d8SfOr+aw$?{?qByN3mnCC3nQg4W&jkLAH(KOMBUmIGV^|Q zp{CpVe)A&*81kW5^r5O~k$Hm(7`;~tvzs^rM4rfBapBV-V$*>Q6)ZoL$p82E?UD{@~e!SK?o`Dw2zJKbLY9u>B`^Q=i;F zR5LUk4p+7=^Xr!m6U>_q8|?2#Om-UwuhC?}<1(k|5HacWjfh|d-fk_feo4_D z=0I|C6T+FoZQx!a?R9uiKv(u8OP^W|QkA?SW76A;IEKvA{h!o~P0$d22X8k^Z-k(USId401yV3btSLG?n_i<`#F_mT%#_9}I^WyZw zl}8=n;eko5kYWZWn&`E)@!o(V!1RZq=;=jm3?Lm8i8p|4+K2HSt8lr`tDQr=zk z+0Hu63~p9k3otpsO3CRNXE(#i3>5c2`HPa#VyC5X;Y)HZnuLsVETnuXrXzmMc9GCtVb&#$jWt9o`0tpq?r^uC zjhqHGekc5EHCUOtZQE*&YD|Nsl+XqwJTwTRAI1b_5QTkFg(-qqZ`4X~Fi1_@AAzQkU&d*nw4<|9VheYtv^yu>Z zOJ>FpbMx-5*xtGD@Q_Qy@MOk{(ABOmB&3oP)v=EN|sjSKg}c}!G(CMxcxFe#kM{DX>bO;I0j z-a^~VihAYtmtTIS_zqdJ`8qw`c`%$)(B>dCidv}AC!>H-PhPt!P~0#VT0J{|P?fZR zcmRR=S&f0Jk!!g3!g*Pk9r!;`RXe!wb-6#=uXr&XN5DU85b^KZpm2KNC$c_s=OVWc zotk>)ZOH#=N0Rkj<^jO)TomsVtS!C45vaL>qH7c!ps?#x);AeHzyZ*@y+ie?nRs)3&JF$pI`B3vYEVWhM10TquGTIKVyGACFH6FdEUZq`@-&;sdYj;ik62 z#0OJYNQJ&}RY{8e*>fvCh;U9Rl;j;mTJkO_nSOQFL}I+-Q;g8@#Mdi#HlxHkb^o>k z9Y#%uB2?Gm@NtTxKg`x!?Q3i54j+_jzhn)E0ggZuu!%vs-Sp-akJo{!-P``9>h7S- zn_9Yk1G%ydA<{WWa_iovJ!@|^|M5$y(z#2pJz7*5t8*N!k)CLljc06MQ zHJ})|w=CP~H4f@|IMhp^#>DFlYOQpK2j>kE^gfKDhV-8HSH`H*G_{LAEp&(5=B>Me zy+={Ogn{a)@pHqEdG27X<}xm-KXoFfq$u;;H>Gtup(a+9(Gy+H^rur-k*YSNBawAk zhX(v`Pw(>kVQm2Os;qJ`aj+EZMPgi+Q3E5D#K^PeQ@gcK3L+O+a7n_123jny~eqoT1pSu`@HqbTL(j zP_>si3a2qq+ep-sy{#jtZRzN|+7U19FjmwL<`cetR?~;y)9jt2S1f_wHxr_!9UVoL z-7WrR9!5PXwdu}~z!G>2H|##wjiRnzcuCM5nLyKYi(6aOHQ~4-PsZhOP-XQdc1v*f zeyIumbANF#8L6~O99*!DKHqDiikfeA`@>SHK=AjdriJ&1!jil>&tDA2k(v;97iYb? zuXo@?%!q&L$-TQbEUDk=HIaw?f5g48eoWkV2Zl|jz6u4y*^sR8o;9w@K+x@6{M?L^ z>lP79=s51C`a<7{?o`nl<|cIsa}W}~4dL&p#Kb<1E|^9Zg=uue`>S=&wuF1=O~P*z zoi$2~3P#OEFEgk2ML!AvF`yY(&MxzOXp6YDn<*(%xfV+%46I%oL&f-QVJlOXbr z(-I8j4Fi+cF(!;lR_CI|+2?$!ddixugAUcb{AdBcTn>C8?7u6Ra5_;GN|>mT|3Ibf zvl~IhyM=G3U0C`lnicint-H7CFzVhX9Cr>&SwiXB7pj4-qo_-Ed}toRs3P+xz0>?^ z0cYEU2Oe3BqW(I3fkeH~Ys3BIyahaxa{N3V>aiUY@s8^-sUp4<#zc0Z5vo$$XdCIa zM@UuiDaWbTKY9i{JBa+R{#etP%e=+!vJ=&s@6)`qH2}H|76uz#SP!jMFI{$ zeOeQb77Iq3?0B;Q!hA9(U*54EvZj4W3TucU{JkoXnP^SuG7+}3cFlkM3SYj9FT(cU zsyfvYs{SXalq$X9Yd6+|f5wf)jrUnm+b35Wo7S&~(1c~hepgvhPj?mF{e@&-kY??3rl$p*H>?no4(ExZ-SQ+T~OIyClfYljyMqSSkY zjUNXGf0QD&EVub_10pSd{E>E4dBPqQl{svY>mpRmW1_nJ1C{3I^#|3V5Y5|QQ+59w zD=M$;{petfI@P&z+a(Jx_#z5>;*bu;Tm=p74FYRM%M4_5KW0IkC

i9FqhtGCm5>Y9OECfCXZ2ODdv@k{7r8#BOPF_JPnqkCt9zg zRc(e)q}u6&Zrse{l*$!Ip!3wG2P*?XVP>wp;%s-w3a~Xw_!JH}4wAwx`mGKzuE0Ik zATIy=%mV9&ZV;7V&{7t|IGraFRfaOQ9)0oVj4xE&EeN;5emfMI`TBDRIaA*H6AK)J zzfv5HLW%e%^_|@RgZgbbTBZJ7=!G>a^{4Iz!xxzPVAmxlr0fnaV-4Jv{T!t}v18(v zXiTlOzH)|~gP=S3JT*I?x^I;F-0kHPzhLUnd;3MDp1Z-dn#p&BzMZJ%o=uHW-NoSb zC}pIK76DQU;u$uDc^pS00Vu=|o@kKi`9gGcrd;F6uJ_1((=Nw1*38zImXEv_-xteV zDJA4+m#m5rIy4ovD~QeB#5<^Mtbpb$+LvOhKb zE-Urw!?$>>b)BHxd-11bw-NM|%-^vZ9}~tVFhBs^ItHGYmGi!lt0)jaTUp%vX zN2#5j3K^I@wT5>u7Pc)GL4X##eEi9pLA^zW`;LUDd^RGYQI0c9sz4NB(_X%frn{`- zCI*cig=pjb$xqMczel>ub(aj)GOvf=3*=3fM;3gDw*r-+_s&9h)M1L5^9{#Sc7P+m zV0 zQ>D!R9*BGCVSPobnWQDmjqf9*zP)ncv_GW!MQHC=X^G4VR#Jy`Z>J_~u!3z`mUSmC zsY65ec?G{+se^N$%ysYEqof<#1!tZRGlaueg8bWf)S+1A z=sYtK7E(4t?lbrh0kcO7h#<7Y^_Wr5hKfSyx7L%IR_{Q^@(wnAGrEG>gZsN{Ri`t? z3G-4=vj?SbAkGFf9j*4ioIVe9sue4i*^#8wSjj*n5G^7aoT3j4%r^S}*fSS?xZ^`r zVrzkT5MO5;ve1|+c=SX~^Op}#7M2#yh55R6a|wI2@nsCeyL<^z{}a@dOBt1t{PRF) zPoc;3b*!kjU)6PacW=vgp9+1L`bl6%{TwA&T_01+uVDf>La$s}1lpExp|*R5G6=^971(Gi z!W+TIoUuL22tN$@XP{{(4yaE|SqA>CcaOK9Re*?+tqXsq>y8nZobJ!PM^uP;OwvLo zDUBqhlx!U#g)DSCyV~l>IHZX?cjmn;*9YGrYILfL65QO8)bF7?N=i95#bqBR?bF=n z9r#@lA`G;t}Z*w8oKFARj8eh5*!eJ zG89%;zjOi*pQNZhQ&agGgi$CI5%K`L3)_w)x~&us(br5XLy}*99^7;K0eb7ZHgDPK z<&08LQi^EHBO9`NJYeyOo!y^g4I$V%P*Hd%IiR5MPme`=tWyt0n5}_mjpkj69R@HM zVHb9{e;wfGxNve5A?;I!VsXDYTkt8|rEb`N_5RV>ggvHRB$vdU=J4QRfJevpgBXa3 z8cw1@%)t><(K7VF_UyiaGpwjBVF%TxVAQwNdyN3UiU|CW&fZ%Rm*oEw*h%j{#Ua92*z)n=DV5q-a7B1GSntd_F?10-*Wj zJLnR|q^d2Yh!1s}cHe!5te0zDV0*!QSqWBKdVX$Kg0U_9+#>nxe47qT;PJd7x!nbD z)?Hj@ZbnyM&Q5e&(%T%2@-tTD>Og}?SSn#-Kf1812sLbGn=V~P8=Dn}NS@{+557c8!)Upz#FZSs=AXPd(s>~Xm?^MweW&yBdu26L_ zMjdI++vqm}NmZwoqLQj$v4sNH5WCvIBcU*+suksEwaS3Lde}OMwNsU8Z?y)qt@nTJ zfn=`s)h+f(&bTB5f<^{+1lq^)7QLLn4BCd44ZET4o&9e03$= z8dCI1N&9bAH8v5d{wJt%?`L$serOHX7CS^KO=U%WP&w%L{*pDUtIy;8&PlGN{sZ-F zzU{_|1=e6^JU(hi5D#AV&B;Mn0}AC7bJ;h5(b}fZ*i0A9AysYIg;;h?bAvDdr9;5uhMFaOOX!0}veWhNo2j@-{p6}E|l@KbiVMrh~_{3GaDBqieme3{Yp;p5L?>+4l1%Q>cpC46S#IQ z%d5_Y6>8CtctVQ334EStyIkW6E0osS;4%|w6G)F~N*WJpSrgB4swf_Jt%uZ4Bnzn)_xfkMbt3Mydy6#(=V+6kz!y|HtUc$rf3SxqPi|)N{aOzC zwUhYt@%sa?pJwbFh|IodM7>uYtLx$mp zW*W1gPP(WTF5kEu9{o5yE~>&)9#g6)qjSV}2_q4aSBf&JYVYH9o9R2)d5M1gSvE?$ z`N;K3+yk`KdA|C(tIRD%jqNB)>GADb^?q=$Zoksk(@P;C^2|HypBsS)bSNqsT}zdb z_2^j#uF)1pij|f^agXAadD3LFPrVtOf*kwnXSeKh1@GnYSyaiTpjUoCk^KTb?)Q(k zyw>mH;KiZ_kli@mjQ*-Z#HsScDCny3@hnWkFZH~4Sult+P$)aTbIJA_{^Nj z&!eOR)EU79sn?=d(PeT=;q`AZ^Tau$q{go6UK6BZ-6u@tJC^{50A-teBMa$5ZeH9{ zerC=`_^S}11cf7;NK4M|N~7~CkeW`dM5e-;e9J|Dqk%MwE3^-YQBo61(0ONHlbc+D zAYYnt;$5c?thCp<0DX~wBT(<;?nLTkciSUAyFg{){7SA!A86RW`e%bu2;ryg64;GC zv8@r|#7jwSYrH&P8Ti2LLa%ThzRhDar5{w_b*~SRBSal$qH-}&1xQq?Lcs{?v`Vz` z_T4Qf$3b1TWp&{j&OnF}>AwEyk`H7hY+hd1Gm3hakNUv_qo%BUnAer;1F|99DK{36 zqTXLO@OJ2d8{}Q$Z#C2KftM@GUenpeqSj;7EIb8CLpXc*t7oS3$j#D{52@jFv$dq4 zgIS2zQ<7_W*HhG*)+5YkZb`?Q>~vwwHrZbl%8AZeka0Ta_4C(ny_jx=Gv6)&5u8C%lPDO1UIK;_gu&J zXdz`x%56*tL6w<^_-ZZ=K%wZ9sJ%tGBdEh!Xy%W8<=k;l5BZu(No%>nc|qZP*%wC8 zciT$*O}iK1I3(&R%h!SXFsksYW9sg$MsOxs=Ji13D5`hnV-l53Q2EPy10z`Ip#E?? z)Q{%8lGGUGT!aJT(9Go{#uhZqyf>BcdsAlF=q2a6_$^8)jNdmFBI$(5^Gkf*qj(=X zU0VI0MI}1wshQ?`P00XCcI7U4t)T?s>*KatxsbEXlAUK_kk2+bvsHsiP<$$+bivM- ziXiFuaPqqkD*$7ZeE61yst?p{p+8y*x78)>t}ar9L*8e~+_hJc(;N8^Ebs`m1Cts^ zfhj6O|2H-70k@BBUaZvljq`FG6b)d-)oR|23zeY%$0pkoPNUR@30kKoV`}B&&F>dH z#&f^R^E}v;e~h7y)7kHjsr8a)>g3H+gas{GU+60s)U9E>m#8C4`bVo78oH2;8Tbq& z(@XQn5`2CSGL?XjvMTR_~t?nf#3x()W1NXwL>HQ}~a5({}+cJThZxNX23 z+@4n35cZf7OirKn?j}lb2Xje(BNLUe%tU)KKjjZ9M+rK2^ZeKCcUV!4Q^ouZSGmA3 zw;UBGD+_2I?#k6VHHvDm*H^JGzzj~WeZ7!Z*#c~b_>_X0 zI*<6PV&)mSMz1H!Woi?a?O(Hqt#LmbUTO(tG4lN4J**`6w9Z!_vakg04QJ*pUCT>twVooI&H zy0q&n8d*p=jLY+86|M#TcWwF~YF7c9q^=4*)@l5Y00)*ne=3C zK4O?O?=ZSQ<>2cs`a{&OoBdk;sWqeiUOS8$PC1-RkM@SJUH+dW_M3tJ&yLrVJaTN#pOTQ5#)$q)iI2GaI4=n`(ss->o>7h}-mY7r#xKE;6Yr|&4o|l{ zWatX|hlcvN-1Q++ZU3dhiQa(Yz%<$h^s}I?p|fi(B+Ndep1w*Sg7tsDe%VDX(Uh1u zC!pgs7gm(F>}&qxSJ^F-PuOGRB}TLCw#dk-Emp4AjsUvHLu%sM!}X`zL%eh48e8!dcT;QPa)he}uN0f_7}Ds^SM$ z)K`{#=@Oi1^L2a~cS<-P(T`j!LQgX$ZCRR0FAd>SQ4eU_yZ4r=Ih4eoz4~N|HJEJ9 z^;kBS?8i`~UkTE`u&i!ctoMjtd0)(&A-w>yzX;#Vm(TY95?z#T6V4|x@`%oYdkK5j z;S-2Cdyr{5khww z4ifg5T1E~!G7E@Ci;IbB$V7cbqL%ndj-cWE1S@sK0r-@`5p{{;?D6mrI?XXX$fwS8=j3lBhRg7^uDNb=0-uP9SA!J}#>K z9zF#s2Q0boM}ObKhtFY{rl89cRb$q!tU$AKv}c@Kz?^m9jff-BufO)=TjD|q+-?&p zbfDG#TK>8wvY~|F&T!Q8ntwQQkttmD>dm^6rvo31jdgDxBD*h&?z(uCoRYMU-)^A! zkN=R$g@c4WDj)NW=k|b0)4zmztkq*BQmq5Y&pk!effefV?zF^`N*$2Z{rqm{Hi_@0X zv%h}90!jptHuh6D)bB#YysEDYQ?|5RlRkX5_=65iU8_dgqXIHsaK5CED1U=YRQ!%C z8~~52{fLyxE4Ka*DhD0;C@R0To6U-Pqu+XB2cJGo&9|HwS20~X`9nw0uleO6|qF>a#@o` z59H-1S8Vdo3*+sp51s75r<#W!Z_r=LP!+#nW=8+)hFQDyAUo&ek0sCWp@Mtf2Z(Vx zj!@#(6osI5Kj)+rHCR@J<{UqU;NHYu<6m>hbugE%MSD;dm)>rJQ=41Z?96XE9mJn! z(LZ99d8na;^K#Y+R>Y2;h|3`uQReyd*VTgv_6a5(4zV9a z?2Ig$JOv}3nQ4^p^)7<_msg~D8?z#&_kEVCIgUV_eSAcrDg5|F+<$I8Eobf>hcMJd zHHq{dRZ`8GJt*Mhs^Ct+cI3fhquWE*XQD4-<9w+f6CA2VS|;3I>In z2{mcXgIm$G-8xb?D~>m}9R1{F6KoHLBEj1UoA3vIEJDk_EaH}wWN-Q7SKGRskFft% zQ(uK2m(l+Q^{RrD@M(Ks6P%eOTgHk?N%lBBt=Jw^e$31m4^>k3_kqxD_&sE`JH|zQ zwwNF9Yvq+8oJR`9qn?rWlB9IQ?upeQP8sC_Zo{t-p=-RJjcWUb1=m(CyD1_fq8KbBq1@0bAI0|FR6X0e-_^ zjMW2gZ4bclT=csWQB>H&pH0nraO9$Vl@@;4VvNnS$gY|8gunO9)k@?TksA2>q6-{X za;;y1{gz(G*tV9B9X64h@Uc;Zo_}^7qhYM@cK@==` zyS9>FKg6RP8_}c~>J79#J@;G3Pv)tlBeAIZd*hGC?V51xN5$4Fu0oKnz13;50l9ib zkzDVMK1nsV&)q5swx{$?E6fmrBTpAzZqShQA_UuiM}4pm<8ZR6{uYolEu z^wE#UF3*Dq653hYToi_OdBy)ft(|)~lxZKwXIrngZJTM@Qk&3}A{``)PD{7&PF0*ULH9|2h6MrAvyd8T}fz6*$_3Qv)UI~m0o%8=b0&L>>uxS z_1Asrq&b-WsS>p```eypibOK2~4E1LrVQirNL(Z@y;tWS? z%PMcks=en-+MmARjhS>Ts+kgXni4gKLS-$#`T=z?2mLYU)cT4sPzOsV`Z!|L$W~cm zRJ=DZL;rX_6CcFBN7c)2%v*&~hw64P@Z*jo$albtt z6SZjy9dF%bDpaItr6b$`o`6!ne?H;+Gg0>)rD{$*%3LSDE821yRkt)}%i5J0Ebt9W zR|EE?n~QD0DNY`<*2@~$mKrlAH97#UhacL?M+=SHOWQMMRmW@mH==lvHT1IGE|NC( zd&%|_s4`{TqX6UWAN+x0b;=&4Dz##3G9guE9!B^y-SmmYd5jG!)=ra)Z^yEL+Ot%2L?`Vp$&xB^vXG(OC4 zf;HG=kBu6zoIahUDOa8xK2fSmb0txc58+2Faidhl38Rc=>^T%)SB<1E-D8`dEAs&0 zSZCtZhs!IfR>IKfMJ`4O+K|*w? z!&ONac^aAnP2K4=vo-M!0og;o%T=nR zDahd_E1j6Im(tX@0C6O;ufj~aP(kICJMIe{6f?Ym0wgoUv9{~>LCH?XBYpFDz`SMT zYmg@dTp`+HAC1oCt0gCQR!H0~86KHcfzuIOBeYFZjYP zo3uY2sZ2g8s#t+a+x!tK;*Fp#EkJYI<5wGwftob1`+@~Vz4pNI-eo-=B%U{82}hx> zs8OIc+&GyV*A|9@!wxr(Mt!G5J@`BC^{kRGI3}_k6IC2V$E%Te<%N};8ilG)lE@OQ zD&vUTrzlnVdC2$ttyqJEZZu)Rz>R-uI4V@VlZPVX{5*-9e5jn&-kt8k1xI0WW9`5J zz%{UD926jW@%*?#NtEOpi!1e(FM^=#*DRt8+t`oT$$98?9cTCqy<1Y>GS`UH9Rz(Z z56;ZN_9#`UdGo!~q^h3dp(Qyzn52dQV79reMJa;=mmaNcAafHQnO4Wi~Bx{Y#pK8;OIMe)ih8-^Ag4uv1M^J?9|6HK4*hNoeEIyWJ7xIE3EGR9UKz2gK|3 z94a+a>`5;5$wirk7X+`i+Ewe>-nU7{{?PCq${wXE^;XSkAXW8Iq6R2Y)v^$7i1tMO z0kt?6jR0?806HYK!jtg6z%q$^aR`kwfV0Z+j-E*5-*Nu34xV*p24}gBc##7O%!QrS+kc(WeAiqpLdKV{V7pF z+GKY3KF**8^-r}wAuYkA`E%4*0UFW7)9)$e&}kGN>pVa6@&J-v(+jTJZL3hO=p>q| z=atvYj0E`}t$Qim{xGBEmY3^8iihv&7L5)IIT<_np5T+2F}R5E2c6EzS-Lop8WxTp zydn#UOB!k4nTLV#xF7!E6z>nZCiZ^7=!8A?N|Nb$lw{PzaEd~p_WBLO9l?67DK(raS;7n zg!_ZfW)Xkv-Kh56aJj!xdJpTnZQu5%CsY&*qJV*#= zT=3x;v>xNZ_18`SFWfRwlRZ;&1!!v(= S!vm*QW79EE39nd2#s2`g#|xtX diff --git a/soepy/test/resources/update_vault.py b/soepy/test/resources/update_vault.py index 6e6db2e..0134fa9 100644 --- a/soepy/test/resources/update_vault.py +++ b/soepy/test/resources/update_vault.py @@ -5,6 +5,13 @@ from soepy.simulate.simulate_python import simulate from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.test.resources.aux_funcs import cleanup +from soepy.test.resources.initial_states import create_initial_states_from_probs +from soepy.pre_processing.model_processing import read_model_params_init +from soepy.pre_processing.model_processing import read_model_spec_init +from soepy.exogenous_processes.children import gen_prob_child_init_age_vector +from soepy.exogenous_processes.education import gen_prob_educ_level_vector +from soepy.exogenous_processes.experience import gen_prob_init_exp_component_vector +from soepy.exogenous_processes.partner import gen_prob_partner_present_vector def update_sim_objectes(): @@ -44,9 +51,39 @@ def update_sim_objectes(): # Sort index after modifications random_model_params_df = random_model_params_df.sort_index() - calculated_df_sim = simulate(random_model_params_df, model_spec_init_dict) + model_params_df, model_params = read_model_params_init(random_model_params_df) + model_spec = read_model_spec_init(model_spec_init_dict, model_params_df) + + prob_educ_level = gen_prob_educ_level_vector(model_spec) + prob_child_age = gen_prob_child_init_age_vector(model_spec) + prob_partner_present = gen_prob_partner_present_vector(model_spec) + prob_exp_pt = gen_prob_init_exp_component_vector( + model_spec, model_spec.pt_exp_shares_file_name + ) + prob_exp_ft = gen_prob_init_exp_component_vector( + model_spec, model_spec.ft_exp_shares_file_name + ) + + initial_states = create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) + + calculated_df_sim = simulate( + random_model_params_df, + model_spec_init_dict, + initial_states=initial_states, + ) unbiased_calc_df = simulate( - random_model_params_df, model_spec_init_dict, biased_exp=False + random_model_params_df, + model_spec_init_dict, + initial_states=initial_states, + biased_exp=False, ) vault[i] = ( diff --git a/soepy/test/test_childless.py b/soepy/test/test_childless.py index 4031df3..4eef8d3 100644 --- a/soepy/test/test_childless.py +++ b/soepy/test/test_childless.py @@ -14,6 +14,7 @@ from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init from soepy.simulate.simulate_auxiliary import pyth_simulate +from soepy.test.resources.initial_states import create_initial_states_from_probs from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve @@ -132,6 +133,16 @@ def input_data(): ) # Simulate + initial_states = create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) + calculated_df = pyth_simulate( model_params=model_params, model_spec=model_spec, @@ -141,14 +152,10 @@ def input_data(): covariates=covariates, non_consumption_utilities=non_consumption_utilities, child_age_update_rule=child_age_update_rule, - prob_educ_level=prob_educ_level, - prob_child_age=prob_child_age, - prob_partner_present=prob_partner_present, - prob_exp_pt=prob_exp_pt, prob_child=prob_child, - prob_exp_ft=prob_exp_ft, prob_partner=prob_partner, biased_exp=False, + initial_states=initial_states, data_sparse=False, ) diff --git a/soepy/test/test_expectation.py b/soepy/test/test_expectation.py index e447e2a..c02f831 100644 --- a/soepy/test/test_expectation.py +++ b/soepy/test/test_expectation.py @@ -3,6 +3,7 @@ from soepy.simulate.simulate_python import simulate from soepy.test.random_init import random_init from soepy.test.resources.aux_funcs import cleanup +from soepy.test.resources.initial_states import create_initial_states def test_simulation_func_exp(): @@ -25,9 +26,14 @@ def test_simulation_func_exp(): model_params_df = pd.read_pickle("test.soepy.pkl") + initial_states = create_initial_states( + model_params_init_file_name=model_params_df, + model_spec_init_file_name="test.soepy.yml", + ) calculated_df_false = simulate( model_params_init_file_name=model_params_df, model_spec_init_file_name="test.soepy.yml", + initial_states=initial_states, biased_exp=False, ) @@ -42,6 +48,7 @@ def test_simulation_func_exp(): calculated_df_true = simulate( model_params_init_file_name=model_params_df, model_spec_init_file_name="test.soepy.yml", + initial_states=initial_states, biased_exp=True, ) diff --git a/soepy/test/test_get_simulate.py b/soepy/test/test_get_simulate.py index c648a47..9bfba00 100644 --- a/soepy/test/test_get_simulate.py +++ b/soepy/test/test_get_simulate.py @@ -5,6 +5,7 @@ from soepy.simulate.simulate_python import simulate from soepy.test.random_init import random_init from soepy.test.resources.aux_funcs import cleanup +from soepy.test.resources.initial_states import create_initial_states def test_simulation_func(): @@ -26,9 +27,15 @@ def test_simulation_func(): } random_init(constr) + initial_states = create_initial_states( + model_params_init_file_name="test.soepy.pkl", + model_spec_init_file_name="test.soepy.yml", + ) + df_sim = simulate( model_params_init_file_name="test.soepy.pkl", model_spec_init_file_name="test.soepy.yml", + initial_states=initial_states, ) simulate_func = get_simulate_func( model_params_init_file_name="test.soepy.pkl", @@ -37,6 +44,7 @@ def test_simulation_func(): df_partial_sim = simulate_func( model_params_init_file_name_inner="test.soepy.pkl", model_spec_init_file_name_inner="test.soepy.yml", + initial_states=initial_states, ) pd.testing.assert_series_equal( diff --git a/soepy/test/test_quadrature.py b/soepy/test/test_quadrature.py index 38df0c8..9a06572 100644 --- a/soepy/test/test_quadrature.py +++ b/soepy/test/test_quadrature.py @@ -13,6 +13,7 @@ from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init from soepy.simulate.simulate_auxiliary import pyth_simulate +from soepy.test.resources.initial_states import create_initial_states_from_probs from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve @@ -102,6 +103,16 @@ def input_data(): ) # Simulate + initial_states = create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) + calculated_df = pyth_simulate( model_params=model_params, model_spec=model_spec, @@ -111,14 +122,10 @@ def input_data(): covariates=covariates, non_consumption_utilities=non_consumption_utilities, child_age_update_rule=child_age_update_rule, - prob_educ_level=prob_educ_level, - prob_child_age=prob_child_age, - prob_partner_present=prob_partner_present, - prob_exp_pt=prob_exp_pt, - prob_exp_ft=prob_exp_ft, prob_child=prob_child, prob_partner=prob_partner, biased_exp=False, + initial_states=initial_states, ) out[name] = create_disc_sum_av_utility( diff --git a/soepy/test/test_regression.py b/soepy/test/test_regression.py index 1e26c36..d9d68da 100644 --- a/soepy/test/test_regression.py +++ b/soepy/test/test_regression.py @@ -15,6 +15,8 @@ from soepy.simulate.constants_sim import LABELS_DATA_SPARSE from soepy.simulate.simulate_auxiliary import pyth_simulate from soepy.simulate.simulate_python import simulate +from soepy.test.resources.initial_states import create_initial_states_from_probs + from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve @@ -131,6 +133,16 @@ def test_pyth_simulate(input_vault, test_id): ) # Simulate + initial_states = create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) + calculated_df = pyth_simulate( model_params=model_params, model_spec=model_spec, @@ -140,14 +152,10 @@ def test_pyth_simulate(input_vault, test_id): covariates=covariates, non_consumption_utilities=non_consumption_utilities, child_age_update_rule=child_age_update_rule, - prob_educ_level=prob_educ_level, - prob_child_age=prob_child_age, - prob_partner_present=prob_partner_present, - prob_exp_pt=prob_exp_pt, - prob_exp_ft=prob_exp_ft, prob_child=prob_child, prob_partner=prob_partner, biased_exp=False, + initial_states=initial_states, ) pd.testing.assert_series_equal( @@ -186,7 +194,41 @@ def test_simulation_func(input_vault, test_id): exog_partner_arrival_info.to_pickle("test.soepy.partner.arrival.pkl") exog_partner_separation_info.to_pickle("test.soepy.partner.separation.pkl") - calculated_df = simulate(random_model_params_df, model_spec_init_dict) + model_params_df, model_params = read_model_params_init( + model_params_init_file_name=random_model_params_df + ) + model_spec = read_model_spec_init( + model_spec_init_dict=model_spec_init_dict, + model_params=model_params_df, + ) + + prob_educ_level = gen_prob_educ_level_vector(model_spec=model_spec) + prob_child_age = gen_prob_child_init_age_vector(model_spec=model_spec) + prob_partner_present = gen_prob_partner_present_vector(model_spec=model_spec) + prob_exp_pt = gen_prob_init_exp_component_vector( + model_spec=model_spec, + model_spec_exp_file_key=model_spec.pt_exp_shares_file_name, + ) + prob_exp_ft = gen_prob_init_exp_component_vector( + model_spec=model_spec, + model_spec_exp_file_key=model_spec.ft_exp_shares_file_name, + ) + + initial_states = create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) + + calculated_df = simulate( + random_model_params_df, + model_spec_init_dict, + initial_states=initial_states, + ) pd.testing.assert_series_equal( expected_df.loc[DATA_LABLES_CHECK], @@ -224,8 +266,41 @@ def test_simulation_func_unbiased(input_vault, test_id): exog_partner_arrival_info.to_pickle("test.soepy.partner.arrival.pkl") exog_partner_separation_info.to_pickle("test.soepy.partner.separation.pkl") + model_params_df, model_params = read_model_params_init( + model_params_init_file_name=random_model_params_df + ) + model_spec = read_model_spec_init( + model_spec_init_dict=model_spec_init_dict, + model_params=model_params_df, + ) + + prob_educ_level = gen_prob_educ_level_vector(model_spec=model_spec) + prob_child_age = gen_prob_child_init_age_vector(model_spec=model_spec) + prob_partner_present = gen_prob_partner_present_vector(model_spec=model_spec) + prob_exp_pt = gen_prob_init_exp_component_vector( + model_spec=model_spec, + model_spec_exp_file_key=model_spec.pt_exp_shares_file_name, + ) + prob_exp_ft = gen_prob_init_exp_component_vector( + model_spec=model_spec, + model_spec_exp_file_key=model_spec.ft_exp_shares_file_name, + ) + + initial_states = create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) + calculated_df = simulate( - random_model_params_df, model_spec_init_dict, biased_exp=False + random_model_params_df, + model_spec_init_dict, + initial_states=initial_states, + biased_exp=False, ) pd.testing.assert_series_equal( @@ -264,8 +339,41 @@ def test_simulation_func_data_sparse(input_vault, test_id): exog_partner_arrival_info.to_pickle("test.soepy.partner.arrival.pkl") exog_partner_separation_info.to_pickle("test.soepy.partner.separation.pkl") + model_params_df, model_params = read_model_params_init( + model_params_init_file_name=random_model_params_df + ) + model_spec = read_model_spec_init( + model_spec_init_dict=model_spec_init_dict, + model_params=model_params_df, + ) + + prob_educ_level = gen_prob_educ_level_vector(model_spec=model_spec) + prob_child_age = gen_prob_child_init_age_vector(model_spec=model_spec) + prob_partner_present = gen_prob_partner_present_vector(model_spec=model_spec) + prob_exp_pt = gen_prob_init_exp_component_vector( + model_spec=model_spec, + model_spec_exp_file_key=model_spec.pt_exp_shares_file_name, + ) + prob_exp_ft = gen_prob_init_exp_component_vector( + model_spec=model_spec, + model_spec_exp_file_key=model_spec.ft_exp_shares_file_name, + ) + + initial_states = create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) + calculated_df = simulate( - random_model_params_df, model_spec_init_dict, data_sparse=True + random_model_params_df, + model_spec_init_dict, + initial_states=initial_states, + data_sparse=True, ) pd.testing.assert_series_equal( diff --git a/soepy/test/test_single_woman.py b/soepy/test/test_single_woman.py index ee0095d..7956bdc 100644 --- a/soepy/test/test_single_woman.py +++ b/soepy/test/test_single_woman.py @@ -13,6 +13,7 @@ from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init from soepy.simulate.simulate_auxiliary import pyth_simulate +from soepy.test.resources.initial_states import create_initial_states_from_probs from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve @@ -98,6 +99,16 @@ def input_data(): ) # Simulate + initial_states = create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) + calculated_df = pyth_simulate( model_params=model_params, model_spec=model_spec, @@ -107,14 +118,10 @@ def input_data(): covariates=covariates, non_consumption_utilities=non_consumption_utilities, child_age_update_rule=child_age_update_rule, - prob_educ_level=prob_educ_level, - prob_child_age=prob_child_age, - prob_partner_present=prob_partner_present, - prob_exp_pt=prob_exp_pt, - prob_exp_ft=prob_exp_ft, prob_child=prob_child, prob_partner=prob_partner, biased_exp=False, + initial_states=initial_states, ) out[name] = create_disc_sum_av_utility( @@ -124,7 +131,7 @@ def input_data(): # Check if really all are single at any time assert (calculated_df["Male_Wages"] == 0).all() - out["regression_disc_sum"] = -0.10864927638945981 + out["regression_disc_sum"] = -0.10947236852045482 return out diff --git a/soepy/test/test_unit.py b/soepy/test/test_unit.py index 913c70c..4b284d0 100644 --- a/soepy/test/test_unit.py +++ b/soepy/test/test_unit.py @@ -2,6 +2,7 @@ from soepy.simulate.simulate_python import simulate from soepy.test.random_init import random_init +from soepy.test.resources.initial_states import create_initial_states def test_unit_nan(): @@ -22,9 +23,14 @@ def test_unit_nan(): "NUM_DRAWS_EMAX": 20, } random_init(constr) + initial_states = create_initial_states( + model_params_init_file_name="test.soepy.pkl", + model_spec_init_file_name="test.soepy.yml", + ) df = simulate( model_params_init_file_name="test.soepy.pkl", model_spec_init_file_name="test.soepy.yml", + initial_states=initial_states, ).reset_index() for educ_level, educ_years in enumerate(constr["EDUC_YEARS"]): @@ -51,9 +57,14 @@ def test_no_children_no_exp(): } random_init(constr) + initial_states = create_initial_states( + model_params_init_file_name="test.soepy.pkl", + model_spec_init_file_name="test.soepy.yml", + ) df = simulate( model_params_init_file_name="test.soepy.pkl", model_spec_init_file_name="test.soepy.yml", + initial_states=initial_states, ).reset_index() # If the initial child-age distribution is degenerate at -1, then at period 0 all @@ -85,12 +96,20 @@ def test_unit_data_frame_shape(): } random_init(constr) + initial_states = create_initial_states( + model_params_init_file_name="test.soepy.pkl", + model_spec_init_file_name="test.soepy.yml", + ) df = simulate( model_params_init_file_name="test.soepy.pkl", model_spec_init_file_name="test.soepy.yml", + initial_states=initial_states, ).reset_index() - counts = [df[df["Education_Level"] == i]["Identifier"].nunique() for i in [0, 1, 2]] + counts = [ + df[df["Education_Level"] == i]["Identifier"].nunique() + for i in [0, 1, 2] + ] expected_rows = sum( n_agents * (constr["PERIODS"] - edu_years) diff --git a/soepy/test/test_validation_childless.py b/soepy/test/test_validation_childless.py index 151abb1..8f66dab 100644 --- a/soepy/test/test_validation_childless.py +++ b/soepy/test/test_validation_childless.py @@ -14,6 +14,7 @@ from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init from soepy.simulate.simulate_auxiliary import pyth_simulate +from soepy.test.resources.initial_states import create_initial_states_from_probs from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve @@ -114,6 +115,16 @@ def input_data(): ) # Simulate + initial_states = create_initial_states_from_probs( + model_params=model_params, + model_spec=model_spec, + prob_educ_level=prob_educ_level, + prob_child_age=prob_child_age, + prob_partner_present=prob_partner_present, + prob_exp_pt=prob_exp_pt, + prob_exp_ft=prob_exp_ft, + ) + calculated_df = pyth_simulate( model_params, model_spec, @@ -123,14 +134,10 @@ def input_data(): covariates, non_consumption_utilities, child_age_update_rule, - prob_educ_level, - prob_child_age, - prob_partner_present, - prob_exp_pt, - prob_exp_ft, prob_child, prob_partner, biased_exp=False, + initial_states=initial_states, ) out[name] = calculated_df diff --git a/soepy/test/test_value_function_simulation_consistency.py b/soepy/test/test_value_function_simulation_consistency.py index f0b9584..40ac01a 100644 --- a/soepy/test/test_value_function_simulation_consistency.py +++ b/soepy/test/test_value_function_simulation_consistency.py @@ -2,6 +2,7 @@ import pandas as pd from soepy.simulate.simulate_python import get_simulate_func +from soepy.test.resources.initial_states import create_initial_states from soepy.test.resources.aux_funcs import cleanup @@ -186,9 +187,14 @@ def test_value_function_matches_mean_realized_discounted_sum(): biased_exp=True, data_sparse=False, ) + initial_states = create_initial_states( + model_params_init_file_name=model_params_df, + model_spec_init_file_name=model_spec_init_dict, + ) df = simulate_func( model_params_init_file_name_inner=model_params_df, model_spec_init_file_name_inner=model_spec_init_dict, + initial_states=initial_states, ) # Compute realized discounted sum of chosen flow utility per agent. From 2211229b635df6433fde647945f465291170ec7b Mon Sep 17 00:00:00 2001 From: MaxBlesch Date: Mon, 9 Feb 2026 14:42:46 +0100 Subject: [PATCH 2/2] Pre commit --- soepy/simulate/simulate_auxiliary.py | 1 + soepy/simulate/simulate_python.py | 3 +-- soepy/test/resources/update_vault.py | 12 ++++++------ soepy/test/test_childless.py | 2 +- soepy/test/test_quadrature.py | 2 +- soepy/test/test_regression.py | 3 +-- soepy/test/test_single_woman.py | 2 +- soepy/test/test_unit.py | 5 +---- soepy/test/test_validation_childless.py | 2 +- .../test_value_function_simulation_consistency.py | 2 +- 10 files changed, 15 insertions(+), 19 deletions(-) diff --git a/soepy/simulate/simulate_auxiliary.py b/soepy/simulate/simulate_auxiliary.py index e9629a4..9bb6db4 100644 --- a/soepy/simulate/simulate_auxiliary.py +++ b/soepy/simulate/simulate_auxiliary.py @@ -18,6 +18,7 @@ from soepy.simulate.income_sim import calculate_employment_consumption_resources from soepy.simulate.initial_states import validate_initial_states + def pyth_simulate( model_params, model_spec, diff --git a/soepy/simulate/simulate_python.py b/soepy/simulate/simulate_python.py index c6bdb62..fd6c284 100644 --- a/soepy/simulate/simulate_python.py +++ b/soepy/simulate/simulate_python.py @@ -4,10 +4,10 @@ from soepy.exogenous_processes.partner import gen_prob_partner from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init +from soepy.simulate.initial_states import validate_initial_states from soepy.simulate.simulate_auxiliary import pyth_simulate from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import get_solve_function -from soepy.simulate.initial_states import validate_initial_states def simulate( @@ -66,7 +66,6 @@ def get_simulate_func( biased_exp=biased_exp, ) - def simulate_func( model_params_init_file_name_inner, model_spec_init_file_name_inner, diff --git a/soepy/test/resources/update_vault.py b/soepy/test/resources/update_vault.py index 0134fa9..1c86cdb 100644 --- a/soepy/test/resources/update_vault.py +++ b/soepy/test/resources/update_vault.py @@ -2,16 +2,16 @@ import jax.numpy as jnp -from soepy.simulate.simulate_python import simulate -from soepy.soepy_config import TEST_RESOURCES_DIR -from soepy.test.resources.aux_funcs import cleanup -from soepy.test.resources.initial_states import create_initial_states_from_probs -from soepy.pre_processing.model_processing import read_model_params_init -from soepy.pre_processing.model_processing import read_model_spec_init from soepy.exogenous_processes.children import gen_prob_child_init_age_vector from soepy.exogenous_processes.education import gen_prob_educ_level_vector from soepy.exogenous_processes.experience import gen_prob_init_exp_component_vector from soepy.exogenous_processes.partner import gen_prob_partner_present_vector +from soepy.pre_processing.model_processing import read_model_params_init +from soepy.pre_processing.model_processing import read_model_spec_init +from soepy.simulate.simulate_python import simulate +from soepy.soepy_config import TEST_RESOURCES_DIR +from soepy.test.resources.aux_funcs import cleanup +from soepy.test.resources.initial_states import create_initial_states_from_probs def update_sim_objectes(): diff --git a/soepy/test/test_childless.py b/soepy/test/test_childless.py index 4eef8d3..e60a8af 100644 --- a/soepy/test/test_childless.py +++ b/soepy/test/test_childless.py @@ -14,10 +14,10 @@ from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init from soepy.simulate.simulate_auxiliary import pyth_simulate -from soepy.test.resources.initial_states import create_initial_states_from_probs from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve +from soepy.test.resources.initial_states import create_initial_states_from_probs @pytest.fixture(scope="module") diff --git a/soepy/test/test_quadrature.py b/soepy/test/test_quadrature.py index 9a06572..1e4e7a1 100644 --- a/soepy/test/test_quadrature.py +++ b/soepy/test/test_quadrature.py @@ -13,11 +13,11 @@ from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init from soepy.simulate.simulate_auxiliary import pyth_simulate -from soepy.test.resources.initial_states import create_initial_states_from_probs from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve from soepy.test.resources.aux_funcs import create_disc_sum_av_utility +from soepy.test.resources.initial_states import create_initial_states_from_probs @pytest.fixture(scope="module") diff --git a/soepy/test/test_regression.py b/soepy/test/test_regression.py index d9d68da..fdc9ce8 100644 --- a/soepy/test/test_regression.py +++ b/soepy/test/test_regression.py @@ -15,12 +15,11 @@ from soepy.simulate.constants_sim import LABELS_DATA_SPARSE from soepy.simulate.simulate_auxiliary import pyth_simulate from soepy.simulate.simulate_python import simulate -from soepy.test.resources.initial_states import create_initial_states_from_probs - from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve from soepy.test.resources.aux_funcs import cleanup +from soepy.test.resources.initial_states import create_initial_states_from_probs CASES_TEST = random.sample(range(0, 100), 10) diff --git a/soepy/test/test_single_woman.py b/soepy/test/test_single_woman.py index 7956bdc..ecd9a86 100644 --- a/soepy/test/test_single_woman.py +++ b/soepy/test/test_single_woman.py @@ -13,11 +13,11 @@ from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init from soepy.simulate.simulate_auxiliary import pyth_simulate -from soepy.test.resources.initial_states import create_initial_states_from_probs from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve from soepy.test.resources.aux_funcs import create_disc_sum_av_utility +from soepy.test.resources.initial_states import create_initial_states_from_probs @pytest.fixture(scope="module") diff --git a/soepy/test/test_unit.py b/soepy/test/test_unit.py index 4b284d0..08bacf2 100644 --- a/soepy/test/test_unit.py +++ b/soepy/test/test_unit.py @@ -106,10 +106,7 @@ def test_unit_data_frame_shape(): initial_states=initial_states, ).reset_index() - counts = [ - df[df["Education_Level"] == i]["Identifier"].nunique() - for i in [0, 1, 2] - ] + counts = [df[df["Education_Level"] == i]["Identifier"].nunique() for i in [0, 1, 2]] expected_rows = sum( n_agents * (constr["PERIODS"] - edu_years) diff --git a/soepy/test/test_validation_childless.py b/soepy/test/test_validation_childless.py index 8f66dab..b1825f2 100644 --- a/soepy/test/test_validation_childless.py +++ b/soepy/test/test_validation_childless.py @@ -14,10 +14,10 @@ from soepy.pre_processing.model_processing import read_model_params_init from soepy.pre_processing.model_processing import read_model_spec_init from soepy.simulate.simulate_auxiliary import pyth_simulate -from soepy.test.resources.initial_states import create_initial_states_from_probs from soepy.soepy_config import TEST_RESOURCES_DIR from soepy.solve.create_state_space import create_state_space_objects from soepy.solve.solve_python import pyth_solve +from soepy.test.resources.initial_states import create_initial_states_from_probs @pytest.fixture(scope="module") diff --git a/soepy/test/test_value_function_simulation_consistency.py b/soepy/test/test_value_function_simulation_consistency.py index 40ac01a..2de66d7 100644 --- a/soepy/test/test_value_function_simulation_consistency.py +++ b/soepy/test/test_value_function_simulation_consistency.py @@ -2,8 +2,8 @@ import pandas as pd from soepy.simulate.simulate_python import get_simulate_func -from soepy.test.resources.initial_states import create_initial_states from soepy.test.resources.aux_funcs import cleanup +from soepy.test.resources.initial_states import create_initial_states def _write_minimal_exog_files(*, num_periods: int) -> None: