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
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() {}
274
275 Battery::Battery(const std::string& name, double state_of_charge, double nominal_charge_power_w,
276                  double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
277                  double initial_capacity_wh, int cycles)
278     : name_(name)
279     , nominal_charge_power_w_(nominal_charge_power_w)
280     , nominal_discharge_power_w_(nominal_discharge_power_w)
281     , charge_efficiency_(charge_efficiency)
282     , discharge_efficiency_(discharge_efficiency)
283     , initial_capacity_wh_(initial_capacity_wh)
284     , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
285     , capacity_wh_(initial_capacity_wh)
286     , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
287 {
288   xbt_assert(nominal_charge_power_w <= 0, " : nominal charge power must be <= 0 (provided: %f)",
289              nominal_charge_power_w);
290   xbt_assert(nominal_discharge_power_w >= 0, " : nominal discharge power must be non-negative (provided: %f)",
291              nominal_discharge_power_w);
292   xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
293              state_of_charge);
294   xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
295              charge_efficiency);
296   xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
297              " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
298   xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
299   xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
300 }
301
302 /** @ingroup plugin_battery
303  *  @brief Init a Battery with this constructor makes it only usable as a connector.
304  *         A connector has no capacity and only delivers as much power as it receives
305            with a transfer efficiency of 100%.
306  *  @return A BatteryPtr pointing to the new Battery.
307  */
308 BatteryPtr Battery::init()
309 {
310   static bool plugin_inited = false;
311   if (not plugin_inited) {
312     init_plugin();
313     plugin_inited = true;
314   }
315   auto battery = BatteryPtr(new Battery());
316   battery_model_->add_battery(battery);
317   return battery;
318 }
319
320 /** @ingroup plugin_battery
321  *  @param name The name of the Battery.
322  *  @param state_of_charge The initial state of charge of the Battery [0,1].
323  *  @param nominal_charge_power_w The maximum power absorbed by the Battery in W (<= 0).
324  *  @param nominal_discharge_power_w The maximum power delivered by the Battery in W (>= 0).
325  *  @param charge_efficiency The charge efficiency of the Battery [0,1].
326  *  @param discharge_efficiency The discharge efficiency of the Battery [0,1].
327  *  @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
328  *  @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
329  *  @return A BatteryPtr pointing to the new Battery.
330  */
331 BatteryPtr Battery::init(const std::string& name, double state_of_charge, double nominal_charge_power_w,
332                          double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
333                          double initial_capacity_wh, int cycles)
334 {
335   static bool plugin_inited = false;
336   if (not plugin_inited) {
337     init_plugin();
338     plugin_inited = true;
339   }
340   auto battery = BatteryPtr(new Battery(name, state_of_charge, nominal_charge_power_w, nominal_discharge_power_w,
341                                         charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
342   battery_model_->add_battery(battery);
343   return battery;
344 }
345
346 /** @ingroup plugin_battery
347  *  @param name The name of the load
348  *  @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
349  */
350 void Battery::set_load(const std::string& name, double power_w)
351 {
352   kernel::actor::simcall_answered([this, &name, &power_w] {
353     if (named_loads_.find(name) == named_loads_.end())
354       named_loads_[name] = std::make_pair(true, power_w);
355     else
356       named_loads_[name].second = power_w;
357   });
358 }
359
360 /** @ingroup plugin_battery
361  *  @param name The name of the load
362  *  @param active Status of the load. If false then the load is ignored by the Battery.
363  */
364 void Battery::set_load(const std::string& name, bool active)
365 {
366   kernel::actor::simcall_answered([this, &name, &active] { named_loads_[name].first = active; });
367 }
368
369 /** @ingroup plugin_battery
370  *  @param host The Host to connect.
371  *  @param active Status of the connected Host (default true).
372  *  @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
373  from the Battery. To modify this status connect again the same Host with a different status.
374     @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
375     In this case all Batteries would have the full consumption from this Host.
376  */
377 void Battery::connect_host(s4u::Host* host, bool active)
378 {
379   kernel::actor::simcall_answered([this, &host, &active] { host_loads_[host] = active; });
380 }
381
382 /** @ingroup plugin_battery
383  *  @return The state of charge of the battery.
384  */
385 double Battery::get_state_of_charge()
386 {
387   return energy_stored_j_ / (3600 * capacity_wh_);
388 }
389
390 /** @ingroup plugin_battery
391  *  @return The state of health of the Battery.
392  */
393 double Battery::get_state_of_health()
394 {
395   return 1 -
396          ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
397 }
398
399 /** @ingroup plugin_battery
400  *  @return The current capacity of the Battery.
401  */
402 double Battery::get_capacity()
403 {
404   return capacity_wh_;
405 }
406
407 /** @ingroup plugin_battery
408  *  @return The energy provided by the Battery.
409  *  @note It is the energy provided from an external point of view, after application of the discharge efficiency.
410           It means that the Battery lost more energy than it has provided.
411  */
412 double Battery::get_energy_provided()
413 {
414   return energy_provided_j_;
415 }
416
417 /** @ingroup plugin_battery
418  *  @return The energy consumed by the Battery.
419  *  @note It is the energy consumed from an external point of view, before application of the charge efficiency.
420           It means that the Battery consumed more energy than is has absorbed.
421  */
422 double Battery::get_energy_consumed()
423 {
424   return energy_consumed_j_;
425 }
426
427 /** @ingroup plugin_battery
428  *  @param unit Valid units are J (default) and Wh.
429  *  @return Energy stored in the Battery.
430  */
431 double Battery::get_energy_stored(std::string unit)
432 {
433   if (unit == "J")
434     return energy_stored_j_;
435   else if (unit == "Wh")
436     return energy_stored_j_ / 3600;
437   else
438     xbt_die("Invalid unit. Valid units are J (default) or Wh.");
439 }
440
441 /** @ingroup plugin_battery
442  *  @brief Schedule a new Handler.
443  *  @param state_of_charge The state of charge at which the Handler will happen.
444  *  @param flow The flow in which the Handler will happen, either when the Battery is charging or discharging.
445  *  @param callback The callable to trigger when the Handler happen.
446  *  @param p If the Handler is recurrent or unique.
447  *  @return A shared pointer of the new Handler.
448  */
449 std::shared_ptr<Battery::Handler> Battery::schedule_handler(double state_of_charge, Flow flow, Handler::Persistancy p,
450                                                             std::function<void()> callback)
451 {
452   auto handler = Handler::init(state_of_charge, flow, p, callback);
453   handlers_.push_back(handler);
454   return handler;
455 }
456
457 /** @ingroup plugin_battery
458  *  @return A vector containing the Handlers associated to the Battery.
459  */
460 std::vector<std::shared_ptr<Battery::Handler>> Battery::get_handlers()
461 {
462   return handlers_;
463 }
464
465 /** @ingroup plugin_battery
466  *  @brief Remove an Handler from the Battery.
467  */
468 void Battery::delete_handler(std::shared_ptr<Handler> handler)
469 {
470   handlers_.erase(std::remove_if(handlers_.begin(), handlers_.end(),
471                                  [&handler](std::shared_ptr<Handler> e) { return handler == e; }),
472                   handlers_.end());
473 }
474 } // namespace simgrid::plugins