def optimal_maneuver_from_saved_setup(saved_setup_file, **kwargs): """ Retrieves the inputs specifying the maneuver setup (velocity, detection position, etc), not the specific attributes (radius, thrusts, etc) of the particular maneuver object saved. Calculates those anew instead. Any maneuver input can be altered by passing it as a keyword argument. """ with open(saved_setup_file) as f: loaded = json.load(f) fish = ManeuveringFish( kwargs.get('fork_length', loaded['fish_fork_length']), kwargs.get('mean_water_velocity', loaded['mean_water_velocity']), kwargs.get('base_mass', loaded['fish_base_mass']), kwargs.get('temperature', loaded['fish_temperature']), kwargs.get('SMR', loaded['fish_SMR']), kwargs.get('max_thrust', loaded['fish_max_thrust']), kwargs.get('NREI', loaded['fish_NREI']), kwargs.get('use_total_cost', loaded['fish_use_total_cost']), kwargs.get('disable_wait_time', loaded['fish_disable_wait_time'])) return optimal_maneuver(fish, n=kwargs.get('n', DEFAULT_OPT_N), max_iterations=kwargs.get('max_iterations', DEFAULT_OPT_ITERATIONS), detection_point_3D=kwargs.get( 'detection_point_3D', (loaded['det_x'], loaded['det_y'], 0)), tracked=True, return_optimization_model=True)
def typical_maneuver(species, **kwargs): fish = kwargs.get('modified_fish', typical_fish[species]) default_detection_point_3D = (-typical[species]['detection_distance'] / 1.414, typical[species]['detection_distance'] / 1.414, 0.0) detection_point_3D = kwargs.get('modified_detection_point_3D', default_detection_point_3D) return optimize.optimal_maneuver(fish, detection_point_3D=detection_point_3D, **kwargs)
def check_johansen_et_al(display=False, suppress_output=False): # Checks maneuver cost predictions against empirical measurements from Johansen et al # It looks like they had costs per maneuver of 1.28 mg O2 / kg for freestream swimming and 0.78 for refuge swimming, # both at a temperature of 15 C and velocity of 68 cm/s, with rainbow trout of size 33 cm and 423 g. With the width x # depth of the tube being 25 x 26 cm, we might assume the average detected prey's lateral distance was around 15 cm or # so, and I'll assume prey were detected fairly early (say 30 cm upstream) on average. fork_length = 33 mean_water_velocity = 68 xd = -30 yd = 15 max_thrust = 2.4 * fork_length + 40 fish_mass = 423 # this input defaults the model to a rainbow trout length-mass regression fish_SMR = 0.0 # default to sockeye SMR for given temperature fish_NREI = 0.0 # unused in this case temperature = 15 # only for SMR use_total_cost = False disable_wait_time = False fish = maneuveringfish.ManeuveringFish(fork_length, mean_water_velocity, fish_mass, temperature, fish_SMR, max_thrust, fish_NREI, use_total_cost, disable_wait_time) detection_point_3D = (xd, yd, 0.0) maneuver = optimize.optimal_maneuver(fish, detection_point_3D=detection_point_3D) visualize.summarize_solution(maneuver, display=display, title='Cost Table Check', export_path=None, detailed=True) joulesPerMgO2 = 3.36 * 4.184 costMgO2 = maneuver.activity_cost / joulesPerMgO2 costMgO2PerKG = costMgO2 / (fish_mass / 1000) print("Maneuver cost of locomotion alone is ", costMgO2PerKG, "mg O2 / kg") print("Focal swimming cost including SMR in is ", 3600 * (fish.focal_swimming_cost_including_SMR / joulesPerMgO2), " mg O2 / kg / hr" ) # convert from focal_swimming_cost_including_SMR in J/s SMR_per_hour = 3600 * (fish.SMR_J_per_s / joulesPerMgO2) / (fish_mass / 1000) print("SMR per hour is ", SMR_per_hour, " mg O2 / kg / hr" ) # convert from focal_swimming_cost_including_SMR in J/s print("Cost of SMR during maneuver is ", SMR_per_hour * maneuver.duration / 3600) print("Locomotion cost for focal swimming for duration of maneuver is ", (maneuver.duration * fish.focal_swimming_cost_of_locomotion / joulesPerMgO2) / (fish_mass / 1000), " mg O2 / kg") total_cost_during_maneuver = costMgO2PerKG + SMR_per_hour * maneuver.duration / 3600 print( f"MAIN COMPARISON: estimated cost (locomotion + SMR) of maneuver is {total_cost_during_maneuver} mg O2 / kg, compared to Johansen's 1.28 mg O2 / kg" ) return maneuver, fish
def check_cost_table_value(fork_length, mean_water_velocity, xd, yd, display=False, suppress_output=False): max_thrust = 2.4 * fork_length + 40 fish_mass = 0.0 # this input defaults the model to a rainbow trout length-mass regression fish_SMR = 0.0 # unused in this case fish_NREI = 0.0 # also unused in this case temperature = 10 # also unused in this case use_total_cost = False disable_wait_time = False fish = maneuveringfish.ManeuveringFish(fork_length, mean_water_velocity, fish_mass, temperature, fish_SMR, max_thrust, fish_NREI, use_total_cost, disable_wait_time) detection_point_3D = (xd, yd, 0.0) maneuver = optimize.optimal_maneuver(fish, detection_point_3D=detection_point_3D) # visualize.summarize_solution(maneuver, display=display, title='Cost Table Check', export_path=None, detailed=True) return maneuver
errors_A = [] errors_B = [] for i in range(1, 300): # Uniform test throughout the possible reaction distances or an inner subset thereof #distfact=1 #testx = random.uniform(min(xs)/distfact,max(xs)/distfact) #testy = random.uniform(min(ys)/distfact,max(ys)/distfact) # Test weighted to actual reaction distances (in bodylengths) by choosing randomly from real fish data from all_foraging_attempts in Maneuver Paper Calculations.py attempt = random.choice(all_foraging_attempts) testx = fork_length * attempt.reaction_vector[0] / attempt.fish.best_length testy = fork_length * attempt.lateral_reaction_distance / attempt.fish.best_length test_energy_model = optimize.optimal_maneuver( fish, detection_point_3D=(testx, testy, 0.0), popsize=4, variant_scale=1.5, mixing_ratio=3.0, iterations=4500, use_starting_iterations=True, num_starting_populations=12, num_starting_iterations=500).dynamics.activity_cost errors_A.append(100 * abs(spl_ec_665.ev(testx, testy) - test_energy_model) / test_energy_model) # percent error in log spline model errors_B.append(100 * abs(spl_ec_416.ev(testx, testy) - test_energy_model) / test_energy_model) # percent error in lin spline model print( "Mean A (665) error is {0:.2f}, median {1:.2f}, 95th percentile {2:.2f}, max {3:.2f}. Mean B (416) error is {4:.2f}, median {5:.2f}, 95th percentile is {6:.2f}, max is {7:.2f}." .format(np.mean(errors_A), np.median(errors_A), percentile(errors_A, 95), max(errors_A), np.mean(errors_B), np.median(errors_B), percentile(errors_B, 95), max(errors_B)))
fork_length = 3.0 velocity = 1.0 fish = maneuveringfish.ManeuveringFish( fork_length=fork_length, mean_water_velocity=velocity, base_mass=0, # defaults to regression from length temperature=10, # irrelevant in this case SMR=0, # irrelevant in this case max_thrust=250, NREI=0, # irrelevant in this case use_total_cost=False, disable_wait_time=False) opt, opt_model = optimize.optimal_maneuver( fish, detection_point_3D=(test_x, test_y, 0.0), max_iterations=(DEFAULT_OPT_ITERATIONS), max_n=(DEFAULT_OPT_N), tracked=True, return_optimization_model=True, suppress_output=False) print( f"Had {opt_model.nfe_nonconvergent} nonconvergent evaluations out of {opt_model.nfe}" ) # failure code 8 is a loop-de-loop # nonconvergents (thousands): 15, 31, 23, 41, 40, 47, 17, 22 # new nonconvergents: 7, 9, 11, 11, 12, 9 -- much better # new random maneuvers have mostly 6.2 and 8 with occasional 6.1 convergence problems # basic problem is the fish generally needs either a very long wait time to put the focal point in front or massively doubling back # to return to the region below the focal point... and that doubling back generally involves one of the figure-8-ish maneuvers
def calculate_cost_tables(fork_length, velocity, taskid): fish = maneuveringfish.ManeuveringFish(fork_length=fork_length, mean_water_velocity=velocity, base_mass=0, # defaults to regression from length temperature=10, # irrelevant in this case SMR=0, # irrelevant in this case max_thrust=250, NREI=0, # irrelevant in this case use_total_cost=False, disable_wait_time=False) prey_length_mm_for_max_distance = 25 # mm, set the max distance considered to the max at which 25-mm prey could be resolved (asymptotically approaches 3 m for large fish) max_visible_distance = 12 * prey_length_mm_for_max_distance * (1. - np.exp(-0.2 * fork_length)) # max visible distance of prey in cm # the minimum lateral (y) distance we'll consider for maneuver is 0.2 cm, basically just a head-snap maneuver for the smallest drift foragers # We also insert a couple of manual values in there to get consistent coverage at short distances before the scaled values kick in for all fish. (xmin, xmax, ymin, ymax) = (-max_visible_distance, max_visible_distance, 0.2, max_visible_distance) sp = 4 # sp = spacing power used to emphasize points closer to the fish while still covering much more distant points adequately def scale(x): return (x)**sp def scale_inv(x): return abs(abs(x)**(1.0/sp)) xs = np.concatenate([-abs(np.flip(scale(np.linspace(scale_inv(1), scale_inv(-xmin), 24))[1:], axis=0)), [-0.1], scale(np.linspace(scale_inv(1), scale_inv(xmax), 14)[1:])]) ys = np.concatenate([[ymin, 0.7, 1.2, 1.8], scale(np.linspace(scale_inv(1), scale_inv(xmax), 25)[2:])]) if FAST_TEST: xs = xs[::5] ys = ys[::5] # This grid has 999 values per sheet. # for x in xs: print("x = {0:.5f}".format(x)) # print statements that can be used to check grid spacing # for y in ys: print("y = {0:.5f}".format(y)) ac = np.zeros(shape=(len(xs),len(ys))) pd = np.zeros(shape=(len(xs),len(ys))) rd = np.zeros(shape=(len(xs),len(ys))) count = 1 final_count = float(len(xs) * len(ys)) for i in range(len(xs)): for j in range(len(ys)): print("Calculating optimal maneuver for detection point ", xs[i], ", ", ys[j], ", 0") sol = optimize.optimal_maneuver(fish, detection_point_3D=(xs[i], ys[j], 0.0), max_iterations=(100 if FAST_TEST else DEFAULT_OPT_ITERATIONS), max_n=(30 if FAST_TEST else DEFAULT_OPT_N), suppress_output=True) if sol.activity_cost != CONVERGENCE_FAILURE_COST: ac[i,j] = sol.activity_cost pd[i,j] = sol.pursuit_duration rd[i,j] = sol.return_duration else: ac[i,j] = np.nan pd[i,j] = np.nan rd[i, j] = np.nan if IS_MAC: print(f"Solution {count} of {final_count:.0f}: For fl={fork_length:.1f} cm, v={velocity:.1f} cm/s, at x={xs[i]:.2f} and y={ys[j]:.2f}, energy cost is {sol.activity_cost:.5f} J and pursuit duration is {sol.pursuit_duration:.3f} and return duration is {sol.return_duration:.3f} s.") if count % 5 == 0 and not IS_MAC: db_execute("UPDATE maneuver_model_tasks SET progress={0} WHERE taskid={1}".format(count/final_count, taskid)) count += 1 # Now, run quality control check on the ec and pd values, redoing calculation if they're too far off from their neighbors or came out np.nan the first time for table in [ac]: # base the check only on ac imax = table.shape[0] jmax = table.shape[1] for i in range(imax): for j in range(jmax): i_min = 0 if i == 0 else i-1 i_max = imax+1 if i == imax else i+2 j_min = 0 if j == 0 else j-1 j_max = jmax+1 if j == jmax else j+2 neighbors = table[i_min:i_max, j_min:j_max] notnan_neighbors = neighbors[~np.isnan(neighbors)] if len(notnan_neighbors) >= 3 or np.isnan(table[i,j]): # only do the neighbor-based QC check if there are enough "neighbors" (2 + current number) to check against the median neighbor_median = np.median(notnan_neighbors) # median of 4 (corner), 6 (edge), or 9-value (center) block around present cell ratio_to_median = table[i,j] / neighbor_median worst_allowable_ratio = 3.0 # assume optimal solution wasn't found if solution differs from neighbors by factor of 3 if ratio_to_median < 1/worst_allowable_ratio or ratio_to_median > worst_allowable_ratio or np.isnan(table[i,j]): for retry in (2,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4): # If we didn't get reasonable values the first time, try again with more rigorous but time-consuming algorithm parameters if not IS_MAC: db_execute("UPDATE maneuver_model_tasks SET retries=retries+1 WHERE taskid={0}".format(taskid)) sol = optimize.optimal_maneuver(fish, detection_point_3D=(xs[i], ys[j], 0.0), max_iterations=(retry*DEFAULT_OPT_ITERATIONS), max_n=(retry*DEFAULT_OPT_N), suppress_output=True) if sol.activity_cost < ac[i, j]: ac[i,j] = sol.activity_cost pd[i,j] = sol.pursuit_duration rd[i,j] = sol.return_duration ratio_to_median = table[i,j] / neighbor_median if 1/worst_allowable_ratio <= ratio_to_median <= worst_allowable_ratio: break if np.isnan(table[i,j]): print(f"Retries still produced NaN activity cost for x={xs[i]}, y={ys[j]} with fl={fork_length}, velocity={velocity}.") if ratio_to_median < 1/worst_allowable_ratio or ratio_to_median > worst_allowable_ratio: print(f"Retries to match neighbors failed for x={xs[i]}, y={ys[j]} with fl={fork_length}, velocity={velocity}, ratio_to_median={ratio_to_median}.") if not IS_MAC: db_execute(f"UPDATE maneuver_model_tasks SET has_failed_retries=1 WHERE taskid={taskid}") # Count up final number of NaNs if any, using the activity cost table nan_count = np.count_nonzero(np.isnan(ac)) if nan_count > 0 and not IS_MAC: db_execute(f"UPDATE maneuver_model_tasks SET nan_count={nan_count} WHERE taskid={taskid}") # Now add on the mirror image of the first 4 columns to each extrapolation, with negative y values, to facilitate smooth interpolation near y=0 ys = np.concatenate([np.flip(-ys[:4], axis=0), ys]) ac = np.concatenate([np.flip(ac[:,:4], axis=1), ac], axis=1) pd = np.concatenate([np.flip(pd[:,:4], axis=1), pd], axis=1) rd = np.concatenate([np.flip(rd[:,:4], axis=1), rd], axis=1) return ac, pd, rd, xs, ys