def test_child_indices(): """Testing existence of properties for calculation of child indices!""" point_constr = {"n_periods": 2, "n_lagged_choices": 1} params, options = generate_random_model(point_constr=point_constr) # Add some inadmissible states optim_paras, options = process_params_and_options(params, options) state_space = create_state_space_class(optim_paras, options) # Create all relevant columns core_columns = ["period"] + create_core_state_space_columns(optim_paras) # compose child indices of first choice initial_state = state_space.core.iloc[0][core_columns].to_numpy() # Get all the future states states = [] for i in range(len(optim_paras["choices"])): child = initial_state.copy() child[0] += 1 child[i + 1] += 1 child[-1] = i ix = state_space.indexer[tuple(child)] states.append(np.array(ix).reshape(1, 2)) manual = np.concatenate(states, axis=0) np.testing.assert_array_equal(state_space.child_indices[0][0], manual)
def test_wage_nonpecs(): """Replace constants in reward functions with constants due to observables.""" point_constr = {"n_periods": 3, "n_lagged_choices": 1, "observables": [2]} params, options = generate_random_model(point_constr=point_constr) solve = get_solve_func(params, options) state_space = solve(params) # Replace constants in choices with constant utility by observables. optim_paras, _ = process_params_and_options(params, options) wage_choice = ("wage", np.random.choice(optim_paras["choices_w_wage"])) nonpec_choice = ("nonpec", np.random.choice(list(optim_paras["choices"]))) # Change specs accordingly for reward in [wage_choice, nonpec_choice]: constant = params.loc[(f"{reward[0]}_{reward[1]}", "constant"), "value"] params = params.drop(index=[(f"{reward[0]}_{reward[1]}", "constant")]) for obs in range(2): params.loc[(f"{reward[0]}_{reward[1]}", f"observable_0_{obs}"), "value"] += constant solve = get_solve_func(params, options) state_space_ = solve(params) for attribute in ["wages", "nonpecs"]: apply_to_attributes_of_two_state_spaces( getattr(state_space, attribute), getattr(state_space_, attribute), np.testing.assert_array_almost_equal, )
def test_distribution_of_observables(seed): """Test that the distribution of observables matches the simulated distribution.""" np.random.seed(seed) # Now specify a set of observables point_constr = { "observables": [np.random.randint(2, 6)], "simulation_agents": 1000, "n_periods": 1, } params, options = generate_random_model(point_constr=point_constr) simulate = rp.get_simulate_func(params, options) df = simulate(params) # Check observable probabilities probs = df["Observable_0"].value_counts(normalize=True, sort=False) # Check proportions np.testing.assert_almost_equal( probs.to_numpy(), params.loc[params.index.get_level_values(0).str. contains("observable_observable_0"), "value", ].to_numpy(), decimal=1, )
def test_equality_for_myopic_agents_and_tiny_delta(): """Test equality of simulated data and likelihood with myopia and tiny delta.""" # Get simulated data and likelihood for myopic model. params, options = generate_random_model(myopic=True) simulate = rp.get_simulate_func(params, options) df = simulate(params) log_like = get_log_like_func(params, options, df) likelihood = log_like(params) # Get simulated data and likelihood for model with tiny delta. params.loc["delta", "value"] = 1e-12 df_ = simulate(params) log_like = rp.get_log_like_func(params, options, df_) likelihood_ = log_like(params) # The continuation values are different because for delta = 0 the backward induction # is completely skipped and all continuation values are set to zero whereas for a # tiny delta, the delta ensures that continuation have no impact. columns = df.filter(like="Continu").columns.tolist() pd.testing.assert_frame_equal(df.drop(columns=columns), df_.drop(columns=columns)) np.testing.assert_almost_equal(likelihood, likelihood_, decimal=12)
def test_raise_exception_for_missing_meas_error(): params, options = generate_random_model() params = params.drop(index=("meas_error", "sd_b")) with pytest.raises(KeyError): _parse_measurement_errors(params, options)
def test_raise_exception_for_missing_shock_matrix(): params, _ = generate_random_model() params = params.drop(index="shocks_sdcorr", level="category") with pytest.raises(KeyError): _parse_shocks({}, params)
def test_invariance_of_wage_calc(): """The model reproduces invariant properties of wage outcomes.""" point_constr = {"n_periods": 2, "observables": [3]} params, options = generate_random_model(point_constr=point_constr) # Add some inadmissible states optim_paras, _ = process_params_and_options(params, options) # Solve first model solve = get_solve_func(params, options) state_space = solve(params) pos = np.random.choice(range(len(state_space.dense))) dense_combination = list(state_space.dense.keys())[pos] dense_index = state_space.dense_covariates_to_dense_index[ dense_combination] idx = state_space.core_key_and_dense_index_to_dense_key[(1, dense_index)] # Solve relevant wages wages_b = state_space.wages[idx][:, 1] # Impose some restriction options["negative_choice_set"] = {"a": ["period == 1"]} solve = get_solve_func(params, options) state_space = solve(params) wages_b_alt = state_space.wages[idx][:, 0] np.testing.assert_array_equal(wages_b, wages_b_alt)
def test_distribution_of_observables(): """Test that the distribution of observables matches the simulated distribution.""" # Now specify a set of observables point_constr = { "observables": [np.random.randint(2, 6)], "simulation_agents": 1000 } params, options = generate_random_model(point_constr=point_constr) simulate = rp.get_simulate_func(params, options) df = simulate(params) # Check observable probabilities probs = df["Observable_0"].value_counts(normalize=True, sort=False) # Check proportions n_levels = point_constr["observables"][0] for level in range(n_levels): # Some observables might be missing in the simulated data because of small # probabilities. Test for zero probability in this case. probability = probs.loc[level] if level in probs.index else 0 params_probability = params.loc[(f"observable_observable_0_{level}", "probability"), "value"] np.testing.assert_allclose(probability, params_probability, atol=0.05)
def test_run_through_of_solve_with_interpolation(): params, options = generate_random_model(point_constr={ "n_periods": 5, "interpolation_points": 10 }) solve = get_solve_func(params, options) solve(params)
def test_raise_exception_for_observable_with_one_value(observables): point_constr = {"observables": observables} params, _ = generate_random_model(point_constr=point_constr) params = params.drop(index="observable_observable_0_0", level="category")["value"] with pytest.raises(ValueError, match=r"Observables and exogenous processes"): _parse_observables({}, params)
def test_generate_random_model(): """Test if random model specifications can be simulated and processed.""" params, options = generate_random_model() df = simulate_truncated_data(params, options) log_like = get_log_like_func(params, options, df) crit_val = log_like(params) assert isinstance(crit_val, float)
def process_model_or_seed(model_or_seed, **kwargs): if isinstance(model_or_seed, str): params, options = rp.get_example_model(model_or_seed, with_data=False) else: np.random.seed(model_or_seed) params, options = generate_random_model(**kwargs) if "kw_97" in str(model_or_seed): options["n_periods"] = 10 return params, options
def _create_single(idx): """Create a single test.""" np.random.seed(idx) params, options = generate_random_model() crit_val = compute_log_likelihood(params, options) if not isinstance(crit_val, float): raise AssertionError(" ... value of criterion function too large.") return params, options, crit_val
def test_simulation_and_estimation_with_different_models(): """Test the evaluation of the criterion function not at the true parameters.""" # Simulate a dataset params, options = generate_random_model() df = simulate_truncated_data(params, options) # Evaluate at different points, ensuring that the simulated dataset still fits. crit_func = get_crit_func(params, options, df) params_ = add_noise_to_params(params, options) params.equals(params_) crit_func(params)
def test_invariant_results_for_two_estimations(): params, options = generate_random_model() df = simulate_truncated_data(params, options) crit_func = get_crit_func(params, options, df) # First estimation. crit_val = crit_func(params) # Second estimation. crit_val_ = crit_func(params) assert crit_val == crit_val_
def test_simulation_and_estimation_with_different_models(): """Test the evaluation of the criterion function not at the true parameters.""" # Set constraints. num_agents = np.random.randint(5, 100) constr = { "simulation_agents": num_agents, "n_periods": np.random.randint(1, 4), "edu_max": 15, "edu_start": [7], "edu_share": [1], "n_lagged_choices": np.random.choice(2), } # Simulate a dataset params, options = generate_random_model(point_constr=constr) df = simulate_truncated_data(params, options) # Evaluate at different points, ensuring that the simulated dataset still fits. params, options = generate_random_model(point_constr=constr) crit_func = get_crit_func(params, options, df) crit_func(params)
def test_generate_random_model(seed): """Test if random model specifications can be simulated and processed.""" np.random.seed(seed) params, options = generate_random_model() df = simulate_truncated_data(params, options) crit_func = get_crit_func(params, options, df) crit_val = crit_func(params) assert isinstance(crit_val, float)
def test_equality_of_models_with_and_without_observables(seed): """Test equality of models with and without observables. First, generate a model where the parameter values of observables is set to zero. The second model is obtained by assigning all observable indicators the value of the constant in the reward functions and set the constants to zero. The two models should be equivalent. """ np.random.seed(seed) # Now specify a set of observables observables = [np.random.randint(2, 6)] point_constr = {"observables": observables} # Get simulated data and likelihood for myopic model. params, options = generate_random_model(myopic=True, point_constr=point_constr) # Get all reward values index_reward = [ x for x in set(params.index.get_level_values(0)) if "nonpec" in x or "wage" in x ] # Get all indices that have obs_labels = generate_obs_labels(observables, index_reward) # Set these values to zero params.loc[obs_labels, "value"] = 0 # Simulate the base model simulate = rp.get_simulate_func(params, options) df = simulate(params) # Put two new values into the eq for x in obs_labels: params.loc[x, "value"] = params.loc[(x[0], "constant"), "value"] for x in index_reward: params.loc[(x, "constant"), "value"] = 0 # Simulate the new model df_ = simulate(params) # test for equality pd.testing.assert_frame_equal(df_, df)
def process_model_or_seed(model_or_seed=None, **kwargs): if isinstance(model_or_seed, str): params, options = rp.get_example_model(model_or_seed, with_data=False) elif isinstance(model_or_seed, int): np.random.seed(model_or_seed) params, options = generate_random_model(**kwargs) else: raise ValueError if "kw_94" in str(model_or_seed): options["n_periods"] = 10 if "kw_97" in str(model_or_seed): options["n_periods"] = 5 elif "kw_2000" in str(model_or_seed): options["n_periods"] = 3 return params, options
def test_invariant_results_for_two_estimations(): num_agents = np.random.randint(5, 100) constr = { "simulation_agents": num_agents, "n_periods": np.random.randint(1, 4), "n_lagged_choices": np.random.choice(2), } # Simulate a dataset. params, options = generate_random_model(point_constr=constr) df = simulate_truncated_data(params, options) crit_func = get_crit_func(params, options, df) # First estimation. crit_val = crit_func(params) # Second estimation. crit_val_ = crit_func(params) assert crit_val == crit_val_
def process_model_or_seed(model_or_seed=None, **kwargs): if isinstance(model_or_seed, str): params, options = rp.get_example_model(model_or_seed, with_data=False) elif isinstance(model_or_seed, int): np.random.seed(model_or_seed) params, options = generate_random_model(**kwargs) else: raise ValueError if "kw_94" in str(model_or_seed): options["n_periods"] = 10 elif "kw_97" in str(model_or_seed): options["n_periods"] = 5 elif "kw_2000" in str(model_or_seed): options["n_periods"] = 3 elif "robinson_crusoe_extended" in str(model_or_seed): options["n_periods"] = 5 elif "robinson_crusoe_with_observed_characteristics" in str(model_or_seed): options["n_periods"] = 5 return params, options
def test_normalize_probabilities(): constraints = {"observables": [3]} params, options = generate_random_model(point_constr=constraints) optim_paras_1, _ = process_params_and_options(params, options) for group in ["initial_exp_edu", "observable_"]: mask = params.index.get_level_values(0).str.contains(group) params.loc[mask, "value"] = params.loc[mask, "value"].to_numpy() / 2 with pytest.warns(UserWarning, match=r"The probabilities for parameter group"): optim_paras_2, _ = process_params_and_options(params, options) for key in optim_paras_1["choices"]["edu"]["start"]: np.testing.assert_array_almost_equal( optim_paras_1["choices"]["edu"]["start"][key], optim_paras_2["choices"]["edu"]["start"][key], ) for level in optim_paras_1["observables"]["observable_0"]: np.testing.assert_array_almost_equal( optim_paras_1["observables"]["observable_0"][level], optim_paras_2["observables"]["observable_0"][level], )
def test_equality_for_myopic_agents_and_tiny_delta(seed): """Test equality of simulated data and likelihood with myopia and tiny delta.""" np.random.seed(seed) # Get simulated data and likelihood for myopic model. params, options = generate_random_model(myopic=True) simulate = rp.get_simulate_func(params, options) df = simulate(params) crit_func = get_crit_func(params, options, df) likelihood = crit_func(params) # Get simulated data and likelihood for model with tiny delta. params.loc["delta", "value"] = 1e-12 df_ = simulate(params) crit_func_ = rp.get_crit_func(params, options, df_) likelihood_ = crit_func_(params) pd.testing.assert_frame_equal(df, df_) np.testing.assert_almost_equal(likelihood, likelihood_, decimal=12)
def test_dense_choice_cores(): """ Check whether continuation values are equal for paths where the restrictions do not make any difference. We check continuation values at states where one choice leads to a remaining decision tree that is equivalent to the unrestricted problem and one where this is not the case! """ if CHAOSPY_INSTALLED: point_constr = { "n_periods": 6, "observables": [3], "n_lagged_choices": 1 } params, options = generate_random_model(point_constr=point_constr) options["monte_carlo_sequence"] = "sobol" # Add some inadmissible states optim_paras, _ = process_params_and_options(params, options) # Solve the base model solve = get_solve_func(params, options) state_space = solve(params) # Retrieve index edu_start = np.random.choice( list(optim_paras["choices"]["edu"]["start"].keys())) state = (3, 0, 3, edu_start, 1) core_ix = state_space.indexer[state] # Choose dense covar pos = np.random.choice(range(len(state_space.dense))) # Get indices dense_combination = list(state_space.dense.keys())[pos] dense_index = state_space.dense_covariates_to_dense_index[ dense_combination] ix = ( state_space.core_key_and_dense_index_to_dense_key[core_ix[0], dense_index], core_ix[1], ) unrestricted_cont = state_space.get_continuation_values(3)[ix[0]][ ix[1]] # Impose some restriction options["negative_choice_set"] = {"a": ["period == 4 & exp_b ==4"]} # Solve the restricted model solve = get_solve_func(params, options) state_space = solve(params) core_ix = state_space.indexer[state] # Get indices dense_combination = list(state_space.dense.keys())[pos] dense_index = state_space.dense_covariates_to_dense_index[ dense_combination] ix = ( state_space.core_key_and_dense_index_to_dense_key[core_ix[0], dense_index], core_ix[1], ) # Check some features of the state_space restricted_cont = state_space.get_continuation_values(3)[ix[0]][ix[1]] for i in [0, 2, 3]: assert restricted_cont[i] == unrestricted_cont[i] assert restricted_cont[1] != unrestricted_cont[1]