Note
Go to the end to download the full example code.
File gamma_forecasting.py
Michel Bierlaire, EPFL Fri Jul 25 2025, 16:38:12 Forecasting with a MDCEV model and the “gamma_profile” specification.
Example: gamma profile utility
Forecasting observation 0 / 2 [10 draws]
============ Comparison ===================
Brute force: {1: '19.3', 2: '191', 3: '184', 4: '106'} objective 75.9, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '19.5', 2: '190', 3: '185', 4: '105'} objective 75.9, constraint 500, choice set {1, 2, 3, 4}
Difference between optimal utility with analytical [75.9] and brute force [75.89] algorithms.
Solution with brute force: 1: 19.3, 2: 191, 3: 184, 4: 106
Solution with analytical: 1: 19.5, 2: 190, 3: 185, 4: 105
============ Comparison ===================
Brute force: {1: '96.5', 2: '184', 3: '148', 4: '71.4'} objective 48.1, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '95.6', 2: '184', 3: '148', 4: '71.9'} objective 48.1, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '98.8', 2: '224', 3: '128', 4: '48.6'} objective 55.2, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '97.5', 2: '227', 3: '126', 4: '49.4'} objective 55.2, constraint 500, choice set {1, 2, 3, 4}
Difference between optimal utility with analytical [55.22] and brute force [55.22] algorithms.
Solution with brute force: 1: 98.8, 2: 224, 3: 128, 4: 48.6
Solution with analytical: 1: 97.5, 2: 227, 3: 126, 4: 49.4
============ Comparison ===================
Brute force: {1: '36.5', 2: '160', 3: '271', 4: '32.7'} objective 70.3, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '36.5', 2: '160', 3: '271', 4: '32.7'} objective 70.3, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '69.3', 2: '227', 3: '169', 4: '35'} objective 65, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '69.3', 2: '227', 3: '169', 4: '35'} objective 65, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '43.4', 2: '262', 3: '150', 4: '44.3'} objective 54.9, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '43', 2: '264', 3: '148', 4: '45'} objective 54.9, constraint 500, choice set {1, 2, 3, 4}
Difference between optimal utility with analytical [54.86] and brute force [54.86] algorithms.
Solution with brute force: 1: 43.4, 2: 262, 3: 150, 4: 44.3
Solution with analytical: 1: 43, 2: 264, 3: 148, 4: 45
============ Comparison ===================
Brute force: {1: '38.6', 2: '190', 3: '208', 4: '63.7'} objective 52.2, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '38.6', 2: '189', 3: '209', 4: '63.7'} objective 52.2, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '49.2', 2: '247', 3: '143', 4: '61.2'} objective 47.2, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '49.1', 2: '248', 3: '141', 4: '61.6'} objective 47.2, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '42.5', 2: '197', 3: '197', 4: '63.6'} objective 56.9, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '42.4', 2: '195', 3: '198', 4: '63.7'} objective 56.9, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '48.4', 2: '256', 3: '163', 4: '32.7'} objective 68.2, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '48.5', 2: '257', 3: '162', 4: '32.7'} objective 68.2, constraint 500, choice set {1, 2, 3, 4}
Forecasting observation 1 / 2 [10 draws]
============ Comparison ===================
Brute force: {1: '41.4', 2: '196', 3: '226', 4: '37.2'} objective 66.5, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '41.6', 2: '194', 3: '227', 4: '37.1'} objective 66.5, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '58.4', 2: '138', 3: '260', 4: '44'} objective 63, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '59.2', 2: '136', 3: '261', 4: '43.8'} objective 63, constraint 500, choice set {1, 2, 3, 4}
Difference between optimal utility with analytical [62.98] and brute force [62.98] algorithms.
Solution with brute force: 1: 58.4, 2: 138, 3: 260, 4: 44
Solution with analytical: 1: 59.2, 2: 136, 3: 261, 4: 43.8
============ Comparison ===================
Brute force: {1: '51.4', 2: '189', 3: '204', 4: '55.8'} objective 75.6, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '51', 2: '187', 3: '206', 4: '56.3'} objective 75.6, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '33.5', 2: '233', 3: '213', 4: '21'} objective 90, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '33.5', 2: '232', 3: '214', 4: '20.9'} objective 90, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '46', 2: '237', 3: '181', 4: '36'} objective 59.4, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '46', 2: '237', 3: '181', 4: '36'} objective 59.4, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '53.9', 2: '249', 3: '168', 4: '29.7'} objective 79.9, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '54', 2: '249', 3: '167', 4: '29.7'} objective 79.9, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '55.7', 2: '129', 3: '281', 4: '33.9'} objective 65, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '55.7', 2: '129', 3: '281', 4: '33.9'} objective 65, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '75.9', 2: '182', 3: '180', 4: '61.8'} objective 59.4, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '75.1', 2: '182', 3: '181', 4: '62.4'} objective 59.4, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '54.6', 2: '204', 3: '192', 4: '49.1'} objective 103, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '54.6', 2: '203', 3: '193', 4: '49'} objective 103, constraint 500, choice set {1, 2, 3, 4}
============ Comparison ===================
Brute force: {1: '63.6', 2: '224', 3: '187', 4: '24.7'} objective 77.1, constraint 500, choice set {1, 2, 3, 4}
Analytical: {1: '63.7', 2: '224', 3: '188', 4: '24.7'} objective 77.1, 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: 12.3 seconds
Forecasting observation 0 / 2 [2000 draws]
Forecasting observation 1 / 2 [2000 draws]
Execution time for 2000 draws with analytical algorithm: 0.608 seconds
1 2 3 4
count 2000.000000 2000.000000 2000.000000 2000.000000
mean 51.518231 211.054094 187.229841 50.197834
std 23.130843 53.947558 53.337971 21.550578
min 8.728227 43.901284 44.890577 8.076306
25% 36.143132 175.047718 153.027933 36.446719
50% 47.029913 206.956391 182.614542 46.386606
75% 60.692928 242.079979 215.805571 59.587119
max 234.492435 408.033021 419.227802 366.715204
1 2 3 4
count 2000.000000 2000.000000 2000.000000 2000.000000
mean 51.379089 210.774331 187.507669 50.338911
std 22.941211 54.448191 53.867760 21.476516
min 8.728471 43.905352 44.893016 8.076337
25% 36.194367 173.900701 152.041808 36.585668
50% 47.048983 206.302898 183.120942 46.715967
75% 60.471824 242.938136 217.143337 59.804237
max 234.449458 408.017802 419.227170 366.717718
1 2 3 4
count 2000.000000 2000.000000 2000.000000 2000.000000
mean 53.343868 195.614404 213.815285 37.226442
std 22.464387 50.835646 52.409488 16.870743
min 10.628288 47.110956 52.865721 9.782055
25% 38.782480 161.272774 177.984059 26.807845
50% 48.828279 191.583525 209.828357 33.587426
75% 62.290773 225.470817 245.341326 43.262908
max 247.160308 408.160130 426.049392 177.230517
1 2 3 4
count 2000.000000 2000.000000 2000.000000 2000.000000
mean 53.363183 195.091525 214.290462 37.254830
std 22.388807 51.122401 52.624925 16.869329
min 10.626606 47.111895 52.871675 9.781980
25% 38.782628 159.984587 178.026109 26.777725
50% 48.899298 190.640290 211.003794 33.561971
75% 62.348376 225.067649 246.438383 43.377101
max 247.026101 408.156280 426.049082 176.091517
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 gamma_specification import the_gamma_profile
from process_data import database
logger = blog.get_screen_logger(level=blog.INFO)
logger.info('Example: gamma profile utility')
result_file = 'saved_results/gamma_profile.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_gamma_profile.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_gamma_profile.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_gamma_profile.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 = 2000
# %
# We generate the draws
epsilons = [
np.random.gumbel(
loc=0, scale=1, size=(number_of_draws, the_gamma_profile.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_gamma_profile.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_gamma_profile.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 14.301 seconds)