"""Implementation of the "generalized" MDCEV model. See section 3 in
the technical report.
:author: Michel Bierlaire
:date: Sun Nov 5 15:43:18 2023
"""
from typing import Optional
from biogeme.expressions import Expression, log
from .mdcev import mdcev, info_gamma_parameters, SpecificModel
[docs]
def generalized(
baseline_utilities: dict[int, Expression],
consumed_quantities: dict[int, Expression],
alpha_parameters: dict[int, Expression],
gamma_parameters: dict[int, Optional[Expression]],
prices: Optional[dict[int, Expression]] = None,
):
"""Calculates the determinant entries for the Bhat 2008 specification Eq (18)
:param baseline_utilities: see the module documentation :mod:`biogeme.mdcev`
:param consumed_quantities: see the module documentation :mod:`biogeme.mdcev`
:param alpha_parameters: see the module documentation :mod:`biogeme.mdcev`
:param gamma_parameters: see the module documentation :mod:`biogeme.mdcev`
:param prices: see the module documentation :mod:`biogeme.mdcev`.
If None, assumed to be 1.
"""
info_gamma_parameters(gamma_parameters)
def calculate_utility(
the_id: int, consumption: Expression, price: Optional[Expression]
) -> Expression:
"""Calculate the utility. The formula is different is it is an
outside good, characterized by the absence of a gamma
parameter.
:param the_id: identifier of the good.
:param consumption: expression for the consumption.
:param price: expression for the price, or None if prices are not considered.
"""
gamma = gamma_parameters[the_id]
if gamma is None:
if price is None:
# gamma is None. price is None
return baseline_utilities[the_id] + (
alpha_parameters[the_id] - 1
) * log(consumption)
# gamma is None. price is not None
return (
baseline_utilities[the_id]
+ (alpha_parameters[the_id] - 1) * log(consumption)
- alpha_parameters[the_id] * log(price)
)
# gamma is not None. price is None
if price is None:
return baseline_utilities[the_id] + (alpha_parameters[the_id] - 1) * log(
1.0 + consumption / gamma
)
# gamma is not None. price is not None
return (
baseline_utilities[the_id]
- log(price)
+ (alpha_parameters[the_id] - 1) * log(1.0 + consumption / (price * gamma))
)
def calculate_log_determinant(
the_id: int, consumption: Expression, price: Optional[Expression]
) -> Expression:
"""Calculate the log of the entries for the determinant. For
the outside good, gamma is equal to 0.
:param the_id: identifier of the good.
:param consumption: expression for the consumption.
:param price: expression for the price, or None if prices are not considered.
"""
gamma = gamma_parameters[the_id]
if gamma is None:
return log(Numeric(1) - alpha_parameters[the_id]) - log(consumption)
if price is None:
return log(Numeric(1) - alpha_parameters[the_id]) - log(consumption + gamma)
return log(Numeric(1) - alpha_parameters[the_id]) - log(
consumption + price * gamma
)
def calculate_inverse_determinant(
the_id: int, consumption: Expression, price: Optional[Expression]
) -> Expression:
"""Calculate the inverse of the entries for the determinant. For
the outside good, gamma is equal to 0.
:param the_id: identifier of the good.
:param consumption: expression for the consumption.
:param price: expression for the price, or None if prices are not considered.
"""
gamma = gamma_parameters[the_id]
if gamma is None:
return consumption / (Numeric(1) - alpha_parameters[the_id])
if price is None:
return (consumption + gamma) / (Numeric(1) - alpha_parameters[the_id])
return (consumption + price * gamma) / (Numeric(1) - alpha_parameters[the_id])
if prices:
utilities = {
the_id: (calculate_utility(the_id, consumption, prices[the_id]))
for the_id, consumption in consumed_quantities.items()
}
log_determinant_entries = {
the_id: calculate_log_determinant(the_id, consumption, prices[the_id])
for the_id, consumption in consumed_quantities.items()
}
inverse_of_determinant_entries = {
the_id: calculate_inverse_determinant(the_id, consumption, prices[the_id])
for the_id, consumption in consumed_quantities.items()
}
return SpecificModel(
utilities=utilities,
log_determinant_entries=log_determinant_entries,
inverse_of_determinant_entries=inverse_of_determinant_entries,
)
# Use unit prices if prices is set to None
utilities = {
the_id: (calculate_utility(the_id, consumption, None))
for the_id, consumption in consumed_quantities.items()
}
log_determinant_entries = {
the_id: calculate_log_determinant(the_id, consumption, None)
for the_id, consumption in consumed_quantities.items()
}
inverse_of_determinant_entries = {
the_id: calculate_inverse_determinant(the_id, consumption, None)
for the_id, consumption in consumed_quantities.items()
}
return SpecificModel(
utilities=utilities,
log_determinant_entries=log_determinant_entries,
inverse_of_determinant_entries=inverse_of_determinant_entries,
)
[docs]
def mdcev_generalized(
number_of_chosen_alternatives: Expression,
consumed_quantities: dict[int, Expression],
baseline_utilities: dict[int, Expression],
alpha_parameters: dict[int, Expression],
gamma_parameters: dict[int, Optional[Expression]],
prices: Optional[dict[int, Expression]] = None,
scale_parameter: Optional[Expression] = None,
):
"""Generate the Biogeme formula for the log probability of the
MDCEV model using the generalized translated utility function
:param number_of_chosen_alternatives: see the module documentation
:mod:`biogeme.mdcev`
:param consumed_quantities: see the module documentation :mod:`biogeme.mdcev`
:param baseline_utilities: see the module documentation :mod:`biogeme.mdcev`
:param alpha_parameters: see the module documentation :mod:`biogeme.mdcev`
:param gamma_parameters: see the module documentation :mod:`biogeme.mdcev`
:param prices: see the module documentation :mod:`biogeme.mdcev`
:param scale_parameter: see the module documentation :mod:`biogeme.mdcev`
A detailed explanation is provided in the technical report
"Estimating the MDCEV model with Biogeme"
"""
specific_model = generalized(
baseline_utilities,
consumed_quantities,
alpha_parameters,
gamma_parameters,
prices,
)
return mdcev(
number_of_chosen_alternatives=number_of_chosen_alternatives,
consumed_quantities=consumed_quantities,
specific_model=specific_model,
scale_parameter=scale_parameter,
)