"""Simplified nested logit model using the dedicated LogNested expression."""
from __future__ import annotations
import logging
import biogeme.exceptions as excep
from biogeme.deprecated import deprecated
from biogeme.expressions import (
ConditionalSum,
ConditionalTermTuple,
Expression,
ExpressionOrNumeric,
LogNested,
MultipleSum,
Numeric,
exp,
log,
)
from biogeme.nests import NestsForNestedLogit, OldNestsForNestedLogit
logger = logging.getLogger(__name__)
def _normalize_and_check_nests(
util: dict[int, ExpressionOrNumeric],
nests: NestsForNestedLogit | OldNestsForNestedLogit,
) -> NestsForNestedLogit:
"""Convert old nest syntax if needed and check partition validity."""
if not isinstance(nests, NestsForNestedLogit):
logger.warning(
'It is recommended to define the nests of the nested logit model using '
'the objects OneNestForNestedLogit and NestsForNestedLogit defined '
'in biogeme.nests.'
)
nests = NestsForNestedLogit(choice_set=list(util), tuple_of_nests=nests)
ok, message = nests.check_partition()
if not ok:
raise excep.BiogemeError(message)
return nests
[docs]
def get_mev_generating_for_nested(
util: dict[int, ExpressionOrNumeric],
availability: dict[int, ExpressionOrNumeric] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
) -> Expression:
"""Implements the MEV generating function for the nested logit model.
Kept for backward compatibility.
"""
nests = _normalize_and_check_nests(util, nests)
terms_for_nests = []
for nest in nests:
if availability is None:
sum_terms = [
exp(nest.nest_param * util[i]) for i in nest.list_of_alternatives
]
nest_sum = MultipleSum(sum_terms)
else:
sum_terms = [
ConditionalTermTuple(
condition=availability[i] != Numeric(0),
term=exp(nest.nest_param * util[i]),
)
for i in nest.list_of_alternatives
]
nest_sum = ConditionalSum(list_of_terms=sum_terms)
terms_for_nests.append(nest_sum ** (1.0 / nest.nest_param))
if nests.alone is not None:
for i in nests.alone:
terms_for_nests.append(util[i])
return MultipleSum(terms_for_nests)
[docs]
@deprecated(get_mev_generating_for_nested)
def getMevGeneratingForNested(
util: dict[int, Expression],
availability: dict[int, Expression],
nests: NestsForNestedLogit | OldNestsForNestedLogit,
) -> Expression:
"""Deprecated name for get_mev_generating_for_nested."""
return get_mev_generating_for_nested(util, availability, nests)
[docs]
def get_mev_for_nested(
util: dict[int, ExpressionOrNumeric],
availability: dict[int, ExpressionOrNumeric] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
) -> dict[int, Expression]:
"""Implements the derivatives of the MEV generating function.
Kept for backward compatibility.
"""
nests = _normalize_and_check_nests(util, nests)
if nests.alone is None:
log_gi = {}
else:
log_gi = {i: Numeric(0) for i in nests.alone}
for nest in nests:
if availability is None:
nest_sum = MultipleSum(
[exp(nest.nest_param * util[i]) for i in nest.list_of_alternatives]
)
else:
nest_sum = ConditionalSum(
list_of_terms=[
ConditionalTermTuple(
condition=availability[i] != Numeric(0),
term=exp(nest.nest_param * util[i]),
)
for i in nest.list_of_alternatives
]
)
for i in nest.list_of_alternatives:
log_gi[i] = (nest.nest_param - 1.0) * util[i] + (
1.0 / nest.nest_param - 1.0
) * log(nest_sum)
return log_gi
[docs]
@deprecated(get_mev_for_nested)
def getMevForNested(
V: dict[int, Expression],
availability: dict[int, Expression] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
) -> dict[int, Expression]:
"""Deprecated name for get_mev_for_nested."""
return get_mev_for_nested(V, availability, nests)
[docs]
def get_mev_for_nested_mu(
util: dict[int, ExpressionOrNumeric],
availability: dict[int, ExpressionOrNumeric] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
mu: ExpressionOrNumeric,
) -> dict[int, Expression]:
"""Implements the MEV derivative terms for explicit-mu nested logit.
Kept for backward compatibility.
"""
nests = _normalize_and_check_nests(util, nests)
if nests.alone is None:
log_gi = {}
else:
log_gi = {i: log(mu) + (mu - 1) * util[i] for i in nests.alone}
for nest in nests:
if availability is None:
nest_sum = MultipleSum(
[exp(nest.nest_param * util[i]) for i in nest.list_of_alternatives]
)
else:
nest_sum = ConditionalSum(
list_of_terms=[
ConditionalTermTuple(
condition=availability[i] != Numeric(0),
term=exp(nest.nest_param * util[i]),
)
for i in nest.list_of_alternatives
]
)
for i in nest.list_of_alternatives:
log_gi[i] = (
log(mu)
+ (nest.nest_param - 1.0) * util[i]
+ (mu / nest.nest_param - 1.0) * log(nest_sum)
)
return log_gi
[docs]
@deprecated(get_mev_for_nested_mu)
def getMevForNestedMu(
util: dict[int, Expression],
availability: dict[int, Expression] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
mu: Expression,
) -> dict[int, Expression]:
"""Deprecated name for get_mev_for_nested_mu."""
return get_mev_for_nested_mu(util, availability, nests, mu)
[docs]
def nested(
util: dict[int, ExpressionOrNumeric],
availability: dict[int, ExpressionOrNumeric] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
choice: ExpressionOrNumeric,
) -> Expression:
"""Choice probability for the nested logit model."""
return exp(lognested(util, availability, nests, choice))
[docs]
def lognested(
util: dict[int, ExpressionOrNumeric],
availability: dict[int, ExpressionOrNumeric] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
choice: ExpressionOrNumeric,
) -> Expression:
"""Log probability for the nested logit model."""
return LogNested(
util=util,
av=availability,
nests=nests,
choice=choice,
)
[docs]
def nested_mev_mu(
util: dict[int, ExpressionOrNumeric],
availability: dict[int, ExpressionOrNumeric] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
choice: ExpressionOrNumeric,
mu: ExpressionOrNumeric,
) -> Expression:
"""Choice probability for the nested logit model with explicit mu."""
return exp(lognested_mev_mu(util, availability, nests, choice, mu))
[docs]
@deprecated(nested_mev_mu)
def nestedMevMu(
util: dict[int, Expression],
availability: dict[int, Expression] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
choice: Expression,
mu: Expression,
) -> Expression:
"""Deprecated name for nested_mev_mu."""
return nested_mev_mu(util, availability, nests, choice, mu)
[docs]
def lognested_mev_mu(
util: dict[int, ExpressionOrNumeric],
availability: dict[int, ExpressionOrNumeric] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
choice: ExpressionOrNumeric,
mu: ExpressionOrNumeric,
) -> Expression:
"""Log probability for the nested logit model with explicit mu."""
return LogNested(
util=util,
av=availability,
nests=nests,
choice=choice,
mu=mu,
)
[docs]
@deprecated(lognested_mev_mu)
def lognestedMevMu(
util: dict[int, Expression],
availability: dict[int, Expression] | None,
nests: NestsForNestedLogit | OldNestsForNestedLogit,
choice: Expression,
mu: Expression,
) -> Expression:
"""Deprecated name for lognested_mev_mu."""
return lognested_mev_mu(util, availability, nests, choice, mu)