Note
Go to the end to download the full example code.
File generalized_forecasting.py
Michel Bierlaire, EPFL Fri Jul 25 2025, 17:05:32
Forecasting with a MDCEV model and the “generalized translated utility” specification.
Example: generalized translated utility
Forecasting observation 0 / 2 [10 draws]
============ Comparison ===================
Brute force: {1: '29.4', 2: '426', 3: '34.2', 4: '10.9'} objective 180, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '29.4', 2: '426', 3: '34', 4: '10.9'} objective 180, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '36.2', 2: '407', 3: '42.6', 4: '14.1'} objective 180, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '36.2', 2: '407', 3: '42.6', 4: '14.1'} objective 180, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '14.1', 2: '427', 3: '43.6', 4: '15.1'} objective 199, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '14.1', 2: '427', 3: '44', 4: '15'} objective 199, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '16.7', 2: '437', 3: '33', 4: '13.7'} objective 183, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '16.7', 2: '437', 3: '33', 4: '13.7'} objective 183, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '34.9', 2: '429', 3: '26.3', 4: '10.1'} objective 169, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '35', 2: '429', 3: '26.3', 4: '10.1'} objective 169, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '16.5', 2: '406', 3: '57.8', 4: '20.2'} objective 195, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '16.4', 2: '406', 3: '57.7', 4: '20.2'} objective 195, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '10.6', 2: '452', 3: '30.4', 4: '7.34'} objective 221, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '10.6', 2: '452', 3: '30.4', 4: '7.34'} objective 221, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '8.84', 2: '436', 3: '47.8', 4: '7.77'} objective 239, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '8.84', 2: '436', 3: '47.8', 4: '7.76'} objective 239, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '21.2', 2: '432', 3: '35', 4: '12.1'} objective 171, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '21.2', 2: '432', 3: '35', 4: '12.2'} objective 171, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '34.5', 2: '378', 3: '76.7', 4: '10.4'} objective 200, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '34.6', 2: '378', 3: '77.3', 4: '10.4'} objective 200, constraint 500, choice set {1, 2, 3, 4}
Forecasting observation 1 / 2 [10 draws]
============ Comparison ===================
Brute force: {1: '8.17', 2: '455', 3: '31.5', 4: '5.67'} objective 244, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '8.09', 2: '455', 3: '31.7', 4: '5.66'} objective 244, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '24.3', 2: '401', 3: '61.1', 4: '13.9'} objective 177, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '24.3', 2: '401', 3: '61.1', 4: '13.9'} objective 177, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '37.5', 2: '411', 3: '42.1', 4: '9.52'} objective 210, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '37.5', 2: '411', 3: '42.1', 4: '9.5'} objective 210, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '12.1', 2: '443', 3: '33.7', 4: '10.8'} objective 206, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '12.1', 2: '443', 3: '33.7', 4: '10.8'} objective 206, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '26.2', 2: '415', 3: '46.4', 4: '12.3'} objective 176, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '26.2', 2: '415', 3: '46.4', 4: '12.3'} objective 176, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '51.5', 2: '378', 3: '60.6', 4: '9.91'} objective 195, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '51.4', 2: '378', 3: '60.6', 4: '9.9'} objective 195, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '9.94', 2: '452', 3: '32.5', 4: '5.42'} objective 248, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '9.94', 2: '452', 3: '32.5', 4: '5.42'} objective 248, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '19.9', 2: '450', 3: '16.3', 4: '14.1'} objective 273, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '19.9', 2: '450', 3: '16.3', 4: '14.1'} objective 273, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '26.4', 2: '423', 3: '45', 4: '5.76'} objective 210, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '26.4', 2: '423', 3: '44.8', 4: '5.77'} objective 210, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '31.7', 2: '353', 3: '107', 4: '8.95'} objective 237, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '31.6', 2: '353', 3: '107', 4: '9.06'} objective 237, constraint 500, choice set {1, 2, 3, 4}
Forecasting observation 0 / 2 [2000 draws]
Forecasting observation 1 / 2 [2000 draws]
Execution time for 2000 draws with brute force algorithm: 20.5 seconds
Forecasting observation 0 / 2 [2000 draws]
Forecasting observation 1 / 2 [2000 draws]
Execution time for 2000 draws with analytical algorithm: 0.694 seconds
1 2 3 4
count 2.000000e+03 2000.000000 2000.000000 2000.000000
mean 2.439792e+01 426.929193 35.366062 13.306824
std 2.538291e+01 35.357368 16.587736 7.251341
min 3.891492e-16 81.458056 0.000000 1.295123
25% 1.171063e+01 410.720424 24.893909 8.972792
50% 1.841752e+01 430.479544 33.310344 11.952136
75% 2.867985e+01 449.590603 43.615841 15.749595
max 3.735747e+02 498.346029 148.925689 107.930952
1 2 3 4
count 2000.000000 2000.000000 2000.000000 2000.000000
mean 24.384192 426.950104 35.359549 13.306155
std 25.317514 35.294916 16.592635 7.247057
min 0.000000 81.419828 0.000000 1.295016
25% 11.713627 410.828540 24.889764 8.973056
50% 18.428000 430.437278 33.315996 11.929500
75% 28.729678 449.570674 43.571832 15.739476
max 373.612178 498.346598 148.858217 107.952013
1 2 3 4
count 2.000000e+03 2000.000000 2000.000000 2000.000000
mean 2.751289e+01 418.312799 44.009956 10.164356
std 2.328927e+01 37.856372 21.457813 5.170982
min 7.601052e-16 110.756965 0.000000 0.000000
25% 1.347040e+01 398.061316 30.703689 6.847331
50% 2.173273e+01 422.534456 41.124187 9.315500
75% 3.405653e+01 444.519786 52.669373 12.297088
max 3.559648e+02 499.719068 256.407717 45.326247
1 2 3 4
count 2000.000000 2000.000000 2000.000000 2000.000000
mean 27.508135 418.335139 43.992754 10.163971
std 23.260041 37.821507 21.433937 5.170667
min 0.000000 110.851464 0.000000 0.000000
25% 13.483257 398.308127 30.761719 6.846872
50% 21.733449 422.483878 41.136792 9.310790
75% 34.060759 444.571312 52.697874 12.294282
max 355.797541 499.718958 254.564340 45.334397
import sys
import time
import numpy as np
import pandas as pd
from IPython.core.display_functions import display
import biogeme.biogeme_logging as blog
from biogeme.database import Database
from biogeme.results_processing import EstimationResults
from generalized_specification import the_generalized
from process_data import database
logger = blog.get_screen_logger(level=blog.INFO)
logger.info('Example: generalized translated utility')
result_file = 'saved_results/generalized.yaml'
try:
results = EstimationResults.from_yaml_file(filename=result_file)
except FileNotFoundError as e:
print(e)
print(f'File {result_file} is missing.')
sys.exit()
the_generalized.estimation_results = results
# %
# We apply the model only on the first two rows of the database.
two_rows_of_database: Database = database.extract_rows([0, 1])
# %
budget_in_hours = 500
# %
# # Validation
# %
# As the implementation is still experimental, we compare the result obtained by the bruteforce algorithm and
# the analytical algorithm for a few draws.
# Note that minor discrepancies between the outcome of the two algorithms are likely to occur, due to numerical
# imprecision, inevitable in finite arithmetic.
# However, if there are major differences, it should be reported.
# %
number_of_draws = 10
# %
# We generate the draws
epsilons = [
np.random.gumbel(
loc=0, scale=1, size=(number_of_draws, the_generalized.number_of_alternatives)
)
for _ in range(two_rows_of_database.num_rows())
]
# %
# We first compare the results obtained from the brute force and the analytical algorithms, for each draw.
the_generalized.validate_forecast(
database=two_rows_of_database, total_budget=budget_in_hours, epsilons=epsilons
)
# %
# # Forecasting
# We use a larger number of draws to obtain the forecast.
# %
number_of_draws = 2_000
# %
# We generate the draws
epsilons = [
np.random.gumbel(
loc=0, scale=1, size=(number_of_draws, the_generalized.number_of_alternatives)
)
for _ in range(two_rows_of_database.num_rows())
]
# %
# First, the brute force algorithm.
start_time = time.time()
optimal_consumptions_brute_force: list[pd.DataFrame] = the_generalized.forecast(
database=two_rows_of_database,
total_budget=budget_in_hours,
epsilons=epsilons,
brute_force=True,
)
end_time = time.time()
# %
print(
f'Execution time for {number_of_draws} draws with brute force algorithm: {end_time-start_time:.3g} seconds'
)
# %
# Then, the analytical algorithm.
start_time = time.time()
optimal_consumptions_analytical: list[pd.DataFrame] = the_generalized.forecast(
database=two_rows_of_database,
total_budget=budget_in_hours,
epsilons=epsilons,
brute_force=False,
)
end_time = time.time()
# %
print(
f'Execution time for {number_of_draws} draws with analytical algorithm: {end_time-start_time:.3g} seconds'
)
# %
# Results for the first observation, brute force method
display(optimal_consumptions_brute_force[0].describe())
# %
# Results for the first observation, analytical method
display(optimal_consumptions_analytical[0].describe())
# %
# Results for the second observation, brute force method
display(optimal_consumptions_brute_force[1].describe())
# %
# Results for the second observation, analytical method
display(optimal_consumptions_analytical[1].describe())
Total running time of the script: (0 minutes 22.622 seconds)