Logo AND Algorithmique Numérique Distribuée

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