Note
Go to the end to download the full example code.
Choice model specification used in the latent-variable tutorial.
This module defines the systematic utility functions of the mode-choice model. It illustrates how a choice model is specified independently of the latent-variable model and how latent variables can subsequently be injected into the utility functions.
The specification considers three transportation alternatives:
public transport,
car,
slow modes.
The utilities are generated by the function
generate_utility_functions and returned as a dictionary indexed by
alternative identifiers. The resulting dictionary can be passed directly
to a Biogeme logit model.
The example is intentionally structured in two layers:
A baseline specification based exclusively on observed attributes (travel time, travel cost, distance, and trip purpose).
An extended specification in which latent variables may enter the utility functions through interaction terms.
The latent variables themselves are not defined in this module. They are
constructed elsewhere from structural and measurement equations and are
provided here as Biogeme expressions through the argument
latent_expressions. This separation reflects the conceptual structure
of hybrid choice models, where latent variables are first generated and
then incorporated into the choice model.
In the current example, the latent variable
car_centric_attitude may influence the utility of the car
alternative. If no latent expressions are provided, the function reduces
to the baseline mode-choice specification.
The cost coefficient is normalized to -1 and a positive scale parameter is estimated. This normalization defines the unit of utility while allowing the overall scale of the choice model to be estimated.
Michel Bierlaire Fri Jun 12 2026, 15:00:22
from optima import (
CostCarCHF,
MarginalCostPT,
PurpHWH,
TimeCar_hour,
TimePT_hour,
distance_km,
)
from biogeme.expressions import (
Beta,
Expression,
)
def generate_utility_functions(
latent_expressions: dict[str, Expression] | None = None,
) -> dict[int, Expression]:
"""Generate the systematic utility functions of the choice model.
The function creates the utility expressions associated with the three
transportation alternatives considered in the tutorial:
- alternative 0: public transport,
- alternative 1: car,
- alternative 2: slow modes.
The utilities are expressed using Biogeme expressions and include the
estimated parameters declared in this function.
Baseline specification
----------------------
Public transport:
.. math::
V_{\mathrm{PT}}
= \beta_{\mathrm{asc,PT}}
+ \beta_{\mathrm{time,PT}}\,\mathrm{TimePT}
+ \beta_{\mathrm{cost}}\,\mathrm{MarginalCostPT}
Car:
.. math::
V_{\mathrm{Car}}
= \beta_{\mathrm{asc,Car}}
+ \beta_{\mathrm{time,Car}}\,\mathrm{TimeCar}
+ \beta_{\mathrm{cost}}\,\mathrm{CostCar}
Slow modes:
.. math::
V_{\mathrm{Slow}}
= \beta_{\mathrm{dist}}\,\mathrm{Distance}
where the distance coefficient depends on trip purpose.
Latent-variable extension
-------------------------
When ``latent_expressions`` is provided, the latent variable
``car_centric_attitude`` enters the utility of the car alternative
through an additional coefficient:
.. math::
V_{\mathrm{Car}}
\leftarrow
V_{\mathrm{Car}}
+ \beta_{\mathrm{car\_centric}}
x^*_{\mathrm{car\_centric}}
This illustrates the standard mechanism used in hybrid choice models:
latent variables are treated as additional explanatory variables in the
utility specification.
Utility scaling
---------------
After the utilities are constructed, they are multiplied by a positive
scale parameter. Because the cost coefficient is fixed to -1, the scale
parameter can be estimated and captures the overall sensitivity of
choice probabilities to utility differences.
:param latent_expressions: Optional dictionary mapping latent-variable
names to Biogeme expressions. If ``None``, the baseline choice model
is generated. The current specification expects the key
``'car_centric_attitude'`` when latent variables are used.
:return: Dictionary mapping alternative identifiers to systematic
utility expressions.
"""
work_trip = PurpHWH == 1
other_trip_purposes = PurpHWH != 1
# Choice model: parameters
choice_beta_cost = Beta('choice_beta_cost', -1, None, 0, 1)
choice_asc_car = Beta('choice_asc_car', 0.0, None, None, 0)
choice_asc_pt = Beta('choice_asc_pt', 0, None, None, 0)
choice_beta_dist_work = Beta('choice_beta_dist_work', 0, None, 0, 0)
choice_beta_dist_other_purposes = Beta(
'choice_beta_dist_other_purposes', 0, None, 0, 0
)
choice_beta_dist = (
choice_beta_dist_work * work_trip
+ choice_beta_dist_other_purposes * other_trip_purposes
)
scale_parameter = Beta('choice_scale_parameter', 1, 0.0001, None, 0)
# Time coefficients with optional LV interactions
choice_beta_time_car = Beta('choice_beta_time_car', 0, None, 0, 0)
choice_beta_time_pt = Beta('choice_beta_time_pt', 0, None, 0, 0)
choice_beta_car_centric_attitude_car = Beta(
'choice_beta_car_centric_attitude_car', 0, None, None, 0
)
v_public_transport_det = (
choice_asc_pt
+ choice_beta_time_pt * TimePT_hour
+ choice_beta_cost * MarginalCostPT
)
v_public_transport = v_public_transport_det
v_car_det = (
choice_asc_car
+ choice_beta_time_car * TimeCar_hour
+ choice_beta_cost * CostCarCHF
)
v_car = (
v_car_det
if latent_expressions is None
else v_car_det
+ choice_beta_car_centric_attitude_car
* latent_expressions['car_centric_attitude']
)
v_slow_modes_det = choice_beta_dist * distance_km
v_slow_modes = v_slow_modes_det
v = {
0: scale_parameter * v_public_transport,
1: scale_parameter * v_car,
2: scale_parameter * v_slow_modes,
}
return v