+/* Copyright (c) 2023. The SimGrid Team. All rights reserved. */
+
+/* This program is free software; you can redistribute it and/or modify it
+ * under the terms of the license (GNU LGPL) which comes with this package. */
#include <simgrid/Exception.hpp>
#include <simgrid/plugins/battery.hpp>
-#include <simgrid/s4u/Actor.hpp>
+#include <simgrid/plugins/energy.h>
#include <simgrid/s4u/Engine.hpp>
#include <simgrid/s4u/Host.hpp>
-#include <simgrid/s4u/VirtualMachine.hpp>
#include <simgrid/simix.hpp>
+#include <xbt/asserts.h>
+#include <xbt/log.h>
#include "src/kernel/resource/CpuImpl.hpp"
#include "src/simgrid/module.hpp"
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/split.hpp>
-
-SIMGRID_REGISTER_PLUGIN(battery, "Battery management", &sg_battery_plugin_init)
-
+SIMGRID_REGISTER_PLUGIN(battery, "Battery management", nullptr)
/** @defgroup plugin_battery plugin_battery Plugin Battery
@beginrst
-This is the battery plugin, enabling management of batteries on hosts.
-To activate this plugin, first call :cpp:func:`sg_battery_plugin_init()`.
-
-We consider a constant energy exchange model.
+This is the battery plugin, enabling management of batteries.
-Properties of batteries such as State of Charge and State of Health are lazily updated, ie., when reading values and
-when the power is modified.
+Batteries
+.........
-State of Charge (SoC)
-.....................
-
-If the power of a battery is set to a negative value then the battery will act as a load and fill over time until it
-reaches its maximal SoC. Conversely, if the power of a battery is set to a positive value then the battery will act as a
-generator and deplete over time until it reaches its minimal SoC. When reaching either its maximal or minimal SoC it
-will set its power to 0.
-
-The natural depletion of batteries over time is not taken into account.
+A battery has an initial State of Charge :math:`SoC`, a nominal charge power, a nominal discharge power, a charge
+efficiency :math:`\eta_{charge}`, a discharge efficiency :math:`\eta_{discharge}`, an initial capacity
+:math:`C_{initial}` and a number of cycle :math:`N`.
-State of Health (SoH)
-.....................
+The nominal charge(discharge) power is the maximum power the Battery can consume(provide), before application of the
+charge(discharge) efficiency factor. For instance, if a load provides(consumes) 100W to(from) the Battery with a nominal
+charge(discharge) power of 50W and a charge(discharge) efficiency of 0.9, the Battery will only gain(provide) 45W.
-A battery starts with an energy budget :math:`E` such as:
+We distinguish the energy provided :math:`E_{provided}` / consumed :math:`E_{consumed}` from the energy lost
+:math:`E_{lost}` / gained :math:`E_{gained}`. The energy provided / consumed shows the external point of view, and the
+energy lost / gained shows the internal point of view:
.. math::
- E = C \times U \times D \times N \times 2
+ E_{lost} = {E_{provided} \over \eta_{discharge}}
-Where :math:`C` is the initial capacity, :math:`U` is the ratio of usable capacity, :math:`D` is the depth of discharge
-and :math:`N` is the number of cycles of the battery.
+ E_{gained} = E_{consumed} \times \eta_{charge}
-The SoH represents the consumption of this energy budget during the lifetime of the battery.
-Use the battery reduces its SoH and its capacity in consequence.
-When the SoH reaches 0, the battery becomes unusable.
+For instance, if you apply a load of 100W to a battery for 10s with a discharge efficiency of 0.8, the energy provided
+will be equal to 10kJ, and the energy lost will be equal to 12.5kJ.
-.. warning::
+All the energies are positive, but loads connected to a Battery may be positive or negative, as explained in the next
+section.
- Due to the decrease of the battery capacity with the SoH, a large usable capacity leads to very tiny battery capacity
- when reaching low SoH. This may results in charge and discharge cycles too short to be evaluated by the simulator. To
- avoid this situation you should not try to reach a SoH of 0 with a usable capacity set to 1.
+Use the battery reduces its State of Health :math:`SoH` and its capacity :math:`C` linearly in consequence:
-Plotting the output of the example "battery-degradation" highlights the linear decrease of the SoH due to a continuous
-use of the battery and the decreasing cycle duration as its capacity reduces:
+.. math::
-.. image:: /img/battery_degradation.svg
- :align: center
+ SoH = 1 - {E_{lost} + E_{gained} \over E_{budget}}
-Batteries properties
-....................
-
-Properties of the batteries are defined as properties of hosts in the platform XML file.
-
-Here is an example of XML declaration:
-
-.. code-block:: xml
-
- <host id="Host" speed="100.0Mf" core="1">
- <prop id="battery_active" value="1" />
- <prop id="battery_capacity" value="10" />
- <prop id="battery_cycles" value="200" />
- <prop id="battery_state_of_charge" value="0.8" />
- </host>
-
-The different properties are:
-
-- ``battery_active``: Set the battery as active if set to 1 (default=0)
-- ``battery_power``: Set the initial power of the battery in W (default=0). A negative value indicates that the battery act as a load and charge. A positive value indicates that the battery act as a generator and discharge
-- ``battery_state_of_charge``: Set the initial state of charge of the battery (default=0)
-- ``battery_state_of_charge_min``: Set the minimal state of charge of the battery (default=0.2)
-- ``battery_state_of_charge_max``: Set the maximal state of charge of the battery (default=0.8)
-- ``battery_capacity``: Set the initial capacity of the battery in Wh (default=0)
-- ``battery_usable_capacity``: Set the ratio of usable capacity of the battery (default=0.8)
-- ``battery_depth_of_discharge``: Set the depth of discharge of the battery (default=0.9)
-- ``battery_charge_efficiency``: Set the charge efficiency of the battery (default=1)
-- ``battery_discharge_efficiency``: Set the charge efficiency of the battery (default=1)
-- ``battery_cycles``: Set the number of cycle of the battery (default=1)
-- ``battery_depth_of_discharge``: Set the number of cycle of the battery (default=1)
-- ``battery_eval_cost``: Evaluate the cost of the battery during the simulation if set to 1 (defaulf=0)
-- ``battery_investment_cost``: Set the investment cost of the battery (default=0)
-- ``battery_static_maintenance_cost``: Set the static maintenance cost of the battery (default=0)
-- ``battery_variable_maintenance_cost``: Set the variable maintenance cost of the battery (default=0)
+ C = C_{initial} \times SoH
- @endrst
- */
-XBT_LOG_NEW_DEFAULT_SUBCATEGORY(battery, kernel, "Logging specific to the battery plugin");
-
-namespace simgrid::plugin {
-class Battery {
-private:
- simgrid::s4u::Host* host_ = nullptr;
-
- double state_of_charge_min_ = 0.2;
- double state_of_charge_max_ = 0.8;
- double charge_efficiency_ = 1;
- double discharge_efficiency_ = 1;
- double initial_capacity_wh_ = 0;
- double cycles_ = 1; // total complete cycles (charge + discharge) the battery can do before complete depletion.
- double depth_of_discharge_ = 0.9;
- double usable_capacity_ = 0.8;
- double energy_budget_j_ = 0;
-
- bool active_ = false;
- double power_w_ = 0; // NEGATIVE when charging (consumes power) POSITIVE when discharging (generates power)
- double state_of_charge_ = 0;
- double capacity_wh_ = 0;
- double next_event_ = -1;
- double energy_exchanged_j_ = 0;
- double last_updated_ = 0;
-
- // Calculation of costs from Bei Li thesis (link :https://tel.archives-ouvertes.fr/tel-02077668/document) (chapter 4)
- bool eval_cost_ = false;
- double cumulative_cost_ = 0;
- double investment_cost_per_wh_ = 0;
- double static_maintenance_cost_per_wh_times_h_ = 0;
- double variable_maintenance_cost_per_wh_ = 0;
-
- void init_battery_params();
- void init_cost_params();
- void update();
-
- void set_state_of_charge(double soc);
- void set_state_of_charge_min(double soc);
- void set_state_of_charge_max(double soc);
- void set_capacity(double c);
- void set_initial_capacity(double c);
- void set_cycles(int c);
- void set_depth_of_discharge(double d);
- void set_usable_capacity(double c);
- void set_charge_efficiency(double e);
- void set_discharge_efficiency(double e);
- void set_eval_cost(bool eval);
- void set_investment_cost(double c);
- void set_static_maintenance_cost(double c);
- void set_variable_maintenance_cost(double c);
-
- bool is_charging();
-
-public:
- static simgrid::xbt::Extension<simgrid::s4u::Host, Battery> EXTENSION_ID;
-
- explicit Battery(simgrid::s4u::Host* host);
- ~Battery();
-
- void set_state(bool state);
- void set_power(const double power);
-
- bool is_active() const;
- double get_power();
- double get_state_of_charge();
- double get_state_of_charge_min() const;
- double get_state_of_charge_max() const;
- double get_state_of_health();
- double get_capacity();
- double get_cumulative_cost();
- double get_next_event_date();
-};
-
-simgrid::xbt::Extension<simgrid::s4u::Host, Battery> Battery::EXTENSION_ID;
-
-void Battery::set_power(const double p)
-{
- update();
- xbt_assert(p == 0 or (p > 0 and state_of_charge_ > state_of_charge_min_) or
- (p < 0 and state_of_charge_ < state_of_charge_max_),
- "Incoherent power and state of charge. A battery cannot charge(discharge) past its maximal(minimal) state "
- "of charge.");
- xbt_assert(p == 0 or energy_exchanged_j_ < energy_budget_j_, "Cannot set power of a fully used battery.");
- simgrid::kernel::actor::simcall_answered([this, p] {
- power_w_ = p;
- if (power_w_ == 0) {
- next_event_ = -1;
- return;
- }
- double soc_shutdown;
- double soh_shutdown;
- if (power_w_ > 0) {
- soc_shutdown = capacity_wh_ * 3600 * (state_of_charge_ - state_of_charge_min_) / (power_w_ * charge_efficiency_);
- soh_shutdown = (energy_budget_j_ - energy_exchanged_j_) / (power_w_ * charge_efficiency_);
- } else { // power_w_ < 0
- soc_shutdown =
- capacity_wh_ * 3600 * (state_of_charge_max_ - state_of_charge_) / (abs(power_w_) / discharge_efficiency_);
- soh_shutdown = (energy_budget_j_ - energy_exchanged_j_) / (abs(power_w_) / discharge_efficiency_);
- }
- if (soh_shutdown <= 0)
- next_event_ = simgrid::s4u::Engine::get_clock() + soc_shutdown;
- else
- next_event_ = simgrid::s4u::Engine::get_clock() + std::min(soc_shutdown, soh_shutdown);
- });
-}
-
-void Battery::set_state(bool state)
-{
- update();
- simgrid::kernel::actor::simcall_answered([this, state] { active_ = state; });
-}
-
-void Battery::set_state_of_charge(double soc)
-{
- xbt_assert(soc > 0 and soc <= 1, " : state of charge should be in ]0,1] (provided: %f)", soc);
- simgrid::kernel::actor::simcall_answered([this, soc] { state_of_charge_ = soc; });
-}
+With:
-void Battery::set_state_of_charge_min(double soc)
-{
- xbt_assert(soc > 0 and soc <= 1 and soc < state_of_charge_max_,
- " : state of charge min should be in ]0,1] and below state of charge max (provided: %f)", soc);
- simgrid::kernel::actor::simcall_answered([this, soc] { state_of_charge_min_ = soc; });
-}
-
-void Battery::set_state_of_charge_max(double soc)
-{
- xbt_assert(soc > 0 and soc <= 1 and soc > state_of_charge_min_,
- " : state of charge max should be in ]0,1] and above state of charge min (provided: %f)", soc);
- simgrid::kernel::actor::simcall_answered([this, soc] { state_of_charge_max_ = soc; });
-}
-
-void Battery::set_initial_capacity(double c)
-{
- xbt_assert(c > 0, " : capacity should be > 0 (provided: %f)", c);
- simgrid::kernel::actor::simcall_answered([this, c] { initial_capacity_wh_ = c; });
-}
+.. math::
-void Battery::set_capacity(double c)
-{
- xbt_assert(c > 0, " : capacity should be > 0 (provided: %f)", c);
- simgrid::kernel::actor::simcall_answered([this, c] { capacity_wh_ = c; });
-}
+ E_{budget} = C_{initial} \times N \times 2
-void Battery::set_cycles(int c)
-{
- xbt_assert(c > 0, " : cycles should be > 0 (provided: %d)", c);
- simgrid::kernel::actor::simcall_answered([this, c] { cycles_ = c; });
-}
+Plotting the output of the example "battery-degradation" highlights the linear decrease of the :math:`SoH` due to a
+continuous use of the battery alternating between charge and discharge:
-void Battery::set_depth_of_discharge(double d)
-{
- xbt_assert(d > 0 and d <= 1, " : depth of discharge should be in ]0, 1] (provided: %f)", d);
- simgrid::kernel::actor::simcall_answered([this, d] { depth_of_discharge_ = d; });
-}
+.. image:: /img/battery_degradation.svg
+ :align: center
-void Battery::set_usable_capacity(double c)
-{
- xbt_assert(c > 0 and c <= 1, " : usable capacity should be in ]0, 1] (provided: %f)", c);
- simgrid::kernel::actor::simcall_answered([this, c] { usable_capacity_ = c; });
-}
+The natural depletion of batteries over time is not taken into account.
-void Battery::set_charge_efficiency(double e)
-{
- xbt_assert(e > 0 and e <= 1, " : charge efficiency should be in [0,1] (provided: %f)", e);
- simgrid::kernel::actor::simcall_answered([this, e] { charge_efficiency_ = e; });
-}
+Loads & Hosts
+.............
-void Battery::set_discharge_efficiency(double e)
-{
- xbt_assert(e > 0 and e <= 1, " : discharge efficiency should be in [0,1] (provided: %f)", e);
- simgrid::kernel::actor::simcall_answered([this, e] { discharge_efficiency_ = e; });
-}
+You can add named loads to a battery. Those loads may be positive and consume energy from the battery, or negative and
+provide energy to the battery. You can also connect hosts to a battery. Theses hosts will consume their energy from the
+battery until the battery is empty or until the connection between the hosts and the battery is set inactive.
-void Battery::set_eval_cost(bool eval)
-{
- simgrid::kernel::actor::simcall_answered([this, eval] { eval_cost_ = eval; });
-}
+Handlers
+........
-void Battery::set_investment_cost(double c)
-{
- xbt_assert(c >= 0, " : investment cost should be >= 0 (provided: %f)", c);
- simgrid::kernel::actor::simcall_answered([this, c] { investment_cost_per_wh_ = c; });
-}
+You can schedule handlers that will happen at specific SoC of the battery and trigger a callback.
+Theses handlers may be recurrent, for instance you may want to always set all loads to zero and deactivate all hosts
+connections when the battery reaches 20% SoC.
-void Battery::set_static_maintenance_cost(double c)
-{
- xbt_assert(c >= 0, " : static maintenance cost should be >= 0 (provided: %f)", c);
- simgrid::kernel::actor::simcall_answered([this, c] { static_maintenance_cost_per_wh_times_h_ = c; });
-}
+ @endrst
+ */
+XBT_LOG_NEW_DEFAULT_SUBCATEGORY(Battery, kernel, "Logging specific to the battery plugin");
-void Battery::set_variable_maintenance_cost(double c)
-{
- xbt_assert(c >= 0, " : variable maintenance cost should be >= 0 (provided: %f)", c);
- simgrid::kernel::actor::simcall_answered([this, c] { variable_maintenance_cost_per_wh_ = c; });
-}
+namespace simgrid::plugins {
-bool Battery::is_charging()
-{
- update();
- return power_w_ < 0;
-}
+/* BatteryModel */
-bool Battery::is_active() const
-{
- return active_;
-}
+BatteryModel::BatteryModel() : Model("BatteryModel") {}
-double Battery::get_power()
+void BatteryModel::add_battery(BatteryPtr b)
{
- update();
- return power_w_;
+ batteries_.push_back(b);
}
-double Battery::get_state_of_charge()
+void BatteryModel::update_actions_state(double now, double delta)
{
- update();
- return state_of_charge_;
+ for (auto battery : batteries_)
+ battery->update();
}
-double Battery::get_state_of_charge_min() const
+double BatteryModel::next_occurring_event(double now)
{
- return state_of_charge_min_;
+ double time_delta = -1;
+ for (auto battery : batteries_) {
+ double time_delta_battery = battery->next_occurring_handler();
+ time_delta = time_delta == -1 or time_delta_battery < time_delta ? time_delta_battery : time_delta;
+ }
+ return time_delta;
}
-double Battery::get_state_of_charge_max() const
-{
- return state_of_charge_max_;
-}
+/* Handler */
-double Battery::get_state_of_health()
+Battery::Handler::Handler(double state_of_charge, Flow flow, Persistancy p, std::function<void()> callback)
+ : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), persistancy_(p)
{
- update();
- return 1 - (energy_exchanged_j_ / energy_budget_j_);
}
-double Battery::get_capacity()
+std::shared_ptr<Battery::Handler> Battery::Handler::init(double state_of_charge, Flow flow, Persistancy p,
+ std::function<void()> callback)
{
- update();
- return capacity_wh_;
+ return std::make_shared<Battery::Handler>(state_of_charge, flow, p, callback);
}
-double Battery::get_cumulative_cost()
-{
- update();
- return cumulative_cost_;
-}
+/* Battery */
-double Battery::get_next_event_date()
-{
- update();
- return next_event_;
-}
+std::shared_ptr<BatteryModel> Battery::battery_model_;
-void Battery::init_battery_params()
+void Battery::init_plugin()
{
- const char* prop_chars;
- prop_chars = host_->get_property("battery_capacity");
- if (prop_chars) {
- set_capacity(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- set_initial_capacity(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- }
- prop_chars = host_->get_property("battery_usable_capacity");
- if (prop_chars)
- set_usable_capacity(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_depth_of_discharge");
- if (prop_chars)
- set_depth_of_discharge(
- xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_cycles");
- if (prop_chars)
- set_cycles(xbt_str_parse_int(prop_chars, ("cannot parse int: " + std::string(prop_chars)).c_str()));
- simgrid::kernel::actor::simcall_answered([this] {
- energy_budget_j_ = (initial_capacity_wh_ * usable_capacity_ * depth_of_discharge_ * 3600 * cycles_ * 2);
- });
- prop_chars = host_->get_property("battery_active");
- if (prop_chars)
- set_state(xbt_str_parse_int(prop_chars, ("cannot parse int: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_state_of_charge_min");
- if (prop_chars)
- set_state_of_charge_min(
- xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_state_of_charge_max");
- if (prop_chars)
- set_state_of_charge_max(
- xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_charge_efficiency");
- if (prop_chars)
- set_charge_efficiency(
- xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_discharge_efficiency");
- if (prop_chars)
- set_discharge_efficiency(
- xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_state_of_charge");
- if (prop_chars)
- set_state_of_charge(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_eval_cost");
- if (prop_chars)
- set_eval_cost(xbt_str_parse_int(prop_chars, ("cannot parse int: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_investment_cost");
- if (prop_chars)
- set_investment_cost(xbt_str_parse_int(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_static_maintenance_cost");
- if (prop_chars)
- set_static_maintenance_cost(
- xbt_str_parse_int(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_variable_maintenance_cost");
- if (prop_chars)
- set_variable_maintenance_cost(
- xbt_str_parse_int(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- prop_chars = host_->get_property("battery_power");
- if (prop_chars)
- set_power(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
- simgrid::kernel::actor::simcall_answered([this] { last_updated_ = simgrid::s4u::Engine::get_clock(); });
+ auto model = std::make_shared<BatteryModel>();
+ simgrid::s4u::Engine::get_instance()->add_model(model);
+ Battery::battery_model_ = model;
}
void Battery::update()
{
- simgrid::kernel::actor::simcall_answered([this] {
- double now = simgrid::s4u::Engine::get_clock();
- double time_delta_real = now - last_updated_;
- if (time_delta_real <= 0 or not is_active())
+ kernel::actor::simcall_answered([this] {
+ double now = s4u::Engine::get_clock();
+ double time_delta_s = now - last_updated_;
+
+ // Nothing to update
+ if (time_delta_s <= 0)
return;
- double time_delta_until_event = next_event_ - last_updated_;
- bool event = next_event_ != -1 and time_delta_until_event <= time_delta_real;
- double power_real_w = power_w_ < 0 ? power_w_ * charge_efficiency_ : power_w_ / discharge_efficiency_;
- state_of_charge_ -= power_real_w * (event ? time_delta_until_event : time_delta_real) / (3600 * capacity_wh_);
- energy_exchanged_j_ += (event ? time_delta_until_event : time_delta_real) * abs(power_real_w);
- capacity_wh_ = initial_capacity_wh_ * usable_capacity_ * (1 - (energy_exchanged_j_ / energy_budget_j_)) +
- initial_capacity_wh_ * (1 - usable_capacity_);
- capacity_wh_ = std::max(capacity_wh_, 0.0);
- if (eval_cost_) {
- double usage_cost_per_wh =
- (investment_cost_per_wh_ / (depth_of_discharge_ * cycles_ * 2) + variable_maintenance_cost_per_wh_);
- double usage_cost =
- usage_cost_per_wh * abs(power_real_w) * (event ? time_delta_until_event : time_delta_real) / 3600;
- double static_maintenance_cost =
- static_maintenance_cost_per_wh_times_h_ * initial_capacity_wh_ * time_delta_real / 3600;
- cumulative_cost_ += usage_cost + static_maintenance_cost;
+
+ // Calculate energy provided / consumed during this step
+ double provided_power_w = 0;
+ double consumed_power_w = 0;
+ for (auto const& [host, active] : host_loads_)
+ provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
+ for (auto const& [name, load] : named_loads_) {
+ if (load > 0)
+ provided_power_w += load;
+ else
+ consumed_power_w += -load;
}
- if (event) {
- power_w_ = 0;
- next_event_ = -1;
+ provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
+ consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
+
+ double energy_lost_delta_j = provided_power_w / discharge_efficiency_ * time_delta_s;
+ double energy_gained_delta_j = consumed_power_w * charge_efficiency_ * time_delta_s;
+
+ // Check that the provided/consumed energy is valid
+ energy_lost_delta_j = std::min(energy_lost_delta_j, energy_stored_j_ + energy_gained_delta_j);
+ /* Charging deteriorate the capacity, but the capacity is used to evaluate the maximum charge so
+ we need to evaluate the theorethical new capacity in the worst case when we fully charge the battery */
+ double new_tmp_capacity_wh =
+ (initial_capacity_wh_ *
+ (1 - (energy_provided_j_ + energy_lost_delta_j * discharge_efficiency_ + energy_consumed_j_ -
+ (energy_stored_j_ + energy_lost_delta_j) / charge_efficiency_) /
+ energy_budget_j_)) /
+ (1 + 3600 * initial_capacity_wh_ / (charge_efficiency_ * energy_budget_j_));
+ energy_gained_delta_j =
+ std::min(energy_gained_delta_j, (3600 * new_tmp_capacity_wh) - energy_stored_j_ + energy_lost_delta_j);
+
+ // Updating battery
+ energy_provided_j_ += energy_lost_delta_j * discharge_efficiency_;
+ energy_consumed_j_ += energy_gained_delta_j / charge_efficiency_;
+ capacity_wh_ =
+ initial_capacity_wh_ *
+ (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
+ energy_stored_j_ += energy_gained_delta_j - energy_lost_delta_j;
+ energy_stored_j_ = std::min(energy_stored_j_, 3600 * capacity_wh_);
+ last_updated_ = now;
+
+ std::vector<std::shared_ptr<Handler>> to_delete = {};
+ for (auto handler : handlers_) {
+ if (abs(handler->time_delta_ - time_delta_s) < 0.000000001) {
+ handler->callback_();
+ if (handler->persistancy_ == Handler::Persistancy::PERSISTANT)
+ handler->time_delta_ = -1;
+ else
+ to_delete.push_back(handler);
+ }
}
- last_updated_ = now;
+ for (auto handler : to_delete)
+ delete_handler(handler);
});
}
-Battery::Battery(simgrid::s4u::Host* host) : host_(host)
-{
- init_battery_params();
-}
-
-Battery::~Battery() = default;
-} // namespace simgrid::plugin
-
-using simgrid::plugin::Battery;
-
-/* **************************** events callback *************************** */
-
-static void on_creation(simgrid::s4u::Host& host)
+double Battery::next_occurring_handler()
{
- if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
- return;
- host.extension_set(new Battery(&host));
-}
-
-/* **************************** Public interface *************************** */
+ double provided_power_w = 0;
+ double consumed_power_w = 0;
+ for (auto const& [host, active] : host_loads_)
+ provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
+ for (auto const& [name, load] : named_loads_) {
+ if (load > 0)
+ provided_power_w += load;
+ else
+ consumed_power_w += -load;
+ }
-static void ensure_plugin_inited()
-{
- if (not Battery::EXTENSION_ID.valid())
- throw simgrid::xbt::InitializationError("The Battery plugin is not active. Please call sg_battery_plugin_init() "
- "before calling any function related to that plugin.");
+ provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
+ consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
+
+ double time_delta = -1;
+ for (auto& handler : handlers_) {
+ double lost_power_w = provided_power_w / discharge_efficiency_;
+ double gained_power_w = consumed_power_w * charge_efficiency_;
+ // Handler cannot happen
+ if ((lost_power_w == gained_power_w) or (handler->state_of_charge_ == energy_stored_j_ / (3600 * capacity_wh_)) or
+ (lost_power_w > gained_power_w and handler->flow_ == Flow::CHARGE) or
+ (lost_power_w < gained_power_w and handler->flow_ == Flow::DISCHARGE)) {
+ continue;
+ }
+ // Evaluate time until handler happen
+ else {
+ /* The time to reach a state of charge depends on the capacity, but charging and discharging deteriorate the
+ * capacity, so we need to evaluate the time considering a capacity that also depends on time
+ */
+ handler->time_delta_ =
+ (3600 * handler->state_of_charge_ * initial_capacity_wh_ *
+ (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) /
+ energy_budget_j_) -
+ energy_stored_j_) /
+ (gained_power_w - lost_power_w +
+ 3600 * handler->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) /
+ energy_budget_j_);
+ if ((time_delta == -1 or handler->time_delta_ < time_delta) and abs(handler->time_delta_) > 0.000000001)
+ time_delta = handler->time_delta_;
+ }
+ }
+ return time_delta;
+}
+
+Battery::Battery(const std::string& name, double state_of_charge, double nominal_charge_power_w,
+ double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
+ double initial_capacity_wh, int cycles)
+ : name_(name)
+ , nominal_charge_power_w_(nominal_charge_power_w)
+ , nominal_discharge_power_w_(nominal_discharge_power_w)
+ , charge_efficiency_(charge_efficiency)
+ , discharge_efficiency_(discharge_efficiency)
+ , initial_capacity_wh_(initial_capacity_wh)
+ , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
+ , capacity_wh_(initial_capacity_wh)
+ , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
+{
+ xbt_assert(nominal_charge_power_w <= 0, " : nominal charge power must be non-negative (provided: %f)",
+ nominal_charge_power_w);
+ xbt_assert(nominal_discharge_power_w >= 0, " : nominal discharge power must be non-negative (provided: %f)",
+ nominal_discharge_power_w);
+ xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
+ state_of_charge);
+ xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
+ charge_efficiency);
+ xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
+ " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
+ xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
+ xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
}
/** @ingroup plugin_battery
- * @brief Enable battery plugin.
+ * @param name The name of the Battery.
+ * @param state_of_charge The initial state of charge of the Battery [0,1].
+ * @param nominal_charge_power_w The maximum power delivered by the Battery in W (<= 0).
+ * @param nominal_discharge_power_w The maximum power absorbed by the Battery in W (>= 0).
+ * @param charge_efficiency The charge efficiency of the Battery [0,1].
+ * @param discharge_efficiency The discharge efficiency of the Battery [0,1].
+ * @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
+ * @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
+ * @return A BatteryPtr pointing to the new Battery.
*/
-void sg_battery_plugin_init()
-{
- if (Battery::EXTENSION_ID.valid())
- return;
- Battery::EXTENSION_ID = simgrid::s4u::Host::extension_create<Battery>();
- simgrid::s4u::Host::on_creation_cb(&on_creation);
+BatteryPtr Battery::init(const std::string& name, double state_of_charge, double nominal_charge_power_w,
+ double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
+ double initial_capacity_wh, int cycles)
+{
+ static bool plugin_inited = false;
+ if (not plugin_inited) {
+ init_plugin();
+ plugin_inited = true;
+ }
+ auto battery = BatteryPtr(new Battery(name, state_of_charge, nominal_charge_power_w, nominal_discharge_power_w,
+ charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
+ battery_model_->add_battery(battery);
+ return battery;
}
/** @ingroup plugin_battery
- * @param state The state to set.
- * @brief Set the state of the battery.
- * A battery set to inactive (false) will neither update its state of charge nor its state of health.
+ * @param name The name of the load
+ * @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
*/
-void sg_battery_set_state(const_sg_host_t host, bool state)
+void Battery::set_load(const std::string& name, double power_w)
{
- ensure_plugin_inited();
- host->extension<Battery>()->set_state(state);
+ named_loads_[name] = power_w;
}
/** @ingroup plugin_battery
- * @param power The power to set in W.
- * @brief Set the power of the battery.
- * @note A negative value makes the battery act as a load and charge.
- * A positive value makes the battery act as a generator and discharge.
+ * @param host The Host to connect.
+ * @param active Status of the connected Host (default true).
+ * @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
+ from the Battery. To modify this status connect again the same Host with a different status.
+ @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
+ In this case all Batteries would have the full consumption from this Host.
*/
-void sg_battery_set_power(const_sg_host_t host, double power)
+void Battery::connect_host(s4u::Host* host, bool active)
{
- ensure_plugin_inited();
- host->extension<Battery>()->set_power(power);
+ host_loads_[host] = active;
}
/** @ingroup plugin_battery
- * @brief Return true if the battery is active.
+ * @return The state of charge of the battery.
*/
-bool sg_battery_is_active(const_sg_host_t host)
+double Battery::get_state_of_charge()
{
- ensure_plugin_inited();
- return host->extension<Battery>()->is_active();
+ return energy_stored_j_ / (3600 * capacity_wh_);
}
/** @ingroup plugin_battery
- * @brief Return the power of the battery in W.
- * @note A negative value indicates that the battery act as a load and charge.
- * A positive value indicates that the battery act as a generator and discharge.
+ * @return The state of health of the Battery.
*/
-double sg_battery_get_power(const_sg_host_t host)
+double Battery::get_state_of_health()
{
- ensure_plugin_inited();
- return host->extension<Battery>()->get_power();
+ return 1 -
+ ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
}
/** @ingroup plugin_battery
- * @brief Return the state of charge of the battery.
+ * @return The current capacity of the Battery.
*/
-double sg_battery_get_state_of_charge(const_sg_host_t host)
+double Battery::get_capacity()
{
- ensure_plugin_inited();
- return host->extension<Battery>()->get_state_of_charge();
+ return capacity_wh_;
}
/** @ingroup plugin_battery
- * @brief Return the minimal state of charge of the battery.
+ * @return The energy provided by the Battery.
+ * @note It is the energy provided from an external point of view, after application of the discharge efficiency.
+ It means that the Battery lost more energy than it has provided.
*/
-double sg_battery_get_state_of_charge_min(const_sg_host_t host)
+double Battery::get_energy_provided()
{
- ensure_plugin_inited();
- return host->extension<Battery>()->get_state_of_charge_min();
+ return energy_provided_j_;
}
/** @ingroup plugin_battery
- * @brief Return the maximal state of charge of the battery.
+ * @return The energy consumed by the Battery.
+ * @note It is the energy consumed from an external point of view, before application of the charge efficiency.
+ It means that the Battery consumed more energy than is has absorbed.
*/
-double sg_battery_get_state_of_charge_max(const_sg_host_t host)
+double Battery::get_energy_consumed()
{
- ensure_plugin_inited();
- return host->extension<Battery>()->get_state_of_charge_max();
+ return energy_consumed_j_;
}
/** @ingroup plugin_battery
- * @brief Return the state of health of the battery.
+ * @param unit Valid units are J (default) and Wh.
+ * @return Energy stored in the Battery.
*/
-double sg_battery_get_state_of_health(const_sg_host_t host)
+double Battery::get_energy_stored(std::string unit)
{
- ensure_plugin_inited();
- return host->extension<Battery>()->get_state_of_health();
+ if (unit == "J")
+ return energy_stored_j_;
+ else if (unit == "Wh")
+ return energy_stored_j_ / 3600;
+ else
+ xbt_die("Invalid unit. Valid units are J (default) or Wh.");
}
/** @ingroup plugin_battery
- * @brief Return the capacity of the battery in Wh.
- * @note capacity is reduced by the state of health of the battery.
+ * @brief Schedule a new Handler.
+ * @param state_of_charge The state of charge at which the Handler will happen.
+ * @param flow The flow in which the Handler will happen, either when the Battery is charging or discharging.
+ * @param callback The callable to trigger when the Handler happen.
+ * @param p If the Handler is recurrent or unique.
+ * @return A shared pointer of the new Handler.
*/
-double sg_battery_get_capacity(const_sg_host_t host)
+std::shared_ptr<Battery::Handler> Battery::schedule_handler(double state_of_charge, Flow flow, Handler::Persistancy p,
+ std::function<void()> callback)
{
- ensure_plugin_inited();
- return host->extension<Battery>()->get_capacity();
+ auto handler = Handler::init(state_of_charge, flow, p, callback);
+ handlers_.push_back(handler);
+ return handler;
}
/** @ingroup plugin_battery
- * @brief Return the cumulative cost of the battery.
+ * @return A vector containing the Handlers associated to the Battery.
*/
-double sg_battery_get_cumulative_cost(const_sg_host_t host)
+std::vector<std::shared_ptr<Battery::Handler>> Battery::get_handlers()
{
- ensure_plugin_inited();
- return host->extension<Battery>()->get_cumulative_cost();
+ return handlers_;
}
/** @ingroup plugin_battery
- * @brief Return the date of the next event, i.e., when the battery will be empty or full.
- * @note If power is null then return -1.
+ * @brief Remove an Handler from the Battery.
*/
-double sg_battery_get_next_event_date(const_sg_host_t host)
+void Battery::delete_handler(std::shared_ptr<Handler> handler)
{
- ensure_plugin_inited();
- return host->extension<Battery>()->get_next_event_date();
+ handlers_.erase(std::remove_if(handlers_.begin(), handlers_.end(),
+ [&handler](std::shared_ptr<Handler> e) { return handler == e; }),
+ handlers_.end());
}
+} // namespace simgrid::plugins
\ No newline at end of file