Beispiel #1
0
def frechnet_dev(ev, trans_mat, obs_costs, disc_fac):
    """
    Calculating the Frechnet derivative of the contraction mapping.

    Parameters
    ----------
    ev : numpy.array
        see :ref:`ev`
    trans_mat : numpy.array
        see :ref:`trans_mat`
    obs_costs : numpy.array
        see :ref:`costs`
    disc_fac : numpy.float
        see :ref:`disc_fac`

    Returns
    -------
    t_prime : numpy.array
        A num_states x num_states matrix containing the frechnet derivative of the
        contraction mapping. For details see Rust (2000).

    """
    choice_probs = choice_prob_gumbel(ev, obs_costs, disc_fac)
    t_prime_pre = trans_mat[:, 1:] * choice_probs[1:, 0]
    t_prime = disc_fac * np.column_stack(
        (1 - np.sum(t_prime_pre, axis=1), t_prime_pre))
    return t_prime
Beispiel #2
0
def loglike_cost_params_individual(
    params,
    maint_func,
    maint_func_dev,
    num_states,
    trans_mat,
    state_mat,
    decision_mat,
    disc_fac,
    scale,
    alg_details,
):
    """
    This is the individual logliklihood function for the estimation of the cost parameters
    needed for the BHHH optimizer.


    Parameters
    ----------
    params : pandas.DataFrame
        see :ref:`params`
    maint_func: func
        see :ref:`maint_func`
    num_states : int
        The size of the state space.
    disc_fac : numpy.float
        see :ref:`disc_fac`
    trans_mat : numpy.array
        see :ref:`trans_mat`
    state_mat : numpy.array
        see :ref:`state_mat`
    decision_mat : numpy.array
        see :ref:`decision_mat`

    Returns
    -------
    log_like : numpy.array
        A num_buses times num_periods dimensional array containing the negative
        log-likelihood contributions of the individuals.


    """
    params = params["value"].to_numpy()
    costs = calc_obs_costs(num_states, maint_func, params, scale)

    ev, contr_step_count, newt_kant_step_count = get_ev(
        params, trans_mat, costs, disc_fac, alg_details)
    config.total_contr_count += contr_step_count
    config.total_newt_kant_count += newt_kant_step_count

    p_choice = choice_prob_gumbel(ev, costs, disc_fac)
    log_like = like_hood_data_individual(np.log(p_choice), decision_mat,
                                         state_mat)
    return log_like
Beispiel #3
0
def mpec_loglike_cost_params_derivative(
    maint_func,
    maint_func_dev,
    num_states,
    num_params,
    disc_fac,
    scale,
    decision_mat,
    state_mat,
    mpec_params,
):
    """
    Computing the analytical gradient of the objective function for MPEC.

    Parameters
    ----------
    maint_func: func
        see :ref:`maint_func`
    maint_func_dev: func
        see :ref:`maint_func`
    num_states : int
        The size of the state space.
    num_params : int
        Length of cost parameter vector.
    disc_fac : numpy.float
        see :ref:`disc_fac`
    scale : numpy.float
        see :ref:`scale`
    decision_mat : numpy.array
        see :ref:`decision_mat`
    state_mat : numpy.array
        see :ref:`state_mat`
    mpec_params : numpy.array
        see :ref:`mpec_params`

    Returns
    -------
    gradient : numpy.array
        Vector that holds the derivative of the negative log likelihood function
        to the parameters.

    """
    # Calculate choice probabilities
    costs = calc_obs_costs(num_states, maint_func, mpec_params[num_states:],
                           scale)
    p_choice = choice_prob_gumbel(mpec_params[0:num_states], costs, disc_fac)

    # calculate the derivative based on the model
    derivative_both = mpec_loglike_cost_params_derivative_model(
        num_states, num_params, disc_fac, scale, maint_func_dev, p_choice)

    # Calculate actual gradient depending on the given data
    # get decision matrix into the needed shape
    decision_mat_temp = np.vstack((
        np.tile(decision_mat[0], (num_states + num_params, 1)),
        np.tile(decision_mat[1], (num_states + num_params, 1)),
    ))

    # calculate the gradient
    gradient_temp = -np.sum(
        decision_mat_temp * np.dot(derivative_both, state_mat), axis=1)
    # bring the calculated gradient into the correct shape
    gradient = np.reshape(gradient_temp, (num_states + num_params, 2),
                          order="F").sum(axis=1)

    return gradient
