Logo AND Algorithmique Numérique Distribuée

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