Logo AND Algorithmique Numérique Distribuée

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