Advanced Algorithms - Greedy Search and Greedy Randomized Adaptive Search Procedure (GRASP)

Brute force algorithms are effective up to a certain point, but it can quickly become impossible to evaluate all possible combinations of sites as the possible number of combinations grows very quickly.

For example, let’s imagine you have 30 possible places to put a site, but you only want 1 site. That’s 30 combinations to try.

If you want 2 sites from those 30 possibilities, that’s 435 combinations to try.

3 sites from those 30 possibilities is 4060 combinations…

4 is 27,405 combinations…

5 is 142,506 combinations…

6 is 75,287,520 combinations!

n_sites_to_include total_n_sites combinations
0 1 30 30
1 2 30 435
2 3 30 4060
3 4 30 27405
4 5 30 142506
5 6 30 593775
6 7 30 2035800
7 8 30 5852925
8 9 30 14307150
9 10 30 30045015
10 11 30 54627300
11 12 30 86493225
12 13 30 119759850
13 14 30 145422675
14 15 30 155117520
15 16 30 145422675
16 17 30 119759850
17 18 30 86493225
18 19 30 54627300
19 20 30 30045015
20 21 30 14307150
21 22 30 5852925
22 23 30 2035800
23 24 30 593775
24 25 30 142506
25 26 30 27405
26 27 30 4060
27 28 30 435
28 29 30 30

For our 30 site problem, if we want a total of 15 sites, we can see that’s nearly 160 million combinations to evaluate.

# echo: false
import plotly.express as px

px.bar(combos_df, x="n_sites_to_include", y="combinations")

While we can handle the memory implications of trying to evaluate that many combinations using the keep_best_n and keep_worst_n arguments in .solve() so we’re not storing every single result, it’s still going to take an extremely long time to evaluate that many solutions.

Instead, we can turn to some different possible solutions, which we’ll explore in these notebooks.

Greedy search on a larger problem

For the problem we’ve been working with so far, greedy search isn’t really that useful as there are a small enough number of sites that for any value of p, we can brute force (evaluate every single possible combination) without difficulty.

The value of the greedy search becomes much more apparent when we look at a bigger problem.

problem = SiteProblem()

problem.add_demand("https://github.com/health-data-science-OR/healthcare-logistics/blob/master/optimisation/data/sh_demand.csv", demand_col="n_patients", location_id_col="sector")
problem.add_travel_matrix("https://github.com/health-data-science-OR/healthcare-logistics/blob/master/optimisation/data/clinic_car_travel_time.csv", source_col="sector")
problem.total_n_sites

With a total of 28 sites, if we were to solve for 10 sites, we would need to evaluate…

print(f"{int(math.factorial(30)/(math.factorial(30-10) * math.factorial(10))):,d}")
30,045,015

sites. That’s a lot of sites!

However, using the greedy algorithm, we can get a solution we can be reasonably confident is somewhat decent fairly quickly.

While we can’t pass in a maximum travel time threshold for our greedy algorithm, we can pass in the threshold_for_coverage parameter to calculate the proportion of regions that meet a given target travel time.