Beispiel #4
0
def mpec_loglike_cost_params(
    maint_func,
    maint_func_dev,
    num_states,
    num_params,
    state_mat,
    decision_mat,
    disc_fac,
    scale,
    gradient,
    mpec_params,
    grad,
):
    """
    Calculate the negative partial log likelihood for MPEC depending on cost parameters
    as well as the discretized expected values.

    Parameters
    ----------
    maint_func: func
        see :ref:`maint_func`
    maint_func_dev: func
        see :ref:`maint_func`
    num_states : int
        The size of the state space.
    num_params : int
        Length of cost parameter vector.
    state_mat : numpy.array
        see :ref:`state_mat`
    decision_mat : numpy.array
        see :ref:`decision_mat`
    disc_fac : numpy.float
        see :ref:`disc_fac`
    scale : numpy.float
        see :ref:`scale`
    gradient : str
        Indicates whether analytical or numerical gradient should be used.
    mpec_params : numpy.array
        see :ref:`mpec_params`
    grad : numpy.array, optional
        The gradient of the function. The default is np.array([]).

    Returns
    -------
    log_like: float
        Contains the negative partial log likelihood for the given parameters.

    """
    if grad.size > 0:
        if gradient == "No":
            # numerical gradient
            partial_loglike_mpec = partial(
                mpec_loglike_cost_params,
                maint_func,
                maint_func_dev,
                num_states,
                num_params,
                state_mat,
                decision_mat,
                disc_fac,
                scale,
                gradient,
                grad=np.array([]),
            )
            grad[:] = approx_derivative(partial_loglike_mpec,
                                        mpec_params,
                                        method="2-point")
        else:
            # analytical gradient
            grad[:] = mpec_loglike_cost_params_derivative(
                maint_func,
                maint_func_dev,
                num_states,
                num_params,
                disc_fac,
                scale,
                decision_mat,
                state_mat,
                mpec_params,
            )

    costs = calc_obs_costs(num_states, maint_func, mpec_params[num_states:],
                           scale)
    p_choice = choice_prob_gumbel(mpec_params[0:num_states], costs, disc_fac)
    log_like = like_hood_data(np.log(p_choice), decision_mat, state_mat)
    return float(log_like)
def test_choice_probs(inputs, outputs):
    assert_array_almost_equal(
        choice_prob_gumbel(outputs["fixp"], outputs["costs"],
                           inputs["disc_fac"]),
        outputs["choice_probs"],
    )
def get_demand(init_dict, demand_dict, demand_params):
    """
    Calculates the implied demand for a range of replacement costs
    for a certain number of buses over a certain time period.

    Parameters
    ----------
    init_dict : dict
        see :ref:`init_dict`.
    demand_dict : dict
        see :ref:`demand_dict`.
    demand_params : np.array
        see :ref:`demand_params`

    Returns
    -------
    demand_results : pd.DataFrame
        see :ref:`demand_results`

    """
    params = demand_params.copy()
    (
        disc_fac,
        num_states,
        maint_func,
        maint_func_dev,
        num_params,
        scale,
    ) = select_model_parameters(init_dict)

    # Initialize the loop over the replacement costs
    rc_range = np.linspace(
        demand_dict["RC_lower_bound"],
        demand_dict["RC_upper_bound"],
        demand_dict["demand_evaluations"],
    )
    demand_results = pd.DataFrame(index=rc_range, columns=["demand", "success"])
    demand_results.index.name = "RC"

    for rc in rc_range:
        params[-num_params] = rc
        demand_results.loc[(rc), "success"] = "No"

        # solve the model for the given paramaters
        trans_mat = create_transition_matrix(num_states, params[:-num_params])

        obs_costs = calc_obs_costs(num_states, maint_func, params[-num_params:], scale)
        ev = calc_fixp(trans_mat, obs_costs, disc_fac)[0]
        p_choice = choice_prob_gumbel(ev, obs_costs, disc_fac)

        # calculate initial guess for pi and run contraction iterations
        pi_new = np.full((num_states, 2), 1 / (2 * num_states))
        tol = 1
        iteration = 1
        while tol >= demand_dict["tolerance"]:
            pi = pi_new
            pi_new = p_choice * (
                np.dot(trans_mat.T, pi[:, 0])
                + np.dot(np.tile(trans_mat[0, :], (num_states, 1)).T, pi[:, 1])
            ).reshape((num_states, 1))
            tol = np.max(np.abs(pi_new - pi))
            iteration = +1
            if iteration > 200:
                break
            if tol < demand_dict["tolerance"]:
                demand_results.loc[(rc), "success"] = "Yes"

        demand_results.loc[(rc), "demand"] = (
            demand_dict["num_buses"] * demand_dict["num_periods"] * pi_new[:, 1].sum()
        )

    return demand_results
