advent_of_code_2021

My attempts to work through the 2021 Advent of Code problems.
git clone https://git.eamoncaddigan.net/advent_of_code_2021.git
Log | Files | Refs | README | LICENSE

day07_part2.py (2395B)


      1 #!/usr/bin/env python
      2 """Advent of Code 2021, day 7 (part 2): optimize 'crab submarine' alignment to
      3 minimize fuel"""
      4 
      5 # Well the actual code I wrote for part 1 isn't very useful here, but the
      6 # general approach works so that's cool I guess. It's probably faster to
      7 # evaluate the fuel use at every position than it is to deploy an optimizer to
      8 # solve the problem. HOWEVER, I'm using AoC to learn, so today I'm learning how
      9 # to use `scipy.optimize.minimize()`.
     10 
     11 from scipy.optimize import minimize
     12 from utils import get_puzzle_input, convert_int_line_to_series
     13 
     14 def find_minimum_fuel_use(positions):
     15     """Given the positions of the 'crab submarines', find the minimum fuel use
     16     to line them up"""
     17     # Unlike part 1, we need to actually work to find the solution. What's
     18     # interesting is that an optimization procedure found that, for the example
     19     # input, a position of 4.7 had better fuel use (167.55) than the example
     20     # solution of 5 (168). The problem doesn't explicitly state that only
     21     # integer solutions are allowed, but we'll round the result. Knowing this,
     22     # we can loose up the tolerances quite a bit, which gives us a nice
     23     # speed boost.
     24     best_position_solution = minimize(
     25         lambda x: calculate_fuel_use_to(positions, x[0]),
     26         0, method='nelder-mead', options={'xatol': 0.1, 'fatol': 0.01}
     27     )
     28     if not best_position_solution.success:
     29         raise RuntimeError('optimizer failed to find solution')
     30     return calculate_fuel_use_to(positions,
     31                                  float(best_position_solution.x[0].round()))
     32 
     33 def calculate_fuel_use_to(start_positions, end_position):
     34     """Given a series that represents the (1-D) positions of a collection of 'crab
     35     submarines', find the fuel used to move to the given end position"""
     36     # \sum_{i=1}^{n}i=\frac{n(n+1)}{2}
     37     return (
     38         start_positions
     39         .sub(end_position)
     40         .abs()
     41         .apply(lambda n: n*(n+1)/2)
     42         .sum()
     43     )
     44 
     45 def solve_puzzle(input_string):
     46     """Return the numeric solution to the puzzle"""
     47     return find_minimum_fuel_use(convert_int_line_to_series(input_string))
     48 
     49 def main():
     50     """Run when the file is called as a script"""
     51     assert solve_puzzle("16,1,2,0,4,2,7,1,2,14\n") == 168.0
     52     print("Actual fuel spent to align crabs:",
     53           f"{solve_puzzle(get_puzzle_input(7)):.0f}")
     54 
     55 if __name__ == "__main__":
     56     main()