Esempio n. 1
0
def _params_spec_from_attributes(attr):
    csv = csv_template(attr["num_types"])
    bounds = np.array(attr["optim_paras"]["paras_bounds"])
    csv["lower"] = bounds[:, 0]
    csv["upper"] = bounds[:, 1]
    csv["fixed"] = attr["optim_paras"]["paras_fixed"]
    csv["para"] = get_optim_paras(
        paras_dict=attr["optim_paras"],
        num_paras=attr["num_paras"],
        which="all",
        is_debug=True,
    )
    return csv
Esempio n. 2
0
    def test_1(self):
        """ Testing whether back-and-forth transformation have no effect.
        """
        for _ in range(10):
            num_types = np.random.randint(1, 5)
            num_paras = 53 + (num_types - 1) * 6

            # Create random parameter vector
            base = np.random.uniform(size=num_paras)

            x = base.copy()

            # Apply numerous transformations
            for _ in range(10):
                optim_paras = distribute_parameters(x, is_debug=True)
                args = (optim_paras, num_paras, "all", True)
                x = get_optim_paras(*args)

            np.testing.assert_allclose(base, x)
Esempio n. 3
0
def scripts_modify(identifiers, action, init_file, values=None, bounds=None):
    """ Modify optimization parameters by either changing their status or values.
    """
    # Select interface
    is_bounds = action == "bounds"
    is_fixed = action == "fix"

    # Baseline
    init_dict = read_init_file(init_file)
    respy_obj = RespyCls(init_file)

    optim_paras, num_paras, num_types = dist_class_attributes(
        respy_obj, "optim_paras", "num_paras", "num_types")

    # We now need to ensure a consistent perspective, i.e. all are the parameter values
    # as specified in the initialization file.
    x = get_optim_paras(optim_paras, num_paras, "all", True)
    x[43:53] = cholesky_to_coeffs(optim_paras["shocks_cholesky"])

    if action == "value":
        for i, j in enumerate(identifiers):
            x[j] = values[i]

    for identifier in identifiers:
        if identifier in [0]:
            j = identifier
            init_dict["BASICS"]["coeffs"][j] = x[identifier]
            if is_fixed:
                init_dict["BASICS"]["fixed"][j] = is_fixed
            elif is_bounds:
                init_dict["BASICS"]["bounds"][j] = bounds
        elif identifier in list(range(1, 3)):
            j = identifier - 1
            init_dict["COMMON"]["coeffs"][j] = x[identifier]
            if is_fixed:
                init_dict["COMMON"]["fixed"][j] = is_fixed
            elif is_bounds:
                init_dict["COMMON"]["bounds"][j] = bounds
        elif identifier in list(range(3, 18)):
            j = identifier - 3
            init_dict["OCCUPATION A"]["coeffs"][j] = x[identifier]
            if is_fixed:
                init_dict["OCCUPATION A"]["fixed"][j] = is_fixed
            elif is_bounds:
                init_dict["OCCUPATION A"]["bounds"][j] = bounds
        elif identifier in list(range(18, 33)):
            j = identifier - 18
            init_dict["OCCUPATION B"]["coeffs"][j] = x[identifier]
            if is_fixed:
                init_dict["OCCUPATION B"]["fixed"][j] = is_fixed
            elif is_bounds:
                init_dict["OCCUPATION B"]["bounds"][j] = bounds
        elif identifier in list(range(33, 40)):
            j = identifier - 33
            init_dict["EDUCATION"]["coeffs"][j] = x[identifier]
            if is_fixed:
                init_dict["EDUCATION"]["fixed"][j] = is_fixed
            elif is_bounds:
                init_dict["EDUCATION"]["bounds"][j] = bounds
        elif identifier in list(range(40, 43)):
            j = identifier - 40
            init_dict["HOME"]["coeffs"][j] = x[identifier]
            if is_fixed:
                init_dict["HOME"]["fixed"][j] = is_fixed
            elif is_bounds:
                init_dict["HOME"]["bounds"][j] = bounds
        elif identifier in list(range(43, 53)):
            j = identifier - 43
            init_dict["SHOCKS"]["coeffs"][j] = x[identifier]
            if is_fixed:
                init_dict["SHOCKS"]["fixed"][j] = is_fixed
            elif is_bounds:
                init_dict["SHOCKS"]["bounds"][j] = bounds
        elif identifier in list(range(53, 53 + (num_types - 1) * 2)):
            j = identifier - 53
            init_dict["TYPE SHARES"]["coeffs"][j] = x[identifier]
            if is_fixed:
                init_dict["TYPE SHARES"]["fixed"][j] = is_fixed
            elif is_bounds:
                init_dict["TYPE SHARES"]["bounds"][j] = bounds
        elif identifier in list(range(53 + (num_types - 1) * 2, num_paras)):
            j = identifier - (53 + (num_types - 1) * 2)
            init_dict["TYPE SHIFTS"]["coeffs"][j] = x[identifier]
            if is_fixed:
                init_dict["TYPE SHIFTS"]["fixed"][j] = is_fixed
            elif is_bounds:
                init_dict["TYPE SHIFTS"]["bounds"][j] = bounds
        else:
            raise NotImplementedError

    # Check that the new candidate initialization file is valid. If so, go ahead and
    # replace the original file.
    write_init_file(init_dict, ".tmp.respy.ini")
    RespyCls(".tmp.respy.ini")
    shutil.move(".tmp.respy.ini", init_file)
