Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
add nominal charge and discharge power to batteries
[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 Events
81 ......
82
83 You can create events that will happen at specific SoC of the battery and trigger a callback.
84 Theses events 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_event();
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 /* Event */
119
120 Battery::Event::Event(double state_of_charge, Flow flow, std::function<void()> callback, bool repeat)
121     : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), repeat_(repeat)
122 {
123 }
124
125 std::shared_ptr<Battery::Event> Battery::Event::init(double state_of_charge, Flow flow, std::function<void()> callback,
126                                                      bool repeat)
127 {
128   return std::make_shared<Battery::Event>(state_of_charge, flow, callback, repeat);
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<Event>> to_delete = {};
193     for (auto event : events_) {
194       if (abs(event->time_delta_ - time_delta_s) < 0.000000001) {
195         event->callback_();
196         if (event->repeat_)
197           event->time_delta_ = -1;
198         else
199           to_delete.push_back(event);
200       }
201     }
202     for (auto event : to_delete)
203       delete_event(event);
204   });
205 }
206
207 double Battery::next_occurring_event()
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& event : events_) {
225     double lost_power_w   = provided_power_w / discharge_efficiency_;
226     double gained_power_w = consumed_power_w * charge_efficiency_;
227     // Event cannot happen
228     if ((lost_power_w == gained_power_w) or (event->state_of_charge_ == energy_stored_j_ / (3600 * capacity_wh_)) or
229         (lost_power_w > gained_power_w and event->flow_ == Flow::CHARGE) or
230         (lost_power_w < gained_power_w and event->flow_ == Flow::DISCHARGE)) {
231       continue;
232     }
233     // Evaluate time until event 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       event->time_delta_ =
239           (3600 * event->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 * event->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) / energy_budget_j_);
245       if ((time_delta == -1 or event->time_delta_ < time_delta) and abs(event->time_delta_) > 0.000000001)
246         time_delta = event->time_delta_;
247     }
248   }
249   return time_delta;
250 }
251
252 Battery::Battery(const std::string& name, double state_of_charge, double nominal_charge_power_w,
253                  double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
254                  double initial_capacity_wh, int cycles)
255     : name_(name)
256     , nominal_charge_power_w_(nominal_charge_power_w)
257     , nominal_discharge_power_w_(nominal_discharge_power_w)
258     , charge_efficiency_(charge_efficiency)
259     , discharge_efficiency_(discharge_efficiency)
260     , initial_capacity_wh_(initial_capacity_wh)
261     , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
262     , capacity_wh_(initial_capacity_wh)
263     , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
264 {
265   xbt_assert(nominal_charge_power_w <= 0, " : nominal charge power must be non-negative (provided: %f)",
266              nominal_charge_power_w);
267   xbt_assert(nominal_discharge_power_w >= 0, " : nominal discharge power must be non-negative (provided: %f)",
268              nominal_discharge_power_w);
269   xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
270              state_of_charge);
271   xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
272              charge_efficiency);
273   xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
274              " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
275   xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
276   xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
277 }
278
279 /** @ingroup plugin_battery
280  *  @param name The name of the Battery.
281  *  @param state_of_charge The initial state of charge of the Battery [0,1].
282  *  @param nominal_charge_power_w The maximum power delivered by the Battery in W (<= 0).
283  *  @param nominal_discharge_power_w The maximum power absorbed by the Battery in W (>= 0).
284  *  @param charge_efficiency The charge efficiency of the Battery [0,1].
285  *  @param discharge_efficiency The discharge efficiency of the Battery [0,1].
286  *  @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
287  *  @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
288  *  @return A BatteryPtr pointing to the new Battery.
289  */
290 BatteryPtr Battery::init(const std::string& name, double state_of_charge, double nominal_charge_power_w,
291                          double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
292                          double initial_capacity_wh, int cycles)
293 {
294   static bool plugin_inited = false;
295   if (not plugin_inited) {
296     init_plugin();
297     plugin_inited = true;
298   }
299   auto battery = BatteryPtr(new Battery(name, state_of_charge, nominal_charge_power_w, nominal_discharge_power_w,
300                                         charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
301   battery_model_->add_battery(battery);
302   return battery;
303 }
304
305 /** @ingroup plugin_battery
306  *  @param name The name of the load
307  *  @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
308  */
309 void Battery::set_load(const std::string& name, double power_w)
310 {
311   named_loads_[name] = power_w;
312 }
313
314 /** @ingroup plugin_battery
315  *  @param host The Host to connect.
316  *  @param active Status of the connected Host (default true).
317  *  @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
318  from the Battery. To modify this status connect again the same Host with a different status.
319     @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
320     In this case all Batteries would have the full consumption from this Host.
321  */
322 void Battery::connect_host(s4u::Host* host, bool active)
323 {
324   host_loads_[host] = active;
325 }
326
327 /** @ingroup plugin_battery
328  *  @return The state of charge of the battery.
329  */
330 double Battery::get_state_of_charge()
331 {
332   return energy_stored_j_ / (3600 * capacity_wh_);
333 }
334
335 /** @ingroup plugin_battery
336  *  @return The state of health of the Battery.
337  */
338 double Battery::get_state_of_health()
339 {
340   return 1 -
341          ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
342 }
343
344 /** @ingroup plugin_battery
345  *  @return The current capacity of the Battery.
346  */
347 double Battery::get_capacity()
348 {
349   return capacity_wh_;
350 }
351
352 /** @ingroup plugin_battery
353  *  @return The energy provided by the Battery.
354  *  @note It is the energy provided from an external point of view, after application of the discharge efficiency.
355           It means that the Battery lost more energy than it has provided.
356  */
357 double Battery::get_energy_provided()
358 {
359   return energy_provided_j_;
360 }
361
362 /** @ingroup plugin_battery
363  *  @return The energy consumed by the Battery.
364  *  @note It is the energy consumed from an external point of view, before application of the charge efficiency.
365           It means that the Battery consumed more energy than is has absorbed.
366  */
367 double Battery::get_energy_consumed()
368 {
369   return energy_consumed_j_;
370 }
371
372 /** @ingroup plugin_battery
373  *  @param unit Valid units are J (default) and Wh.
374  *  @return Energy stored in the Battery.
375  */
376 double Battery::get_energy_stored(std::string unit)
377 {
378   if (unit == "J")
379     return energy_stored_j_;
380   else if (unit == "Wh")
381     return energy_stored_j_ / 3600;
382   else
383     xbt_die("Invalid unit. Valid units are J (default) or Wh.");
384 }
385
386 /** @ingroup plugin_battery
387  *  @brief Create a new Event.
388  *  @param state_of_charge The state of charge at which the Event will happen.
389  *  @param flow The flow in which the Event will happen, either when the Battery is charging or discharging.
390  *  @param callback The callable to trigger when the Event happen.
391  *  @param repeat If the Event is a recurrent Event or a single Event.
392  *  @return A shared pointer of the new Event.
393  */
394 std::shared_ptr<Battery::Event> Battery::create_event(double state_of_charge, Flow flow, std::function<void()> callback,
395                                                       bool repeat)
396 {
397   auto event = Event::init(state_of_charge, flow, callback, repeat);
398   events_.push_back(event);
399   return event;
400 }
401
402 /** @ingroup plugin_battery
403  *  @return A vector containing the Events associated to the Battery.
404  */
405 std::vector<std::shared_ptr<Battery::Event>> Battery::get_events()
406 {
407   return events_;
408 }
409
410 /** @ingroup plugin_battery
411  *  @brief Remove an Event from the Battery.
412  */
413 void Battery::delete_event(std::shared_ptr<Event> event)
414 {
415   events_.erase(
416       std::remove_if(events_.begin(), events_.end(), [&event](std::shared_ptr<Event> e) { return event == e; }),
417       events_.end());
418 }
419 } // namespace simgrid::plugins