def noise_build(eco: np.ndarray, t: int, bb: Dict) -> np.ndarray: noise_sd = bb.get('noise_sd') res_forcing_amps = bb.get('res_forcing_amps') include_zoo = bb.get('include_zoo') rhs = np.zeros_like(eco) phy_indices = helpers.eco_indices('phy', bio=bb) res_indices = helpers.eco_indices('res', bio=bb) zoo_indices = helpers.eco_indices('zoo', bio=bb) rhs[phy_indices] = -noise_sd * eco[phy_indices] rhs[res_indices] = noise_sd * (res_forcing_amps - eco[res_indices]) if include_zoo in (1, True, None): rhs[zoo_indices] = -noise_sd * eco[zoo_indices] return rhs
# DEBUG out_list = list() out_keys = list() xaxis = None num_years_plot = 3 params = helpers.load(params_name, 'params', 'single') res_phy_stoich_ratio = params.get('bio').get('res_phy_stoich_ratio') num_phy = params.get('bio').get('num_phy') num_zoo = params.get('bio').get('num_zoo') debug = helpers.load(params_name, 'debug', 'single') eco = helpers.load(params_name, 'data', 'single') phy = eco[helpers.eco_indices('phy', params=params), :] zoo = eco[helpers.eco_indices('zoo', params=params), :] zoo_prey_pref = params.get('bio').get('zoo_prey_pref') # we can compute res_zoo_makeup_ratio prey = np.zeros((num_phy + 1, num_zoo)) t, ind = np.unique(debug['t'][0], return_index=True) t_y = helpers.get_last_n_years(t, num_years_plot) / c.NUM_DAYS_PER_YEAR index = 0 # 0 = nitrate (or nitrogen if single_nit = True), 2 = silicate, 3 = phosphate, 4 = iron keys = list(debug.keys()) keys.remove('res_zoo_remin_frac') keys.remove('t')
def time_series_plot(self, kind: str = 'indiv', ax: mat_ax.Axes = None, plotting_threshold: float = -np.infty, labels: List = None, return_lines: bool = True, num_years: int = None, num_days: int = None, color_kw: Dict = None, plot_kw: Dict = None, legend_kw: Dict = None, legend_off: bool = False, resize_box: tuple = (0, 0.2, 0, 0.7), compartments: Iterable[Dict] = ({'phy': 'all'},), res_to_carbon: Union[bool, int, list] = True, phy_index: int = None): """Plot one or more time series from the stored ecosystem Parameters ---------- kind 'indiv' (default), 'shannon', 'total' ax Axis we'd like to plot on. num_years How many years to include num_days How many days? plotting_threshold Don't plot any items below this threshold labels Legend labels resize_box Compress figure box legend_off Leave off legend return_lines Returns lines associated with plot object res_to_carbon Convert resources to carbon units phy_index If we're plotting resource, which phyto compartment(s) to use to convert to carbon units plot_kw Keyword arguments to pass to matplotlib color_kw colors: list of colors to use for plotting cmap: used to create list of colors legend_kw dict of keyword arguments to be passed directly to `legend` method of `ax` compartments List of compartment dictionaries """ ty_vec = self.t_in / c.NUM_DAYS_PER_YEAR if ax is None: ax = plt.subplot(111) if num_years is not None: ty_vec = helpers.get_last_n_years(self.t_in, num_years) / c.NUM_DAYS_PER_YEAR if num_days is not None: ty_vec = helpers.get_last_n_days(self.t_in, num_days) / c.NUM_DAYS_PER_YEAR num_years = num_days / c.NUM_DAYS_PER_YEAR box1 = ax.get_position() ax.set_position([box1.x0 + box1.width * resize_box[0], box1.y0 + box1.height * resize_box[1], box1.width + box1.width * resize_box[2], box1.height * resize_box[3]]) ax.set_xlim(left=self.num_years - num_years, right=self.num_years) plt_obj = list() # colors if kind in ('shannon', 'total'): eco = helpers.restrict_ts(self.eco_in, self.params, num_years=num_years, num_days=num_days, kind=kind, compartments=compartments) if color_kw is not None: if 'colors' in color_kw: colors = iter(color_kw['colors']) else: colors = iter(helpers.color_cycle(1, cmap=color_kw.get('cmap'))) else: colors = iter(helpers.color_cycle(1)) if np.mean(eco) > plotting_threshold: line, = ax.plot(ty_vec, eco, **plot_kw if plot_kw is not None else {}) line.set_color(next(colors)) plt_obj.append(line) if return_lines: return plt_obj else: eco = helpers.restrict_ts(self.eco_in, self.params, num_years=num_years, num_days=num_days) name_list = helpers.get_name_list(self.params, compartments=compartments, for_plot=True) new_name_list = list() for list_dict in compartments: key = list(list_dict.keys())[0] if list_dict[key] == 'all' or list_dict[key] is None: indices = list(range(self.params['bio']['num_{}'.format(key)])) else: indices = list_dict[key] conversion = None # get name_list for just this set of compartments curr_name_list = [x for x in name_list if x.startswith(helpers.short_name(key))] if key == 'res': if res_to_carbon not in (False, 0): conversion = al.res_to_carbon_conversions(eco, self.params, phy_index=phy_index) for ind, r in enumerate(indices): start_index = list(helpers.eco_indices(key, self.params['bio']))[0] name = curr_name_list[ind] output = np.squeeze(eco[start_index + r, :]) # convert resource to phyto units if key == 'res' and res_to_carbon is not False: output = conversion[r, :] if np.mean(output) >= plotting_threshold: new_name_list.append(name) line, = ax.plot(ty_vec, output, label=name, **plot_kw if plot_kw is not None else {}) plt_obj.append(line) # NOW set colors if color_kw is not None: if 'colors' in color_kw: colors = iter(color_kw.get('colors')) else: colors = iter(helpers.color_cycle(len(new_name_list), cmap=color_kw.get('cmap'))) else: colors = iter(helpers.color_cycle(len(new_name_list))) for i in range(len(plt_obj)): plt_obj[i].set_color(next(colors)) if labels: new_name_list = labels if len(new_name_list) > 0: if not legend_off: if legend_kw is not None: ax.legend(labels=new_name_list, **legend_kw) else: ax.legend(labels=new_name_list, loc='upper center', bbox_to_anchor=(0.5, -0.2), fancybox=True, shadow=True, ncol=5) if return_lines: return plt_obj
def phase_plot(self, ax: mat_ax.Axes = None, fig: List = None, compartments: Iterable[Dict] = None, num_years: int = None, num_days: int = None, plot3d: bool = False, res_phy: bool = False): """Phase plot from stored ecosystem Parameters ---------- fig Figure we're plotting on. Only used if plot3d is true ax Axis we'd like to plot on. num_years How many years to include num_days How many days to include? num_days How many days? plot3d Plot the three arguments with the highest average concentrations against each other in 3D res_phy Plot total resources vs. total phytoplankton. Only applies if plot3d=False compartments List of compartment dictionaries """ eco = helpers.restrict_ts(self.eco_in, self.params, num_years=num_years, num_days=num_days, compartments=compartments) # find time average eco_time_avg = np.mean(eco, 1) # three largest quantities indices = eco_time_avg.argsort()[::-1][:3] # indices name_list = helpers.get_name_list(self.params, for_plot=True) # take the last end_pct of entries if plot3d: ax = Axes3D(fig) num_divisions = 5 ax.locator_params(nbins=num_divisions, nticks=num_divisions) ax.plot(eco[indices[0], :], eco[indices[1], :], eco[indices[2], :]) ax.set_xlabel(name_list[indices[0]]) ax.set_ylabel(name_list[indices[1]]) ax.set_zlabel(name_list[indices[2]]) else: if ax is None: ax = fig.add_subplot(111) # plot P vs. R if res_phy: bio = self.params['bio'] ax.plot(np.sum(eco[helpers.eco_indices('phy', bio), :], 0), np.sum(eco[helpers.eco_indices('res', bio), :], 0), linewidth=2) ax.set_xlabel('P') ax.set_ylabel('R') else: # plot two largest quantities against each other ax.plot(eco[indices[0], :], eco[indices[1], :], linewidth=2.0) ax.set_xlabel(name_list[indices[0]]) ax.set_ylabel(name_list[indices[1]]) return ax
def rhs_build(eco: np.ndarray, t: int, num_phy: int, num_zoo: int, num_res: int, res_phy_stoich_ratio: np.ndarray, res_phy_remin_frac: np.ndarray, phy_growth_sat: np.ndarray, res_forcing_amps: np.ndarray, num_compartments: int, phy_mort_rate: Union[np.ndarray, float], phy_growth_rate_max: np.ndarray, turnover_rate: float = 0.04, zoo_hill_coeff: float = 1, phy_self_shade: float = 0, shade_background: float = 0, turnover_min: float = None, turnover_max: float = None, turnover_radius: float = None, turnover_period: Union[float, np.ndarray] = 360, turnover_series: np.ndarray = None, turnover_phase_shift: float = 0, light_series: np.ndarray = None, light_min: float = None, light_max: float = None, light_kind: str = 'ramp', turnover_kind: str = 'sine', mixed_layer_ramp_times: np.ndarray = None, mixed_layer_ramp_lengths: np.ndarray = None, phy_source: float = 0, res_source: float = 0, zoo_source: float = 0, dilute_zoo: bool = True, zoo_mort_rate: tuple = None, linear_zoo_mort_rate: tuple = None, zoo_grazing_rate_max: np.ndarray = None, zoo_slop_feed: np.ndarray = None, zoo_prey_pref: np.ndarray = None, zoo_grazing_sat: np.ndarray = None, noise: np.ndarray = None, noise_additive: bool = False, include_zoo: bool = False, res_uptake_type: str = 'perfect', zoo_model_type: str = 'Real', debug_dict: Dict = None, dummy_param: int = 0 ) -> Union[np.ndarray, Tuple[np.ndarray, Dict]]: """Build the RHS of the bio equations Parameters ---------- eco Current values of ecosystem variables t Current time debug_dict Return key-value pairs for RHS terms, to study relative magnitudes num_phy Number of phytoplankton num_zoo Number of zooplankton num_res Number of resources res_phy_stoich_ratio `num_res` by `num_phy` matrix of stoichiometric coefficients. Convert from carbon to nutrient units res_phy_remin_frac Converts carbon from phytoplankton into resource units turnover_series If we want to pass the full turnover series in as a function light_series If we want to pass the full light series in as a function mixed_layer_ramp_times If we want to represent mixed layer: at what times do ramps occur? mixed_layer_ramp_lengths If we want to represent mixed layer: how long are the ramps? turnover_rate (Constant) Dilution coefficient turnover_min Minimum dilution coefficient (for time-varying case) turnover_max Maximum dilution coefficient (for time-varying case) turnover_radius Fraction of mean for radius: between 0 and 1 (for time-varying case) turnover_period Period of nutrient delivery term. If a list, assume a range (or spectrum; TODO: decide on this) shade_background Background light attenuation phy_self_shade Self-shading coefficient for phyto phy_growth_sat `num_res` by `num_phy` matrix of Monod/Michaelis-Menten half-saturation coefficients. res_forcing_amps Deep nutrient supply in resource equations zoo_grazing_rate_max max growth rates of zooplankton zoo_prey_pref Prey preference matrix; built off zoo_high_pref and zoo_low_pref zoo_slop_feed fraction of phyto biomass that is utilized by zoo when grazing phy_source additional source to add to phytoplankton compartments res_source additional source to add to resource compartments zoo_source additional source to add to zooplankton compartments dilute_zoo Subject zooplankton to dilution? zoo_mort_rate quadratic mortality rate for zooplankton linear_zoo_mort_rate (optional) linear mortality rate for zooplankton zoo_grazing_sat saturation coefficients for zooplankton grazing num_compartments total number of stored compartments in ecosystem. should be `num_phy` + 7, even if zooplankton disabled zoo_hill_coeff Exponent for grazing. n=1: Holling II n=2: Holling III phy_mort_rate mortality rates for phytoplankton phy_growth_rate_max maximal growth rates for phytoplankton include_zoo are we including zooplankton? res_uptake_type how are we uptaking resources? - 'perfect' (law of the minimum) by default - 'interactive' (product of Monod terms, rather than the minimum) also an option no3_inhibit_decay controls how much nitrate intake is inhibited by ammonium zoo_model_type form of zooplankton grazing. 'Real' (i.e. Michaelis-Menten) by default. 'KTW' also an option noise optional vector of noise to add to turnover rate noise_additive is noise additive or multiplicative (default)? dummy_param Dummy parameter for running the same simulation multiple times for sweeps (e.g. for ensemble of noisy sims) Returns --------- np.ndarray RHS in a `num_compartments` x 1 vector """ ######################################################################## # Assign indices for phyto, resources, predator num_dict = {'num_phy': num_phy, 'num_res': num_res, 'num_zoo': num_zoo} phy_indices = helpers.eco_indices('phy', num_dict) res_indices = helpers.eco_indices('res', num_dict) zoo_indices = helpers.eco_indices('zoo', num_dict) res_zoo_remin_frac = None def compute_turnover(turnover_rate=turnover_rate): if turnover_series is not None: if turnover_min is None or turnover_max is None: return turnover_series[int(t)] else: # Scale by turnover min and turnover max series_max = np.max(turnover_series) series_min = np.min(turnover_series) return turnover_min + (turnover_series[int(t)] - series_min) / (series_max - series_min) * (turnover_max - turnover_min) if None not in (turnover_min, turnover_max): # Mixed layer representation if turnover_kind == 'ramp': turnover_rate = helpers.ml_profile(low_val=turnover_min, high_val=turnover_max, phase_shift=turnover_phase_shift, mixed_layer_ramp_lengths=mixed_layer_ramp_lengths, mixed_layer_ramp_times=mixed_layer_ramp_times, t=t) return turnover_rate elif turnover_kind == 'sine': mean_value = 0.5 * (turnover_min + turnover_max) return mean_value + 0.5 * (turnover_max - turnover_min) * np.sin(2 * np.pi / turnover_period * t + np.pi/4) if turnover_radius is not None: tau_max = turnover_rate * (1 + turnover_radius) tau_min = turnover_rate * (1 - turnover_radius) return turnover_rate + 0.5 * (tau_max - tau_min) * np.sin(2 * np.pi / turnover_period * t) else: return turnover_rate turnover_rate = compute_turnover() if noise is not None: # Additive or multiplicative noise? if noise_additive: turnover_rate += turnover_rate * noise[int(t)] else: turnover_rate *= noise[int(t)] if debug_dict: # add current time (if integer, approximately) if len(debug_dict['t']) == 0 or int(t) != int(debug_dict['t'][-1]): for key in debug_dict: if len(np.unique(debug_dict[key][0])) == 1 and np.unique(debug_dict[key][0])[0] == c.NAN_VALUE: debug_dict[key][0] = np.zeros(debug_dict[key][0].shape) else: debug_dict[key].append(np.zeros(debug_dict[key][-1].shape)) debug_dict['t'][-1] = np.array([t]) rhs = np.zeros((num_compartments,)) # res_forcing/phy_forcing are prescribed forcings # May be "Newtonian cooling" like term for Tilman-like model phy = eco[phy_indices] res = eco[res_indices] zoo = eco[zoo_indices] prey = np.zeros(num_phy + num_zoo,) prey[:num_phy] = phy prey[num_phy:] = zoo # grazing terms food_total_const = np.zeros(num_zoo, ) # F_rho in Vallina prey_preference_var = np.zeros((num_phy + num_zoo, num_zoo)) # phi in Vallina food_total_var = np.zeros(num_zoo, ) # F_phi in Vallina zoo_graze_mat = np.zeros((num_phy + num_zoo, num_zoo)) # grazing matrix zoo_grazing_sat_var = np.zeros(num_zoo, ) # variable saturation coefficient if include_zoo: prey_preference_product = prey[:, None] * zoo_prey_pref # [num_phy+num_zoo, num_zoo] food_total_const = np.sum(prey_preference_product, axis=0) # [num_zoo,] prey_preference_var = prey_preference_product / (food_total_const[None, :] + 1e-6) food_total_var = np.sum(prey[:, None] * prey_preference_var, axis=0) # variable saturation coefficient zoo_grazing_sat_var = zoo_grazing_sat * food_total_var / (food_total_const + 1e-6) # compute res_zoo_remin_frac dynamically # add small factor to prey/food if zoo_model_type == 'Real': # num_res x num_zoo res_zoo_remin_frac = (res_phy_remin_frac @ prey_preference_product[:num_phy, :]) / \ (food_total_const[None, :] + 1e-6) res_zoo_remin_frac += (res_zoo_remin_frac @ prey_preference_product[num_phy:, :]) / \ (food_total_const[None, :] + 1e-6) elif zoo_model_type == 'KTW': res_zoo_remin_frac = (res_phy_remin_frac @ prey_preference_var[:num_phy, :]) / \ (food_total_var[None, :] + 1e-6) res_zoo_remin_frac += (res_zoo_remin_frac @ prey_preference_var[num_phy:, :]) / \ (food_total_var[None, :] + 1e-6) if debug_dict: debug_dict['res_zoo_remin_frac'][-1] = res_zoo_remin_frac # phy terms phy_mort_term = phy_mort_rate if isinstance(phy_mort_rate, (list, np.ndarray)) \ else phy_mort_rate * np.ones(num_phy,) # used to build zoo_graze_mat def get_grazing_term(): switching_term = feeding_probability = None if zoo_model_type == 'Real': switching_term = zoo_prey_pref / (food_total_const[None, :] + 1e-6) feeding_probability = (food_total_const ** zoo_hill_coeff / (zoo_grazing_sat ** zoo_hill_coeff + food_total_const ** zoo_hill_coeff)) elif zoo_model_type == 'KTW': switching_term = prey_preference_var / (food_total_var[None, :] + 1e-6) feeding_probability = (food_total_var ** zoo_hill_coeff / (zoo_grazing_sat_var ** zoo_hill_coeff + food_total_var ** zoo_hill_coeff)) return zoo_grazing_rate_max[None, :] * switching_term * feeding_probability[None, :] phy_growth_list = eco[res_indices, None] / (eco[res_indices, None] + phy_growth_sat) # TODO: Add factor for changes due to light # We could add a parameter that allows us to yield a function of time # We want to ramp down gradually during winter, and up quickly during winter-spring bloom # This will be a piecewise linear combo if light_series is not None: light_factor = light_series[int(t)] elif None not in (light_min, light_max): if light_kind == 'ramp': light_factor = helpers.light_profile(low_val=light_min, high_val=light_max, mixed_layer_ramp_lengths=mixed_layer_ramp_lengths, mixed_layer_ramp_times=mixed_layer_ramp_times, t=t) elif light_kind == 'sine': mean_value = (light_min + light_max)/2 light_factor = mean_value + (light_max - light_min)/2 * np.sin(2 * np.pi / c.NUM_DAYS_PER_YEAR * t + 5 * np.pi/4) else: light_factor = 1 phy_res_growth_term = None if res_uptake_type == 'interactive': phy_res_growth_term = np.prod(phy_growth_list, axis=0) elif res_uptake_type == 'perfect': phy_res_growth_term = np.min(phy_growth_list, axis=0) phy_growth_vec = phy_growth_rate_max * phy_res_growth_term phy_growth_vec *= np.exp(-(shade_background + phy_self_shade * np.sum(phy))) # Scale growth rate by light phy_growth_vec *= light_factor # Gain in phytoplankton biomass corresponds to loss in resource res_uptake = -(phy_growth_vec[None, :] * res_phy_stoich_ratio) @ phy # -cji * U(I(t), R) rhs[res_indices] += res_uptake if debug_dict: debug_dict['res_uptake'][-1] += res_uptake if include_zoo: zoo_graze_mat = get_grazing_term() phy_zoomort = (-zoo_graze_mat[:num_phy, :] @ zoo) * phy # -sum_n Gin * Z[n] rhs[phy_indices] += phy_zoomort if debug_dict: debug_dict['phy_zoomort'][-1] += phy_zoomort # Phytoplankton terms phy_netgrowth = (phy_growth_vec - phy_mort_term - turnover_rate) * phy # (1-fe) * rji * phy_mort_term rhs[phy_indices] += phy_netgrowth if debug_dict: debug_dict['phy_growth'][-1] += phy_growth_vec * phy debug_dict['phy_mort'][-1] += -phy_mort_term * phy debug_dict['phy_turnover'][-1] += -turnover_rate * phy if include_zoo: zoo_mort_term = np.array(zoo_mort_rate) * zoo if linear_zoo_mort_rate is not None: zoo_mort_term += np.array(linear_zoo_mort_rate) zoo_graze_sum = zoo_slop_feed * (zoo_graze_mat[:num_phy, :].T @ phy) # beta_n * sum (Gin * Pi) - zoo_mort_term - tau rhs[zoo_indices] += (zoo_graze_sum - zoo_mort_term) * zoo if dilute_zoo: rhs[zoo_indices] += -turnover_rate * zoo if debug_dict: debug_dict['zoo_growth'][-1] += zoo_graze_sum * zoo debug_dict['zoo_mort'][-1] += -zoo_mort_term * zoo - (turnover_rate if dilute_zoo else 0) * zoo zoo_zoo_graze = zoo_slop_feed * (zoo_graze_mat[num_phy:, :].T @ zoo) zoo_zoo_mort = -zoo_graze_mat[num_phy:, :] @ zoo rhs[zoo_indices] += (zoo_zoo_graze + zoo_zoo_mort) * zoo if debug_dict: debug_dict['zoo_growth'][-1] += zoo_zoo_graze * zoo debug_dict['zoo_mort'][-1] += zoo_zoo_mort * zoo res_forcing_rhs = turnover_rate * (res_forcing_amps - res) rhs[res_indices] += res_forcing_rhs # add forcing if debug_dict: debug_dict['res_forcing'][-1] += res_forcing_rhs # If any value is below extinction threshold and rhs < 0, set rhs to 0 and eco to 0 there # this prevents negative blowup. # phy_condition = np.where(phy < c.EXTINCT_THRESH)[0] # rhs[phy_indices][phy_condition] = 0 # eco[phy_indices][phy_condition] = 0 # any additional sources rhs[phy_indices] += phy_source rhs[res_indices] += res_source if include_zoo: rhs[zoo_indices] += zoo_source return rhs
ax[1].set_xticks([]) ######################### # ZOOPLANKTON zoo_indices = [1] # Just plot Z^l (Z2) zoo_indiv_alpha = 0.6 zoo_total_alpha = 0.8 zoo_indiv_lw = 1.5 zoo_total_lw = 1.0 zoo = helpers.get_last_n_years( plotter.eco_in[helpers.eco_indices('zoo', params=params), :], num_years_ts) zoo_legend_kw = { 'fontsize': 11, 'loc': 'upper center', 'bbox_to_anchor': (0.5, -0.3), 'ncol': 3 } zoo_colors = ['#0033cb', '#008800'] zoo_labels = ['Z$^l$', 'Total Z'] for i in range(len(zoo_indices)): ax[2].plot(t_y, zoo[zoo_indices[i], :],