Esempio n. 4
0
def respy_interface(respy_obj, request, data=None):
    """Provide the interface to the PYTHON functionality."""
    # Distribute class attributes
    (
        optim_paras,
        num_periods,
        edu_spec,
        is_debug,
        num_draws_prob,
        seed_prob,
        num_draws_emax,
        seed_emax,
        is_interpolated,
        num_points_interp,
        maxfun,
        optimizer_used,
        tau,
        optimizer_options,
        seed_sim,
        num_agents_sim,
        file_sim,
        precond_spec,
        num_types,
        num_paras,
        num_agents_est,
    ) = dist_class_attributes(
        respy_obj,
        "optim_paras",
        "num_periods",
        "edu_spec",
        "is_debug",
        "num_draws_prob",
        "seed_prob",
        "num_draws_emax",
        "seed_emax",
        "is_interpolated",
        "num_points_interp",
        "maxfun",
        "optimizer_used",
        "tau",
        "optimizer_options",
        "seed_sim",
        "num_agents_sim",
        "file_sim",
        "precond_spec",
        "num_types",
        "num_paras",
        "num_agents_est",
    )

    if request == "estimate":

        periods_draws_prob = create_draws(
            num_periods, num_draws_prob, seed_prob, is_debug
        )
        periods_draws_emax = create_draws(
            num_periods, num_draws_emax, seed_emax, is_debug
        )

        # Construct starting values
        x_optim_free_unscaled_start = get_optim_paras(
            optim_paras, num_paras, "free", is_debug
        )
        x_optim_all_unscaled_start = get_optim_paras(
            optim_paras, num_paras, "all", is_debug
        )

        # Construct the state space
        state_space = StateSpace(
            num_periods, num_types, edu_spec["start"], edu_spec["max"]
        )

        # Collect arguments that are required for the criterion function.
        # These must be in the correct order already.
        args = (
            is_interpolated,
            num_points_interp,
            is_debug,
            data,
            tau,
            periods_draws_emax,
            periods_draws_prob,
            state_space,
        )

        # Special case where just one evaluation at the starting values is
        # requested is accounted for. Note, that the relevant value of the
        # criterion function is always the one indicated by the class attribute
        # and not the value returned by the optimization algorithm.
        num_free = optim_paras["paras_fixed"].count(False)

        # Take only bounds from unfixed parameters and insert default bounds.
        mask_paras_fixed = np.array(optim_paras["paras_fixed"])
        paras_bounds_free_unscaled = np.array(optim_paras["paras_bounds"])[
            ~mask_paras_fixed
        ]
        paras_bounds_free_unscaled[:, 0] = np.where(
            paras_bounds_free_unscaled[:, 0] == None,  # noqa: E711
            -HUGE_FLOAT,
            paras_bounds_free_unscaled[:, 0],
        )
        paras_bounds_free_unscaled[:, 1] = np.where(
            paras_bounds_free_unscaled[:, 1] == None,  # noqa: E711
            HUGE_FLOAT,
            paras_bounds_free_unscaled[:, 1],
        )

        record_estimation_scaling(
            x_optim_free_unscaled_start,
            None,
            None,
            None,
            optim_paras["paras_fixed"],
            True,
        )

        precond_matrix = get_precondition_matrix(
            precond_spec,
            optim_paras,
            x_optim_all_unscaled_start,
            args,
            maxfun,
            num_paras,
            num_types,
        )

        x_optim_free_scaled_start = apply_scaling(
            x_optim_free_unscaled_start, precond_matrix, "do"
        )

        paras_bounds_free_scaled = np.full((num_free, 2), np.nan)
        for i in range(2):
            paras_bounds_free_scaled[:, i] = apply_scaling(
                paras_bounds_free_unscaled[:, i], precond_matrix, "do"
            )

        record_estimation_scaling(
            x_optim_free_unscaled_start,
            x_optim_free_scaled_start,
            paras_bounds_free_scaled,
            precond_matrix,
            optim_paras["paras_fixed"],
            False,
        )

        opt_obj = OptimizationClass(
            x_optim_all_unscaled_start,
            optim_paras["paras_fixed"],
            precond_matrix,
            num_types,
        )
        opt_obj.maxfun = maxfun

        if maxfun == 0:

            record_estimation_scalability("Start")
            opt_obj.crit_func(x_optim_free_scaled_start, *args)
            record_estimation_scalability("Finish")

            success = True
            message = "Single evaluation of criterion function at starting values."

        elif optimizer_used == "SCIPY-BFGS":

            bfgs_maxiter = optimizer_options["SCIPY-BFGS"]["maxiter"]
            bfgs_gtol = optimizer_options["SCIPY-BFGS"]["gtol"]
            bfgs_eps = optimizer_options["SCIPY-BFGS"]["eps"]

            try:
                rslt = fmin_bfgs(
                    opt_obj.crit_func,
                    x_optim_free_scaled_start,
                    args=args,
                    gtol=bfgs_gtol,
                    epsilon=bfgs_eps,
                    maxiter=bfgs_maxiter,
                    full_output=True,
                    disp=False,
                )

                success = rslt[6] not in [1, 2]
                message = "Optimization terminated successfully."
                if rslt[6] == 1:
                    message = "Maximum number of iterations exceeded."
                elif rslt[6] == 2:
                    message = "Gradient and/or function calls not changing."

            except MaxfunError:
                success = False
                message = "Maximum number of iterations exceeded."

        elif optimizer_used == "SCIPY-LBFGSB":

            lbfgsb_maxiter = optimizer_options["SCIPY-LBFGSB"]["maxiter"]
            lbfgsb_maxls = optimizer_options["SCIPY-LBFGSB"]["maxls"]
            lbfgsb_factr = optimizer_options["SCIPY-LBFGSB"]["factr"]
            lbfgsb_pgtol = optimizer_options["SCIPY-LBFGSB"]["pgtol"]
            lbfgsb_eps = optimizer_options["SCIPY-LBFGSB"]["eps"]
            lbfgsb_m = optimizer_options["SCIPY-LBFGSB"]["m"]

            try:
                rslt = fmin_l_bfgs_b(
                    opt_obj.crit_func,
                    x_optim_free_scaled_start,
                    args=args,
                    approx_grad=True,
                    bounds=paras_bounds_free_scaled,
                    m=lbfgsb_m,
                    factr=lbfgsb_factr,
                    pgtol=lbfgsb_pgtol,
                    epsilon=lbfgsb_eps,
                    iprint=-1,
                    maxfun=maxfun,
                    maxiter=lbfgsb_maxiter,
                    maxls=lbfgsb_maxls,
                )

                success = rslt[2]["warnflag"] in [0]
                message = rslt[2]["task"]

            except MaxfunError:
                success = False
                message = "Maximum number of iterations exceeded."

        elif optimizer_used == "SCIPY-POWELL":

            powell_maxiter = optimizer_options["SCIPY-POWELL"]["maxiter"]
            powell_maxfun = optimizer_options["SCIPY-POWELL"]["maxfun"]
            powell_xtol = optimizer_options["SCIPY-POWELL"]["xtol"]
            powell_ftol = optimizer_options["SCIPY-POWELL"]["ftol"]

            try:
                rslt = fmin_powell(
                    opt_obj.crit_func,
                    x_optim_free_scaled_start,
                    args,
                    powell_xtol,
                    powell_ftol,
                    powell_maxiter,
                    powell_maxfun,
                    disp=0,
                )

                success = rslt[5] not in [1, 2]
                message = "Optimization terminated successfully."
                if rslt[5] == 1:
                    message = "Maximum number of function evaluations."
                elif rslt[5] == 2:
                    message = "Maximum number of iterations."

            except MaxfunError:
                success = False
                message = "Maximum number of iterations exceeded."

        else:
            raise NotImplementedError

        record_estimation_final(success, message)
        record_estimation_stop()

    elif request == "simulate":

        # Draw draws for the simulation.
        periods_draws_sims = create_draws(
            num_periods, num_agents_sim, seed_sim, is_debug
        )

        # Draw standard normal deviates for the solution and evaluation step.
        periods_draws_emax = create_draws(
            num_periods, num_draws_emax, seed_emax, is_debug
        )

        # Collect arguments for different implementations of the simulation.
        state_space = pyth_solve(
            is_interpolated,
            num_points_interp,
            num_periods,
            is_debug,
            periods_draws_emax,
            edu_spec,
            optim_paras,
            file_sim,
            num_types,
        )

        simulated_data = pyth_simulate(
            state_space,
            num_agents_sim,
            periods_draws_sims,
            seed_sim,
            file_sim,
            edu_spec,
            optim_paras,
            is_debug,
        )

        args = (state_space, simulated_data)

    else:
        raise NotImplementedError("This request is not implemented.")

    return args
    def test_10(self):
        """ This test ensures that the order of the initial schooling level specified in
        the initialization files does not matter for the simulation of a dataset and
        subsequent evaluation of the criterion function.

        Warning
        -------
        This test fails if types have the identical intercept as no unique ordering is
        determined than.

        """
        point_constr = {
            "estimation": {
                "maxfun": 0
            },
            # We cannot allow for interpolation as the order of states within each
            # period changes and thus the prediction model is altered even if the same
            # state identifier is used.
            "interpolation": {
                "flag": False
            },
        }

        params_spec, options_spec = generate_random_model(
            point_constr=point_constr)

        respy_obj = RespyCls(params_spec, options_spec)

        edu_baseline_spec, num_types, num_paras, optim_paras = dist_class_attributes(
            respy_obj, "edu_spec", "num_types", "num_paras", "optim_paras")

        # We want to randomly shuffle the list of initial schooling but need to maintain
        # the order of the shares.
        edu_shuffled_start = np.random.permutation(
            edu_baseline_spec["start"]).tolist()

        edu_shuffled_share, edu_shuffled_lagged = [], []
        for start in edu_shuffled_start:
            idx = edu_baseline_spec["start"].index(start)
            edu_shuffled_lagged += [edu_baseline_spec["lagged"][idx]]
            edu_shuffled_share += [edu_baseline_spec["share"][idx]]

        edu_shuffled_spec = copy.deepcopy(edu_baseline_spec)
        edu_shuffled_spec["lagged"] = edu_shuffled_lagged
        edu_shuffled_spec["start"] = edu_shuffled_start
        edu_shuffled_spec["share"] = edu_shuffled_share

        # We are only looking at a single evaluation as otherwise the reordering affects
        # the optimizer that is trying better parameter values one-by-one. The
        # reordering might also violate the bounds.
        for i in range(53, num_paras):
            optim_paras["paras_bounds"][i] = [None, None]
            optim_paras["paras_fixed"][i] = False

        # We need to ensure that the baseline type is still in the first position.
        types_order = [0] + np.random.permutation(range(1, num_types)).tolist()

        type_shares = []
        for i in range(num_types):
            lower, upper = i * 2, (i + 1) * 2
            type_shares += [optim_paras["type_shares"][lower:upper].tolist()]

        optim_paras_baseline = copy.deepcopy(optim_paras)
        optim_paras_shuffled = copy.deepcopy(optim_paras)

        list_ = [
            optim_paras["type_shifts"][i, :].tolist() for i in types_order
        ]
        optim_paras_shuffled["type_shifts"] = np.array(list_)

        list_ = [type_shares[i] for i in types_order]
        optim_paras_shuffled["type_shares"] = np.array(list_).flatten()

        base_data, base_val = None, None

        k = 0

        for optim_paras in [optim_paras_baseline, optim_paras_shuffled]:
            for edu_spec in [edu_baseline_spec, edu_shuffled_spec]:

                respy_obj.unlock()
                respy_obj.set_attr("edu_spec", edu_spec)
                respy_obj.lock()

                # There is some more work to do to update the coefficients as we
                # distinguish between the economic and optimization version of the
                # parameters.
                x = get_optim_paras(optim_paras, num_paras, "all", True)
                shocks_cholesky, _ = extract_cholesky(x)
                shocks_coeffs = cholesky_to_coeffs(shocks_cholesky)
                x[43:53] = shocks_coeffs
                respy_obj.update_optim_paras(x)

                respy_obj.reset()

                simulate_observed(respy_obj)

                # This part checks the equality of simulated dataset.
                data_frame = pd.read_csv("data.respy.dat",
                                         delim_whitespace=True)

                if base_data is None:
                    base_data = data_frame.copy()

                assert_frame_equal(base_data, data_frame)

                # This part checks the equality of a single function evaluation.
                _, val = respy_obj.fit()
                if base_val is None:
                    base_val = val
                np.testing.assert_almost_equal(base_val, val)

                respy_obj.reset()
                k += 1
