1 /* Copyright (c) 2023. The SimGrid Team. All rights reserved. */
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/s4u/Actor.hpp>
8 #include <simgrid/s4u/Engine.hpp>
9 #include <simgrid/s4u/Host.hpp>
10 #include <simgrid/s4u/VirtualMachine.hpp>
11 #include <simgrid/simix.hpp>
13 #include "src/kernel/resource/CpuImpl.hpp"
14 #include "src/simgrid/module.hpp"
16 #include <boost/algorithm/string/classification.hpp>
17 #include <boost/algorithm/string/split.hpp>
19 SIMGRID_REGISTER_PLUGIN(battery, "Battery management", &sg_battery_plugin_init)
21 /** @defgroup plugin_battery plugin_battery Plugin Battery
25 This is the battery plugin, enabling management of batteries on hosts.
26 To activate this plugin, first call :cpp:func:`sg_battery_plugin_init()`.
28 We consider a constant energy exchange model.
30 Properties of batteries such as State of Charge and State of Health are lazily updated, ie., when reading values and
31 when the power is modified.
36 If the power of a battery is set to a negative value then the battery will act as a load and fill over time until it
37 reaches its maximal SoC. Conversely, if the power of a battery is set to a positive value then the battery will act as a
38 generator and deplete over time until it reaches its minimal SoC. When reaching either its maximal or minimal SoC it
39 will set its power to 0.
41 The natural depletion of batteries over time is not taken into account.
46 A battery starts with an energy budget :math:`E` such as:
50 E = C \times U \times D \times N \times 2
52 Where :math:`C` is the initial capacity, :math:`U` is the ratio of usable capacity, :math:`D` is the depth of discharge
53 and :math:`N` is the number of cycles of the battery.
55 The SoH represents the consumption of this energy budget during the lifetime of the battery.
56 Use the battery reduces its SoH and its capacity in consequence.
57 When the SoH reaches 0, the battery becomes unusable.
61 Due to the decrease of the battery capacity with the SoH, a large usable capacity leads to very tiny battery capacity
62 when reaching low SoH. This may results in charge and discharge cycles too short to be evaluated by the simulator. To
63 avoid this situation you should not try to reach a SoH of 0 with a usable capacity set to 1.
65 Plotting the output of the example "battery-degradation" highlights the linear decrease of the SoH due to a continuous
66 use of the battery and the decreasing cycle duration as its capacity reduces:
68 .. image:: /img/battery_degradation.svg
74 Properties of the batteries are defined as properties of hosts in the platform XML file.
76 Here is an example of XML declaration:
80 <host id="Host" speed="100.0Mf" core="1">
81 <prop id="battery_active" value="1" />
82 <prop id="battery_capacity" value="10" />
83 <prop id="battery_cycles" value="200" />
84 <prop id="battery_state_of_charge" value="0.8" />
87 The different properties are:
89 - ``battery_active``: Set the battery as active if set to 1 (default=0)
90 - ``battery_power``: Set the initial power of the battery in W (default=0). A negative value indicates that the battery act as a load and charge. A positive value indicates that the battery act as a generator and discharge
91 - ``battery_state_of_charge``: Set the initial state of charge of the battery (default=0)
92 - ``battery_state_of_charge_min``: Set the minimal state of charge of the battery (default=0.2)
93 - ``battery_state_of_charge_max``: Set the maximal state of charge of the battery (default=0.8)
94 - ``battery_capacity``: Set the initial capacity of the battery in Wh (default=0)
95 - ``battery_usable_capacity``: Set the ratio of usable capacity of the battery (default=0.8)
96 - ``battery_depth_of_discharge``: Set the depth of discharge of the battery (default=0.9)
97 - ``battery_charge_efficiency``: Set the charge efficiency of the battery (default=1)
98 - ``battery_discharge_efficiency``: Set the charge efficiency of the battery (default=1)
99 - ``battery_cycles``: Set the number of cycle of the battery (default=1)
100 - ``battery_depth_of_discharge``: Set the number of cycle of the battery (default=1)
101 - ``battery_eval_cost``: Evaluate the cost of the battery during the simulation if set to 1 (defaulf=0)
102 - ``battery_investment_cost``: Set the investment cost of the battery (default=0)
103 - ``battery_static_maintenance_cost``: Set the static maintenance cost of the battery (default=0)
104 - ``battery_variable_maintenance_cost``: Set the variable maintenance cost of the battery (default=0)
108 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(battery, kernel, "Logging specific to the battery plugin");
110 namespace simgrid::plugin {
113 simgrid::s4u::Host* host_ = nullptr;
115 double state_of_charge_min_ = 0.2;
116 double state_of_charge_max_ = 0.8;
117 double charge_efficiency_ = 1;
118 double discharge_efficiency_ = 1;
119 double initial_capacity_wh_ = 0;
120 double cycles_ = 1; // total complete cycles (charge + discharge) the battery can do before complete depletion.
121 double depth_of_discharge_ = 0.9;
122 double usable_capacity_ = 0.8;
123 double energy_budget_j_ = 0;
125 bool active_ = false;
126 double power_w_ = 0; // NEGATIVE when charging (consumes power) POSITIVE when discharging (generates power)
127 double state_of_charge_ = 0;
128 double capacity_wh_ = 0;
129 double next_event_ = -1;
130 double energy_exchanged_j_ = 0;
131 double last_updated_ = 0;
133 // Calculation of costs from Bei Li thesis (link :https://tel.archives-ouvertes.fr/tel-02077668/document) (chapter 4)
134 bool eval_cost_ = false;
135 double cumulative_cost_ = 0;
136 double investment_cost_per_wh_ = 0;
137 double static_maintenance_cost_per_wh_times_h_ = 0;
138 double variable_maintenance_cost_per_wh_ = 0;
140 void init_battery_params();
141 void init_cost_params();
144 void set_state_of_charge(double soc);
145 void set_state_of_charge_min(double soc);
146 void set_state_of_charge_max(double soc);
147 void set_capacity(double c);
148 void set_initial_capacity(double c);
149 void set_cycles(int c);
150 void set_depth_of_discharge(double d);
151 void set_usable_capacity(double c);
152 void set_charge_efficiency(double e);
153 void set_discharge_efficiency(double e);
154 void set_eval_cost(bool eval);
155 void set_investment_cost(double c);
156 void set_static_maintenance_cost(double c);
157 void set_variable_maintenance_cost(double c);
162 static simgrid::xbt::Extension<simgrid::s4u::Host, Battery> EXTENSION_ID;
164 explicit Battery(simgrid::s4u::Host* host);
167 void set_state(bool state);
168 void set_power(const double power);
170 bool is_active() const;
172 double get_state_of_charge();
173 double get_state_of_charge_min() const;
174 double get_state_of_charge_max() const;
175 double get_state_of_health();
176 double get_capacity();
177 double get_cumulative_cost();
178 double get_next_event_date();
181 simgrid::xbt::Extension<simgrid::s4u::Host, Battery> Battery::EXTENSION_ID;
183 void Battery::set_power(const double p)
186 xbt_assert(p == 0 or (p > 0 and state_of_charge_ > state_of_charge_min_) or
187 (p < 0 and state_of_charge_ < state_of_charge_max_),
188 "Incoherent power and state of charge. A battery cannot charge(discharge) past its maximal(minimal) state "
190 xbt_assert(p == 0 or energy_exchanged_j_ < energy_budget_j_, "Cannot set power of a fully used battery.");
191 simgrid::kernel::actor::simcall_answered([this, p] {
200 soc_shutdown = capacity_wh_ * 3600 * (state_of_charge_ - state_of_charge_min_) / (power_w_ * charge_efficiency_);
201 soh_shutdown = (energy_budget_j_ - energy_exchanged_j_) / (power_w_ * charge_efficiency_);
202 } else { // power_w_ < 0
204 capacity_wh_ * 3600 * (state_of_charge_max_ - state_of_charge_) / (abs(power_w_) / discharge_efficiency_);
205 soh_shutdown = (energy_budget_j_ - energy_exchanged_j_) / (abs(power_w_) / discharge_efficiency_);
207 if (soh_shutdown <= 0)
208 next_event_ = simgrid::s4u::Engine::get_clock() + soc_shutdown;
210 next_event_ = simgrid::s4u::Engine::get_clock() + std::min(soc_shutdown, soh_shutdown);
214 void Battery::set_state(bool state)
217 simgrid::kernel::actor::simcall_answered([this, state] { active_ = state; });
220 void Battery::set_state_of_charge(double soc)
222 xbt_assert(soc > 0 and soc <= 1, " : state of charge should be in ]0,1] (provided: %f)", soc);
223 simgrid::kernel::actor::simcall_answered([this, soc] { state_of_charge_ = soc; });
226 void Battery::set_state_of_charge_min(double soc)
228 xbt_assert(soc > 0 and soc <= 1 and soc < state_of_charge_max_,
229 " : state of charge min should be in ]0,1] and below state of charge max (provided: %f)", soc);
230 simgrid::kernel::actor::simcall_answered([this, soc] { state_of_charge_min_ = soc; });
233 void Battery::set_state_of_charge_max(double soc)
235 xbt_assert(soc > 0 and soc <= 1 and soc > state_of_charge_min_,
236 " : state of charge max should be in ]0,1] and above state of charge min (provided: %f)", soc);
237 simgrid::kernel::actor::simcall_answered([this, soc] { state_of_charge_max_ = soc; });
240 void Battery::set_initial_capacity(double c)
242 xbt_assert(c > 0, " : capacity should be > 0 (provided: %f)", c);
243 simgrid::kernel::actor::simcall_answered([this, c] { initial_capacity_wh_ = c; });
246 void Battery::set_capacity(double c)
248 xbt_assert(c > 0, " : capacity should be > 0 (provided: %f)", c);
249 simgrid::kernel::actor::simcall_answered([this, c] { capacity_wh_ = c; });
252 void Battery::set_cycles(int c)
254 xbt_assert(c > 0, " : cycles should be > 0 (provided: %d)", c);
255 simgrid::kernel::actor::simcall_answered([this, c] { cycles_ = c; });
258 void Battery::set_depth_of_discharge(double d)
260 xbt_assert(d > 0 and d <= 1, " : depth of discharge should be in ]0, 1] (provided: %f)", d);
261 simgrid::kernel::actor::simcall_answered([this, d] { depth_of_discharge_ = d; });
264 void Battery::set_usable_capacity(double c)
266 xbt_assert(c > 0 and c <= 1, " : usable capacity should be in ]0, 1] (provided: %f)", c);
267 simgrid::kernel::actor::simcall_answered([this, c] { usable_capacity_ = c; });
270 void Battery::set_charge_efficiency(double e)
272 xbt_assert(e > 0 and e <= 1, " : charge efficiency should be in [0,1] (provided: %f)", e);
273 simgrid::kernel::actor::simcall_answered([this, e] { charge_efficiency_ = e; });
276 void Battery::set_discharge_efficiency(double e)
278 xbt_assert(e > 0 and e <= 1, " : discharge efficiency should be in [0,1] (provided: %f)", e);
279 simgrid::kernel::actor::simcall_answered([this, e] { discharge_efficiency_ = e; });
282 void Battery::set_eval_cost(bool eval)
284 simgrid::kernel::actor::simcall_answered([this, eval] { eval_cost_ = eval; });
287 void Battery::set_investment_cost(double c)
289 xbt_assert(c >= 0, " : investment cost should be >= 0 (provided: %f)", c);
290 simgrid::kernel::actor::simcall_answered([this, c] { investment_cost_per_wh_ = c; });
293 void Battery::set_static_maintenance_cost(double c)
295 xbt_assert(c >= 0, " : static maintenance cost should be >= 0 (provided: %f)", c);
296 simgrid::kernel::actor::simcall_answered([this, c] { static_maintenance_cost_per_wh_times_h_ = c; });
299 void Battery::set_variable_maintenance_cost(double c)
301 xbt_assert(c >= 0, " : variable maintenance cost should be >= 0 (provided: %f)", c);
302 simgrid::kernel::actor::simcall_answered([this, c] { variable_maintenance_cost_per_wh_ = c; });
305 bool Battery::is_charging()
311 bool Battery::is_active() const
316 double Battery::get_power()
322 double Battery::get_state_of_charge()
325 return state_of_charge_;
328 double Battery::get_state_of_charge_min() const
330 return state_of_charge_min_;
333 double Battery::get_state_of_charge_max() const
335 return state_of_charge_max_;
338 double Battery::get_state_of_health()
341 return 1 - (energy_exchanged_j_ / energy_budget_j_);
344 double Battery::get_capacity()
350 double Battery::get_cumulative_cost()
353 return cumulative_cost_;
356 double Battery::get_next_event_date()
362 void Battery::init_battery_params()
364 const char* prop_chars;
365 prop_chars = host_->get_property("battery_capacity");
367 set_capacity(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
368 set_initial_capacity(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
370 prop_chars = host_->get_property("battery_usable_capacity");
372 set_usable_capacity(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
373 prop_chars = host_->get_property("battery_depth_of_discharge");
375 set_depth_of_discharge(
376 xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
377 prop_chars = host_->get_property("battery_cycles");
379 set_cycles(xbt_str_parse_int(prop_chars, ("cannot parse int: " + std::string(prop_chars)).c_str()));
380 simgrid::kernel::actor::simcall_answered([this] {
381 energy_budget_j_ = (initial_capacity_wh_ * usable_capacity_ * depth_of_discharge_ * 3600 * cycles_ * 2);
383 prop_chars = host_->get_property("battery_active");
385 set_state(xbt_str_parse_int(prop_chars, ("cannot parse int: " + std::string(prop_chars)).c_str()));
386 prop_chars = host_->get_property("battery_state_of_charge_min");
388 set_state_of_charge_min(
389 xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
390 prop_chars = host_->get_property("battery_state_of_charge_max");
392 set_state_of_charge_max(
393 xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
394 prop_chars = host_->get_property("battery_charge_efficiency");
396 set_charge_efficiency(
397 xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
398 prop_chars = host_->get_property("battery_discharge_efficiency");
400 set_discharge_efficiency(
401 xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
402 prop_chars = host_->get_property("battery_state_of_charge");
404 set_state_of_charge(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
405 prop_chars = host_->get_property("battery_eval_cost");
407 set_eval_cost(xbt_str_parse_int(prop_chars, ("cannot parse int: " + std::string(prop_chars)).c_str()));
408 prop_chars = host_->get_property("battery_investment_cost");
410 set_investment_cost(xbt_str_parse_int(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
411 prop_chars = host_->get_property("battery_static_maintenance_cost");
413 set_static_maintenance_cost(
414 xbt_str_parse_int(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
415 prop_chars = host_->get_property("battery_variable_maintenance_cost");
417 set_variable_maintenance_cost(
418 xbt_str_parse_int(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
419 prop_chars = host_->get_property("battery_power");
421 set_power(xbt_str_parse_double(prop_chars, ("cannot parse double: " + std::string(prop_chars)).c_str()));
422 simgrid::kernel::actor::simcall_answered([this] { last_updated_ = simgrid::s4u::Engine::get_clock(); });
425 void Battery::update()
427 simgrid::kernel::actor::simcall_answered([this] {
428 double now = simgrid::s4u::Engine::get_clock();
429 double time_delta_real = now - last_updated_;
430 if (time_delta_real <= 0 || not is_active())
432 double time_delta_until_event = next_event_ - last_updated_;
433 bool event = next_event_ != -1 and time_delta_until_event <= time_delta_real;
434 double power_real_w = power_w_ < 0 ? power_w_ * charge_efficiency_ : power_w_ / discharge_efficiency_;
435 state_of_charge_ -= power_real_w * (event ? time_delta_until_event : time_delta_real) / (3600 * capacity_wh_);
436 energy_exchanged_j_ += (event ? time_delta_until_event : time_delta_real) * abs(power_real_w);
437 capacity_wh_ = initial_capacity_wh_ * usable_capacity_ * (1 - (energy_exchanged_j_ / energy_budget_j_)) +
438 initial_capacity_wh_ * (1 - usable_capacity_);
439 capacity_wh_ = std::max(capacity_wh_, 0.0);
441 double usage_cost_per_wh =
442 (investment_cost_per_wh_ / (depth_of_discharge_ * cycles_ * 2) + variable_maintenance_cost_per_wh_);
444 usage_cost_per_wh * abs(power_real_w) * (event ? time_delta_until_event : time_delta_real) / 3600;
445 double static_maintenance_cost =
446 static_maintenance_cost_per_wh_times_h_ * initial_capacity_wh_ * time_delta_real / 3600;
447 cumulative_cost_ += usage_cost + static_maintenance_cost;
457 Battery::Battery(simgrid::s4u::Host* host) : host_(host)
459 init_battery_params();
462 Battery::~Battery() = default;
463 } // namespace simgrid::plugin
465 using simgrid::plugin::Battery;
467 /* **************************** events callback *************************** */
469 static void on_creation(simgrid::s4u::Host& host)
471 if (dynamic_cast<simgrid::s4u::VirtualMachine*>(&host)) // Ignore virtual machines
473 host.extension_set(new Battery(&host));
476 /* **************************** Public interface *************************** */
478 static void ensure_plugin_inited()
480 if (not Battery::EXTENSION_ID.valid())
481 throw simgrid::xbt::InitializationError("The Battery plugin is not active. Please call sg_battery_plugin_init() "
482 "before calling any function related to that plugin.");
485 /** @ingroup plugin_battery
486 * @brief Enable battery plugin.
488 void sg_battery_plugin_init()
490 if (Battery::EXTENSION_ID.valid())
492 Battery::EXTENSION_ID = simgrid::s4u::Host::extension_create<Battery>();
493 simgrid::s4u::Host::on_creation_cb(&on_creation);
496 /** @ingroup plugin_battery
497 * @param state The state to set.
498 * @brief Set the state of the battery.
499 * A battery set to inactive (false) will neither update its state of charge nor its state of health.
501 void sg_battery_set_state(const_sg_host_t host, bool state)
503 ensure_plugin_inited();
504 host->extension<Battery>()->set_state(state);
507 /** @ingroup plugin_battery
508 * @param power The power to set in W.
509 * @brief Set the power of the battery.
510 * @note A negative value makes the battery act as a load and charge.
511 * A positive value makes the battery act as a generator and discharge.
513 void sg_battery_set_power(const_sg_host_t host, double power)
515 ensure_plugin_inited();
516 host->extension<Battery>()->set_power(power);
519 /** @ingroup plugin_battery
520 * @brief Return true if the battery is active.
522 bool sg_battery_is_active(const_sg_host_t host)
524 ensure_plugin_inited();
525 return host->extension<Battery>()->is_active();
528 /** @ingroup plugin_battery
529 * @brief Return the power of the battery in W.
530 * @note A negative value indicates that the battery act as a load and charge.
531 * A positive value indicates that the battery act as a generator and discharge.
533 double sg_battery_get_power(const_sg_host_t host)
535 ensure_plugin_inited();
536 return host->extension<Battery>()->get_power();
539 /** @ingroup plugin_battery
540 * @brief Return the state of charge of the battery.
542 double sg_battery_get_state_of_charge(const_sg_host_t host)
544 ensure_plugin_inited();
545 return host->extension<Battery>()->get_state_of_charge();
548 /** @ingroup plugin_battery
549 * @brief Return the minimal state of charge of the battery.
551 double sg_battery_get_state_of_charge_min(const_sg_host_t host)
553 ensure_plugin_inited();
554 return host->extension<Battery>()->get_state_of_charge_min();
557 /** @ingroup plugin_battery
558 * @brief Return the maximal state of charge of the battery.
560 double sg_battery_get_state_of_charge_max(const_sg_host_t host)
562 ensure_plugin_inited();
563 return host->extension<Battery>()->get_state_of_charge_max();
566 /** @ingroup plugin_battery
567 * @brief Return the state of health of the battery.
569 double sg_battery_get_state_of_health(const_sg_host_t host)
571 ensure_plugin_inited();
572 return host->extension<Battery>()->get_state_of_health();
575 /** @ingroup plugin_battery
576 * @brief Return the capacity of the battery in Wh.
577 * @note capacity is reduced by the state of health of the battery.
579 double sg_battery_get_capacity(const_sg_host_t host)
581 ensure_plugin_inited();
582 return host->extension<Battery>()->get_capacity();
585 /** @ingroup plugin_battery
586 * @brief Return the cumulative cost of the battery.
588 double sg_battery_get_cumulative_cost(const_sg_host_t host)
590 ensure_plugin_inited();
591 return host->extension<Battery>()->get_cumulative_cost();
594 /** @ingroup plugin_battery
595 * @brief Return the date of the next event, i.e., when the battery will be empty or full.
596 * @note If power is null then return -1.
598 double sg_battery_get_next_event_date(const_sg_host_t host)
600 ensure_plugin_inited();
601 return host->extension<Battery>()->get_next_event_date();