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.
86 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(Battery, kernel, "Logging specific to the battery plugin");
88 namespace simgrid::plugins {
92 BatteryModel::BatteryModel() : Model("BatteryModel") {}
94 void BatteryModel::add_battery(BatteryPtr b)
96 batteries_.push_back(b);
99 void BatteryModel::update_actions_state(double now, double delta)
101 for (auto battery : batteries_)
105 double BatteryModel::next_occurring_event(double now)
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;
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)
122 std::shared_ptr<Battery::Handler> Battery::Handler::init(double state_of_charge, Flow flow, Persistancy p,
123 std::function<void()> callback)
125 return std::make_shared<Battery::Handler>(state_of_charge, flow, p, callback);
130 std::shared_ptr<BatteryModel> Battery::battery_model_;
132 void Battery::init_plugin()
134 auto model = std::make_shared<BatteryModel>();
135 simgrid::s4u::Engine::get_instance()->add_model(model);
136 Battery::battery_model_ = model;
139 void Battery::update()
141 kernel::actor::simcall_answered([this] {
142 double now = s4u::Engine::get_clock();
143 double time_delta_s = now - last_updated_;
146 if (time_delta_s <= 0)
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_) {
158 provided_power_w += pair.second;
160 consumed_power_w += -pair.second;
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_);
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;
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_) /
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);
182 energy_provided_j_ += energy_lost_delta_j * discharge_efficiency_;
183 energy_consumed_j_ += energy_gained_delta_j / charge_efficiency_;
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_);
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;
198 to_delete.push_back(handler);
201 for (auto handler : to_delete)
202 delete_handler(handler);
206 double Battery::next_occurring_handler()
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_) {
216 provided_power_w += pair.second;
218 consumed_power_w += -pair.second;
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_);
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()))) {
235 // Evaluate time until handler happen
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
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_) /
245 (gained_power_w - lost_power_w +
246 3600 * handler->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) /
248 if ((time_delta == -1 or handler->time_delta_ < time_delta) and abs(handler->time_delta_) > 0.000000001)
249 time_delta = handler->time_delta_;
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)
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)
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)",
274 xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
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);
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.
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)
297 static bool plugin_inited = false;
298 if (not plugin_inited) {
300 plugin_inited = true;
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);
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.
312 void Battery::set_load(const std::string& name, double power_w)
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);
318 named_loads_[name].second = power_w;
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.
326 void Battery::set_load(const std::string& name, bool active)
328 kernel::actor::simcall_answered([this, &name, &active] { named_loads_[name].first = active; });
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.
339 void Battery::connect_host(s4u::Host* host, bool active)
341 kernel::actor::simcall_answered([this, &host, &active] { host_loads_[host] = active; });
344 /** @ingroup plugin_battery
345 * @return The state of charge of the battery.
347 double Battery::get_state_of_charge()
349 return energy_stored_j_ / (3600 * capacity_wh_);
352 /** @ingroup plugin_battery
353 * @return The state of health of the Battery.
355 double Battery::get_state_of_health()
358 ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
361 /** @ingroup plugin_battery
362 * @return The current capacity of the Battery.
364 double Battery::get_capacity()
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.
374 double Battery::get_energy_provided()
376 return energy_provided_j_;
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.
384 double Battery::get_energy_consumed()
386 return energy_consumed_j_;
389 /** @ingroup plugin_battery
390 * @param unit Valid units are J (default) and Wh.
391 * @return Energy stored in the Battery.
393 double Battery::get_energy_stored(std::string unit)
396 return energy_stored_j_;
397 else if (unit == "Wh")
398 return energy_stored_j_ / 3600;
400 xbt_die("Invalid unit. Valid units are J (default) or Wh.");
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.
411 std::shared_ptr<Battery::Handler> Battery::schedule_handler(double state_of_charge, Flow flow, Handler::Persistancy p,
412 std::function<void()> callback)
414 auto handler = Handler::init(state_of_charge, flow, p, callback);
415 handlers_.push_back(handler);
419 /** @ingroup plugin_battery
420 * @return A vector containing the Handlers associated to the Battery.
422 std::vector<std::shared_ptr<Battery::Handler>> Battery::get_handlers()
427 /** @ingroup plugin_battery
428 * @brief Remove an Handler from the Battery.
430 void Battery::delete_handler(std::shared_ptr<Handler> handler)
432 handlers_.erase(std::remove_if(handlers_.begin(), handlers_.end(),
433 [&handler](std::shared_ptr<Handler> e) { return handler == e; }),
436 } // namespace simgrid::plugins