Esempio n. 6
0
def check_model_attributes(attr_dict):
    a = attr_dict

    # Number of parameters
    assert isinstance(a["num_paras"], int)
    assert a["num_paras"] >= 53

    # Parallelism
    assert isinstance(a["num_procs"], int)
    assert a["num_procs"] > 0
    if a["num_procs"] > 1:
        assert a["version"] == "fortran"

    assert isinstance(a["num_procs"], int)
    assert a["num_procs"] > 0
    if a["num_procs"] > 1:
        assert a["version"] == "fortran"
        assert IS_PARALLELISM_MPI

    # Version version of package
    assert a["version"] in ["fortran", "python"]
    if a["version"] == "fortran":
        assert IS_FORTRAN

    assert isinstance(a["num_threads"], int)
    assert a["num_threads"] >= 1
    if a["num_threads"] >= 2:
        assert a["version"] == "fortran"
        assert IS_PARALLELISM_OMP

    # Debug status
    assert a["is_debug"] in [True, False]

    # Forward-looking agents
    assert a["is_myopic"] in [True, False]

    # Seeds
    for seed in [a["seed_emax"], a["seed_sim"], a["seed_prob"]]:
        assert np.isfinite(seed)
        assert isinstance(seed, int)
        assert seed > 0

    # Number of agents
    for num_agents in [a["num_agents_sim"], a["num_agents_est"]]:
        assert np.isfinite(num_agents)
        assert isinstance(num_agents, int)
        assert num_agents > 0

    # Number of periods
    assert np.isfinite(a["num_periods"])
    assert isinstance(a["num_periods"], int)
    assert a["num_periods"] > 0

    # Number of draws for Monte Carlo integration
    assert np.isfinite(a["num_draws_emax"])
    assert isinstance(a["num_draws_emax"], int)
    assert a["num_draws_emax"] >= 0

    # Debugging mode
    assert a["is_debug"] in [True, False]

    # Window for smoothing parameter
    assert isinstance(a["tau"], float)
    assert a["tau"] > 0

    # Interpolation
    assert a["is_interpolated"] in [True, False]
    assert isinstance(a["num_points_interp"], int)
    assert a["num_points_interp"] > 0

    # Simulation of S-ML
    assert isinstance(a["num_draws_prob"], int)
    assert a["num_draws_prob"] > 0

    # Maximum number of iterations
    assert isinstance(a["maxfun"], int)
    assert a["maxfun"] >= 0

    # Optimizers
    assert a["optimizer_used"] in OPT_EST_FORT + OPT_EST_PYTH

    # Scaling
    assert a["precond_spec"]["type"] in ["identity", "gradient", "magnitudes"]
    for key_ in ["minimum", "eps"]:
        assert isinstance(a["precond_spec"][key_], float)
        assert a["precond_spec"][key_] > 0.0

    # Education
    assert isinstance(a["edu_spec"]["max"], int)
    assert a["edu_spec"]["max"] > 0
    assert isinstance(a["edu_spec"]["start"], list)
    assert len(a["edu_spec"]["start"]) == len(set(a["edu_spec"]["start"]))
    assert all(isinstance(item, int) for item in a["edu_spec"]["start"])
    assert all(item > 0 for item in a["edu_spec"]["start"])
    assert all(item <= a["edu_spec"]["max"] for item in a["edu_spec"]["start"])
    assert all(isinstance(item, float) for item in a["edu_spec"]["share"])
    assert all(0 <= item <= 1 for item in a["edu_spec"]["lagged"])
    assert all(0 <= item <= 1 for item in a["edu_spec"]["share"])
    np.testing.assert_almost_equal(np.sum(a["edu_spec"]["share"]),
                                   1.0,
                                   decimal=4)

    # Derivatives
    assert a["derivatives"] in ["forward-differences"]

    # Check model parameters
    check_model_parameters(a["optim_paras"])

    # Check that all parameter values are within the bounds.
    x = get_optim_paras(a["optim_paras"], a["num_paras"], "all", True)

    # It is not clear at this point how to impose parameter constraints on
    # the covariance matrix in a flexible manner. So, either all fixed or
    # none. As a special case, we also allow for all off-diagonal elements
    # to be fixed to zero.
    shocks_coeffs = a["optim_paras"]["shocks_cholesky"][np.tril_indices(4)]
    shocks_fixed = np.array(a["optim_paras"]["paras_fixed"][43:53])

    all_free = not shocks_fixed.any()

    dim = len(a["optim_paras"]["shocks_cholesky"])
    helper = np.zeros((dim, dim))
    helper[np.tril_indices(dim)] = shocks_coeffs
    off_diagonals_zero = np.diag(helper).sum() == helper.sum()

    helper = np.zeros((dim, dim), dtype=bool)
    helper[np.tril_indices(dim)] = shocks_fixed
    off_diagonals_fixed = (helper[np.tril_indices(dim, k=-1)]).all()

    diagonal_matrix = off_diagonals_zero & off_diagonals_fixed

    if not (all_free or shocks_fixed.all() or diagonal_matrix):
        raise UserError(" Misspecified constraints for covariance matrix")

    # Discount rate and type shares need to be larger than on at all times.
    for label in ["paras_fixed", "paras_bounds"]:
        assert isinstance(a["optim_paras"][label], list)
        assert len(a["optim_paras"][label]) == a["num_paras"]

    for i in range(1):
        assert a["optim_paras"]["paras_bounds"][i][0] >= 0.00

    for i in range(a["num_paras"]):
        lower, upper = a["optim_paras"]["paras_bounds"][i]
        if lower is not None:
            assert isinstance(lower, float)
            assert lower <= x[i]
            assert abs(lower) < PRINT_FLOAT
        if upper is not None:
            assert isinstance(upper, float)
            assert upper >= x[i]
            assert abs(upper) < PRINT_FLOAT
        if (upper is not None) and (lower is not None):
            assert upper >= lower

    _check_optimizer_options(a["optimizer_options"])
