Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Merge branch 'plugins-energy-battery-interaction' into 'master'
[simgrid.git] / src / plugins / battery.cpp
index c2229b90ad8d73bdd092a6324b5ec57f1f96643f..3a870f9945b1d8a06988995dce50e9739b30036d 100644 (file)
@@ -21,34 +21,69 @@ SIMGRID_REGISTER_PLUGIN(battery, "Battery management", nullptr)
 
 This is the battery plugin, enabling management of batteries.
 
-With this plugin you can:
+Batteries
+.........
 
-- create Batteries
-- associate positive or negative load to Batteries
-- connect Hosts to Batteries
-- create Events triggered whenever a Battery reach a specific state of charge
+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`.
 
-The natural depletion of batteries over time is not taken into account.
+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.
+
+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_{lost} = {E_{provided} \over \eta_{discharge}}
 
-A battery starts with an energy budget :math:`E` such as:
+  E_{gained} = E_{consumed} \times \eta_{charge}
+
+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.
+
+All the energies are positive, but loads connected to a Battery may be positive or negative, as explained in the next
+section.
+
+Use the battery reduces its State of Health :math:`SoH` and its capacity :math:`C` linearly in consequence:
 
 .. math::
 
-  E = C \times D \times N \times 2
+  SoH = 1 - {E_{lost} + E_{gained} \over E_{budget}}
 
-Where :math:`C` is the initial capacity, :math:`D` is the depth of discharge
-and :math:`N` is the number of cycles of the battery.
+  C = C_{initial} \times SoH
 
-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.
+With:
+
+.. math::
 
-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:
+  E_{budget} = C_{initial} \times N \times 2
+
+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:
 
 .. image:: /img/battery_degradation.svg
    :align: center
 
+The natural depletion of batteries over time is not taken into account.
+
+Loads & Hosts
+..............
+
+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.
+
+Handlers
+......
+
+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.
+
   @endrst
  */
 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(Battery, kernel, "Logging specific to the battery plugin");
@@ -74,23 +109,23 @@ double BatteryModel::next_occurring_event(double now)
 {
   double time_delta = -1;
   for (auto battery : batteries_) {
-    double time_delta_battery = battery->next_occurring_event();
+    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;
 }
 
-/* Event */
+/* Handler */
 
-Battery::Event::Event(double state_of_charge, Flow flow, std::function<void()> callback, bool repeat)
+Battery::Handler::Handler(double state_of_charge, Flow flow,  bool repeat, std::function<void()> callback)
     : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), repeat_(repeat)
 {
 }
 
-std::shared_ptr<Battery::Event> Battery::Event::init(double state_of_charge, Flow flow, std::function<void()> callback,
-                                                     bool repeat)
+std::shared_ptr<Battery::Handler> Battery::Handler::init(double state_of_charge, Flow flow, bool repeat,
+                                                         std::function<void()> callback)
 {
-  return std::make_shared<Battery::Event>(state_of_charge, flow, callback, repeat);
+  return std::make_shared<Battery::Handler>(state_of_charge, flow, repeat, callback);
 }
 
 /* Battery */
@@ -125,6 +160,9 @@ void Battery::update()
       else
         consumed_power_w += -load;
     }
+    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;
 
@@ -144,27 +182,29 @@ void Battery::update()
     // 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_ + energy_consumed_j_) / energy_budget_j_);
+    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<Event>> to_delete = {};
-    for (auto event : events_) {
-      if (abs(event->time_delta_ - time_delta_s) < 0.000000001) {
-        event->callback_();
-        if (event->repeat_)
-          event->time_delta_ = -1;
+    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->repeat_)
+          handler->time_delta_ = -1;
         else
-          to_delete.push_back(event);
+          to_delete.push_back(handler);
       }
     }
-    for (auto event : to_delete)
-      delete_event(event);
+    for (auto handler : to_delete)
+      delete_handler(handler);
   });
 }
 
-double Battery::next_occurring_event()
+double Battery::next_occurring_handler()
 {
   double provided_power_w = 0;
   double consumed_power_w = 0;
@@ -177,46 +217,56 @@ double Battery::next_occurring_event()
       consumed_power_w += -load;
   }
 
+  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& event : events_) {
+  for (auto& handler : handlers_) {
     double lost_power_w   = provided_power_w / discharge_efficiency_;
     double gained_power_w = consumed_power_w * charge_efficiency_;
-    // Event cannot happen
-    if ((lost_power_w == gained_power_w) or (event->state_of_charge_ == energy_stored_j_ / (3600 * capacity_wh_)) or
-        (lost_power_w > gained_power_w and event->flow_ == Flow::CHARGE) or
-        (lost_power_w < gained_power_w and event->flow_ == Flow::DISCHARGE)) {
+    // 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 event happen
+    // 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
        */
-      event->time_delta_ = (3600 * event->state_of_charge_ * initial_capacity_wh_ *
-                                (1 - (energy_provided_j_ + energy_consumed_j_) / energy_budget_j_) -
-                            energy_stored_j_) /
-                           (gained_power_w - lost_power_w +
-                            3600 * event->state_of_charge_ * initial_capacity_wh_ *
-                                (consumed_power_w + provided_power_w) / energy_budget_j_);
-      if ((time_delta == -1 or event->time_delta_ < time_delta) and abs(event->time_delta_) > 0.000000001)
-        time_delta = event->time_delta_;
+      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 charge_efficiency, double discharge_efficiency,
-                 double initial_capacity_wh, int cycles, double depth_of_discharge)
+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)
-    , cycles_(cycles)
-    , depth_of_discharge_(depth_of_discharge)
-    , energy_budget_j_(initial_capacity_wh * depth_of_discharge * 3600 * cycles * 2)
+    , 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)",
@@ -225,30 +275,30 @@ Battery::Battery(const std::string& name, double state_of_charge, double charge_
              " : 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);
-  xbt_assert(depth_of_discharge > 0 and depth_of_discharge <= 1,
-             " : depth of discharge should be in ]0, 1] (provided: %f)", depth_of_discharge);
 }
 
 /** @ingroup plugin_battery
  *  @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.
- *  @param depth_of_discharge The depth of discharge of the Battery.
  *  @return A BatteryPtr pointing to the new Battery.
  */
-BatteryPtr Battery::init(const std::string& name, double state_of_charge, double charge_efficiency,
-                         double discharge_efficiency, double initial_capacity_wh, int cycles, double depth_of_discharge)
+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, charge_efficiency, discharge_efficiency,
-                                        initial_capacity_wh, cycles, depth_of_discharge));
+  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;
 }
@@ -263,7 +313,7 @@ void Battery::set_load(const std::string& name, double power_w)
 }
 
 /** @ingroup plugin_battery
- *  @param h The Host to connect.
+ *  @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.
@@ -288,7 +338,8 @@ double Battery::get_state_of_charge()
  */
 double Battery::get_state_of_health()
 {
-  return 1 - ((energy_provided_j_ + energy_consumed_j_) / energy_budget_j_);
+  return 1 -
+         ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
 }
 
 /** @ingroup plugin_battery
@@ -320,7 +371,7 @@ double Battery::get_energy_consumed()
 }
 
 /** @ingroup plugin_battery
- *  @param Unit Valid units are J (default) and Wh.
+ *  @param unit Valid units are J (default) and Wh.
  *  @return Energy stored in the Battery.
  */
 double Battery::get_energy_stored(std::string unit)
@@ -334,36 +385,35 @@ double Battery::get_energy_stored(std::string unit)
 }
 
 /** @ingroup plugin_battery
- *  @brief Create a new Event.
- *  @param state_of_charge The state of charge at which the Event will happen.
- *  @param flow The flow in which the Event will happen, either when the Battery is charging or discharging.
- *  @param callback The callable to trigger when the Event happen.
- *  @param repeat If the Event is a recurrent Event or a single Event.
- *  @return A shared pointer of the new Event.
+ *  @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 repeat If the Handler is recurrent or unique.
+ *  @return A shared pointer of the new Handler.
  */
-std::shared_ptr<Battery::Event> Battery::create_event(double state_of_charge, Flow flow, std::function<void()> callback,
-                                                      bool repeat)
+std::shared_ptr<Battery::Handler> Battery::schedule_handler(double state_of_charge, Flow flow, bool repeat, std::function<void()> callback)
 {
-  auto event = Event::init(state_of_charge, flow, callback, repeat);
-  events_.push_back(event);
-  return event;
+  auto handler = Handler::init(state_of_charge, flow, repeat, callback);
+  handlers_.push_back(handler);
+  return handler;
 }
 
 /** @ingroup plugin_battery
- *  @return A vector containing the Events associated to the Battery.
+ *  @return A vector containing the Handlers associated to the Battery.
  */
-std::vector<std::shared_ptr<Battery::Event>> Battery::get_events()
+std::vector<std::shared_ptr<Battery::Handler>> Battery::get_handlers()
 {
-  return events_;
+  return handlers_;
 }
 
 /** @ingroup plugin_battery
- *  @brief Remove an Event from the Battery.
+ *  @brief Remove an Handler from the Battery.
  */
-void Battery::delete_event(std::shared_ptr<Event> event)
+void Battery::delete_handler(std::shared_ptr<Handler> handler)
 {
-  events_.erase(
-      std::remove_if(events_.begin(), events_.end(), [&event](std::shared_ptr<Event> e) { return event == e; }),
-      events_.end());
+  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