Beispiel #7
0
def derivative_loglike_cost_params_individual(
    params,
    maint_func,
    maint_func_dev,
    num_states,
    trans_mat,
    state_mat,
    decision_mat,
    disc_fac,
    scale,
    alg_details,
):
    """
    This is the Jacobian of the individual log likelihood function of the cost
    parameter estimation with respect to all cost parameters needed for the BHHH.


    Parameters
    ----------
    params : pandas.DataFrame
        see :ref:`params`
    maint_func: func
        see :ref:`maint_func`
    num_states : int
        The size of the state space.
    disc_fac : numpy.float
        see :ref:`disc_fac`
    trans_mat : numpy.array
        see :ref:`trans_mat`
    state_mat : numpy.array
        see :ref:`state_mat`
    decision_mat : numpy.array
        see :ref:`decision_mat`

    Returns
    -------
    dev : numpy.array
        A num_buses + num_periods x dim(params) matrix in form of numpy array
        containing the derivative of the individual log-likelihood function for
        every cost parameter.


    """
    params = params["value"].to_numpy()
    dev = np.zeros((decision_mat.shape[1], len(params)))
    obs_costs = calc_obs_costs(num_states, maint_func, params, scale)

    ev = get_ev(params, trans_mat, obs_costs, disc_fac, alg_details)[0]

    p_choice = choice_prob_gumbel(ev, obs_costs, disc_fac)
    maint_cost_dev = maint_func_dev(num_states, scale)

    lh_values_rc = like_hood_vaules_rc(ev, obs_costs, p_choice, trans_mat,
                                       disc_fac)
    like_dev_rc = like_hood_data_individual(lh_values_rc, decision_mat,
                                            state_mat)
    dev[:, 0] = like_dev_rc

    for i in range(len(params) - 1):
        if len(params) == 2:
            cost_dev_param = maint_cost_dev
        else:
            cost_dev_param = maint_cost_dev[:, i]

        log_like_values_params = log_like_values_param(ev, obs_costs, p_choice,
                                                       trans_mat,
                                                       cost_dev_param,
                                                       disc_fac)
        dev[:, i + 1] = like_hood_data_individual(log_like_values_params,
                                                  decision_mat, state_mat)

    return dev
Beispiel #8
0
def loglike_cost_params_individual(
    params,
    maint_func,
    maint_func_dev,
    num_states,
    trans_mat,
    state_mat,
    decision_mat,
    disc_fac,
    scale,
    alg_details,
):
    """
    This is the individual logliklihood function for the estimation of the cost parameters
    needed for the BHHH optimizer.


    Parameters
    ----------
    params : pandas.DataFrame
        see :ref:`params`
    maint_func: func
        see :ref:`maint_func`
    num_states : int
        The size of the state space.
    disc_fac : numpy.float
        see :ref:`disc_fac`
    trans_mat : numpy.array
        see :ref:`trans_mat`
    state_mat : numpy.array
        see :ref:`state_mat`
    decision_mat : numpy.array
        see :ref:`decision_mat`

    Returns
    -------
    log_like : numpy.array
        A num_buses times num_periods dimensional array containing the negative
        log-likelihood contributions of the individuals.


    """

    if "omega" in params.index:
        omega = params.loc["omega", "value"]
        cost_params = params.drop("omega")["value"].to_numpy()
        p_ml = trans_mat[0, 0:3]
        sample_size = 4292 / 78
        rho = chi2.ppf(omega, len(p_ml) - 1) / (2 * (sample_size))

        costs = calc_obs_costs(num_states, maint_func, cost_params, scale)
        v_start, _, _, _ = value_function_contraction(trans_mat,
                                                      costs,
                                                      disc_fac,
                                                      threshold=1e-12,
                                                      max_it=1000000)
        v_worst, worst_trans_mat, success, converge_crit_ev, num_eval = worst_value_fixp(
            v_start,
            trans_mat,
            costs,
            disc_fac,
            rho,
            threshold=1e-3,
            max_it=1000000)
        ev = np.dot(worst_trans_mat, v_worst)
        if not success:
            raise ValueError("Not converging.")
    else:
        cost_params = params["value"].to_numpy()
        costs = calc_obs_costs(num_states, maint_func, cost_params, scale)
        ev, contr_step_count, newt_kant_step_count = get_ev(
            cost_params, trans_mat, costs, disc_fac, alg_details)
        config.total_contr_count += contr_step_count
        config.total_newt_kant_count += newt_kant_step_count

    p_choice = choice_prob_gumbel(ev, costs, disc_fac)
    log_like = like_hood_data_individual(np.log(p_choice), decision_mat,
                                         state_mat)
    return log_like