Esempio n. 7
0
    def test_5(self):
        """ This methods ensures that the core functions yield the same results across
        implementations.
        """
        params_spec, options_spec = generate_random_model()
        respy_obj = RespyCls(params_spec, options_spec)

        # Ensure that backward induction routines use the same grid for the
        # interpolation.
        max_states_period = write_interpolation_grid(respy_obj)

        # Extract class attributes
        (
            num_periods,
            edu_spec,
            optim_paras,
            num_draws_emax,
            is_debug,
            is_interpolated,
            num_points_interp,
            is_myopic,
            num_agents_sim,
            num_draws_prob,
            tau,
            seed_sim,
            num_agents_est,
            optimizer_options,
            file_sim,
            num_types,
            num_paras,
        ) = dist_class_attributes(
            respy_obj,
            "num_periods",
            "edu_spec",
            "optim_paras",
            "num_draws_emax",
            "is_debug",
            "is_interpolated",
            "num_points_interp",
            "is_myopic",
            "num_agents_sim",
            "num_draws_prob",
            "tau",
            "seed_sim",
            "num_agents_est",
            "optimizer_options",
            "file_sim",
            "num_types",
            "num_paras",
        )

        min_idx = edu_spec["max"] + 1
        shocks_cholesky = optim_paras["shocks_cholesky"]
        coeffs_common = optim_paras["coeffs_common"]
        coeffs_home = optim_paras["coeffs_home"]
        coeffs_edu = optim_paras["coeffs_edu"]
        coeffs_a = optim_paras["coeffs_a"]
        coeffs_b = optim_paras["coeffs_b"]
        delta = optim_paras["delta"]

        type_spec_shares = optim_paras["type_shares"]
        type_spec_shifts = optim_paras["type_shifts"]

        # Write out random components and interpolation grid to align the three
        # implementations.
        max_draws = max(num_agents_sim, num_draws_emax, num_draws_prob)
        write_types(type_spec_shares, num_agents_sim)
        write_edu_start(edu_spec, num_agents_sim)
        write_draws(num_periods, max_draws)
        write_lagged_start(num_agents_sim)

        # It is critical that the model is simulated after all files have been written
        # to the disk because they are picked up in the subroutines.
        respy_obj = simulate_observed(respy_obj)

        periods_draws_emax = read_draws(num_periods, num_draws_emax)
        periods_draws_prob = read_draws(num_periods, num_draws_prob)
        periods_draws_sims = read_draws(num_periods, num_agents_sim)

        fort, _ = resfort_interface(respy_obj, "simulate")

        state_space = pyth_solve(
            is_interpolated,
            num_points_interp,
            num_periods,
            is_debug,
            periods_draws_emax,
            edu_spec,
            optim_paras,
            file_sim,
            num_types,
        )

        (
            states_all,
            mapping_state_idx,
            periods_rewards_systematic,
            periods_emax,
        ) = state_space._get_fortran_counterparts()

        py = (
            periods_rewards_systematic,
            state_space.states_per_period,
            mapping_state_idx,
            periods_emax,
            states_all,
        )

        f2py = fort_debug.wrapper_solve(
            is_interpolated,
            num_points_interp,
            num_draws_emax,
            num_periods,
            is_myopic,
            is_debug,
            periods_draws_emax,
            min_idx,
            edu_spec["start"],
            edu_spec["max"],
            coeffs_common,
            coeffs_a,
            coeffs_b,
            coeffs_edu,
            coeffs_home,
            shocks_cholesky,
            delta,
            file_sim,
            max_states_period,
            num_types,
            type_spec_shares,
            type_spec_shifts,
        )

        assert_allclose(py[0], fort[0])
        assert_allclose(py[1], fort[1])
        assert_allclose(py[2], fort[2])
        assert_allclose(py[3], fort[3])
        assert_allclose(py[4], fort[4])

        assert_allclose(py[0], f2py[0])
        assert_allclose(py[1], f2py[1])
        assert_allclose(py[2], f2py[2])
        assert_allclose(py[3], f2py[3])
        assert_allclose(py[4], f2py[4])

        (
            states_all,
            mapping_state_idx,
            periods_rewards_systematic,
            periods_emax,
        ) = state_space._get_fortran_counterparts()

        simulated_data = pyth_simulate(
            state_space,
            num_agents_sim,
            periods_draws_sims,
            seed_sim,
            file_sim,
            edu_spec,
            optim_paras,
            is_debug,
        )
        py = simulated_data.copy().fillna(MISSING_FLOAT).values

        data_array = process_dataset(respy_obj).to_numpy()

        # Is is very important to cut the data array down to the size of the estimation
        # sample for the calculation of contributions.
        data_array = py[:num_agents_est * num_periods, :]

        f2py = fort_debug.wrapper_simulate(
            periods_rewards_systematic,
            mapping_state_idx,
            periods_emax,
            states_all,
            num_periods,
            num_agents_sim,
            periods_draws_sims,
            seed_sim,
            file_sim,
            edu_spec["start"],
            edu_spec["max"],
            edu_spec["share"],
            edu_spec["lagged"],
            optim_paras["coeffs_common"],
            optim_paras["coeffs_a"],
            optim_paras["coeffs_b"],
            shocks_cholesky,
            delta,
            num_types,
            type_spec_shares,
            type_spec_shifts,
            is_debug,
        )
        assert_allclose(py, f2py)

        # We have to cut the simulated data to `num_agents_est` as the Python
        # implementation calculates the likelihood contributions for all agents in the
        # data.
        simulated_data = simulated_data.loc[simulated_data.Identifier.lt(
            num_agents_est)]

        py = pyth_contributions(state_space, simulated_data,
                                periods_draws_prob, tau, optim_paras)

        num_obs_agent = np.bincount(simulated_data.Identifier.to_numpy())

        f2py = fort_debug.wrapper_contributions(
            periods_rewards_systematic,
            mapping_state_idx,
            periods_emax,
            states_all,
            data_array,
            periods_draws_prob,
            tau,
            num_periods,
            num_draws_prob,
            num_agents_est,
            num_obs_agent,
            num_types,
            edu_spec["start"],
            edu_spec["max"],
            shocks_cholesky,
            delta,
            type_spec_shares,
            type_spec_shifts,
        )

        assert_allclose(py, f2py)

        # Evaluation of criterion function
        x0 = get_optim_paras(optim_paras, num_paras, "all", is_debug)

        py = pyth_criterion(
            x0,
            is_interpolated,
            num_points_interp,
            is_debug,
            simulated_data,
            tau,
            periods_draws_emax,
            periods_draws_prob,
            state_space,
        )

        f2py = fort_debug.wrapper_criterion(
            x0,
            is_interpolated,
            num_draws_emax,
            num_periods,
            num_points_interp,
            is_myopic,
            is_debug,
            data_array,
            num_draws_prob,
            tau,
            periods_draws_emax,
            periods_draws_prob,
            states_all,
            state_space.states_per_period,
            mapping_state_idx,
            max_states_period,
            num_agents_est,
            num_obs_agent,
            num_types,
            edu_spec["start"],
            edu_spec["max"],
            edu_spec["share"],
            type_spec_shares,
            type_spec_shifts,
            num_paras,
        )

        assert_allclose(py, f2py)