solution_greedy = problem.solve(p=10, search_strategy="greedy", threshold_for_coverage=10)
solution_greedy
/__w/lokigi/lokigi/lokigi/site.py:1378: UserWarning: No candidate site dataframe was given.
Sites names have been taken from the columns of your travel matrix: clinic_1, clinic_2, clinic_3, clinic_4, clinic_5, clinic_6, clinic_7, clinic_8, clinic_9, clinic_10, clinic_11, clinic_12, clinic_13, clinic_14, clinic_15, clinic_16, clinic_17, clinic_18, clinic_19, clinic_20, clinic_21, clinic_22, clinic_23, clinic_24, clinic_25, clinic_26, clinic_27, clinic_28.
If you wish to override this, run .add_sites() to add your site dataframe before running .solve() again.
You can use the .show_sites_format() to see the expected format beforehand.
  warn(
Best combination for 1 sites: [3]
Best combination for 2 sites: [3 7]
Best combination for 3 sites: [ 3  7 11]
Best combination for 4 sites: [ 3  4  7 11]
Best combination for 5 sites: [ 3  4  7  8 11]
Best combination for 6 sites: [ 3  4  7  8 10 11]
Best combination for 7 sites: [ 3  4  7  8 10 11 22]
Best combination for 8 sites: [ 3  4  7  8 10 11 17 22]
Best combination for 9 sites: [ 3  4  7  8 10 11 17 22 24]
Best combination for 10 sites: [ 3  4  7  8 10 11 15 17 22 24]
<lokigi.site_solutions.SiteSolutionSet at 0x7fb5f7290dd0>
solution_greedy.show_solutions()
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [3, 4, 7, 8, 10, 11, 15, 17, 22, 24] 10 6.81 10.0 19.43 32.37 0.61 sector sector_x  clinic_4  clinic_5  clini...

GRASP

As we can see from the above, while we can now tackle much larger problems than before, we get a single deterministic output from the greedy search problem. This isn’t ideal - we can’t provide multiple near-optimum solutions to our stakeholders, and we have no idea how good our solution is - is it stuck in a local optimum a long way from the global optimum?

Enter GRASP - Greedy Randomised Adaptive Search Procedure.

solution = problem.solve(p=10, search_strategy="grasp", threshold_for_coverage=10)

Finding 5 diverse solutions (max 100 attempts):   0%|          | 0/5 [00:00<?, ?it/s]
Finding 5 diverse solutions (max 100 attempts):  20%|██        | 1/5 [00:00<00:03,  1.27it/s]
Finding 5 diverse solutions (max 100 attempts):  40%|████      | 2/5 [00:02<00:04,  1.52s/it]
Finding 5 diverse solutions (max 100 attempts):  60%|██████    | 3/5 [00:05<00:03,  1.98s/it]
Finding 5 diverse solutions (max 100 attempts):  80%|████████  | 4/5 [00:08<00:02,  2.44s/it]
Finding 5 diverse solutions (max 100 attempts): 100%|██████████| 5/5 [00:11<00:00,  2.55s/it]
Finding 5 diverse solutions (max 100 attempts): 100%|██████████| 5/5 [00:11<00:00,  2.25s/it]
solution.show_solutions()
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [3, 4, 5, 8, 10, 11, 15, 17, 22, 24] 10 6.79 9.89 18.68 32.37 0.62 sector sector_x  clinic_4  clinic_5  clini...
1 None [3, 4, 5, 8, 9, 11, 12, 17, 22, 24] 10 6.86 10.09 20.35 32.37 0.62 sector sector_x  clinic_4  clinic_5  clini...
2 None [3, 4, 5, 8, 9, 11, 12, 17, 22, 27] 10 6.88 10.06 20.11 32.37 0.61 sector sector_x  clinic_4  clinic_5  clini...
3 None [0, 4, 5, 8, 9, 11, 12, 17, 22, 27] 10 6.91 10.21 20.11 32.37 0.60 sector sector_x  clinic_1  clinic_5  clini...
4 None [0, 4, 7, 8, 9, 10, 11, 17, 22, 27] 10 7.00 10.38 20.58 32.37 0.58 sector sector_x  clinic_1  clinic_5  clini...

Let’s compare this to the best solution found by the pure greedy algorithm:

solution_greedy.show_solutions()
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [3, 4, 7, 8, 10, 11, 15, 17, 22, 24] 10 6.81 10.0 19.43 32.37 0.61 sector sector_x  clinic_4  clinic_5  clini...

Interestingly this doesn’t show up in our top 5 solutions found with GRASP, and is between the top two solutions that were found! So it’s worth considering whether to run both.

We can try to run GRASP with a higher number of maximum iterations and a slightly higher alpha to get a more diverse solution pool. In the interests of it not taking forever to regenerate this documentation, below we show a screenshot of the outputs when we’ve run GRASP for much longer.

solution_grasp_big = problem.solve(
    p=10, search_strategy="grasp", threshold_for_coverage=10, grasp_alpha=0.6,
    grasp_max_attempts=50000, grasp_num_solutions=50
    )

Finding 50 diverse solutions (max 50000 attempts):   0%|          | 0/50 [00:00<?, ?it/s]
Finding 50 diverse solutions (max 50000 attempts):   2%|▏         | 1/50 [00:00<00:46,  1.06it/s]
Finding 50 diverse solutions (max 50000 attempts):   4%|▍         | 2/50 [00:03<01:43,  2.15s/it]
Finding 50 diverse solutions (max 50000 attempts):   6%|▌         | 3/50 [00:05<01:33,  1.99s/it]
Finding 50 diverse solutions (max 50000 attempts):   8%|▊         | 4/50 [00:06<01:07,  1.47s/it]
Finding 50 diverse solutions (max 50000 attempts):  10%|█         | 5/50 [00:09<01:29,  1.99s/it]
Finding 50 diverse solutions (max 50000 attempts):  12%|█▏        | 6/50 [00:11<01:29,  2.03s/it]
Finding 50 diverse solutions (max 50000 attempts):  14%|█▍        | 7/50 [00:16<02:06,  2.94s/it]
Finding 50 diverse solutions (max 50000 attempts):  16%|█▌        | 8/50 [00:16<01:31,  2.18s/it]
Finding 50 diverse solutions (max 50000 attempts):  18%|█▊        | 9/50 [00:18<01:23,  2.03s/it]
Finding 50 diverse solutions (max 50000 attempts):  20%|██        | 10/50 [00:26<02:29,  3.74s/it]
Finding 50 diverse solutions (max 50000 attempts):  22%|██▏       | 11/50 [00:28<02:07,  3.26s/it]
Finding 50 diverse solutions (max 50000 attempts):  24%|██▍       | 12/50 [00:31<02:03,  3.26s/it]
Finding 50 diverse solutions (max 50000 attempts):  26%|██▌       | 13/50 [00:36<02:20,  3.80s/it]
Finding 50 diverse solutions (max 50000 attempts):  28%|██▊       | 14/50 [00:37<01:49,  3.03s/it]
Finding 50 diverse solutions (max 50000 attempts):  30%|███       | 15/50 [00:49<03:17,  5.63s/it]
Finding 50 diverse solutions (max 50000 attempts):  32%|███▏      | 16/50 [00:53<02:51,  5.03s/it]
Finding 50 diverse solutions (max 50000 attempts):  34%|███▍      | 17/50 [00:54<02:12,  4.02s/it]
Finding 50 diverse solutions (max 50000 attempts):  36%|███▌      | 18/50 [00:56<01:46,  3.34s/it]
Finding 50 diverse solutions (max 50000 attempts):  38%|███▊      | 19/50 [00:59<01:37,  3.14s/it]
Finding 50 diverse solutions (max 50000 attempts):  40%|████      | 20/50 [01:01<01:25,  2.85s/it]
Finding 50 diverse solutions (max 50000 attempts):  42%|████▏     | 21/50 [01:01<01:02,  2.15s/it]
Finding 50 diverse solutions (max 50000 attempts):  44%|████▍     | 22/50 [01:02<00:46,  1.67s/it]
Finding 50 diverse solutions (max 50000 attempts):  46%|████▌     | 23/50 [01:03<00:36,  1.35s/it]
Finding 50 diverse solutions (max 50000 attempts):  48%|████▊     | 24/50 [01:07<00:59,  2.30s/it]
Finding 50 diverse solutions (max 50000 attempts):  50%|█████     | 25/50 [01:10<01:02,  2.49s/it]
Finding 50 diverse solutions (max 50000 attempts):  52%|█████▏    | 26/50 [01:11<00:51,  2.15s/it]
Finding 50 diverse solutions (max 50000 attempts):  54%|█████▍    | 27/50 [01:14<00:49,  2.17s/it]
Finding 50 diverse solutions (max 50000 attempts):  56%|█████▌    | 28/50 [01:15<00:40,  1.82s/it]
Finding 50 diverse solutions (max 50000 attempts):  58%|█████▊    | 29/50 [01:15<00:30,  1.43s/it]
Finding 50 diverse solutions (max 50000 attempts):  60%|██████    | 30/50 [01:20<00:49,  2.49s/it]
Finding 50 diverse solutions (max 50000 attempts):  62%|██████▏   | 31/50 [01:22<00:41,  2.20s/it]
Finding 50 diverse solutions (max 50000 attempts):  64%|██████▍   | 32/50 [01:29<01:05,  3.64s/it]
Finding 50 diverse solutions (max 50000 attempts):  66%|██████▌   | 33/50 [01:31<00:56,  3.30s/it]
Finding 50 diverse solutions (max 50000 attempts):  68%|██████▊   | 34/50 [01:38<01:11,  4.48s/it]
Finding 50 diverse solutions (max 50000 attempts):  70%|███████   | 35/50 [01:42<01:04,  4.28s/it]
Finding 50 diverse solutions (max 50000 attempts):  72%|███████▏  | 36/50 [01:44<00:50,  3.59s/it]
Finding 50 diverse solutions (max 50000 attempts):  74%|███████▍  | 37/50 [01:45<00:35,  2.70s/it]
Finding 50 diverse solutions (max 50000 attempts):  76%|███████▌  | 38/50 [01:50<00:42,  3.57s/it]
Finding 50 diverse solutions (max 50000 attempts):  78%|███████▊  | 39/50 [01:52<00:31,  2.89s/it]
Finding 50 diverse solutions (max 50000 attempts):  80%|████████  | 40/50 [01:53<00:23,  2.39s/it]
Finding 50 diverse solutions (max 50000 attempts):  82%|████████▏ | 41/50 [01:55<00:20,  2.24s/it]
Finding 50 diverse solutions (max 50000 attempts):  84%|████████▍ | 42/50 [01:56<00:16,  2.02s/it]
Finding 50 diverse solutions (max 50000 attempts):  86%|████████▌ | 43/50 [01:59<00:15,  2.14s/it]
Finding 50 diverse solutions (max 50000 attempts):  88%|████████▊ | 44/50 [02:02<00:14,  2.43s/it]
Finding 50 diverse solutions (max 50000 attempts):  90%|█████████ | 45/50 [02:07<00:15,  3.18s/it]
Finding 50 diverse solutions (max 50000 attempts):  92%|█████████▏| 46/50 [02:08<00:10,  2.53s/it]
Finding 50 diverse solutions (max 50000 attempts):  94%|█████████▍| 47/50 [02:08<00:05,  1.98s/it]
Finding 50 diverse solutions (max 50000 attempts):  96%|█████████▌| 48/50 [02:09<00:03,  1.54s/it]
Finding 50 diverse solutions (max 50000 attempts):  98%|█████████▊| 49/50 [02:13<00:02,  2.43s/it]
Finding 50 diverse solutions (max 50000 attempts): 100%|██████████| 50/50 [02:16<00:00,  2.62s/it]
Finding 50 diverse solutions (max 50000 attempts): 100%|██████████| 50/50 [02:16<00:00,  2.74s/it]
solution_grasp_big.show_solutions().head(10)
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [3, 4, 5, 8, 10, 11, 15, 17, 22, 24] 10 6.79 9.89 18.68 32.37 0.62 sector sector_x  clinic_4  clinic_5  clini...
1 None [3, 4, 7, 8, 10, 11, 15, 17, 22, 24] 10 6.81 10.00 19.43 32.37 0.61 sector sector_x  clinic_4  clinic_5  clini...
2 None [3, 4, 5, 8, 10, 11, 15, 17, 22, 27] 10 6.82 9.89 18.21 32.37 0.61 sector sector_x  clinic_4  clinic_5  clini...
3 None [1, 3, 4, 5, 8, 10, 11, 15, 17, 22] 10 6.82 9.90 18.30 32.37 0.61 sector sector_x  clinic_2  clinic_4  clini...
4 None [3, 4, 5, 8, 10, 11, 14, 17, 22, 24] 10 6.83 9.88 19.45 32.37 0.62 sector sector_x  clinic_4  clinic_5  clini...
5 None [3, 4, 6, 7, 8, 10, 11, 17, 22, 24] 10 6.83 10.09 20.08 32.37 0.61 sector sector_x  clinic_4  clinic_5  clini...
6 None [0, 3, 4, 5, 8, 10, 11, 17, 22, 27] 10 6.84 10.06 20.11 32.37 0.60 sector sector_x  clinic_1  clinic_4  clini...
7 None [0, 1, 3, 4, 5, 8, 10, 11, 17, 22] 10 6.84 10.08 20.11 32.37 0.60 sector sector_x  clinic_1  clinic_2  clini...
8 None [3, 4, 7, 8, 10, 11, 14, 17, 22, 24] 10 6.84 9.95 19.53 32.37 0.61 sector sector_x  clinic_4  clinic_5  clini...
9 None [3, 4, 5, 7, 8, 10, 11, 17, 22, 24] 10 6.85 10.14 20.44 32.37 0.61 sector sector_x  clinic_4  clinic_5  clini...

Other GRASP parameters

We can change the number of sites that must differ to force a more diverse pool of solutions.

solution_grasp_big_more_diverse = problem.solve(
    p=10, search_strategy="grasp", threshold_for_coverage=10, grasp_alpha=0.6,
    grasp_max_attempts=50000, grasp_num_solutions=30,
    grasp_min_sites_different=4, # set this value - default is 1
    )

Finding 30 diverse solutions (max 50000 attempts):   0%|          | 0/30 [00:00<?, ?it/s]
Finding 30 diverse solutions (max 50000 attempts):   3%|▎         | 1/30 [00:00<00:27,  1.05it/s]
Finding 30 diverse solutions (max 50000 attempts):   7%|▋         | 2/30 [00:04<01:01,  2.19s/it]
Finding 30 diverse solutions (max 50000 attempts):  10%|█         | 3/30 [00:06<01:02,  2.32s/it]
Finding 30 diverse solutions (max 50000 attempts):  13%|█▎        | 4/30 [00:09<01:06,  2.56s/it]
Finding 30 diverse solutions (max 50000 attempts):  17%|█▋        | 5/30 [00:11<01:00,  2.40s/it]
Finding 30 diverse solutions (max 50000 attempts):  20%|██        | 6/30 [00:18<01:35,  3.97s/it]
Finding 30 diverse solutions (max 50000 attempts):  23%|██▎       | 7/30 [00:25<01:54,  4.98s/it]
Finding 30 diverse solutions (max 50000 attempts):  27%|██▋       | 8/30 [00:28<01:33,  4.24s/it]
Finding 30 diverse solutions (max 50000 attempts):  30%|███       | 9/30 [00:31<01:22,  3.92s/it]
Finding 30 diverse solutions (max 50000 attempts):  33%|███▎      | 10/30 [00:49<02:44,  8.23s/it]
Finding 30 diverse solutions (max 50000 attempts):  37%|███▋      | 11/30 [00:52<02:09,  6.82s/it]
Finding 30 diverse solutions (max 50000 attempts):  40%|████      | 12/30 [00:56<01:44,  5.80s/it]
Finding 30 diverse solutions (max 50000 attempts):  43%|████▎     | 13/30 [00:59<01:22,  4.86s/it]
Finding 30 diverse solutions (max 50000 attempts):  47%|████▋     | 14/30 [01:01<01:04,  4.02s/it]
Finding 30 diverse solutions (max 50000 attempts):  50%|█████     | 15/30 [01:01<00:44,  2.98s/it]
Finding 30 diverse solutions (max 50000 attempts):  53%|█████▎    | 16/30 [01:02<00:31,  2.27s/it]
Finding 30 diverse solutions (max 50000 attempts):  57%|█████▋    | 17/30 [01:07<00:40,  3.13s/it]
Finding 30 diverse solutions (max 50000 attempts):  60%|██████    | 18/30 [01:10<00:36,  3.08s/it]
Finding 30 diverse solutions (max 50000 attempts):  63%|██████▎   | 19/30 [01:14<00:35,  3.22s/it]
Finding 30 diverse solutions (max 50000 attempts):  67%|██████▋   | 20/30 [01:15<00:27,  2.71s/it]
Finding 30 diverse solutions (max 50000 attempts):  70%|███████   | 21/30 [01:31<01:00,  6.67s/it]
Finding 30 diverse solutions (max 50000 attempts):  73%|███████▎  | 22/30 [01:55<01:33, 11.75s/it]
Finding 30 diverse solutions (max 50000 attempts):  77%|███████▋  | 23/30 [01:59<01:05,  9.41s/it]
Finding 30 diverse solutions (max 50000 attempts):  80%|████████  | 24/30 [02:02<00:45,  7.51s/it]
Finding 30 diverse solutions (max 50000 attempts):  83%|████████▎ | 25/30 [02:08<00:36,  7.24s/it]
Finding 30 diverse solutions (max 50000 attempts):  87%|████████▋ | 26/30 [02:09<00:20,  5.23s/it]
Finding 30 diverse solutions (max 50000 attempts):  90%|█████████ | 27/30 [02:13<00:14,  5.00s/it]
Finding 30 diverse solutions (max 50000 attempts):  93%|█████████▎| 28/30 [02:16<00:08,  4.41s/it]
Finding 30 diverse solutions (max 50000 attempts):  97%|█████████▋| 29/30 [02:33<00:07,  7.97s/it]
Finding 30 diverse solutions (max 50000 attempts): 100%|██████████| 30/30 [02:38<00:00,  7.17s/it]
Finding 30 diverse solutions (max 50000 attempts): 100%|██████████| 30/30 [02:38<00:00,  5.28s/it]
solution_grasp_big_more_diverse.show_solutions().head(10)
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [3, 4, 6, 7, 8, 10, 11, 17, 22, 24] 10 6.83 10.09 20.08 32.37 0.61 sector sector_x  clinic_4  clinic_5  clini...
1 None [0, 3, 4, 5, 8, 10, 11, 17, 22, 27] 10 6.84 10.06 20.11 32.37 0.60 sector sector_x  clinic_1  clinic_4  clini...
2 None [3, 4, 5, 8, 9, 11, 12, 17, 22, 24] 10 6.86 10.09 20.35 32.37 0.62 sector sector_x  clinic_4  clinic_5  clini...
3 None [0, 1, 2, 4, 6, 8, 10, 11, 17, 22] 10 6.98 10.20 20.12 32.37 0.59 sector sector_x  clinic_1  clinic_2  clini...
4 None [1, 3, 4, 5, 8, 10, 11, 15, 22, 24] 10 7.04 10.48 18.62 32.37 0.56 sector sector_x  clinic_2  clinic_4  clini...
5 None [0, 1, 4, 7, 8, 10, 11, 13, 14, 22] 10 7.07 10.17 18.66 32.37 0.59 sector sector_x  clinic_1  clinic_2  clini...
6 None [2, 4, 5, 8, 10, 11, 13, 14, 22, 24] 10 7.08 10.06 19.45 32.37 0.63 sector sector_x  clinic_3  clinic_5  clini...
7 None [0, 2, 4, 7, 11, 12, 17, 21, 22, 24] 10 7.17 10.47 20.58 32.37 0.58 sector sector_x  clinic_1  clinic_3  clini...
8 None [3, 4, 5, 6, 10, 11, 13, 21, 22, 27] 10 7.18 10.12 19.96 32.37 0.60 sector sector_x  clinic_4  clinic_5  clini...
9 None [0, 1, 4, 6, 7, 8, 9, 11, 12, 17] 10 7.22 10.87 22.03 32.37 0.56 sector sector_x  clinic_1  clinic_2  clini...

We can also change the chance of a local search taking place.

solution_grasp_less_local_searching = problem.solve(
    p=10, search_strategy="grasp", threshold_for_coverage=10, grasp_alpha=0.6,
    grasp_max_attempts=50000, grasp_num_solutions=20,
    grasp_local_search_chance=0.4 # set this value - default is 0.8
    )

Finding 20 diverse solutions (max 50000 attempts):   0%|          | 0/20 [00:00<?, ?it/s]
Finding 20 diverse solutions (max 50000 attempts):   5%|▌         | 1/20 [00:00<00:18,  1.05it/s]
Finding 20 diverse solutions (max 50000 attempts):  10%|█         | 2/20 [00:01<00:14,  1.23it/s]
Finding 20 diverse solutions (max 50000 attempts):  15%|█▌        | 3/20 [00:03<00:24,  1.46s/it]
Finding 20 diverse solutions (max 50000 attempts):  20%|██        | 4/20 [00:04<00:18,  1.16s/it]
Finding 20 diverse solutions (max 50000 attempts):  25%|██▌       | 5/20 [00:05<00:14,  1.01it/s]
Finding 20 diverse solutions (max 50000 attempts):  30%|███       | 6/20 [00:05<00:12,  1.13it/s]
Finding 20 diverse solutions (max 50000 attempts):  35%|███▌      | 7/20 [00:09<00:20,  1.60s/it]
Finding 20 diverse solutions (max 50000 attempts):  40%|████      | 8/20 [00:11<00:23,  1.94s/it]
Finding 20 diverse solutions (max 50000 attempts):  45%|████▌     | 9/20 [00:12<00:16,  1.51s/it]
Finding 20 diverse solutions (max 50000 attempts):  50%|█████     | 10/20 [00:14<00:16,  1.66s/it]
Finding 20 diverse solutions (max 50000 attempts):  55%|█████▌    | 11/20 [00:16<00:15,  1.73s/it]
Finding 20 diverse solutions (max 50000 attempts):  60%|██████    | 12/20 [00:16<00:11,  1.39s/it]
Finding 20 diverse solutions (max 50000 attempts):  65%|██████▌   | 13/20 [00:17<00:08,  1.17s/it]
Finding 20 diverse solutions (max 50000 attempts):  70%|███████   | 14/20 [00:18<00:05,  1.01it/s]
Finding 20 diverse solutions (max 50000 attempts):  75%|███████▌  | 15/20 [00:18<00:04,  1.12it/s]
Finding 20 diverse solutions (max 50000 attempts):  80%|████████  | 16/20 [00:19<00:03,  1.11it/s]
Finding 20 diverse solutions (max 50000 attempts):  85%|████████▌ | 17/20 [00:21<00:03,  1.26s/it]
Finding 20 diverse solutions (max 50000 attempts):  90%|█████████ | 18/20 [00:24<00:03,  1.63s/it]
Finding 20 diverse solutions (max 50000 attempts):  95%|█████████▌| 19/20 [00:27<00:02,  2.02s/it]
Finding 20 diverse solutions (max 50000 attempts): 100%|██████████| 20/20 [00:27<00:00,  1.59s/it]
Finding 20 diverse solutions (max 50000 attempts): 100%|██████████| 20/20 [00:27<00:00,  1.38s/it]

And we can also change the maximum number of swaps that will happen in the local search stage, ensuring a diverse range of solutions is explored, which is particularly important for larger questions.

solution_grasp_less_local_searching = problem.solve(
    p=10, search_strategy="grasp", threshold_for_coverage=10, grasp_alpha=0.6,
    grasp_max_attempts=50000, grasp_num_solutions=20,
    grasp_max_swap_count_local_search=50 # set this value - default is 10
    )

Finding 20 diverse solutions (max 50000 attempts):   0%|          | 0/20 [00:00<?, ?it/s]
Finding 20 diverse solutions (max 50000 attempts):   5%|▌         | 1/20 [00:00<00:14,  1.27it/s]
Finding 20 diverse solutions (max 50000 attempts):  10%|█         | 2/20 [00:05<00:52,  2.90s/it]
Finding 20 diverse solutions (max 50000 attempts):  15%|█▌        | 3/20 [00:07<00:42,  2.51s/it]
Finding 20 diverse solutions (max 50000 attempts):  20%|██        | 4/20 [00:13<01:01,  3.86s/it]
Finding 20 diverse solutions (max 50000 attempts):  25%|██▌       | 5/20 [00:17<01:02,  4.18s/it]
Finding 20 diverse solutions (max 50000 attempts):  30%|███       | 6/20 [00:18<00:41,  2.95s/it]
Finding 20 diverse solutions (max 50000 attempts):  35%|███▌      | 7/20 [00:27<01:05,  5.03s/it]
Finding 20 diverse solutions (max 50000 attempts):  40%|████      | 8/20 [00:34<01:07,  5.59s/it]
Finding 20 diverse solutions (max 50000 attempts):  45%|████▌     | 9/20 [00:55<01:53, 10.31s/it]
Finding 20 diverse solutions (max 50000 attempts):  50%|█████     | 10/20 [00:59<01:22,  8.29s/it]
Finding 20 diverse solutions (max 50000 attempts):  55%|█████▌    | 11/20 [01:02<01:00,  6.76s/it]
Finding 20 diverse solutions (max 50000 attempts):  60%|██████    | 12/20 [01:04<00:43,  5.48s/it]
Finding 20 diverse solutions (max 50000 attempts):  65%|██████▌   | 13/20 [01:05<00:27,  4.00s/it]
Finding 20 diverse solutions (max 50000 attempts):  70%|███████   | 14/20 [01:06<00:17,  2.97s/it]
Finding 20 diverse solutions (max 50000 attempts):  75%|███████▌  | 15/20 [01:10<00:16,  3.37s/it]
Finding 20 diverse solutions (max 50000 attempts):  80%|████████  | 16/20 [01:13<00:12,  3.16s/it]
Finding 20 diverse solutions (max 50000 attempts):  85%|████████▌ | 17/20 [01:19<00:12,  4.08s/it]
Finding 20 diverse solutions (max 50000 attempts):  90%|█████████ | 18/20 [01:33<00:14,  7.05s/it]
Finding 20 diverse solutions (max 50000 attempts):  95%|█████████▌| 19/20 [01:35<00:05,  5.67s/it]
Finding 20 diverse solutions (max 50000 attempts): 100%|██████████| 20/20 [01:41<00:00,  5.81s/it]
Finding 20 diverse solutions (max 50000 attempts): 100%|██████████| 20/20 [01:41<00:00,  5.09s/it]

GRASP on our smaller problem - does it find the known optimal solution in this case?

Let’s also try this out on our smaller problem from earlier - do we find the optimal solution here too?

solution_small_grasp = problem_small.solve(p=3, search_strategy="grasp")

Finding 5 diverse solutions (max 20 attempts):   0%|          | 0/5 [00:00<?, ?it/s]
Finding 5 diverse solutions (max 20 attempts):  40%|████      | 2/5 [00:00<00:00, 21.49it/s]
/__w/lokigi/lokigi/lokigi/site.py:1606: UserWarning: GRASP exhausted attempt budget (20 attempts) before finding 5 sufficiently diverse solutions. Returning 2 solutions.
  outputs = self._grasp(
solution_small_grasp.show_solutions()
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [2, 3, 5] None 5.36 5.36 8.06 16.69 0.0 LSOA                  L...
1 None [2, 3, 4] None 5.45 5.45 8.50 16.69 0.0 LSOA                  L...
solution_small_grasp.plot_best_combination()

It gets quite stuck with the default alpha value, finding only two solutions! Let’s turn up the alpha and see if we can get some more varied solutions out.

solution_small_grasp_higher_alpha = problem_small.solve(
    p=3, search_strategy="grasp",
    grasp_alpha=0.4, grasp_max_attempts=500
    )

Finding 5 diverse solutions (max 500 attempts):   0%|          | 0/5 [00:00<?, ?it/s]
Finding 5 diverse solutions (max 500 attempts):  40%|████      | 2/5 [00:00<00:00, 18.32it/s]
Finding 5 diverse solutions (max 500 attempts):  40%|████      | 2/5 [00:00<00:00, 16.17it/s]
/__w/lokigi/lokigi/lokigi/site.py:1606: UserWarning: GRASP exhausted attempt budget (500 attempts) before finding 5 sufficiently diverse solutions. Returning 2 solutions.
  outputs = self._grasp(
solution_small_grasp_higher_alpha.show_solutions()
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [2, 3, 5] None 5.36 5.36 8.06 16.69 0.0 LSOA                  L...
1 None [2, 3, 4] None 5.45 5.45 8.50 16.69 0.0 LSOA                  L...

It looks like we need to make our alpha higher again!

solution_small_grasp_even_higher_alpha = problem_small.solve(
    p=3, search_strategy="grasp",
    grasp_alpha=0.6, grasp_max_attempts=100000,
    grasp_min_sites_different=1
    )

Finding 5 diverse solutions (max 100000 attempts):   0%|          | 0/5 [00:00<?, ?it/s]
Finding 5 diverse solutions (max 100000 attempts):  60%|██████    | 3/5 [00:00<00:00, 22.70it/s]
Finding 5 diverse solutions (max 100000 attempts): 100%|██████████| 5/5 [00:00<00:00, 34.75it/s]
solution_small_grasp_even_higher_alpha.show_solutions()
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [2, 3, 5] None 5.36 5.36 8.06 16.69 0.0 LSOA                  L...
1 None [2, 3, 4] None 5.45 5.45 8.50 16.69 0.0 LSOA                  L...
2 None [1, 2, 3] None 5.59 5.59 9.00 16.69 0.0 LSOA                  L...
3 None [1, 3, 5] None 6.64 6.64 11.58 22.86 0.0 LSOA                  L...
4 None [3, 4, 5] None 6.73 6.73 12.26 21.71 0.0 LSOA                  L...

Let’s compare this with the best solutions from the brute force approach.

solution_small_brute_force.show_solutions().head(15)
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [2, 3, 5] None 5.36 5.36 8.06 16.69 0.0 LSOA                  L...
1 None [2, 3, 4] None 5.45 5.45 8.50 16.69 0.0 LSOA                  L...
2 None [1, 2, 3] None 5.59 5.59 9.00 16.69 0.0 LSOA                  L...
3 None [0, 2, 3] None 5.67 5.67 9.36 16.69 0.0 LSOA                  L...
4 None [0, 2, 5] None 6.21 6.21 9.33 16.69 0.0 LSOA                  L...
5 None [2, 4, 5] None 6.29 6.29 9.26 16.69 0.0 LSOA                  L...
6 None [1, 2, 5] None 6.31 6.31 9.45 16.69 0.0 LSOA                  L...
7 None [0, 2, 4] None 6.32 6.32 9.70 16.69 0.0 LSOA                  L...
8 None [0, 1, 2] None 6.39 6.39 9.77 16.69 0.0 LSOA                  L...
9 None [1, 3, 5] None 6.64 6.64 11.58 22.86 0.0 LSOA                  L...
10 None [3, 4, 5] None 6.73 6.73 12.26 21.71 0.0 LSOA                  L...
11 None [1, 3, 4] None 6.76 6.76 11.32 21.71 0.0 LSOA                  L...
12 None [0, 3, 4] None 6.81 6.81 12.26 21.71 0.0 LSOA                  L...
13 None [0, 1, 3] None 6.92 6.92 11.93 23.92 0.0 LSOA                  L...
14 None [1, 2, 4] None 7.46 7.46 11.57 16.69 0.0 LSOA                  L...

We can see it found the - best - second best - 3rd best - 10th best - and 11th best solutions

Let’s go with an even higher alpha and ask for it to find more solutions.

solution_small_grasp_higher_alpha_more_sols = problem_small.solve(
    p=3, search_strategy="grasp",
    grasp_alpha=0.95, grasp_max_attempts=50000,
    grasp_num_solutions=20
    )

Finding 20 diverse solutions (max 50000 attempts):   0%|          | 0/20 [00:00<?, ?it/s]
Finding 20 diverse solutions (max 50000 attempts):  15%|█▌        | 3/20 [00:00<00:00, 22.59it/s]
Finding 20 diverse solutions (max 50000 attempts):  75%|███████▌  | 15/20 [00:02<00:00,  7.42it/s]
/__w/lokigi/lokigi/lokigi/site.py:1606: UserWarning: GRASP exhausted attempt budget (50000 attempts) before finding 20 sufficiently diverse solutions. Returning 15 solutions.
  outputs = self._grasp(
solution_small_grasp_higher_alpha_more_sols.show_solutions()
site_names site_indices coverage_threshold weighted_average unweighted_average 90th_percentile max proportion_within_coverage_threshold problem_df
0 None [2, 3, 5] None 5.36 5.36 8.06 16.69 0.0 LSOA                  L...
1 None [2, 3, 4] None 5.45 5.45 8.50 16.69 0.0 LSOA                  L...
2 None [1, 2, 3] None 5.59 5.59 9.00 16.69 0.0 LSOA                  L...
3 None [0, 2, 3] None 5.67 5.67 9.36 16.69 0.0 LSOA                  L...
4 None [0, 2, 5] None 6.21 6.21 9.33 16.69 0.0 LSOA                  L...
5 None [2, 4, 5] None 6.29 6.29 9.26 16.69 0.0 LSOA                  L...
6 None [1, 2, 5] None 6.31 6.31 9.45 16.69 0.0 LSOA                  L...
7 None [0, 2, 4] None 6.32 6.32 9.70 16.69 0.0 LSOA                  L...
8 None [0, 1, 2] None 6.39 6.39 9.77 16.69 0.0 LSOA                  L...
9 None [1, 3, 5] None 6.64 6.64 11.58 22.86 0.0 LSOA                  L...
10 None [3, 4, 5] None 6.73 6.73 12.26 21.71 0.0 LSOA                  L...
11 None [1, 3, 4] None 6.76 6.76 11.32 21.71 0.0 LSOA                  L...
12 None [0, 3, 4] None 6.81 6.81 12.26 21.71 0.0 LSOA                  L...
13 None [0, 1, 3] None 6.92 6.92 11.93 23.92 0.0 LSOA                  L...
14 None [0, 1, 5] None 7.54 7.54 11.90 22.86 0.0 LSOA                  L...

With an even higher alpha, we could choose to run for longer periods and be confident we’re exploring a good range of the solution space without the computational overhead of the brute force method.

Back to top