Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Add new entry in Release_Notes.
[simgrid.git] / src / plugins / battery.cpp
1 /* Copyright (c) 2023. The SimGrid Team. All rights reserved.          */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5 #include <simgrid/Exception.hpp>
6 #include <simgrid/plugins/battery.hpp>
7 #include <simgrid/plugins/energy.h>
8 #include <simgrid/s4u/Engine.hpp>
9 #include <simgrid/simix.hpp>
10
11 #include "src/kernel/resource/CpuImpl.hpp"
12 #include "src/simgrid/module.hpp"
13
14 SIMGRID_REGISTER_PLUGIN(battery, "Battery management", nullptr)
15 /** @defgroup plugin_battery plugin_battery Plugin Battery
16
17   @beginrst
18
19 This is the battery plugin, enabling management of batteries.
20
21 Batteries
22 .........
23
24 A battery has an initial State of Charge :math:`SoC`, a nominal charge power, a nominal discharge power, a charge
25 efficiency :math:`\eta_{charge}`, a discharge efficiency :math:`\eta_{discharge}`, an initial capacity
26 :math:`C_{initial}` and a number of cycle :math:`N`.
27
28 The nominal charge(discharge) power is the maximum power the Battery can consume(provide), before application of the
29 charge(discharge) efficiency factor. For instance, if a load provides(consumes) 100W to(from) the Battery with a nominal
30 charge(discharge) power of 50W and a charge(discharge) efficiency of 0.9, the Battery will only gain(provide) 45W.
31
32 We distinguish the energy provided :math:`E_{provided}` / consumed :math:`E_{consumed}` from the energy lost
33 :math:`E_{lost}` / gained :math:`E_{gained}`. The energy provided / consumed shows the external point of view, and the
34 energy lost / gained shows the internal point of view:
35
36 .. math::
37
38   E_{lost} = {E_{provided} \over \eta_{discharge}}
39
40   E_{gained} = E_{consumed} \times \eta_{charge}
41
42 For instance, if you apply a load of 100W to a battery for 10s with a discharge efficiency of 0.8, the energy provided
43 will be equal to 10kJ, and the energy lost will be equal to 12.5kJ.
44
45 All the energies are positive, but loads connected to a Battery may be positive or negative, as explained in the next
46 section.
47
48 Use the battery reduces its State of Health :math:`SoH` and its capacity :math:`C` linearly in consequence:
49
50 .. math::
51
52   SoH = 1 - {E_{lost} + E_{gained} \over E_{budget}}
53
54   C = C_{initial} \times SoH
55
56 With:
57
58 .. math::
59
60   E_{budget} = C_{initial} \times N \times 2
61
62 Plotting the output of the example "battery-degradation" highlights the linear decrease of the :math:`SoH` due to a
63 continuous use of the battery alternating between charge and discharge:
64
65 .. image:: /img/battery_degradation.svg
66    :align: center
67
68 The natural depletion of batteries over time is not taken into account.
69
70 Loads & Hosts
71 .............
72
73 You can add named loads to a battery. Those loads may be positive and consume energy from the battery, or negative and
74 provide energy to the battery. You can also connect hosts to a battery. Theses hosts will consume their energy from the
75 battery until the battery is empty or until the connection between the hosts and the battery is set inactive.
76
77 Handlers
78 ........
79
80 You can schedule handlers that will happen at specific SoC of the battery and trigger a callback.
81 Theses handlers may be recurrent, for instance you may want to always set all loads to zero and deactivate all hosts
82 connections when the battery reaches 20% SoC.
83
84 Connector
85 .........
86
87 A Battery can act as a connector to connect Solar Panels direcly to loads. Such Battery is created without any
88 parameter, cannot store energy and has a transfer efficiency of 100%.
89
90   @endrst
91  */
92 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(Battery, kernel, "Logging specific to the battery plugin");
93
94 namespace simgrid::plugins {
95
96 /* BatteryModel */
97
98 BatteryModel::BatteryModel() : Model("BatteryModel") {}
99
100 void BatteryModel::add_battery(BatteryPtr b)
101 {
102   batteries_.push_back(b);
103 }
104
105 void BatteryModel::update_actions_state(double now, double delta)
106 {
107   for (auto battery : batteries_)
108     battery->update();
109 }
110
111 double BatteryModel::next_occurring_event(double now)
112 {
113   static bool init = false;
114   if (!init) {
115     init = true;
116     return 0;
117   }
118   double time_delta = -1;
119   for (auto battery : batteries_) {
120     double time_delta_battery = battery->next_occurring_handler();
121     time_delta                = time_delta == -1 or time_delta_battery < time_delta ? time_delta_battery : time_delta;
122   }
123   return time_delta;
124 }
125
126 /* Handler */
127
128 Battery::Handler::Handler(double state_of_charge, Flow flow, Persistancy p, std::function<void()> callback)
129     : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), persistancy_(p)
130 {
131 }
132
133 std::shared_ptr<Battery::Handler> Battery::Handler::init(double state_of_charge, Flow flow, Persistancy p,
134                                                          std::function<void()> callback)
135 {
136   return std::make_shared<Battery::Handler>(state_of_charge, flow, p, callback);
137 }
138
139 /* Battery */
140
141 std::shared_ptr<BatteryModel> Battery::battery_model_;
142
143 void Battery::init_plugin()
144 {
145   auto model = std::make_shared<BatteryModel>();
146   simgrid::s4u::Engine::get_instance()->add_model(model);
147   Battery::battery_model_ = model;
148 }
149
150 void Battery::update()
151 {
152   kernel::actor::simcall_answered([this] {
153     double now          = s4u::Engine::get_clock();
154     double time_delta_s = now - last_updated_;
155
156     // Nothing to update
157     if (time_delta_s <= 0)
158       return;
159
160     // Calculate energy provided / consumed during this step
161     double provided_power_w = 0;
162     double consumed_power_w = 0;
163     for (auto const& [host, active] : host_loads_)
164       provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
165     for (auto const& [name, pair] : named_loads_) {
166       if (not pair.first)
167         continue;
168       if (pair.second > 0)
169         provided_power_w += pair.second;
170       else
171         consumed_power_w += -pair.second;
172     }
173
174     provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
175     consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
176
177     double energy_lost_delta_j   = provided_power_w / discharge_efficiency_ * time_delta_s;
178     double energy_gained_delta_j = consumed_power_w * charge_efficiency_ * time_delta_s;
179
180     // Check that the provided/consumed energy is valid
181     energy_lost_delta_j = std::min(energy_lost_delta_j, energy_stored_j_ + energy_gained_delta_j);
182     /* Charging deteriorate the capacity, but the capacity is used to evaluate the maximum charge so
183        we need to evaluate the theorethical new capacity in the worst case when we fully charge the battery */
184     double new_tmp_capacity_wh =
185         (initial_capacity_wh_ *
186          (1 - (energy_provided_j_ + energy_lost_delta_j * discharge_efficiency_ + energy_consumed_j_ -
187                (energy_stored_j_ + energy_lost_delta_j) / charge_efficiency_) /
188                   energy_budget_j_)) /
189         (1 + 3600 * initial_capacity_wh_ / (charge_efficiency_ * energy_budget_j_));
190     energy_gained_delta_j =
191         std::min(energy_gained_delta_j, (3600 * new_tmp_capacity_wh) - energy_stored_j_ + energy_lost_delta_j);
192
193     // Updating battery
194     energy_provided_j_ += energy_lost_delta_j * discharge_efficiency_;
195     energy_consumed_j_ += energy_gained_delta_j / charge_efficiency_;
196
197     // This battery is a simple connector, we only update energy provided and consumed
198     if (energy_budget_j_ == 0) {
199       energy_consumed_j_ = energy_provided_j_;
200       last_updated_      = now;
201       return;
202     }
203
204     capacity_wh_ =
205         initial_capacity_wh_ *
206         (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
207     energy_stored_j_ += energy_gained_delta_j - energy_lost_delta_j;
208     energy_stored_j_ = std::min(energy_stored_j_, 3600 * capacity_wh_);
209     last_updated_    = now;
210
211     auto handlers_2 = handlers_;
212     for (auto handler : handlers_2) {
213       if (abs(handler->time_delta_ - time_delta_s) < 0.000000001) {
214         handler->callback_();
215         if (handler->persistancy_ == Handler::Persistancy::PERSISTANT)
216           handler->time_delta_ = -1;
217         else
218           delete_handler(handler);
219       }
220     }
221   });
222 }
223
224 double Battery::next_occurring_handler()
225 {
226   double provided_power_w = 0;
227   double consumed_power_w = 0;
228   for (auto const& [host, active] : host_loads_)
229     provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
230   for (auto const& [name, pair] : named_loads_) {
231     if (not pair.first)
232       continue;
233     if (pair.second > 0)
234       provided_power_w += pair.second;
235     else
236       consumed_power_w += -pair.second;
237   }
238
239   provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
240   consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
241
242   double time_delta = -1;
243   for (auto& handler : handlers_) {
244     double lost_power_w   = provided_power_w / discharge_efficiency_;
245     double gained_power_w = consumed_power_w * charge_efficiency_;
246     if ((lost_power_w == gained_power_w) or (handler->state_of_charge_ == get_state_of_charge()) or
247         (lost_power_w > gained_power_w and
248          (handler->flow_ == Flow::CHARGE or handler->state_of_charge_ > get_state_of_charge())) or
249         (lost_power_w < gained_power_w and
250          (handler->flow_ == Flow::DISCHARGE or handler->state_of_charge_ < get_state_of_charge()))) {
251       continue;
252     }
253     // Evaluate time until handler happen
254     else {
255       /* The time to reach a state of charge depends on the capacity, but charging and discharging deteriorate the
256        * capacity, so we need to evaluate the time considering a capacity that also depends on time
257        */
258       handler->time_delta_ =
259           (3600 * handler->state_of_charge_ * initial_capacity_wh_ *
260                (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) /
261                         energy_budget_j_) -
262            energy_stored_j_) /
263           (gained_power_w - lost_power_w +
264            3600 * handler->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) /
265                energy_budget_j_);
266       if ((time_delta == -1 or handler->time_delta_ < time_delta) and abs(handler->time_delta_) > 0.000000001)
267         time_delta = handler->time_delta_;
268     }
269   }
270   return time_delta;
271 }
272
273 Battery::Battery(const std::string& name, double state_of_charge, double nominal_charge_power_w,
274                  double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
275                  double initial_capacity_wh, int cycles)
276     : name_(name)
277     , nominal_charge_power_w_(nominal_charge_power_w)
278     , nominal_discharge_power_w_(nominal_discharge_power_w)
279     , charge_efficiency_(charge_efficiency)
280     , discharge_efficiency_(discharge_efficiency)
281     , initial_capacity_wh_(initial_capacity_wh)
282     , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
283     , capacity_wh_(initial_capacity_wh)
284     , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
285 {
286   xbt_assert(nominal_charge_power_w <= 0, " : nominal charge power must be <= 0 (provided: %f)",
287              nominal_charge_power_w);
288   xbt_assert(nominal_discharge_power_w >= 0, " : nominal discharge power must be non-negative (provided: %f)",
289              nominal_discharge_power_w);
290   xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
291              state_of_charge);
292   xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
293              charge_efficiency);
294   xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
295              " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
296   xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
297   xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
298 }
299
300 /** @ingroup plugin_battery
301  *  @brief Init a Battery with this constructor makes it only usable as a connector.
302  *         A connector has no capacity and only delivers as much power as it receives
303            with a transfer efficiency of 100%.
304  *  @return A BatteryPtr pointing to the new Battery.
305  */
306 BatteryPtr Battery::init()
307 {
308   static bool plugin_inited = false;
309   if (not plugin_inited) {
310     init_plugin();
311     plugin_inited = true;
312   }
313   auto battery = BatteryPtr(new Battery());
314   battery_model_->add_battery(battery);
315   return battery;
316 }
317
318 /** @ingroup plugin_battery
319  *  @param name The name of the Battery.
320  *  @param state_of_charge The initial state of charge of the Battery [0,1].
321  *  @param nominal_charge_power_w The maximum power absorbed by the Battery in W (<= 0).
322  *  @param nominal_discharge_power_w The maximum power delivered by the Battery in W (>= 0).
323  *  @param charge_efficiency The charge efficiency of the Battery [0,1].
324  *  @param discharge_efficiency The discharge efficiency of the Battery [0,1].
325  *  @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
326  *  @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
327  *  @return A BatteryPtr pointing to the new Battery.
328  */
329 BatteryPtr Battery::init(const std::string& name, double state_of_charge, double nominal_charge_power_w,
330                          double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
331                          double initial_capacity_wh, int cycles)
332 {
333   static bool plugin_inited = false;
334   if (not plugin_inited) {
335     init_plugin();
336     plugin_inited = true;
337   }
338   auto battery = BatteryPtr(new Battery(name, state_of_charge, nominal_charge_power_w, nominal_discharge_power_w,
339                                         charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
340   battery_model_->add_battery(battery);
341   return battery;
342 }
343
344 /** @ingroup plugin_battery
345  *  @param name The name of the load
346  *  @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
347  */
348 void Battery::set_load(const std::string& name, double power_w)
349 {
350   kernel::actor::simcall_answered([this, &name, &power_w] {
351     if (named_loads_.find(name) == named_loads_.end())
352       named_loads_[name] = std::make_pair(true, power_w);
353     else
354       named_loads_[name].second = power_w;
355   });
356 }
357
358 /** @ingroup plugin_battery
359  *  @param name The name of the load
360  *  @param active Status of the load. If false then the load is ignored by the Battery.
361  */
362 void Battery::set_load(const std::string& name, bool active)
363 {
364   kernel::actor::simcall_answered([this, &name, &active] { named_loads_[name].first = active; });
365 }
366
367 /** @ingroup plugin_battery
368  *  @param host The Host to connect.
369  *  @param active Status of the connected Host (default true).
370  *  @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
371  from the Battery. To modify this status connect again the same Host with a different status.
372     @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
373     In this case all Batteries would have the full consumption from this Host.
374  */
375 void Battery::connect_host(s4u::Host* host, bool active)
376 {
377   kernel::actor::simcall_answered([this, &host, &active] { host_loads_[host] = active; });
378 }
379
380 /** @ingroup plugin_battery
381  *  @return The state of charge of the battery.
382  */
383 double Battery::get_state_of_charge()
384 {
385   return energy_stored_j_ / (3600 * capacity_wh_);
386 }
387
388 /** @ingroup plugin_battery
389  *  @return The state of health of the Battery.
390  */
391 double Battery::get_state_of_health()
392 {
393   return 1 -
394          ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
395 }
396
397 /** @ingroup plugin_battery
398  *  @return The current capacity of the Battery.
399  */
400 double Battery::get_capacity()
401 {
402   return capacity_wh_;
403 }
404
405 /** @ingroup plugin_battery
406  *  @return The energy provided by the Battery.
407  *  @note It is the energy provided from an external point of view, after application of the discharge efficiency.
408           It means that the Battery lost more energy than it has provided.
409  */
410 double Battery::get_energy_provided()
411 {
412   return energy_provided_j_;
413 }
414
415 /** @ingroup plugin_battery
416  *  @return The energy consumed by the Battery.
417  *  @note It is the energy consumed from an external point of view, before application of the charge efficiency.
418           It means that the Battery consumed more energy than is has absorbed.
419  */
420 double Battery::get_energy_consumed()
421 {
422   return energy_consumed_j_;
423 }
424
425 /** @ingroup plugin_battery
426  *  @param unit Valid units are J (default) and Wh.
427  *  @return Energy stored in the Battery.
428  */
429 double Battery::get_energy_stored(std::string unit)
430 {
431   if (unit == "J")
432     return energy_stored_j_;
433   else if (unit == "Wh")
434     return energy_stored_j_ / 3600;
435   else
436     xbt_die("Invalid unit. Valid units are J (default) or Wh.");
437 }
438
439 /** @ingroup plugin_battery
440  *  @brief Schedule a new Handler.
441  *  @param state_of_charge The state of charge at which the Handler will happen.
442  *  @param flow The flow in which the Handler will happen, either when the Battery is charging or discharging.
443  *  @param callback The callable to trigger when the Handler happen.
444  *  @param p If the Handler is recurrent or unique.
445  *  @return A shared pointer of the new Handler.
446  */
447 std::shared_ptr<Battery::Handler> Battery::schedule_handler(double state_of_charge, Flow flow, Handler::Persistancy p,
448                                                             std::function<void()> callback)
449 {
450   auto handler = Handler::init(state_of_charge, flow, p, callback);
451   handlers_.push_back(handler);
452   return handler;
453 }
454
455 /** @ingroup plugin_battery
456  *  @return A vector containing the Handlers associated to the Battery.
457  */
458 std::vector<std::shared_ptr<Battery::Handler>> Battery::get_handlers()
459 {
460   return handlers_;
461 }
462
463 /** @ingroup plugin_battery
464  *  @brief Remove an Handler from the Battery.
465  */
466 void Battery::delete_handler(std::shared_ptr<Handler> handler)
467 {
468   handlers_.erase(std::remove_if(handlers_.begin(), handlers_.end(),
469                                  [&handler](std::shared_ptr<Handler> e) { return handler == e; }),
470                   handlers_.end());
471 }
472 } // namespace simgrid::plugins