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 std::vector<std::shared_ptr<Handler>> to_delete = {};
212 for (auto handler : handlers_) {
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 to_delete.push_back(handler);
221 for (auto handler : to_delete)
222 delete_handler(handler);
226 double Battery::next_occurring_handler()
228 double provided_power_w = 0;
229 double consumed_power_w = 0;
230 for (auto const& [host, active] : host_loads_)
231 provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
232 for (auto const& [name, pair] : named_loads_) {
236 provided_power_w += pair.second;
238 consumed_power_w += -pair.second;
241 provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
242 consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
244 double time_delta = -1;
245 for (auto& handler : handlers_) {
246 double lost_power_w = provided_power_w / discharge_efficiency_;
247 double gained_power_w = consumed_power_w * charge_efficiency_;
248 if ((lost_power_w == gained_power_w) or (handler->state_of_charge_ == get_state_of_charge()) or
249 (lost_power_w > gained_power_w and
250 (handler->flow_ == Flow::CHARGE or handler->state_of_charge_ > get_state_of_charge())) or
251 (lost_power_w < gained_power_w and
252 (handler->flow_ == Flow::DISCHARGE or handler->state_of_charge_ < get_state_of_charge()))) {
255 // Evaluate time until handler happen
257 /* The time to reach a state of charge depends on the capacity, but charging and discharging deteriorate the
258 * capacity, so we need to evaluate the time considering a capacity that also depends on time
260 handler->time_delta_ =
261 (3600 * handler->state_of_charge_ * initial_capacity_wh_ *
262 (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) /
265 (gained_power_w - lost_power_w +
266 3600 * handler->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) /
268 if ((time_delta == -1 or handler->time_delta_ < time_delta) and abs(handler->time_delta_) > 0.000000001)
269 time_delta = handler->time_delta_;
275 Battery::Battery() {}
277 Battery::Battery(const std::string& name, double state_of_charge, double nominal_charge_power_w,
278 double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
279 double initial_capacity_wh, int cycles)
281 , nominal_charge_power_w_(nominal_charge_power_w)
282 , nominal_discharge_power_w_(nominal_discharge_power_w)
283 , charge_efficiency_(charge_efficiency)
284 , discharge_efficiency_(discharge_efficiency)
285 , initial_capacity_wh_(initial_capacity_wh)
286 , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
287 , capacity_wh_(initial_capacity_wh)
288 , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
290 xbt_assert(nominal_charge_power_w <= 0, " : nominal charge power must be <= 0 (provided: %f)",
291 nominal_charge_power_w);
292 xbt_assert(nominal_discharge_power_w >= 0, " : nominal discharge power must be non-negative (provided: %f)",
293 nominal_discharge_power_w);
294 xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
296 xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
298 xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
299 " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
300 xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
301 xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
304 /** @ingroup plugin_battery
305 * @brief Init a Battery with this constructor makes it only usable as a connector.
306 * A connector has no capacity and only delivers as much power as it receives
307 with a transfer efficiency of 100%.
308 * @return A BatteryPtr pointing to the new Battery.
310 BatteryPtr Battery::init()
312 static bool plugin_inited = false;
313 if (not plugin_inited) {
315 plugin_inited = true;
317 auto battery = BatteryPtr(new Battery());
318 battery_model_->add_battery(battery);
322 /** @ingroup plugin_battery
323 * @param name The name of the Battery.
324 * @param state_of_charge The initial state of charge of the Battery [0,1].
325 * @param nominal_charge_power_w The maximum power absorbed by the Battery in W (<= 0).
326 * @param nominal_discharge_power_w The maximum power delivered by the Battery in W (>= 0).
327 * @param charge_efficiency The charge efficiency of the Battery [0,1].
328 * @param discharge_efficiency The discharge efficiency of the Battery [0,1].
329 * @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
330 * @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
331 * @return A BatteryPtr pointing to the new Battery.
333 BatteryPtr Battery::init(const std::string& name, double state_of_charge, double nominal_charge_power_w,
334 double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
335 double initial_capacity_wh, int cycles)
337 static bool plugin_inited = false;
338 if (not plugin_inited) {
340 plugin_inited = true;
342 auto battery = BatteryPtr(new Battery(name, state_of_charge, nominal_charge_power_w, nominal_discharge_power_w,
343 charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
344 battery_model_->add_battery(battery);
348 /** @ingroup plugin_battery
349 * @param name The name of the load
350 * @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
352 void Battery::set_load(const std::string& name, double power_w)
354 kernel::actor::simcall_answered([this, &name, &power_w] {
355 if (named_loads_.find(name) == named_loads_.end())
356 named_loads_[name] = std::make_pair(true, power_w);
358 named_loads_[name].second = power_w;
362 /** @ingroup plugin_battery
363 * @param name The name of the load
364 * @param active Status of the load. If false then the load is ignored by the Battery.
366 void Battery::set_load(const std::string& name, bool active)
368 kernel::actor::simcall_answered([this, &name, &active] { named_loads_[name].first = active; });
371 /** @ingroup plugin_battery
372 * @param host The Host to connect.
373 * @param active Status of the connected Host (default true).
374 * @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
375 from the Battery. To modify this status connect again the same Host with a different status.
376 @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
377 In this case all Batteries would have the full consumption from this Host.
379 void Battery::connect_host(s4u::Host* host, bool active)
381 kernel::actor::simcall_answered([this, &host, &active] { host_loads_[host] = active; });
384 /** @ingroup plugin_battery
385 * @return The state of charge of the battery.
387 double Battery::get_state_of_charge()
389 return energy_stored_j_ / (3600 * capacity_wh_);
392 /** @ingroup plugin_battery
393 * @return The state of health of the Battery.
395 double Battery::get_state_of_health()
398 ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
401 /** @ingroup plugin_battery
402 * @return The current capacity of the Battery.
404 double Battery::get_capacity()
409 /** @ingroup plugin_battery
410 * @return The energy provided by the Battery.
411 * @note It is the energy provided from an external point of view, after application of the discharge efficiency.
412 It means that the Battery lost more energy than it has provided.
414 double Battery::get_energy_provided()
416 return energy_provided_j_;
419 /** @ingroup plugin_battery
420 * @return The energy consumed by the Battery.
421 * @note It is the energy consumed from an external point of view, before application of the charge efficiency.
422 It means that the Battery consumed more energy than is has absorbed.
424 double Battery::get_energy_consumed()
426 return energy_consumed_j_;
429 /** @ingroup plugin_battery
430 * @param unit Valid units are J (default) and Wh.
431 * @return Energy stored in the Battery.
433 double Battery::get_energy_stored(std::string unit)
436 return energy_stored_j_;
437 else if (unit == "Wh")
438 return energy_stored_j_ / 3600;
440 xbt_die("Invalid unit. Valid units are J (default) or Wh.");
443 /** @ingroup plugin_battery
444 * @brief Schedule a new Handler.
445 * @param state_of_charge The state of charge at which the Handler will happen.
446 * @param flow The flow in which the Handler will happen, either when the Battery is charging or discharging.
447 * @param callback The callable to trigger when the Handler happen.
448 * @param p If the Handler is recurrent or unique.
449 * @return A shared pointer of the new Handler.
451 std::shared_ptr<Battery::Handler> Battery::schedule_handler(double state_of_charge, Flow flow, Handler::Persistancy p,
452 std::function<void()> callback)
454 auto handler = Handler::init(state_of_charge, flow, p, callback);
455 handlers_.push_back(handler);
459 /** @ingroup plugin_battery
460 * @return A vector containing the Handlers associated to the Battery.
462 std::vector<std::shared_ptr<Battery::Handler>> Battery::get_handlers()
467 /** @ingroup plugin_battery
468 * @brief Remove an Handler from the Battery.
470 void Battery::delete_handler(std::shared_ptr<Handler> handler)
472 handlers_.erase(std::remove_if(handlers_.begin(), handlers_.end(),
473 [&handler](std::shared_ptr<Handler> e) { return handler == e; }),
476 } // namespace simgrid::plugins