Logo AND Algorithmique Numérique Distribuée

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