def three_comp_two_objective_functions(obj_vars, hz: int, ttes: SimpleTTEMeasures, recovery_measures: SimpleRecMeasures): """ Two objective functions for recovery and expenditure error that get all required params as arguments :param obj_vars: values that define the three comp agent [anf, ans, m_u, m_lf, m_ls, theta, gamma, phi] :param hz: estimations per second for agent :param ttes: time to exhaustion tests to use :param recovery_measures: recovery trials to compare to :return: tte_nrmse and rec_nrmse values to minimise (the smaller the better) """ # differences in exhaustion times determine fitness tte_se = [] # TTE standard errors ttes_exp = [] # TTEs that are expected (original) rec_se = [] # Recovery standard errors recs_exp = [] # Recovery ratios expected (original) three_comp_agent = ThreeCompHydAgent(hz=hz, lf=obj_vars[0], ls=obj_vars[1], m_u=obj_vars[2], m_ls=obj_vars[3], m_lf=obj_vars[4], the=obj_vars[5], gam=obj_vars[6], phi=obj_vars[7]) # compare tte times for tte_t, tte_p in ttes.iterate_pairs(): # use the simulator try: tte = ThreeCompHydSimulator.tte(agent=three_comp_agent, p_work=tte_p) except UserWarning: tte = 5000 # square time difference tte_se.append(pow(tte - tte_t, 2)) ttes_exp.append(tte_t) # get NRMSE (Normalised Root Mean Squared Error) tte_nrmse = math.sqrt(sum(tte_se) / len(tte_se)) / np.mean(ttes_exp) # compare all available recovery ratio measures for p_exp, p_rec, t_rec, expected in recovery_measures.iterate_measures(): # use the simulator try: achieved = ThreeCompHydSimulator.get_recovery_ratio_wb1_wb2( three_comp_agent, p_work=p_exp, p_rec=p_rec, t_rec=t_rec) except UserWarning: achieved = 200 # add the squared difference rec_se.append(pow(expected - achieved, 2)) recs_exp.append(expected) # get NRMSE rec_nrmse = math.sqrt(sum(rec_se) / len(rec_se)) / np.mean(recs_exp) # determine return value return tte_nrmse, rec_nrmse
if __name__ == "__main__": # set logging level to highest level logging.basicConfig( level=logging.INFO, format= "%(asctime)s %(levelname)-5s %(name)s - %(message)s. [file=%(filename)s:%(lineno)d]" ) # example configuration params = [ 11532.526538727172, 23240.257042239595, 249.7641585019016, 286.26673813946095, 7.988078323028352, 0.25486842730772163, 0.26874299216869681, 0.2141766056862277 ] # create three component hydraulic agent with example configuration agent = ThreeCompHydAgent(10, lf=params[0], ls=params[1], m_u=params[2], m_ls=params[3], m_lf=params[4], the=params[5], gam=params[6], phi=params[7]) # run the interactive animation ani = ThreeCompInteractiveAnimation(agent) ani.run()
def tte_detail_with_recovery(agent: ThreeCompHydAgent, p_work, p_rec, plot=False): """ The time the agent takes till exhaustion at given power and time till recovery :param agent: agent instance to use :param p_work: expenditure intensity :param p_rec: recovery intensity :param plot: displays a plot of some of the state variables over time :returns: tte, ttr """ agent.reset() t, p, lf, ls, p_u, p_l, m_flow = [], [], [], [], [], [], [] # perform steps until agent is exhausted logging.info("start exhaustion") agent.set_power(p_work) steps = 0 while agent.is_exhausted() is False and steps < 10000: t.append(agent.get_time()) p.append(agent.perform_one_step()) lf.append(agent.get_fill_lf()) ls.append(agent.get_fill_ls() * agent.height_ls + agent.theta) p_u.append(agent.get_p_u()) p_l.append(agent.get_p_l()) m_flow.append(agent.get_m_flow()) steps += 1 # save time tte = agent.get_time() # add recovery at 0 logging.info("start recovery") agent.set_power(p_rec) steps = 0 while agent.is_equilibrium() is False and steps < 20000: t.append(agent.get_time()) p.append(agent.perform_one_step()) lf.append(agent.get_fill_lf()) ls.append(agent.get_fill_ls() * agent.height_ls + agent.theta) p_u.append(agent.get_p_u()) p_l.append(agent.get_p_l()) m_flow.append(agent.get_m_flow()) steps += 1 # save recovery time ttr = agent.get_time() - tte # plot the parameter overview if required if plot is True: ThreeCompHydSimulator.plot_dynamics(t, p, lf, ls, p_u, p_l) # return time till exhaustion and time till recovery return tte, ttr
def get_recovery_ratio_wb1_wb2(agent: ThreeCompHydAgent, p_work: float, p_rec: float, t_rec: float, start_h: float = 0, start_g: float = 0, t_max: float = 5000, step_function=None) -> float: """ Returns recovery ratio of given agent according to WB1 -> RB -> WB2 protocol. Recovery ratio estimations for given exp, rec intensity and time :param agent: three component hydraulic agent to use :param p_work: work bout intensity :param p_rec: recovery bout intensity :param t_rec: recovery bout duration :param start_h: fill level of LF at start :param start_g: fill level of LS at start :param t_max: maximal time in seconds until warning "exhaustion not reached" is raised :param step_function: function of agent to estimate one time step. Default is perform_one_step. :return: ratio in percent """ hz = agent.hz agent.reset() agent.set_h(start_h) agent.set_g(start_g) step_limit = t_max * hz if step_function is None: step_function = agent.perform_one_step # WB1 Exhaust... agent.set_power(p_work) steps = 0 while not agent.is_exhausted() and steps < step_limit: step_function() steps += 1 wb1_t = agent.get_time() if not agent.is_exhausted(): raise UserWarning("exhaustion not reached!") # Recover... agent.set_power(p_rec) for _ in range(0, int(round(t_rec * hz))): step_function() rec_t = agent.get_time() # WB2 Exhaust... agent.set_power(p_work) steps = 0 while not agent.is_exhausted() and steps < step_limit: step_function() steps += 1 wb2_t = agent.get_time() # return ratio of times as recovery ratio return ((wb2_t - rec_t) / wb1_t) * 100.0
def tte_detail(agent: ThreeCompHydAgent, p_work: float, start_h: float = 0, start_g: float = 0, t_max: float = 5000, step_function=None, plot: bool = False): """ simulates a standard time to exhaustion test and collects all state variables of the hydraulic agent in every time step. :param agent: hydraulic agent :param p_work: constant expenditure intensity for TTE :param start_h: fill level of LF at start :param start_g: fill level of LS at start :param t_max: maximal time in seconds until warning "exhaustion not reached" is raised :param step_function: function of agent to estimate one time step. Default is perform_one_step. :param plot: whether state variables over time should be plotted :return: all state variables all state variables throughout for every time step of the TTE [h, g, lf, ls, p_u, p_l, m_flow, w_p_bal]. Pos 0 is time step 1. """ agent.reset() agent.set_h(start_h) agent.set_g(start_g) step_limit = t_max * agent.hz if step_function is None: step_function = agent.perform_one_step # all state variables are logged t, ps = [], [] lf, ls, h, g, p_u, p_l, m_flow, w_p_bal = [], [], [], [], [], [], [], [] steps = 0 agent.set_power(p_work) # perform steps until agent is exhausted or step limit is reached while not agent.is_exhausted() and steps < step_limit: # we don't include values of time step 0 # perform current power step step_function() steps += 1 # ... then collect observed values t.append(agent.get_time()) ps.append(agent.get_power()) h.append(agent.get_h()) g.append(agent.get_g()) lf.append(agent.get_fill_lf()) ls.append(agent.get_fill_ls()) p_u.append(agent.get_p_u()) p_l.append(agent.get_p_l()) m_flow.append(agent.get_m_flow()) w_p_bal.append(agent.get_w_p_ratio()) # a investigation and debug plot if you want to if plot is True: ThreeCompHydSimulator.plot_dynamics(t=t, p=ps, lf=lf, ls=ls, p_u=p_u, p_l=p_l) # return parameters return h, g, h, g, p_u, p_l, m_flow, w_p_bal
def tte(agent: ThreeCompHydAgent, p_work: float, start_h: float = 0, start_g: float = 0, t_max: float = 5000, step_function=None) -> float: """ simulates a standard time to exhaustion test :param agent: hydraulic agent :param p_work: constant expenditure intensity for TTE :param start_h: fill level of LF at start :param start_g: fill level of LS at start :param t_max: maximal time in seconds until warning "exhaustion not reached" is raised :param step_function: function of agent to estimate one time step. Default is perform_one_step. :return: time to exhaustion in seconds """ agent.reset() agent.set_h(start_h) agent.set_g(start_g) step_limit = t_max * agent.hz if step_function is None: step_function = agent.perform_one_step # Exhaust... agent.set_power(p_work) steps = 0 while not agent.is_exhausted() and steps < step_limit: step_function() steps += 1 tte = agent.get_time() if not agent.is_exhausted(): raise UserWarning("exhaustion not reached!") return tte
def simulate_course_detail(agent: ThreeCompHydAgent, powers, step_function=None, plot: bool = False): """ simulates a whole course with given agent :param agent: :param powers: list or array :param plot: displays a plot of some of the state variables over time :param step_function: function of agent to estimate one time step. Default is perform_one_step. :return all state variables throughout for every time step of the course [h, g, lf, ls, p_u, p_l, m_flow, w_p_bal]. Pos 0 is time step 1. """ agent.reset() h, g, lf, ls, p_u, p_l, m_flow, w_p_bal = [], [], [], [], [], [], [], [] if step_function is None: step_function = agent.perform_one_step # let the agent simulate the list of power demands for step in powers: # we don't include values of time step 0 # perform current power step agent.set_power(step) step_function() # ... then collect observed values h.append(agent.get_h()) g.append(agent.get_g()) lf.append(agent.get_fill_lf()) ls.append(agent.get_fill_ls()) p_u.append(agent.get_p_u()) p_l.append(agent.get_p_l()) m_flow.append(agent.get_m_flow()) w_p_bal.append(agent.get_w_p_ratio()) # an investigation and debug plot if you want to if plot is True: ThreeCompHydSimulator.plot_dynamics(t=np.arange(len(powers)), p=powers, lf=lf, ls=ls, p_u=p_u, p_l=p_l) # return parameters return h, g, lf, ls, p_u, p_l, m_flow, w_p_bal
def multiple_exhaustion_comparison_overview(w_p: float, cp: float, ps: list): """ Plots the expenditure energy dynamics of multiple three component hydraulic model configurations in comparison to the CP model. :param w_p: ground truth W' parameter :param cp: ground truth CP parameter :param ps: a list of three component hydraulic model configurations """ hyd_color = "tab:green" two_p_color = "tab:blue" # fig sizes to make optimal use of space in paper fig = plt.figure(figsize=(8, 3.4)) ax = fig.add_subplot(1, 1, 1) resolution = 1 ts_ext = np.arange(120, 1801, 20 / resolution) ts = [120, 240, 360, 600, 900, 1800] powers = [((w_p + x * cp) / x) for x in ts] powers_ext = [((w_p + x * cp) / x) for x in ts_ext] # mark P4 and P8 ax.get_xaxis().set_ticks(ts) ax.set_xticklabels([int(p / 60) for p in ts]) ax.get_yaxis().set_ticks([]) ax.set_yticklabels([]) # small zoomed-in detail window insert_ax = ax.inset_axes([0.3, 0.40, 0.3, 0.45]) detail_obs = resolution * 5 detail_ts = [120, 150, 180, 210] detail_ps = [] # plot three comp agents for p in ps: three_comp_agent = ThreeCompHydAgent(hz=1, lf=p[0], ls=p[1], m_u=p[2], m_ls=p[3], m_lf=p[4], the=p[5], gam=p[6], phi=p[7]) hyd_fitted_times_ext = [ThreeCompHydSimulator.tte(three_comp_agent, x) for x in powers_ext] hyd_powers_ext = powers_ext ax.plot(hyd_fitted_times_ext, hyd_powers_ext, linestyle='-', linewidth=1, color=hyd_color) insert_ax.plot(hyd_fitted_times_ext[:detail_obs], hyd_powers_ext[:detail_obs], linestyle='-', linewidth=1, color=hyd_color) # plot CP curve insert_ax.plot(ts_ext[:detail_obs], powers_ext[:detail_obs], linestyle='-', linewidth=2, label="critical power\nmodel", color=two_p_color) ax.plot(ts_ext, powers_ext, linestyle='-', linewidth=2, label="critical power\nmodel", color=two_p_color) # detailed view formatted = [] for p in detail_ts: val = round((p / 60), 1) if val % 1 == 0: formatted.append(int(val)) else: formatted.append(val) insert_ax.get_xaxis().set_ticks(detail_ts) insert_ax.set_xticklabels(formatted) insert_ax.get_yaxis().set_ticks(detail_ps) insert_ax.set_title("detail view") # label axis and lines ax.set_xlabel("time to exhaustion (min)") ax.set_ylabel("intensity (watt)", labelpad=10) # insert number of models only if more than 1 was plotted if len(ps) > 1: ax.plot([], linestyle='-', linewidth=1, color=hyd_color, label="hydraulic model ({})".format(len(ps))) else: ax.plot([], linestyle='-', linewidth=1, color=hyd_color, label="hydraulic model") ax.legend() plt.tight_layout() plt.subplots_adjust(bottom=0.20, top=0.91) plt.show() plt.close(fig)
def multiple_caen_recovery_overview(w_p: float, cp: float, ps: list): """ plots the energy recovery dynamics comparison of multiple three comonent hydraulic model configurations in comparison to observations by Caen et al. :param w_p: ground truth W' :param cp: ground truth CP :param ps: three component hydraulic model configurations """ # caen observation and hydraulic model colors c_color = "tab:blue" hyd_color = "tab:green" # power level and recovery level estimations for the trials p_4 = (w_p + 240 * cp) / 240 p_8 = (w_p + 480 * cp) / 480 cp_33 = cp * 0.33 cp_66 = cp * 0.66 # create all the comparison trials exp_ps = [p_4, p_8] rec_ps = [cp_33, cp_66] rec_ts = [10, 20, 25, 30, 35, 40, 45, 50, 60, 70, 90, 110, 130, 150, 170, 240, 300, 360] fig = plt.figure(figsize=(8, 3.4)) # using combined CP33 and CP66 measures # only two plots with combined recovery intensities axes = [fig.add_subplot(1, 2, 1), fig.add_subplot(1, 2, 2)] # add three component model data for p in ps: # set up agent according to parameters set three_comp_agent = ThreeCompHydAgent(hz=1, lf=p[0], ls=p[1], m_u=p[2], m_ls=p[3], m_lf=p[4], the=p[5], gam=p[6], phi=p[7]) # data will be stored in here three_comp_data = [] # let the created agent run all the trial combinations combs = list(itertools.product(exp_ps, rec_ps)) for comb in combs: rec_times, rec_percent = [0], [0] for rt in rec_ts: ratio = ThreeCompHydSimulator.get_recovery_ratio_wb1_wb2(three_comp_agent, comb[0], comb[1], rt) rec_times.append(rt) rec_percent.append(ratio) three_comp_data.append([rec_times, rec_percent]) # sort into p4s and p8s p4s, p8s = [], [] for i, comb in enumerate(combs): if comb[0] == exp_ps[0]: p4s.append(three_comp_data[i][1]) else: p8s.append(three_comp_data[i][1]) # get the means p4s = [(p4s[0][x] + p4s[1][x]) / 2 for x in range(len(p4s[0]))] p8s = [(p8s[0][x] + p8s[1][x]) / 2 for x in range(len(p8s[0]))] # plot into both available axes axes[0].plot(three_comp_data[0][0], p4s, linestyle='-', linewidth=1, color=hyd_color) axes[1].plot(three_comp_data[0][0], p8s, linestyle='-', linewidth=1, color=hyd_color) # combined data reported by Caen axes[0].errorbar(caen_combination["P4"][0], caen_combination["P4"][1], caen_combination["P4"][2], label="Caen et al.", linestyle='None', marker='o', capsize=3, color=c_color) axes[0].set_title("P4") axes[1].errorbar(caen_combination["P8"][0], caen_combination["P8"][1], caen_combination["P8"][2], label="Caen et al.", linestyle='None', marker='o', capsize=3, color=c_color) axes[1].set_title("P8") # insert number of models only if more than 1 was plotted if len(ps) > 1: axes[0].plot([], linestyle='-', linewidth=1, color=hyd_color, label="hydraulic model ({})".format(len(ps))) else: axes[0].plot([], linestyle='-', linewidth=1, color=hyd_color, label="hydraulic model") # format axis for ax in axes: ax.set_ylim(0, 70) ax.set_xlim(0, 370) ax.set_xticks([0, 120, 240, 360]) ax.set_xticklabels([0, 2, 4, 6]) ax.set_yticks([0, 20, 40, 60, 80]) ax.legend(loc='lower right') fig.text(0.5, 0.04, 'recovery duration (min)', ha='center') fig.text(0.01, 0.5, 'recovery (%)', va='center', rotation='vertical') plt.tight_layout() plt.subplots_adjust(left=0.09, bottom=0.20, top=0.91) plt.show() plt.close(fig)