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
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
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
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
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
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