Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
add battery-chiller-solar example.
[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   @endrst
85  */
86 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(Battery, kernel, "Logging specific to the battery plugin");
87
88 namespace simgrid::plugins {
89
90 /* BatteryModel */
91
92 BatteryModel::BatteryModel() : Model("BatteryModel") {}
93
94 void BatteryModel::add_battery(BatteryPtr b)
95 {
96   batteries_.push_back(b);
97 }
98
99 void BatteryModel::update_actions_state(double now, double delta)
100 {
101   for (auto battery : batteries_)
102     battery->update();
103 }
104
105 double BatteryModel::next_occurring_event(double now)
106 {
107   double time_delta = -1;
108   for (auto battery : batteries_) {
109     double time_delta_battery = battery->next_occurring_handler();
110     time_delta                = time_delta == -1 or time_delta_battery < time_delta ? time_delta_battery : time_delta;
111   }
112   return time_delta;
113 }
114
115 /* Handler */
116
117 Battery::Handler::Handler(double state_of_charge, Flow flow, Persistancy p, std::function<void()> callback)
118     : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), persistancy_(p)
119 {
120 }
121
122 std::shared_ptr<Battery::Handler> Battery::Handler::init(double state_of_charge, Flow flow, Persistancy p,
123                                                          std::function<void()> callback)
124 {
125   return std::make_shared<Battery::Handler>(state_of_charge, flow, p, callback);
126 }
127
128 /* Battery */
129
130 std::shared_ptr<BatteryModel> Battery::battery_model_;
131
132 void Battery::init_plugin()
133 {
134   auto model = std::make_shared<BatteryModel>();
135   simgrid::s4u::Engine::get_instance()->add_model(model);
136   Battery::battery_model_ = model;
137 }
138
139 void Battery::update()
140 {
141   kernel::actor::simcall_answered([this] {
142     double now          = s4u::Engine::get_clock();
143     double time_delta_s = now - last_updated_;
144
145     // Nothing to update
146     if (time_delta_s <= 0)
147       return;
148
149     // Calculate energy provided / consumed during this step
150     double provided_power_w = 0;
151     double consumed_power_w = 0;
152     for (auto const& [host, active] : host_loads_)
153       provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
154     for (auto const& [name, pair] : named_loads_) {
155       if (not pair.first)
156         continue;
157       if (pair.second > 0)
158         provided_power_w += pair.second;
159       else
160         consumed_power_w += -pair.second;
161     }
162     provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
163     consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
164
165     double energy_lost_delta_j   = provided_power_w / discharge_efficiency_ * time_delta_s;
166     double energy_gained_delta_j = consumed_power_w * charge_efficiency_ * time_delta_s;
167
168     // Check that the provided/consumed energy is valid
169     energy_lost_delta_j = std::min(energy_lost_delta_j, energy_stored_j_ + energy_gained_delta_j);
170     /* Charging deteriorate the capacity, but the capacity is used to evaluate the maximum charge so
171        we need to evaluate the theorethical new capacity in the worst case when we fully charge the battery */
172     double new_tmp_capacity_wh =
173         (initial_capacity_wh_ *
174          (1 - (energy_provided_j_ + energy_lost_delta_j * discharge_efficiency_ + energy_consumed_j_ -
175                (energy_stored_j_ + energy_lost_delta_j) / charge_efficiency_) /
176                   energy_budget_j_)) /
177         (1 + 3600 * initial_capacity_wh_ / (charge_efficiency_ * energy_budget_j_));
178     energy_gained_delta_j =
179         std::min(energy_gained_delta_j, (3600 * new_tmp_capacity_wh) - energy_stored_j_ + energy_lost_delta_j);
180
181     // Updating battery
182     energy_provided_j_ += energy_lost_delta_j * discharge_efficiency_;
183     energy_consumed_j_ += energy_gained_delta_j / charge_efficiency_;
184     capacity_wh_ =
185         initial_capacity_wh_ *
186         (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
187     energy_stored_j_ += energy_gained_delta_j - energy_lost_delta_j;
188     energy_stored_j_ = std::min(energy_stored_j_, 3600 * capacity_wh_);
189     last_updated_    = now;
190
191     std::vector<std::shared_ptr<Handler>> to_delete = {};
192     for (auto handler : handlers_) {
193       if (abs(handler->time_delta_ - time_delta_s) < 0.000000001) {
194         handler->callback_();
195         if (handler->persistancy_ == Handler::Persistancy::PERSISTANT)
196           handler->time_delta_ = -1;
197         else
198           to_delete.push_back(handler);
199       }
200     }
201     for (auto handler : to_delete)
202       delete_handler(handler);
203   });
204 }
205
206 double Battery::next_occurring_handler()
207 {
208   double provided_power_w = 0;
209   double consumed_power_w = 0;
210   for (auto const& [host, active] : host_loads_)
211     provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
212   for (auto const& [name, pair] : named_loads_) {
213     if (not pair.first)
214       continue;
215     if (pair.second > 0)
216       provided_power_w += pair.second;
217     else
218       consumed_power_w += -pair.second;
219   }
220
221   provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
222   consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
223
224   double time_delta = -1;
225   for (auto& handler : handlers_) {
226     double lost_power_w   = provided_power_w / discharge_efficiency_;
227     double gained_power_w = consumed_power_w * charge_efficiency_;
228     if ((lost_power_w == gained_power_w) or (handler->state_of_charge_ == get_state_of_charge()) or
229         (lost_power_w > gained_power_w and
230          (handler->flow_ == Flow::CHARGE or handler->state_of_charge_ > get_state_of_charge())) or
231         (lost_power_w < gained_power_w and
232          (handler->flow_ == Flow::DISCHARGE or handler->state_of_charge_ < get_state_of_charge()))) {
233       continue;
234     }
235     // Evaluate time until handler happen
236     else {
237       /* The time to reach a state of charge depends on the capacity, but charging and discharging deteriorate the
238        * capacity, so we need to evaluate the time considering a capacity that also depends on time
239        */
240       handler->time_delta_ =
241           (3600 * handler->state_of_charge_ * initial_capacity_wh_ *
242                (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) /
243                         energy_budget_j_) -
244            energy_stored_j_) /
245           (gained_power_w - lost_power_w +
246            3600 * handler->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) /
247                energy_budget_j_);
248       if ((time_delta == -1 or handler->time_delta_ < time_delta) and abs(handler->time_delta_) > 0.000000001)
249         time_delta = handler->time_delta_;
250     }
251   }
252   return time_delta;
253 }
254
255 Battery::Battery(const std::string& name, double state_of_charge, double nominal_charge_power_w,
256                  double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
257                  double initial_capacity_wh, int cycles)
258     : name_(name)
259     , nominal_charge_power_w_(nominal_charge_power_w)
260     , nominal_discharge_power_w_(nominal_discharge_power_w)
261     , charge_efficiency_(charge_efficiency)
262     , discharge_efficiency_(discharge_efficiency)
263     , initial_capacity_wh_(initial_capacity_wh)
264     , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
265     , capacity_wh_(initial_capacity_wh)
266     , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
267 {
268   xbt_assert(nominal_charge_power_w <= 0, " : nominal charge power must be <= 0 (provided: %f)",
269              nominal_charge_power_w);
270   xbt_assert(nominal_discharge_power_w >= 0, " : nominal discharge power must be non-negative (provided: %f)",
271              nominal_discharge_power_w);
272   xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
273              state_of_charge);
274   xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
275              charge_efficiency);
276   xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
277              " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
278   xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
279   xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
280 }
281
282 /** @ingroup plugin_battery
283  *  @param name The name of the Battery.
284  *  @param state_of_charge The initial state of charge of the Battery [0,1].
285  *  @param nominal_charge_power_w The maximum power delivered by the Battery in W (<= 0).
286  *  @param nominal_discharge_power_w The maximum power absorbed by the Battery in W (>= 0).
287  *  @param charge_efficiency The charge efficiency of the Battery [0,1].
288  *  @param discharge_efficiency The discharge efficiency of the Battery [0,1].
289  *  @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
290  *  @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
291  *  @return A BatteryPtr pointing to the new Battery.
292  */
293 BatteryPtr Battery::init(const std::string& name, double state_of_charge, double nominal_charge_power_w,
294                          double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
295                          double initial_capacity_wh, int cycles)
296 {
297   static bool plugin_inited = false;
298   if (not plugin_inited) {
299     init_plugin();
300     plugin_inited = true;
301   }
302   auto battery = BatteryPtr(new Battery(name, state_of_charge, nominal_charge_power_w, nominal_discharge_power_w,
303                                         charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
304   battery_model_->add_battery(battery);
305   return battery;
306 }
307
308 /** @ingroup plugin_battery
309  *  @param name The name of the load
310  *  @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
311  */
312 void Battery::set_load(const std::string& name, double power_w)
313 {
314   kernel::actor::simcall_answered([this, &name, &power_w] {
315     if (named_loads_.find(name) == named_loads_.end())
316       named_loads_[name] = std::make_pair(true, power_w);
317     else
318       named_loads_[name].second = power_w;
319   });
320 }
321
322 /** @ingroup plugin_battery
323  *  @param name The name of the load
324  *  @param active Status of the load. If false then the load is ignored by the Battery.
325  */
326 void Battery::set_load(const std::string& name, bool active)
327 {
328   kernel::actor::simcall_answered([this, &name, &active] { named_loads_[name].first = active; });
329 }
330
331 /** @ingroup plugin_battery
332  *  @param host The Host to connect.
333  *  @param active Status of the connected Host (default true).
334  *  @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
335  from the Battery. To modify this status connect again the same Host with a different status.
336     @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
337     In this case all Batteries would have the full consumption from this Host.
338  */
339 void Battery::connect_host(s4u::Host* host, bool active)
340 {
341   kernel::actor::simcall_answered([this, &host, &active] { host_loads_[host] = active; });
342 }
343
344 /** @ingroup plugin_battery
345  *  @return The state of charge of the battery.
346  */
347 double Battery::get_state_of_charge()
348 {
349   return energy_stored_j_ / (3600 * capacity_wh_);
350 }
351
352 /** @ingroup plugin_battery
353  *  @return The state of health of the Battery.
354  */
355 double Battery::get_state_of_health()
356 {
357   return 1 -
358          ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
359 }
360
361 /** @ingroup plugin_battery
362  *  @return The current capacity of the Battery.
363  */
364 double Battery::get_capacity()
365 {
366   return capacity_wh_;
367 }
368
369 /** @ingroup plugin_battery
370  *  @return The energy provided by the Battery.
371  *  @note It is the energy provided from an external point of view, after application of the discharge efficiency.
372           It means that the Battery lost more energy than it has provided.
373  */
374 double Battery::get_energy_provided()
375 {
376   return energy_provided_j_;
377 }
378
379 /** @ingroup plugin_battery
380  *  @return The energy consumed by the Battery.
381  *  @note It is the energy consumed from an external point of view, before application of the charge efficiency.
382           It means that the Battery consumed more energy than is has absorbed.
383  */
384 double Battery::get_energy_consumed()
385 {
386   return energy_consumed_j_;
387 }
388
389 /** @ingroup plugin_battery
390  *  @param unit Valid units are J (default) and Wh.
391  *  @return Energy stored in the Battery.
392  */
393 double Battery::get_energy_stored(std::string unit)
394 {
395   if (unit == "J")
396     return energy_stored_j_;
397   else if (unit == "Wh")
398     return energy_stored_j_ / 3600;
399   else
400     xbt_die("Invalid unit. Valid units are J (default) or Wh.");
401 }
402
403 /** @ingroup plugin_battery
404  *  @brief Schedule a new Handler.
405  *  @param state_of_charge The state of charge at which the Handler will happen.
406  *  @param flow The flow in which the Handler will happen, either when the Battery is charging or discharging.
407  *  @param callback The callable to trigger when the Handler happen.
408  *  @param p If the Handler is recurrent or unique.
409  *  @return A shared pointer of the new Handler.
410  */
411 std::shared_ptr<Battery::Handler> Battery::schedule_handler(double state_of_charge, Flow flow, Handler::Persistancy p,
412                                                             std::function<void()> callback)
413 {
414   auto handler = Handler::init(state_of_charge, flow, p, callback);
415   handlers_.push_back(handler);
416   return handler;
417 }
418
419 /** @ingroup plugin_battery
420  *  @return A vector containing the Handlers associated to the Battery.
421  */
422 std::vector<std::shared_ptr<Battery::Handler>> Battery::get_handlers()
423 {
424   return handlers_;
425 }
426
427 /** @ingroup plugin_battery
428  *  @brief Remove an Handler from the Battery.
429  */
430 void Battery::delete_handler(std::shared_ptr<Handler> handler)
431 {
432   handlers_.erase(std::remove_if(handlers_.begin(), handlers_.end(),
433                                  [&handler](std::shared_ptr<Handler> e) { return handler == e; }),
434                   handlers_.end());
435 }
436 } // namespace simgrid::plugins