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
示例#2
0
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()
示例#3
0
    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
示例#4
0
    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
示例#5
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
示例#6
0
    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
示例#7
0
    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
示例#8
0
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)
示例#9
0
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)