Chris Bloom
December 05, 2022
78

# Simulated Annealing - RubyConf 2022

Simulated annealing is a fascinating algorithm that's designed to help find a particular type of solution (near-optimal, aka "good enough") to a particular type of problem (constrained optimization). It's inspired by the science of metallurgy, and because it's grounded in a real-world process I find it incredibly approachable. In this talk I'll explain in plain terms about what simulated annealing is, what a constrained optimization problem is, why you might want a "good enough" solution, and how we can use the Annealing gem to add simulated annealing to our Ruby apps.

## Chris Bloom

December 05, 2022

## Transcript

1. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Simulated Annealing: The most metal algorithm ever 🤘
2. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Chris Bloom Senior Software Engineer @ GitHub @chrisbloom7
3. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 What is an optimization problem? Breaking the ice: A real-world example When good enough is good enough Solving with Simulated Annealing Simulated Annealing
4. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 What is an optimization problem?
5. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Optimization problems combinatorial Find the optimal arrangement of a set from all potential arrangements constrained Find the optimal solution given one or more constraints on potential solutions Find the best, or optimal, solution from all potential solutions
6. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city exactly once and returns to the origin city? Traveling Salesperson Problem (TSP)
7. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Traveling Metal Band Problem (TSP)
8. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 cost_function = -> (potential_route) { # Include the distance back to the starting city round_trip = potential_route + [potential_route[0]] # Sum the distances between each city based on our matrix DistanceTable.distances_along_route(round_trip) }
9. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 # The list of cities we intend to visit cities = ["Atchison, KS", "Castle Rock, CO", "Delray Beach, FL", "Leominster, MA", "Milpitas, CA", "Norwich, CT", "San Diego, CA", "Sturgis, SD"] # Get a list of all potential routes potential_routes = cities.permutation best_route = nil # Search all potential routes to find the optimal route potential_routes.each do |potential_route| # Assign as best route if this route is better than the last distance = cost_function.call(potential_route) if best_route.nil? || distance < best_route[:distance] best_route = { route: round_trip, distance: distance } end end

@chrisbloom7

@chrisbloom7
12. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 When good enough is good enough
13. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 8! = 8*7*6*5*4*3*2*1 = 40_320 4! = 4*3*2*1 = 24 Permutations 16! = 16*15*...*1 = 20_922_789_888_000
14. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Math Time Math Nerd Slide Permutations of 100 items 99! = 9.33262E+155 100! = 9.33262E+157 Remove inverse arrangements Remove equivalent arrangements 99!/2 = 4.66631E+155 n! (n-1)! (n-1)! 2
15. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Given a list of cities and a means of calculating the cost of traveling between each pair of cities, what is the optimal route that visits each city exactly once and returns to the origin city, minimizing the total cost. Traveling Metal Band Problem Alternative Version
16. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 cost_function = -> (potential_route) { round_trip = potential_route + [potential_route[0]] # Calculate the cost of the trip from historical data. trip = TripCalculator.route(round_trip) # Exclude any routes that are over budget. return Float::INFINITY if trip.cost > MAX_TRIP_BUDGET # Penalize trips that keep salespeople out for too long. days_over_max = trip.days_over_max(MAXIMUM_TRIP_LENGTH) if days_over_max.positive? trip.cost += days_over_max * DAILY_TRAVEL_LIMIT_BONUS end trip.cost }
17. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Breaking the ice A real-world example
18. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 The Programmers’ Credo: we do these things not because they are easy, but because we thought they were going to be easy 3:15 PM · Aug 5, 2016 @Pinboard
19. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 8! = 8*7*6*5*4*3*2*1 = 40_320 4! = 4*3*2*1 = 24 Permutations 16! = 16*15*...*1 = 20_922_789_888_000 100! = 100*99*98*...*1 = 9.33262E+157
20. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 n-1 = 100-1 = 99
21. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Groups may contain subsets, supersets, or exclusive sets Each member prefers unique groupings Given enough time, repeat groupings are inevitable Member may experience repeat groupings at different rates Requirements
22. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Meeting cost matrix
23. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 # Calculate the total cost of all members in all subgroups cost_function -> (potential_grouping) { potential_grouping.reduce(0) do |i, subgroup| # Find all pairs of users in the subgroup i += subgroup.combination(2).reduce(0) do |i2, pair| # Find all previous groupings where this pair met past_meetings = GroupingHistory.meetings(pair) # Calculate a cost based on our history matrix i2 += past_meetings.reduce(0) do |i3, meeting| i3 += Math.exp(meeting.historical_index) end end end }
24. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 potential_arrangements.each do |potential_arrangement| subgroups = potential_arrangement.each_slice(2) cost = cost_function.call(subgroups) # If the cost is zero, accept it outright if cost == 0 best_grouping = subgroups break end if best_grouping.nil? || cost < best_grouping[:cost] best_grouping = { subgroups: subgroups, cost: cost } end end
25. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Solving with Simulated Annealing
26. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 1. Define an annealing schedule 2. Select an initial arrangement as the current state 3. Generate a new state from the current state 4. Measure the energy of both states with a cost function 5. Decided which state to keep as the current state 6. Reduce the current temperature of the annealer 7. Check if the simulation is done, otherwise go to step 3 The simulated annealing process
27. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 1. Define an annealing schedule 2. Select an initial arrangement as the current state 3. Generate a new state from the current state 4. Measure the energy of both states with a cost function 5. Decided which state to keep as the current state 6. Reduce the current temperature of the annealer 7. Check if the simulation is done, otherwise go to step 3 The simulated annealing process
28. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 require "annealer" Annealing.configure do |config| config.temperature = 200 config.cooling_rate = 0.001 end 1. Define an annealing schedule
29. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 1. Define an annealing schedule 2. Select an initial arrangement as the current state 3. Generate a new state from the current state 4. Measure the energy of both states with a cost function 5. Decided which state to keep as the current state 6. Reduce the current temperature of the annealer 7. Check if the simulation is done, otherwise go to step 3 The simulated annealing process
30. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 members = ["E001", "E002", "E003", "E004", "E005", "E006", # ... "E097", "E098", "E099", "E100"] # We'll assume subgroups of 2 for this example initial_grouping = members.shuffle.each_slice(2) # => [["E014", "E034"], ["E040", "E047"], ["E035", "E007"], ...] 2. Select an initial arrangement
31. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 1. Define an annealing schedule 2. Select an initial arrangement as the current state 3. Generate a new state from the current state 4. Measure the energy of both states with a cost function 5. Decided which state to keep as the current state 6. Reduce the current temperature of the annealer 7. Check if the simulation is done, otherwise go to step 3 The simulated annealing process
32. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Annealing.configuration.state_change = -> (current_state) { # make a copy of the current state new_state = current_state.dup # Find two random groups group_index1, group_index2 = (0... new_state.size).to_a.sample(2) group1, group2 = new_state.values_at(group_index1, group_index2) # Now swap one random person from each group subgroup_index1, subgroup_index2 = rand(group1.size - 1), rand(group2.size - 1) group1[subgroup_index1], group2[subgroup_index2] = group2[subgroup_index2], group1[subgroup_index1] # Return the new neighboring state new_state } 3. Generate a new state
33. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 1. Define an annealing schedule 2. Select an initial arrangement as the current state 3. Generate a new state from the current state 4. Measure the energy of both states with a cost function 5. Decided which state to keep as the current state 6. Reduce the current temperature of the annealer 7. Check if the simulation is done, otherwise go to step 3 The simulated annealing process
34. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Annealing.configuration.energy_calculator -> (current_state) { current_state.reduce(0) do |i, subgroup| # Find all pairs of users in the subgroup i += subgroup.combination(2).reduce(0) do |i2, pair| # Find all previous groupings where this pair met past_meetings = GroupingHistory.meetings(pair) # Calculate a cost based on our history matrix i2 += past_meetings.reduce(0) do |i3, meeting| i3 += Math.exp(meeting.historical_index) end end end } 4. Measure the energy of both states
35. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 1. Define an annealing schedule 2. Select an initial arrangement as the current state 3. Generate a new state from the current state 4. Measure the energy of both states with a cost function 5. Decided which state to keep as the current state 6. Reduce the current temperature of the annealer 7. Check if the simulation is done, otherwise go to step 3 The simulated annealing process
36. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 5. Decided which state to keep
37. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 1. Define an annealing schedule 2. Select an initial arrangement as the current state 3. Generate a new state from the current state 4. Measure the energy of both states with a cost function 5. Decided which state to keep as the current state 6. Reduce the current temperature of the annealer 7. Check if the simulation is done, otherwise go to step 3 The simulated annealing process
38. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 1. Define an annealing schedule 2. Select an initial arrangement as the current state 3. Generate a new state from the current state 4. Measure the energy of both states with a cost function 5. Decided which state to keep as the current state 6. Reduce the current temperature of the annealer 7. Check if the simulation is done, otherwise go to step 3 The simulated annealing process
39. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Annealing.configuration.termination_condition = -> (_, energy, temperature) { # Stop early if we encounter any 0-energy state energy == 0 || temperature <= 0 } 7. Check if the simulation is done
40. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Putting it all together
41. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 # Our simulator instance will use the global configuration we # just finished configuring simulation = Annealing::Simulator.new optimal_grouping = simulation.run(initial_grouping) optimal_grouping.state # => [["E023", "E087"], ["E017", "E012"], ["E049", "E066"], ...] optimal_grouping.energy # => 2.718281828459045
42. ### December 1, 2022 Simulated Annealing: The most metal algorithm ever

@chrisbloom7 Optimization by Simulated Annealing Science Magazine, May 13, 1983, Volume 220, Number 4598