"""Implements the probability generating function and the CDF of across-nested logit model. This module is not used by Biogemeitself. It is essentially used for external use. It has been mainlyimplemented to verify the analytical derivatives of these functions.:author: Michel Bierlaire:date: Fri Apr 22 09:39:49 2022"""importnumpyasnpfrombiogeme.calculatorimportCallableExpressionfrombiogeme.deprecatedimportdeprecatedfrombiogeme.exceptionsimportBiogemeErrorfrombiogeme.expressionsimportget_dict_valuesfrombiogeme.function_outputimportFunctionOutputfrombiogeme.nestsimportNestsForCrossNestedLogit
[docs]defcnl_g(alternatives:list[int],nests:NestsForCrossNestedLogit)->CallableExpression:"""Probability generating function and its derivatives :param alternatives: a list of alternatives in a given order. In principle, the alternative ids should be integers (to be consistent with Biogeme), but it may be actually be any object for this specific function. :param nests: the object describing the nests. :return: function that calculates the G function, and its first and second derivatives. :rtype: f, g, H = fct(np.array(float)) """order={alt:indexforindex,altinenumerate(alternatives)}nbr_of_alternatives=len(alternatives)defg_and_deriv(y:np.ndarray,gradient:bool,hessian:bool,bhhh:bool)->FunctionOutput:"""Probability generating function :param y: vector of positive values :param gradient: True if the gradient is calculated. :param hessian: True if the hessian is calculated. :param bhhh: True if the BHHH matrix is calculated. :return: value of the CDF and its derivatives """ifbhhh:raiseBiogemeError('No BHHH matrix can be calculated for this function.')g=0.0g_i=np.zeros(nbr_of_alternatives)ifgradientelseNoneg_ij=np.zeros((nbr_of_alternatives,nbr_of_alternatives))ifhessianelseNoneforminnests:mu_m=m.nest_paramalphas=get_dict_values(m.dict_of_alpha)nest_specific_sum=0.0foralpha_alt,alpha_valueinalphas.items():ifalpha_value!=0andy[order[alpha_alt]]!=0:nest_specific_sum+=(alpha_value*y[order[alpha_alt]])**mu_mp1=(1.0/mu_m)-1.0p2=(1.0/mu_m)-2.0g+=nest_specific_sum**(1.0/mu_m)ifgradient:foriinrange(nbr_of_alternatives):alpha_i=alphas.get(alternatives[i],0)ifalpha_i!=0andy[i]!=0:g_i[i]+=(alpha_i**mu_m*y[i]**(mu_m-1)*nest_specific_sum**p1)ifhessian:g_ij[i][i]+=(1-mu_m)*nest_specific_sum**p2*alpha_i**(2*mu_m)*y[i]**(2*mu_m-2.0)+(mu_m-1)*nest_specific_sum**p1*alpha_i**mu_m*y[i]**(mu_m-2)forjinrange(i+1,nbr_of_alternatives):alpha_j=alphas.get(alternatives[j],0)ifalpha_j!=0andy[j]!=0:g_ij[i][j]+=((1-mu_m)*nest_specific_sum**p2*(alpha_i*alpha_j)**mu_m*(y[i]*y[j])**(mu_m-1.0))ifhessian:foriinrange(nbr_of_alternatives):forjinrange(i+1,nbr_of_alternatives):g_ij[j][i]=g_ij[i][j]returnFunctionOutput(function=g,gradient=g_i,hessian=g_ij,bhhh=None)returng_and_deriv
[docs]defcnl_cdf(alternatives:list[int],nests:NestsForCrossNestedLogit)->CallableExpression:"""Cumulative distribution function and its derivatives :param alternatives: a list of alternatives in a given order. In principle, the alternative ids should be integers (to be consistent with Biogeme), but it may be actually be any object for this specific function. :param nests: a tuple containing as many items as nests. :return: function that calculates the CDF, and its first and second derivatives. """nbr_of_alternatives=len(alternatives)g_fct=cnl_g(alternatives,nests)deff_and_deriv(xi:np.ndarray,gradient:bool,hessian:bool,bhhh:bool)->FunctionOutput:"""Cumulative distribution function :param xi: vector of arguments :param gradient: if True, the gradient is calculated. :param hessian: if True, the hessian is calculated. :param bhhh: if True, the BHHH matrix is calculated. :return: value of the CDF and its derivatives """y=np.where(xi==np.inf,0,np.exp(-xi))g_output:FunctionOutput=g_fct(y,gradient,hessian,bhhh)g=g_output.functiong_i=g_output.gradientg_ii=g_output.hessianf=np.exp(-g)f_i=g_i*y*fifgradientelseNonef_ij=np.zeros((nbr_of_alternatives,nbr_of_alternatives))ifhessianelseNoneifhessian:foriinrange(nbr_of_alternatives):f_ij[i][i]=(f*y[i]*y[i]*(g_i[i]*g_i[i]-g_ii[i][i])-f*g_i[i]*y[i])forjinrange(i+1,nbr_of_alternatives):f_ij[i][j]=f*y[i]*y[j]*(g_i[i]*g_i[j]-g_ii[i][j])foriinrange(nbr_of_alternatives):forjinrange(i+1,nbr_of_alternatives):f_ij[j][i]=f_ij[i][j]returnFunctionOutput(function=f,gradient=f_i,hessian=f_ij,bhhh=None)returnf_and_deriv