Advertisement
Guest User

Untitled

a guest
Dec 16th, 2022
489
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.06 KB | Source Code | 0 0
  1. import pandas as pd
  2. import numpy as np
  3. from copy import deepcopy
  4. with open('./data/01.txt', 'r') as f:
  5.     data = [line.strip() for line in f.readlines()]
  6.  
  7.  
  8. # %%
  9. # extract all need data from line
  10. def interpret_line(line: str):
  11.     valvepart, tunnelpart = line.split(';')
  12.     valvepart, flowrate = valvepart.split('=')
  13.     flowrate = int(flowrate)
  14.     valve = valvepart.split(' ')[1].strip()
  15.     tunnels = tunnelpart.split(',')
  16.     connected = [tunnels[0][-2:].strip(), *[t.strip() for t in tunnels[1:]]]
  17.     return valve, flowrate, connected
  18.  
  19.  
  20. # %%
  21. # save data in DataFrame
  22. df = pd.DataFrame(columns=['Valve', 'Flowrate', 'Connected'])
  23. for line in data:
  24.     df = df.append(
  25.             pd.Series(
  26.                 interpret_line(line),
  27.                 index=['Valve', 'Flowrate', 'Connected']),
  28.             ignore_index=True)
  29.  
  30.  
  31. # %%
  32. def build_adjacency_matrix(df: pd.DataFrame) -> pd.DataFrame:
  33.     # build adjacency matrix by exploding and pivoting dataframe
  34.     index_dict = {}
  35.     for key, val in dict(df.Valve).items():
  36.         index_dict[val] = key
  37.     df_exploded = df.explode(column='Connected')
  38.     df_exploded.drop(columns=['Flowrate'], inplace=True)
  39.     df_exploded['temp_vals'] = 1
  40.     df_exploded.Connected = df_exploded.Connected.str.strip().map(index_dict)
  41.     mat = df_exploded.pivot(
  42.         columns=['Connected'],
  43.         values=['temp_vals'])
  44.     mat = mat.fillna(0)
  45.     mat[mat.columns] = mat[mat.columns].astype(int)
  46.     return mat
  47.  
  48.  
  49. # %%
  50. def build_distance_matrix_from_adjacency(A: np.ndarray) -> np.ndarray:
  51.     # using the fact that A@A returns a matrix that contains
  52.     # the number of ways to move from i to j in 2 steps
  53.     # repeated use of this rule and saving the lowest steps for which
  54.     # you can get from i to j gives distance matrix
  55.     retA = A.copy()
  56.     newA = A.copy()
  57.     # have to set unknown values to "infinity" for np.minimum
  58.     retA[retA == 0] = 10000
  59.     for n in range(2, A.shape[0]):
  60.         newA = newA@A
  61.         mathA = newA.copy()
  62.         # values that are not 0 are reacheable with n steps in at least one way
  63.         # replace all non-zero values with n
  64.         mathA[mathA != 0] = n
  65.         # have to set unknown values to "infinity" for np.minimum
  66.         mathA[mathA == 0] = 10000
  67.         retA = np.minimum(retA, mathA)  # keep minimum known n for each entry
  68.     np.fill_diagonal(retA, 0)
  69.     return retA
  70.  
  71.  
  72. # %%
  73. # build adjacency matrix from dataframe
  74. mat = build_adjacency_matrix(df).to_numpy()
  75.  
  76. # build distance matrix from adjacency matrix
  77. dist = build_distance_matrix_from_adjacency(mat)
  78.  
  79.  
  80. # %%
  81. # ------ PUZZLE 16-01 ------
  82. # calculate the total flow for one path
  83. def calculate_total_flow_for_path(path: list[int], dist, df):
  84.     minutes = 30  # minutes until volcano explodes
  85.     # find index of valve AA
  86.     cur_pos = df.Valve[df.Valve == 'AA'].index.tolist()[0]
  87.     total_flow = 0
  88.     # run through path and calculate pressure release for each node
  89.     for node in path:
  90.         minutes -= dist[cur_pos, node]  # subtract minutes for traveling
  91.         minutes -= 1  # subtract minutes for opening valve
  92.         # break loop if all minutes used up
  93.         if minutes < 0:
  94.             break
  95.         cur_pos = node  # update current position after traveling
  96.         # add flow of opened valve to total flow
  97.         total_flow += df.Flowrate.iloc[node] * minutes
  98.     return total_flow
  99.  
  100.  
  101. # list of valves with flowrate > 0
  102. potential_valves = df[df.Flowrate != 0].index.tolist()
  103. path = np.random.permutation(potential_valves)  # start with random permutation
  104.  
  105. # calculate current pressue released, use negative of
  106. # pressure release to optimize via minimization
  107. old_E = -calculate_total_flow_for_path(path, dist, df)
  108.  
  109. # #---# SIMULATED ANNEALING #---#
  110. Temp = 100  # start temp
  111. Tlow = 0.01  # stop temp
  112. Steps = 5  # thermalization steps at each temp
  113. dT = -0.01  # temp change with every while loop step
  114. lowest_path = deepcopy(path)
  115. count = 0  # while loop counter, used for printing annealing info
  116. # annealing loop
  117. while Temp > Tlow:
  118.     # thermalization loop
  119.     for n in range(Steps):
  120.         new_path = path.copy()
  121.         # choose valves i and j to swap in path
  122.         i = np.random.randint(len(potential_valves))
  123.         j = np.random.randint(len(potential_valves))
  124.         while j == i:
  125.             j = np.random.randint(len(potential_valves))
  126.         new_path[[i, j]] = new_path[[j, i]]
  127.  
  128.         # calculate pressure released for new path
  129.         new_E = -calculate_total_flow_for_path(new_path, dist, df)
  130.         dE = new_E - old_E
  131.         if dE <= 0.0:
  132.             # if new path releases more pressure make new path the
  133.             # reference path for next step in loop
  134.             path = deepcopy(new_path)
  135.             old_E = deepcopy(new_E)
  136.             # new pressure release better than best known pressure release
  137.             # save current path as best path
  138.             best_E = -calculate_total_flow_for_path(lowest_path, dist, df)
  139.             if (new_E - best_E < 0):
  140.                 lowest_path = deepcopy(new_path)
  141.         elif np.exp(-dE/Temp) > np.random.random():
  142.             # if new path worse than last path still change to new path
  143.             # with probability proportional to boltzmann distribution
  144.             path = deepcopy(new_path)
  145.             old_E = deepcopy(new_E)
  146.     count += 1
  147.     if count % 100 == 0:
  148.         print(
  149.             Temp,
  150.             new_E,
  151.             old_E,
  152.             dE,
  153.             new_path,
  154.             path,
  155.             lowest_path,
  156.             calculate_total_flow_for_path(lowest_path, dist, df))
  157.     Temp += dT
  158.  
  159. print(
  160.     "Most pressue Part 1:",
  161.     calculate_total_flow_for_path(lowest_path, dist, df))
  162.  
  163.  
  164. # %%
  165. # ------ PUZZLE 16-02 ------
  166. # calculate the total flow for two paths
  167. def calculate_total_flow_for_paths(
  168.         path0: list[int],
  169.         path1: list[int],
  170.         dist,
  171.         df):
  172.     total_flow = 0
  173.     for path in [path0, path1]:
  174.         minutes = 26  # minutes left at beginning
  175.         # starting position at valve AA
  176.         cur_pos = df.Valve[df.Valve == 'AA'].index.tolist()[0]
  177.         # calculate total flow for path
  178.         total_flow_path = 0
  179.         for node in path:
  180.             minutes -= dist[cur_pos, node]  # minutes used traveling
  181.             minutes -= 1  # minutes used opening valve
  182.             # break loop if all minutes are used up
  183.             if minutes < 0:
  184.                 break
  185.             cur_pos = node  # change current position to node
  186.             # add flow of valve to total flow
  187.             total_flow_path += df.Flowrate.iloc[node] * minutes
  188.         total_flow += total_flow_path  # add up flow for both paths
  189.  
  190.     return total_flow
  191.  
  192.  
  193. # %%
  194. # only get valves with flow rate higher than 0
  195. potential_valves = df[df.Flowrate != 0].index.tolist()
  196. path = np.random.permutation(potential_valves)  # start with random path
  197. # randomly distribute valves between path of elefant and path
  198. # of person
  199. choice = np.random.choice(
  200.     range(path.shape[0]),
  201.     size=int(len(path)/2),
  202.     replace=False)    
  203. ind = np.zeros(path.shape[0], dtype=bool)
  204. ind[choice] = True
  205.  
  206. path_p = list(path[ind])  # path of person
  207. path_e = list(path[~ind])  # path of elefant
  208.  
  209. # calculate current pressue released, use negative of
  210. # pressure release to optimize via minimization
  211. old_E = -calculate_total_flow_for_paths(path_p, path_e, dist, df)
  212.  
  213. # #---# SIMULATED ANNEALING #---#
  214. Temp = 100  # start temp
  215. Tlow = 0.01  # stop temp
  216. Steps = 5  # thermalization steps
  217. dT = -0.01  # temp change after thermalization
  218. # store paths with lowest know "negative pressure release"
  219. lowest_path_p = deepcopy(path_p)
  220. lowest_path_e = deepcopy(path_e)
  221. count = 0  # used to print info every x steps of while loop
  222. # annealing loop
  223. while Temp > Tlow:
  224.     # thermalization loop
  225.     for n in range(Steps):
  226.         new_path_p = deepcopy(path_p)
  227.         new_path_e = deepcopy(path_e)
  228.         # metropolis update
  229.         start_list = np.random.randint(2)  # which list to start from
  230.         # should the valve in question swap lists?
  231.         change_list = np.random.randint(2)
  232.         if change_list:
  233.             # randomly choose item position i in start_list to take item from
  234.             # and item position j in other list to insert item to
  235.             if start_list == 0:
  236.                 i = np.random.randint(len(new_path_p))
  237.                 j = np.random.randint(len(new_path_e))
  238.                 num = new_path_p.pop(i)
  239.                 new_path_e.insert(j, num)
  240.             else:
  241.                 i = np.random.randint(len(new_path_e))
  242.                 j = np.random.randint(len(new_path_p))
  243.                 num = new_path_e.pop(i)
  244.                 new_path_p.insert(j, num)
  245.         else:
  246.             # randomly choose item position i in list start_list to take item
  247.             # from and item position j in same list to insert item to
  248.             if start_list == 0:
  249.                 i = np.random.randint(len(new_path_p))
  250.                 j = np.random.randint(len(new_path_p))
  251.                 num = new_path_p.pop(i)
  252.                 new_path_p.insert(j, num)
  253.             else:
  254.                 i = np.random.randint(len(new_path_e))
  255.                 j = np.random.randint(len(new_path_e))
  256.                 num = new_path_e.pop(i)
  257.                 new_path_e.insert(j, num)
  258.  
  259.         # calculate pressure released with new paths
  260.         new_E = -calculate_total_flow_for_paths(
  261.             new_path_p,
  262.             new_path_e,
  263.             dist,
  264.             df)
  265.         # check if new paths create lower negative pressure release
  266.         # (-> release more pressure than old path)
  267.         dE = new_E - old_E
  268.         if dE <= 0.0:
  269.             # if new paths release more pressure, make these paths
  270.             # the current paths for next iteration
  271.             path_p = deepcopy(new_path_p)
  272.             path_e = deepcopy(new_path_e)
  273.             old_E = deepcopy(new_E)
  274.             # if pressure release better than lowest know solution
  275.             # update lowest solution
  276.             lowest_E = -calculate_total_flow_for_paths(
  277.                 lowest_path_p, lowest_path_e, dist, df)
  278.             if (new_E - lowest_E < 0):
  279.                 lowest_path_p = deepcopy(new_path_p)
  280.                 lowest_path_e = deepcopy(new_path_e)
  281.         elif np.exp(-dE/Temp) > np.random.random():
  282.             # if new paths don't release more pressure still update these
  283.             # paths to current paths for next iteration with probabilities
  284.             # based on boltzman weights
  285.             path_e = deepcopy(new_path_e)
  286.             path_p = deepcopy(new_path_p)
  287.             old_E = deepcopy(new_E)
  288.     count += 1
  289.     # print annealing results
  290.     if count % 100 == 0:
  291.         print(
  292.             Temp,
  293.             new_E,
  294.             old_E,
  295.             dE,
  296.             lowest_path_e,
  297.             lowest_path_p,
  298.             calculate_total_flow_for_paths(
  299.                 lowest_path_e,
  300.                 lowest_path_p,
  301.                 dist,
  302.                 df))
  303.     Temp += dT
  304.  
  305. print(
  306.     "Most pressue:",
  307.     calculate_total_flow_for_paths(
  308.         lowest_path_e,
  309.         lowest_path_p,
  310.         dist,
  311.         df))
  312.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement