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/plugins/energy.h>
8 #include <simgrid/s4u/Engine.hpp>
9 #include <simgrid/simix.hpp>
11 #include "src/kernel/resource/CpuImpl.hpp"
12 #include "src/simgrid/module.hpp"
14 SIMGRID_REGISTER_PLUGIN(battery, "Battery management", nullptr)
15 /** @defgroup plugin_battery plugin_battery Plugin Battery
19 This is the battery plugin, enabling management of batteries.
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`.
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.
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:
38 E_{lost} = {E_{provided} \over \eta_{discharge}}
40 E_{gained} = E_{consumed} \times \eta_{charge}
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.
45 All the energies are positive, but loads connected to a Battery may be positive or negative, as explained in the next
48 Use the battery reduces its State of Health :math:`SoH` and its capacity :math:`C` linearly in consequence:
52 SoH = 1 - {E_{lost} + E_{gained} \over E_{budget}}
54 C = C_{initial} \times SoH
60 E_{budget} = C_{initial} \times N \times 2
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:
65 .. image:: /img/battery_degradation.svg
68 The natural depletion of batteries over time is not taken into account.
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.
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.
87 A Battery can act as a connector to connect Solar Panels direcly to loads. Such Battery is created without any
88 parameter, cannot store energy and has a transfer efficiency of 100%.
92 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(Battery, kernel, "Logging specific to the battery plugin");
94 namespace simgrid::plugins {
98 BatteryModel::BatteryModel() : Model("BatteryModel") {}
100 void BatteryModel::add_battery(BatteryPtr b)
102 batteries_.push_back(b);
105 void BatteryModel::update_actions_state(double now, double delta)
107 for (auto battery : batteries_)
111 double BatteryModel::next_occurring_event(double now)
113 static bool init = false;
118 double time_delta = -1;
119 for (auto battery : batteries_) {
120 double time_delta_battery = battery->next_occurring_handler();
121 time_delta = time_delta == -1 or time_delta_battery < time_delta ? time_delta_battery : time_delta;
128 Battery::Handler::Handler(double state_of_charge, Flow flow, Persistancy p, std::function<void()> callback)
129 : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), persistancy_(p)
133 std::shared_ptr<Battery::Handler> Battery::Handler::init(double state_of_charge, Flow flow, Persistancy p,
134 std::function<void()> callback)
136 return std::make_shared<Battery::Handler>(state_of_charge, flow, p, callback);
141 std::shared_ptr<BatteryModel> Battery::battery_model_;
143 void Battery::init_plugin()
145 auto model = std::make_shared<BatteryModel>();
146 simgrid::s4u::Engine::get_instance()->add_model(model);
147 Battery::battery_model_ = model;
150 void Battery::update()
152 kernel::actor::simcall_answered([this] {
153 double now = s4u::Engine::get_clock();
154 double time_delta_s = now - last_updated_;
157 if (time_delta_s <= 0)
160 // Calculate energy provided / consumed during this step
161 double provided_power_w = 0;
162 double consumed_power_w = 0;
163 for (auto const& [host, active] : host_loads_)
164 provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
165 for (auto const& [name, pair] : named_loads_) {
169 provided_power_w += pair.second;
171 consumed_power_w += -pair.second;
174 provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
175 consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
177 double energy_lost_delta_j = provided_power_w / discharge_efficiency_ * time_delta_s;
178 double energy_gained_delta_j = consumed_power_w * charge_efficiency_ * time_delta_s;
180 // Check that the provided/consumed energy is valid
181 energy_lost_delta_j = std::min(energy_lost_delta_j, energy_stored_j_ + energy_gained_delta_j);
182 /* Charging deteriorate the capacity, but the capacity is used to evaluate the maximum charge so
183 we need to evaluate the theorethical new capacity in the worst case when we fully charge the battery */
184 double new_tmp_capacity_wh =
185 (initial_capacity_wh_ *
186 (1 - (energy_provided_j_ + energy_lost_delta_j * discharge_efficiency_ + energy_consumed_j_ -
187 (energy_stored_j_ + energy_lost_delta_j) / charge_efficiency_) /
189 (1 + 3600 * initial_capacity_wh_ / (charge_efficiency_ * energy_budget_j_));
190 energy_gained_delta_j =
191 std::min(energy_gained_delta_j, (3600 * new_tmp_capacity_wh) - energy_stored_j_ + energy_lost_delta_j);
194 energy_provided_j_ += energy_lost_delta_j * discharge_efficiency_;
195 energy_consumed_j_ += energy_gained_delta_j / charge_efficiency_;
197 // This battery is a simple connector, we only update energy provided and consumed
198 if (energy_budget_j_ == 0) {
199 energy_consumed_j_ = energy_provided_j_;
205 initial_capacity_wh_ *
206 (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
207 energy_stored_j_ += energy_gained_delta_j - energy_lost_delta_j;
208 energy_stored_j_ = std::min(energy_stored_j_, 3600 * capacity_wh_);
211 auto handlers_2 = handlers_;
212 for (auto handler : handlers_2) {
213 if (abs(handler->time_delta_ - time_delta_s) < 0.000000001) {
214 handler->callback_();
215 if (handler->persistancy_ == Handler::Persistancy::PERSISTANT)
216 handler->time_delta_ = -1;
218 delete_handler(handler);
224 double Battery::next_occurring_handler()
226 double provided_power_w = 0;
227 double consumed_power_w = 0;
228 for (auto const& [host, active] : host_loads_)
229 provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
230 for (auto const& [name, pair] : named_loads_) {
234 provided_power_w += pair.second;
236 consumed_power_w += -pair.second;
239 provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
240 consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
242 double time_delta = -1;
243 for (auto& handler : handlers_) {
244 double lost_power_w = provided_power_w / discharge_efficiency_;
245 double gained_power_w = consumed_power_w * charge_efficiency_;
246 if ((lost_power_w == gained_power_w) or (handler->state_of_charge_ == get_state_of_charge()) or
247 (lost_power_w > gained_power_w and
248 (handler->flow_ == Flow::CHARGE or handler->state_of_charge_ > get_state_of_charge())) or
249 (lost_power_w < gained_power_w and
250 (handler->flow_ == Flow::DISCHARGE or handler->state_of_charge_ < get_state_of_charge()))) {
253 // Evaluate time until handler happen
255 /* The time to reach a state of charge depends on the capacity, but charging and discharging deteriorate the
256 * capacity, so we need to evaluate the time considering a capacity that also depends on time
258 handler->time_delta_ =
259 (3600 * handler->state_of_charge_ * initial_capacity_wh_ *
260 (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) /
263 (gained_power_w - lost_power_w +
264 3600 * handler->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) /
266 if ((time_delta == -1 or handler->time_delta_ < time_delta) and abs(handler->time_delta_) > 0.000000001)
267 time_delta = handler->time_delta_;
273 Battery::Battery(const std::string& name, double state_of_charge, double nominal_charge_power_w,
274 double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
275 double initial_capacity_wh, int cycles)
277 , nominal_charge_power_w_(nominal_charge_power_w)
278 , nominal_discharge_power_w_(nominal_discharge_power_w)
279 , charge_efficiency_(charge_efficiency)
280 , discharge_efficiency_(discharge_efficiency)
281 , initial_capacity_wh_(initial_capacity_wh)
282 , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
283 , capacity_wh_(initial_capacity_wh)
284 , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
286 xbt_assert(nominal_charge_power_w <= 0, " : nominal charge power must be <= 0 (provided: %f)",
287 nominal_charge_power_w);
288 xbt_assert(nominal_discharge_power_w >= 0, " : nominal discharge power must be non-negative (provided: %f)",
289 nominal_discharge_power_w);
290 xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
292 xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
294 xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
295 " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
296 xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
297 xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
300 /** @ingroup plugin_battery
301 * @brief Init a Battery with this constructor makes it only usable as a connector.
302 * A connector has no capacity and only delivers as much power as it receives
303 with a transfer efficiency of 100%.
304 * @return A BatteryPtr pointing to the new Battery.
306 BatteryPtr Battery::init()
308 static bool plugin_inited = false;
309 if (not plugin_inited) {
311 plugin_inited = true;
313 auto battery = BatteryPtr(new Battery());
314 battery_model_->add_battery(battery);
318 /** @ingroup plugin_battery
319 * @param name The name of the Battery.
320 * @param state_of_charge The initial state of charge of the Battery [0,1].
321 * @param nominal_charge_power_w The maximum power absorbed by the Battery in W (<= 0).
322 * @param nominal_discharge_power_w The maximum power delivered by the Battery in W (>= 0).
323 * @param charge_efficiency The charge efficiency of the Battery [0,1].
324 * @param discharge_efficiency The discharge efficiency of the Battery [0,1].
325 * @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
326 * @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
327 * @return A BatteryPtr pointing to the new Battery.
329 BatteryPtr Battery::init(const std::string& name, double state_of_charge, double nominal_charge_power_w,
330 double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
331 double initial_capacity_wh, int cycles)
333 static bool plugin_inited = false;
334 if (not plugin_inited) {
336 plugin_inited = true;
338 auto battery = BatteryPtr(new Battery(name, state_of_charge, nominal_charge_power_w, nominal_discharge_power_w,
339 charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
340 battery_model_->add_battery(battery);
344 /** @ingroup plugin_battery
345 * @param name The name of the load
346 * @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
348 void Battery::set_load(const std::string& name, double power_w)
350 kernel::actor::simcall_answered([this, &name, &power_w] {
351 if (named_loads_.find(name) == named_loads_.end())
352 named_loads_[name] = std::make_pair(true, power_w);
354 named_loads_[name].second = power_w;
358 /** @ingroup plugin_battery
359 * @param name The name of the load
360 * @param active Status of the load. If false then the load is ignored by the Battery.
362 void Battery::set_load(const std::string& name, bool active)
364 kernel::actor::simcall_answered([this, &name, &active] { named_loads_[name].first = active; });
367 /** @ingroup plugin_battery
368 * @param host The Host to connect.
369 * @param active Status of the connected Host (default true).
370 * @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
371 from the Battery. To modify this status connect again the same Host with a different status.
372 @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
373 In this case all Batteries would have the full consumption from this Host.
375 void Battery::connect_host(s4u::Host* host, bool active)
377 kernel::actor::simcall_answered([this, &host, &active] { host_loads_[host] = active; });
380 /** @ingroup plugin_battery
381 * @return The state of charge of the battery.
383 double Battery::get_state_of_charge()
385 return energy_stored_j_ / (3600 * capacity_wh_);
388 /** @ingroup plugin_battery
389 * @return The state of health of the Battery.
391 double Battery::get_state_of_health()
394 ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
397 /** @ingroup plugin_battery
398 * @return The current capacity of the Battery.
400 double Battery::get_capacity()
405 /** @ingroup plugin_battery
406 * @return The energy provided by the Battery.
407 * @note It is the energy provided from an external point of view, after application of the discharge efficiency.
408 It means that the Battery lost more energy than it has provided.
410 double Battery::get_energy_provided()
412 return energy_provided_j_;
415 /** @ingroup plugin_battery
416 * @return The energy consumed by the Battery.
417 * @note It is the energy consumed from an external point of view, before application of the charge efficiency.
418 It means that the Battery consumed more energy than is has absorbed.
420 double Battery::get_energy_consumed()
422 return energy_consumed_j_;
425 /** @ingroup plugin_battery
426 * @param unit Valid units are J (default) and Wh.
427 * @return Energy stored in the Battery.
429 double Battery::get_energy_stored(std::string unit)
432 return energy_stored_j_;
433 else if (unit == "Wh")
434 return energy_stored_j_ / 3600;
436 xbt_die("Invalid unit. Valid units are J (default) or Wh.");
439 /** @ingroup plugin_battery
440 * @brief Schedule a new Handler.
441 * @param state_of_charge The state of charge at which the Handler will happen.
442 * @param flow The flow in which the Handler will happen, either when the Battery is charging or discharging.
443 * @param callback The callable to trigger when the Handler happen.
444 * @param p If the Handler is recurrent or unique.
445 * @return A shared pointer of the new Handler.
447 std::shared_ptr<Battery::Handler> Battery::schedule_handler(double state_of_charge, Flow flow, Handler::Persistancy p,
448 std::function<void()> callback)
450 auto handler = Handler::init(state_of_charge, flow, p, callback);
451 handlers_.push_back(handler);
455 /** @ingroup plugin_battery
456 * @return A vector containing the Handlers associated to the Battery.
458 std::vector<std::shared_ptr<Battery::Handler>> Battery::get_handlers()
463 /** @ingroup plugin_battery
464 * @brief Remove an Handler from the Battery.
466 void Battery::delete_handler(std::shared_ptr<Handler> handler)
468 handlers_.erase(std::remove_if(handlers_.begin(), handlers_.end(),
469 [&handler](std::shared_ptr<Handler> e) { return handler == e; }),
472 } // namespace simgrid::plugins