Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Fix a bug in concurrent modif of a collection that was revealed by GLIBCXX_DEBUG
[simgrid.git] / src / plugins / battery.cpp
index df6ae46..c682e17 100644 (file)
@@ -6,10 +6,7 @@
 #include <simgrid/plugins/battery.hpp>
 #include <simgrid/plugins/energy.h>
 #include <simgrid/s4u/Engine.hpp>
-#include <simgrid/s4u/Host.hpp>
 #include <simgrid/simix.hpp>
-#include <xbt/asserts.h>
-#include <xbt/log.h>
 
 #include "src/kernel/resource/CpuImpl.hpp"
 #include "src/simgrid/module.hpp"
@@ -21,33 +18,75 @@ 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.
 
-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 N \times 2
+  E_{lost} = {E_{provided} \over \eta_{discharge}}
+
+  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::
 
-Where :math:`C` is the initial capacity and :math:`N` is the number of cycles of the battery.
+  SoH = 1 - {E_{lost} + E_{gained} \over E_{budget}}
 
-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.
+  C = C_{initial} \times SoH
 
-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:
+With:
+
+.. math::
+
+  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.
+
+Connector
+.........
+
+A Battery can act as a connector to connect Solar Panels direcly to loads. Such Battery is created without any
+parameter, cannot store energy and has a transfer efficiency of 100%.
+
   @endrst
  */
 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(Battery, kernel, "Logging specific to the battery plugin");
@@ -71,25 +110,30 @@ void BatteryModel::update_actions_state(double now, double delta)
 
 double BatteryModel::next_occurring_event(double now)
 {
+  static bool init = false;
+  if (!init) {
+    init = true;
+    return 0;
+  }
   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)
-    : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), repeat_(repeat)
+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)
 {
 }
 
-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, Persistancy p,
+                                                         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, p, callback);
 }
 
 /* Battery */
@@ -118,12 +162,18 @@ void Battery::update()
     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;
+    for (auto const& [name, pair] : named_loads_) {
+      if (not pair.first)
+        continue;
+      if (pair.second > 0)
+        provided_power_w += pair.second;
       else
-        consumed_power_w += -load;
+        consumed_power_w += -pair.second;
     }
+
+    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;
 
@@ -143,70 +193,91 @@ 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_);
+
+    // This battery is a simple connector, we only update energy provided and consumed
+    if (energy_budget_j_ == 0) {
+      energy_consumed_j_ = energy_provided_j_;
+      last_updated_      = now;
+      return;
+    }
+
+    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;
+    auto handlers_2 = handlers_;
+    for (auto handler : handlers_2) {
+      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(event);
+          delete_handler(handler);
       }
     }
-    for (auto event : to_delete)
-      delete_event(event);
   });
 }
 
-double Battery::next_occurring_event()
+double Battery::next_occurring_handler()
 {
   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;
+  for (auto const& [name, pair] : named_loads_) {
+    if (not pair.first)
+      continue;
+    if (pair.second > 0)
+      provided_power_w += pair.second;
     else
-      consumed_power_w += -load;
+      consumed_power_w += -pair.second;
   }
 
+  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)) {
+    if ((lost_power_w == gained_power_w) or (handler->state_of_charge_ == get_state_of_charge()) or
+        (lost_power_w > gained_power_w and
+         (handler->flow_ == Flow::CHARGE or handler->state_of_charge_ > get_state_of_charge())) or
+        (lost_power_w < gained_power_w and
+         (handler->flow_ == Flow::DISCHARGE or handler->state_of_charge_ < get_state_of_charge()))) {
       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,
+Battery::Battery() {}
+
+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)
@@ -214,6 +285,10 @@ Battery::Battery(const std::string& name, double state_of_charge, double charge_
     , 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 <= 0 (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)",
@@ -224,25 +299,46 @@ Battery::Battery(const std::string& name, double state_of_charge, double charge_
   xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
 }
 
+/** @ingroup plugin_battery
+ *  @brief Init a Battery with this constructor makes it only usable as a connector.
+ *         A connector has no capacity and only delivers as much power as it receives
+           with a transfer efficiency of 100%.
+ *  @return A BatteryPtr pointing to the new Battery.
+ */
+BatteryPtr Battery::init()
+{
+  static bool plugin_inited = false;
+  if (not plugin_inited) {
+    init_plugin();
+    plugin_inited = true;
+  }
+  auto battery = BatteryPtr(new Battery());
+  battery_model_->add_battery(battery);
+  return battery;
+}
+
 /** @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 absorbed by the Battery in W (<= 0).
+ *  @param nominal_discharge_power_w The maximum power delivered 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.
  */
-BatteryPtr Battery::init(const std::string& name, double state_of_charge, double charge_efficiency,
-                         double discharge_efficiency, double initial_capacity_wh, int cycles)
+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));
+  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;
 }
@@ -253,11 +349,25 @@ BatteryPtr Battery::init(const std::string& name, double state_of_charge, double
  */
 void Battery::set_load(const std::string& name, double power_w)
 {
-  named_loads_[name] = power_w;
+  kernel::actor::simcall_answered([this, &name, &power_w] {
+    if (named_loads_.find(name) == named_loads_.end())
+      named_loads_[name] = std::make_pair(true, power_w);
+    else
+      named_loads_[name].second = power_w;
+  });
+}
+
+/** @ingroup plugin_battery
+ *  @param name The name of the load
+ *  @param active Status of the load. If false then the load is ignored by the Battery.
+ */
+void Battery::set_load(const std::string& name, bool active)
+{
+  kernel::actor::simcall_answered([this, &name, &active] { named_loads_[name].first = active; });
 }
 
 /** @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.
@@ -266,7 +376,7 @@ void Battery::set_load(const std::string& name, double power_w)
  */
 void Battery::connect_host(s4u::Host* host, bool active)
 {
-  host_loads_[host] = active;
+  kernel::actor::simcall_answered([this, &host, &active] { host_loads_[host] = active; });
 }
 
 /** @ingroup plugin_battery
@@ -282,7 +392,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
@@ -314,7 +425,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)
@@ -328,36 +439,36 @@ 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 p 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, Handler::Persistancy p,
+                                                            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, p, 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