def mutate(child1, child2, p, proti): """ Causes random mutations of the directions in the conformations. """ # convert to lists to make the tuple mutable child1_list = list(child1) child2_list = list(child2) for i in range(len(child1_list)): # random number (0,1) r1 = random.random() r2 = random.random() possible = False if r1 < p: # save copy backup = copy.deepcopy(child1_list[i]) # check if mutation would be possible without making the conformation invalid for d in random.sample(['L', 'R', 'S'], 3): child1_list[i] = d x, y, z = directions(''.join(child1_list)) if not double(x, y, z): possible = True break # undo the mutation if it would invalidate the conformation if not possible: child1_list[i] = backup possible = False if r2 < p: backup = copy.deepcopy(child2_list[i]) for d in random.sample(['L', 'R', 'S'], 3): child2_list[i] = d x, y, z = directions(''.join(child2_list)) if not double(x, y, z): possible = True break if not possible: child2_list[i] = backup child1_list = ''.join(child1_list) child2_list = ''.join(child2_list) # return the mutated conformations along with their scores x1, y1, z1 = directions(child1_list) x2, y2, z2 = directions(child2_list) return (child1_list, score_it(proti, x1, y1, z1)), \ (child2_list, score_it(proti, x2, y2, z2))
def create_individual(proti): conformation = '' direction = ['L', 'R', 'S'] i = 1 while i < proti.length - 1: available_directions = [] for d in direction: x_pos, y_pos, z_pos = directions(conformation + d) if not double(x_pos, y_pos, z_pos): available_directions.append(d) try: random_direction = available_directions[random.randint(0,\ len(available_directions) - 1)] conformation += random_direction except: i = 0 conformation = '' i += 1 return conformation
def crossover(parent1, parent2): """ Uses crossover to producre a child from two parents. """ # select point of adjoinment at random n = random.randint(0, len(parent1[0]) - 1) # get the part of one parent up to the nth element partial_parent1 = parent1[0][:n] # get the part of the other parent from the n+1th elment onwards partial_parent2 = parent2[0][n+1:] # try different adjoinment directions for d in random.sample(['L', 'R', 'S'],3): child_string = partial_parent1 + d + partial_parent2 x, y, z = directions(child_string) # return is the conformation is valid if not double(x, y, z): return child_string # indicate that the conformation is invalid return ('impossible', 1)
def run(proti): start = timeit.default_timer() # create a tree with all possible folds root = create_tree(proti) # extract all possible protein configurations from the tree route_directions = create_routes(root) # variables to hold the optimal solution best_x = [] best_y = [] lowest_score = 0 same_score_counter = 0 invalid_solutions = 0 # loop over each of the possible configurations for r in route_directions: pos_x = [] pos_y = [] x = 0 y = 0 # lists all filled with coordinates of the atoms for d in r: x += d[0] y += d[1] pos_x.append(x) pos_y.append(y) # discard all the structures that fold into themselves if double(pos_x, pos_y): invalid_solutions += 1 continue # calculate te score of the current structure current_score = score_con(pos_x, pos_y, proti) # save the structure with the best score if current_score < lowest_score: best_x = copy.deepcopy(pos_x) best_y = copy.deepcopy(pos_y) lowest_score = copy.deepcopy(current_score) same_score_counter = 0 if current_score == lowest_score: same_score_counter += 1 # plot the structure with the best score stop = timeit.default_timer() total_time = stop - start print(f'Length: {proti.length}') print(f'Score: {lowest_score}') print(f'Total runtime: {total_time}') print(f'Configuration:\nx:{best_x}\ny:{best_y}') tree_plot(best_x, best_y, lowest_score, proti) return total_time, lowest_score, [best_x, best_y]
def partial_score_func(nodes_visited, proti): """ Calculates the socre obtained by the nodes visit so far. """ pos_x, pos_y = nodes_to_xy(nodes_visited) # weed out the proteins folded into itself if double(list_x=pos_x, list_y=pos_y): return 1000 partial_score = score_it(proti=proti, list_x=pos_x, list_y=pos_y) return partial_score
def run(proti): # start timing code start = timeit.default_timer() # create the initial straight string pos_x = [i for i in range(proti.length)] pos_y = [0] * proti.length rotation_counter = 0 N = 5000 # initialize progress bar bar = Bar('Progress', max=N/1000) # lists to keep track of the scores of each rotation and remember the one # with the best score lowest_score = 0 best_x = [] best_y = [] scores = [] # probability functions depens on temperature and the boltzmann constant, # can be set to their actual values if you want to be physically responsible boltzmann = 1 prob = [] # loop that keeps folding the protein while rotation_counter < N: _max = 10 scale = N/20 center = 9 * N / 40 temperature = _max / (1 + math.exp((rotation_counter - center) / scale)) + 0.5 # a copy is made in case the fold is invalid or unfavourable log_pos_x = copy.deepcopy(pos_x) log_pos_y = copy.deepcopy(pos_y) # protein is folded randomly rotating_amino = random.randint(0, proti.length - 1) random_rotation_xy(list_x=pos_x, list_y=pos_y, n=rotating_amino, proti=proti) # check whether the protein has not folded onto itself if double(pos_x, pos_y): # if it is folded wrongly, restore to the previous configuration pos_x = log_pos_x pos_y = log_pos_y continue # calculate the scores of the old and new structure old_score = score_it(proti=proti, list_x=log_pos_x, list_y=log_pos_y) new_score = score_it(proti=proti, list_x=pos_x, list_y=pos_y) # keep track of each score scores.append(old_score) # if a score beats the old one, remember that structure if new_score < lowest_score: best_x = copy.deepcopy(pos_x) best_y = copy.deepcopy(pos_y) lowest_score = copy.deepcopy(new_score) # probability function to determine whether a fold will be 'accepted' p = math.exp(-(new_score - old_score)/(temperature * boltzmann)) # the treshhold for acceptance varies and is randomly determined treshhold = random.random() if p < treshhold: pos_x = log_pos_x pos_y = log_pos_y rotation_counter += 1 # print statement for time indication in long calculations if rotation_counter % 1000 == 0: bar.next() prob.append(p) bar.finish() stop = timeit.default_timer() print('Runtime:', stop - start, 'seconds') # the best structure is copied to a csv file and shown in a graph output(proti=proti, score=lowest_score, list_x=best_x, list_y=best_y) plot(proti, lowest_score, best_x, best_y, 'Score after rotation', 'Rotation', 'Score', scores=scores)
def run(proti): # create the initial straight string pos_x = [i for i in range(proti.length)] pos_y = [0] * proti.length pos_z = [0] * proti.length # number of iterations, higher N is better result N = 1000 rotation_counter = 0 # lists to keep track of the scores and best configuration lowest_score = 0 best_x = [] best_y = [] best_z = [] scores = [] # probability functions depens on temperature and the boltzmann constant, # can be set to their actual values if you want to be physically responsible temperature = 1 boltzmann = 1 # loop that keeps folding the protein while rotation_counter < N: # a copy is made in case the fold is invalid or unfavourable log_pos_x = copy.deepcopy(pos_x) log_pos_y = copy.deepcopy(pos_y) log_pos_z = copy.deepcopy(pos_z) # protein is folded randomly rotating_amino = random.randint(0, proti.length - 1) random_rotation_xyz(list_x=pos_x, list_y=pos_y, list_z=pos_z, n=rotating_amino, proti=proti) # check whether the protein has not folded onto itself if double(pos_x, pos_y, pos_z): # if it is folded wrongly, restore to the previous configuration pos_x = log_pos_x pos_y = log_pos_y pos_z = log_pos_z continue # calculate the scores of the old and new structure old_score = score_it(proti, log_pos_x, log_pos_y, log_pos_z) new_score = score_it(proti, pos_x, pos_y, pos_z) # keep track of each score scores.append(old_score) # if a score beats the old one, remember that structure if new_score < lowest_score: best_x = copy.deepcopy(pos_x) best_y = copy.deepcopy(pos_y) best_z = copy.deepcopy(pos_z) lowest_score = copy.deepcopy(new_score) # probability function to determine whether a fold will be 'accepted' p = math.exp(-(new_score - old_score) / (temperature * boltzmann)) # the treshhold for acceptance varies and is randomly determined treshhold = random.random() if p < treshhold: pos_x = log_pos_x pos_y = log_pos_y pos_z = log_pos_z rotation_counter += 1 # print statement for time indication in long calculations if rotation_counter % 1000 == 0: print(f'{rotation_counter / N * 100}%') # the best structure is copied to a csv file and shown in a graph output(proti, lowest_score, best_x, best_y, best_z) plot(proti, lowest_score, best_x, best_y, 'Score after rotation', 'Rotation', 'Score', best_z, scores=scores)
def run(proti): # start timing the run of the code start = timeit.default_timer() # initialize the protein in a straight configuration x = [i for i in range(proti.length)] y = [0] * proti.length # high number of iterations for optimising result iterations = 1000 rotations = 0 # initialize progress bar bar = Bar('Progress', max=iterations / 1000) # list to keep track of best configuration and scores lowest_score = 0 best_x = [] best_y = [] scores = [] # set start temperature for annealing start_temp = 100 # at the start no local optimum found local_optimum = False local_optimum_rotation = 0 while rotations < iterations: # remember the previous configuration backup_x = copy.deepcopy(x) backup_y = copy.deepcopy(y) # remember the previous score old_score = score_it(list_x=backup_x, list_y=backup_y, proti=proti) scores.append(old_score) # fold protein at a random amino rotating_amino = random.randint(0, proti.length - 1) random_rotation_xy(list_x=x, list_y=y, n=rotating_amino, proti=proti) # if protein folded into itself restore and go back if double(list_x=x, list_y=y): x = backup_x y = backup_y continue # get the score of the current configuration new_score = score_it(list_x=x, list_y=y, proti=proti) if rotations % 1000 == 0 and new_score == old_score: local_optimum = True if local_optimum == True: local_optimum_rotation = rotations local_optimum = False # drop temperature gradually temperature = start_temp * (0.997 **(rotations - local_optimum_rotation)) acceptance_chance = 2**(-(new_score - old_score) / temperature) treshhold = random.random() # if new score is worse and it can't be accepted restore backup if new_score > old_score and acceptance_chance > treshhold: x = backup_x y = backup_y new_score = old_score # check if a lower score is found and remember configuration if new_score < lowest_score: best_x = copy.deepcopy(x) best_y = copy.deepcopy(y) lowest_score = copy.deepcopy(new_score) rotations += 1 # continue the progress bar every thousand configurations if rotations % 1000 == 0: bar.next() # finish the progress bar and th timer and show information bar.finish() stop = timeit.default_timer() print('Runtime', stop - start, 'seconds') # render the output and plot the figure output(list_x=best_x, list_y=best_y, score=lowest_score, proti=proti) plot(proti, lowest_score, best_x, best_y, 'Score after rotation', 'Rotation', 'Score', scores=scores) return stop - start, lowest_score, [best_x, best_y]
def run(proti): length = proti.length protein = proti.listed lowest_known_score = 0 if protein == 'HHPHHHPH': lowest_known_score = -3 if protein == 'HHPHHHPHPHHHPH': lowest_known_score = -6 if protein == 'HPHPPHHPHPPHPHHPPHPH': lowest_known_score = -9 if protein == 'PPPHHPPHHPPPPPHHHHHHHPPHHPPPPHHPPHPP': lowest_known_score = -14 # initiate a progress bar bar = Bar('Progress', max=length) start = timeit.default_timer() # direction of atom relative to the previous ones directions = ['L', 'R', 'S'] # variables to keep track of the optimal configuration best_score = 0 best_x = [] best_y = [] loop_time = [] keep_going = True # loop for constructing the tree for i, p in enumerate(protein): loop_start = timeit.default_timer() # stops when the remaining atoms can't add to the score if keep_going: lowest_score_k = 0 average_scores_k = [0] potential_score = 0 # keep track of how many nodes are added per depth level breadth_counter = 0 # place the first atom # intermezzo: the loop relies heavily on the globals()[...] function # it creates a transforms a string into an actual variable and allows # for dynamic naming and referencing of the nodes if i == 0: name = f'{p}{i}{i}' globals()[name] = Node('start') bar.next() continue # second atom is placed in one direction only, others would merely # be a rotation around the axis with no score advantage if i == 1: name = f'{p}{i}{i}' globals()[name] = Node('R', parent=globals()[f'{protein[0]}00']) bar.next() continue # determines how many nodes there are in in the previous tree layer parent_counter = len(list(PreOrderIter(globals()[f'{protein[0]}00'],\ filter_=lambda node: node.is_leaf))) # for each node, create a new one in every direction for j in range(len(directions) * parent_counter): # get the name of the parent node parent = f'{protein[i - 1]}{i - 1}{j}' # see if that parent exists, if not its score was too high and # no further node along that branch has to be made try: globals()[parent] except: continue # if it exist construct a new node for each direction for d in directions: # name of the node to be name = f'{p}{i}{breadth_counter}' # see which nodes are visited already to get to this point in the tree nodes_visited = [ node.name for node in globals()[parent].path ] # the atoms still to be placed nodes_to_visit = protein[i:] potential_nodes_visited = copy.deepcopy(nodes_visited) potential_nodes_visited.append(d) # score of the protien so far partial_score = partial_score_func(nodes_visited, proti) # lowest score possible given the remaining atoms possible_score = possible_score_func_dee(nodes_to_visit,\ partial_score,\ lowest_known_score,\ proti) if partial_score < lowest_known_score: lowest_known_score = copy.deepcopy(partial_score) # new node is only made if it is possible to get a score # lower than the lowest known score if partial_score + possible_score >= lowest_known_score \ and possible_score != 0: breadth_counter += 1 continue potential_score = partial_score_func( potential_nodes_visited, proti) # create new node globals()[name] = Node(d, parent=globals()[parent]) breadth_counter += 1 average_scores_k.append(potential_score) if potential_score < lowest_score_k: lowest_score_k = copy.deepcopy(potential_score) # this part only runs when the last atom is placed or the # remaining atoms can't add to the score if i == length - 1 or possible_score == 0: # determine the path of the string nodes_visited = [ node.name for node in globals()[name].path ] pos_x, pos_y = nodes_to_xy(nodes_visited) # disregard if it folds onto itself if double(list_x=pos_x, list_y=pos_y): continue # get the structures score current_score = score_it(list_x=pos_x, list_y=pos_y, proti=proti) # save if it is an improvement if current_score <= best_score: best_x = copy.deepcopy(pos_x) best_y = copy.deepcopy(pos_y) best_score = copy.deepcopy(current_score) # stop running if the remaining atoms can't add to the score if possible_score == 0: keep_going = False # log how long each iteration takes loop_stop = timeit.default_timer() loop_time.append(loop_stop - loop_start) # update the progress bar bar.next() # stop the progress bar and timer bar.finish() stop = timeit.default_timer() runtime = stop - start # print some interesting information and plot result print(f'Length: {proti.length}') print(f'Score: {best_score}') print(f'Time: {runtime}') print(f'Conformation: \nx:{best_x}\ny:{best_y}') if len(nodes_to_visit) > 1: print(f'{nodes_to_visit[1:]} are not placed since they would not \ add to the score') if len(best_x) == 0: print('No stable solution') else: dee_plot(best_x, best_y, best_score, loop_time, runtime, proti) return runtime, best_score, [best_x, best_y]
def run(proti): # start timing script run time start = timeit.default_timer() # pruning chance, higher values mean a faster program but the results may not be as good p1 = 0.99 p2 = 0.98 # initialize progress bar bar = Bar('Progress', max=proti.length) bar.next() bar.next() k = 0 amino_time = [] # specifications for breadth first tree building depth = proti.length - 2 q = queue.Queue() q.put(('', 0)) final_configurations = [] # keep track of scores per substring lowest_score_k = {} all_scores_k = {} lowest_score = 0 # set inital values for i in range(proti.length + 1): lowest_score_k[i] = 0 all_scores_k[i] = [0] amino_start = timeit.default_timer() # create a breadth first tree while not q.empty(): state = q.get() # if all aminos are placed, put the string in a list state_x, state_y = direction_to_xy(state[0]) if len(state[0]) == depth and not double(state_x, state_y): final_configurations.append(state) if len(state[0]) < depth: for i in ['L', 'R', 'S']: child = copy.deepcopy(state) temp_list = list(child) temp_list[0] += i child = tuple(temp_list) child_x, child_y = direction_to_xy(child[0]) if double(child_x, child_y): continue if len(child[0]) + 1 > k: amino_stop = timeit.default_timer() amino_time.append(amino_stop - amino_start) bar.next() amino_start = timeit.default_timer() k = len(child[0]) + 1 # P's are always placed, rest have some conditions if not proti.listed[k] == 'P': score = child[1] + score_con(child_x, child_y, proti) possible_score = possible_score_func_dee(proti.listed[k+1:], \ score, proti.min_score, proti) if score + possible_score > lowest_score: continue # random number between 0 and 1 r = random.random() average_score_k = sum(all_scores_k[k]) / len( all_scores_k[k]) # conditions for pruning if score > average_score_k and r < p1: continue elif (average_score_k >= score and score > lowest_score_k[k])\ and r < p2: continue temp_list = list(child) temp_list[1] = score child = tuple(temp_list) # add to tree q.put(child) all_scores_k[k].append(score) if score < lowest_score_k[k]: lowest_score_k[k] = copy.deepcopy(score) if score < lowest_score: lowest_score = copy.deepcopy(score) else: q.put(child) if len(final_configurations) == 0: bar.finish() print('No conformations found.') return lowest_score = 0 # weed out the best configuration from the remaining strings for c in final_configurations: if c[1] < lowest_score: best_config = copy.deepcopy(c[0]) lowest_score = copy.deepcopy(c[1]) best_x, best_y = direction_to_xy(best_config) # plot the result stop = timeit.default_timer() total_time = stop - start bar.finish() print(f'Length: {proti.length}') print(f'Score: {lowest_score}') print(f'Time: {total_time}') print(f'Conformation: {best_config}') plot(proti, lowest_score, best_x, best_y, 'Time per amino placement', 'Amino', 'Time[s]', scores=amino_time) return total_time, lowest_score, best_config
def run(proti): # start timing the run of the code start = timeit.default_timer() # initialize the protein in a straight configuration x = [i for i in range(proti.length)] y = [0] * proti.length z = [0] * proti.length # high number of iterations for optimising result iterations = 50000 rotations = 0 # initialize progress bar bar = Bar('Progress', max=iterations / 1000) # list to keep track of best configuration and scores lowest_score = 0 best_x = [] best_y = [] best_z = [] scores = [] # fold protein for number of iterations while rotations < iterations: # remember the previous configuration backup_x = copy.deepcopy(x) backup_y = copy.deepcopy(y) backup_z = copy.deepcopy(z) # remember the previous score old_score = score_it(list_x=backup_x, list_y=backup_y, list_z=backup_z, proti=proti) scores.append(old_score) # fold protein at a random amino rotating_amino = random.randint(0, proti.length - 1) random_rotation_xyz(list_x=x, list_y=y, list_z=z, n=rotating_amino, proti=proti) # if protein folded into itself restore and go back if double(list_x=x, list_y=y): x = backup_x y = backup_y z = backup_z continue # get the score of the current configuration new_score = score_it(list_x=x, list_y=y, list_z=z, proti=proti) # if the new score is worse set configuration back if new_score > old_score: x = backup_x y = backup_y z = backup_z new_score = old_score # check if a lower score has been found and remember if new_score < lowest_score: best_x = copy.deepcopy(x) best_y = copy.deepcopy(y) best_z = copy.deepcopy(z) lowest_score = copy.deepcopy(new_score) rotations += 1 # continue the progress bar every thousand configuration if rotations % 1000 == 0: bar.next() # finish the progress bar and the timer and show information bar.finish() stop = timeit.default_timer() print('Runtime:', stop - start, 'seconds') # render the output and plot the figure output(list_x=best_x, list_y=best_y, list_z=best_z, score=lowest_score, proti=proti) plot(proti, lowest_score, best_x, best_y, 'Score after rotation', 'Rotation', 'Score', best_z, scores=scores)
def run(proti): # start timer start = timeit.default_timer() bar = Bar('Progress', max=proti.length) bar.next() bar.next() k = 0 amino_time = [] # specifications for depth first tree building depth = proti.length - 2 q = queue.Queue() q.put('') final_configurations = [] # keep track of scores per substring lowest_score_k = {} all_scores_k = {} lowest_score = 0 # (0,1) probabilities of pruning a path, lower is more exact but less fast p1 = 1 p2 = 1 # set inital values for i in range(proti.length + 1): lowest_score_k[i] = 0 all_scores_k[i] = [0] amino_start = timeit.default_timer() # create a breadth first tree while not q.empty(): state = q.get() # if all aminos are placed, put the string in a list state_x, state_y, state_z = directions(state) if len(state) == depth and not double(state_x, state_y, state_z): final_configurations.append(state) if len(state) < depth: for i in ['L', 'R', 'S', 'U', 'D']: # substring child = copy.deepcopy(state) # string after potentialy placing the next amino child += i child_x, child_y, child_z = directions(child) # discard the string folding into themselves if double(child_x, child_y, child_z): continue if len(child) + 1 > k: amino_stop = timeit.default_timer() amino_time.append(amino_stop-amino_start) bar.next() amino_start = timeit.default_timer() # identify how for into the string it is k = len(child) + 1 # P's are always placed, rest have some conditions if not proti.listed[k] == 'P': # score if placed score = score_it(proti, child_x, child_y, child_z) # min score to get from remaining aminos possible_score = possible_score_func_dee(proti.listed[k+1:],\ score, proti.min_score, proti) if score + possible_score > lowest_score: continue # random number between 0 and 1 r = random.random() # avergage of all strings of the same length average_score_k = sum(all_scores_k[k]) / len(all_scores_k[k]) # conditions for pruning if score > average_score_k and r < p1: continue elif (average_score_k >= score and score > lowest_score_k[k])\ and r < p2: continue # add to tree q.put(child) all_scores_k[k].append(score) if score < lowest_score_k[k]: lowest_score_k[k] = copy.deepcopy(score) if score < lowest_score: lowest_score = copy.deepcopy(score) else: q.put(child) lowest_score = 0 # weed out the best configuration from the remaining strings for c in final_configurations: c_x, c_y, c_z = directions(c) if score_it(proti, c_x, c_y, c_z) < lowest_score: best_config = copy.deepcopy(c) lowest_score = copy.deepcopy(score_it(proti, c_x, c_y, c_z)) best_x, best_y, best_z = directions(best_config) bar.finish() # plot the result stop = timeit.default_timer() print(f'Length: {proti.length}') print(f'Score: {lowest_score}') print(f'Time: {stop - start}') print(f'Conformation: {best_config}') plot(proti, lowest_score, best_x, best_y,'Time per amino placement', 'Amino', 'Time[s]', best_z, scores=amino_time)
def run(proti): length = proti.length strings_created = 0 lowest_score = 0 population = 100 N = 300 routes = [] best_route = [] best_yet = [] start = timeit.default_timer() bar = Bar('Progress', max=N / 100) # create a swarm of protein strings that dont fold into themselves while strings_created < population: route = [] for i in range(length - 2): route.append(['S', 'L', 'R'][random.randint(0, 2)]) route_x, route_y = direction_to_xy(route) if not double(route_x, route_y): routes.append(route) strings_created += 1 for k in range(N): # determine which route has the lowest score for r in routes: r_x, r_y = direction_to_xy(r) while double(r_x, r_y): route = [] for i in range(length - 2): route.append(['S', 'L', 'R'][random.randint(0, 2)]) route_x, route_y = direction_to_xy(route) if not double(route_x, route_y): r = route r_x, r_y = direction_to_xy(r) score = score_it(proti, r_x, r_y) if score <= lowest_score: lowest_score = copy.deepcopy(score) best_route = copy.deepcopy(r) best_yet.append(score) # bend the rest so as to be more like the best score for r in routes: log_r = copy.deepcopy(r) r_x, r_y = direction_to_xy(r) b_r_x, b_r_y = direction_to_xy(best_route) before = similarities(r_x, r_y, b_r_x, b_r_y) invalid = True for i in range(10): while invalid: r[random.randint(0, len(r) - 1)] = \ ['S', 'L', 'R'][random.randint(0,2)] r_x, r_y = direction_to_xy(r) if not double(r_x, r_y): invalid = False r_x, r_y = direction_to_xy(r) after = similarities(r_x, r_y, b_r_x, b_r_y) if after < before: r = log_r if k % 100 == 0: bar.next() pos_x, pos_y = direction_to_xy(best_route) score = score_it(proti, pos_x, pos_y) bar.finish() stop = timeit.default_timer() runtime = stop - start route_string = ''.join(best_route) print(f'Length: {proti.length}') print(f'Score: {score}') print(f'Time: {runtime}') print(f'Conformation: {route_string}') plot(proti, score, pos_x, pos_y, 'Lowest score', 'Iteration', 'Score', scores=best_yet) return runtime, score, route_string