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/s4u/Host.hpp>
10 #include <simgrid/simix.hpp>
11 #include <xbt/asserts.h>
14 #include "src/kernel/resource/CpuImpl.hpp"
15 #include "src/simgrid/module.hpp"
17 SIMGRID_REGISTER_PLUGIN(battery, "Battery management", nullptr)
18 /** @defgroup plugin_battery plugin_battery Plugin Battery
22 This is the battery plugin, enabling management of batteries.
27 A battery has an initial State of Charge :math:`SoC`, a nominal charge power, a nominal discharge power, a charge
28 efficiency :math:`\eta_{charge}`, a discharge efficiency :math:`\eta_{discharge}`, an initial capacity
29 :math:`C_{initial}` and a number of cycle :math:`N`.
31 The nominal charge(discharge) power is the maximum power the Battery can consume(provide), before application of the
32 charge(discharge) efficiency factor. For instance, if a load provides(consumes) 100W to(from) the Battery with a nominal
33 charge(discharge) power of 50W and a charge(discharge) efficiency of 0.9, the Battery will only gain(provide) 45W.
35 We distinguish the energy provided :math:`E_{provided}` / consumed :math:`E_{consumed}` from the energy lost
36 :math:`E_{lost}` / gained :math:`E_{gained}`. The energy provided / consumed shows the external point of view, and the
37 energy lost / gained shows the internal point of view:
41 E_{lost} = {E_{provided} \over \eta_{discharge}}
43 E_{gained} = E_{consumed} \times \eta_{charge}
45 For instance, if you apply a load of 100W to a battery for 10s with a discharge efficiency of 0.8, the energy provided
46 will be equal to 10kJ, and the energy lost will be equal to 12.5kJ.
48 All the energies are positive, but loads connected to a Battery may be positive or negative, as explained in the next
51 Use the battery reduces its State of Health :math:`SoH` and its capacity :math:`C` linearly in consequence:
55 SoH = 1 - {E_{lost} + E_{gained} \over E_{budget}}
57 C = C_{initial} \times SoH
63 E_{budget} = C_{initial} \times N \times 2
65 Plotting the output of the example "battery-degradation" highlights the linear decrease of the :math:`SoH` due to a
66 continuous use of the battery alternating between charge and discharge:
68 .. image:: /img/battery_degradation.svg
71 The natural depletion of batteries over time is not taken into account.
76 You can add named loads to a battery. Those loads may be positive and consume energy from the battery, or negative and
77 provide energy to the battery. You can also connect hosts to a battery. Theses hosts will consume their energy from the
78 battery until the battery is empty or until the connection between the hosts and the battery is set inactive.
83 You can schedule handlers that will happen at specific SoC of the battery and trigger a callback.
84 Theses handlers may be recurrent, for instance you may want to always set all loads to zero and deactivate all hosts
85 connections when the battery reaches 20% SoC.
89 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(Battery, kernel, "Logging specific to the battery plugin");
91 namespace simgrid::plugins {
95 BatteryModel::BatteryModel() : Model("BatteryModel") {}
97 void BatteryModel::add_battery(BatteryPtr b)
99 batteries_.push_back(b);
102 void BatteryModel::update_actions_state(double now, double delta)
104 for (auto battery : batteries_)
108 double BatteryModel::next_occurring_event(double now)
110 double time_delta = -1;
111 for (auto battery : batteries_) {
112 double time_delta_battery = battery->next_occurring_handler();
113 time_delta = time_delta == -1 or time_delta_battery < time_delta ? time_delta_battery : time_delta;
120 Battery::Handler::Handler(double state_of_charge, Flow flow, Persistancy p, std::function<void()> callback)
121 : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), persistancy_(p)
125 std::shared_ptr<Battery::Handler> Battery::Handler::init(double state_of_charge, Flow flow, Persistancy p,
126 std::function<void()> callback)
128 return std::make_shared<Battery::Handler>(state_of_charge, flow, p, callback);
133 std::shared_ptr<BatteryModel> Battery::battery_model_;
135 void Battery::init_plugin()
137 auto model = std::make_shared<BatteryModel>();
138 simgrid::s4u::Engine::get_instance()->add_model(model);
139 Battery::battery_model_ = model;
142 void Battery::update()
144 kernel::actor::simcall_answered([this] {
145 double now = s4u::Engine::get_clock();
146 double time_delta_s = now - last_updated_;
149 if (time_delta_s <= 0)
152 // Calculate energy provided / consumed during this step
153 double provided_power_w = 0;
154 double consumed_power_w = 0;
155 for (auto const& [host, active] : host_loads_)
156 provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
157 for (auto const& [name, load] : named_loads_) {
159 provided_power_w += load;
161 consumed_power_w += -load;
163 provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
164 consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
166 double energy_lost_delta_j = provided_power_w / discharge_efficiency_ * time_delta_s;
167 double energy_gained_delta_j = consumed_power_w * charge_efficiency_ * time_delta_s;
169 // Check that the provided/consumed energy is valid
170 energy_lost_delta_j = std::min(energy_lost_delta_j, energy_stored_j_ + energy_gained_delta_j);
171 /* Charging deteriorate the capacity, but the capacity is used to evaluate the maximum charge so
172 we need to evaluate the theorethical new capacity in the worst case when we fully charge the battery */
173 double new_tmp_capacity_wh =
174 (initial_capacity_wh_ *
175 (1 - (energy_provided_j_ + energy_lost_delta_j * discharge_efficiency_ + energy_consumed_j_ -
176 (energy_stored_j_ + energy_lost_delta_j) / charge_efficiency_) /
178 (1 + 3600 * initial_capacity_wh_ / (charge_efficiency_ * energy_budget_j_));
179 energy_gained_delta_j =
180 std::min(energy_gained_delta_j, (3600 * new_tmp_capacity_wh) - energy_stored_j_ + energy_lost_delta_j);
183 energy_provided_j_ += energy_lost_delta_j * discharge_efficiency_;
184 energy_consumed_j_ += energy_gained_delta_j / charge_efficiency_;
186 initial_capacity_wh_ *
187 (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
188 energy_stored_j_ += energy_gained_delta_j - energy_lost_delta_j;
189 energy_stored_j_ = std::min(energy_stored_j_, 3600 * capacity_wh_);
192 std::vector<std::shared_ptr<Handler>> to_delete = {};
193 for (auto handler : handlers_) {
194 if (abs(handler->time_delta_ - time_delta_s) < 0.000000001) {
195 handler->callback_();
196 if (handler->persistancy_ == Handler::Persistancy::PERSISTANT)
197 handler->time_delta_ = -1;
199 to_delete.push_back(handler);
202 for (auto handler : to_delete)
203 delete_handler(handler);
207 double Battery::next_occurring_handler()
209 double provided_power_w = 0;
210 double consumed_power_w = 0;
211 for (auto const& [host, active] : host_loads_)
212 provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
213 for (auto const& [name, load] : named_loads_) {
215 provided_power_w += load;
217 consumed_power_w += -load;
220 provided_power_w = std::min(provided_power_w, nominal_discharge_power_w_ * discharge_efficiency_);
221 consumed_power_w = std::min(consumed_power_w, -nominal_charge_power_w_);
223 double time_delta = -1;
224 for (auto& handler : handlers_) {
225 double lost_power_w = provided_power_w / discharge_efficiency_;
226 double gained_power_w = consumed_power_w * charge_efficiency_;
227 // Handler cannot happen
228 if ((lost_power_w == gained_power_w) or (handler->state_of_charge_ == energy_stored_j_ / (3600 * capacity_wh_)) or
229 (lost_power_w > gained_power_w and handler->flow_ == Flow::CHARGE) or
230 (lost_power_w < gained_power_w and handler->flow_ == Flow::DISCHARGE)) {
233 // Evaluate time until handler happen
235 /* The time to reach a state of charge depends on the capacity, but charging and discharging deteriorate the
236 * capacity, so we need to evaluate the time considering a capacity that also depends on time
238 handler->time_delta_ =
239 (3600 * handler->state_of_charge_ * initial_capacity_wh_ *
240 (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) /
243 (gained_power_w - lost_power_w +
244 3600 * handler->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) /
246 if ((time_delta == -1 or handler->time_delta_ < time_delta) and abs(handler->time_delta_) > 0.000000001)
247 time_delta = handler->time_delta_;
253 Battery::Battery(const std::string& name, double state_of_charge, double nominal_charge_power_w,
254 double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
255 double initial_capacity_wh, int cycles)
257 , nominal_charge_power_w_(nominal_charge_power_w)
258 , nominal_discharge_power_w_(nominal_discharge_power_w)
259 , charge_efficiency_(charge_efficiency)
260 , discharge_efficiency_(discharge_efficiency)
261 , initial_capacity_wh_(initial_capacity_wh)
262 , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
263 , capacity_wh_(initial_capacity_wh)
264 , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
266 xbt_assert(nominal_charge_power_w <= 0, " : nominal charge power must be non-negative (provided: %f)",
267 nominal_charge_power_w);
268 xbt_assert(nominal_discharge_power_w >= 0, " : nominal discharge power must be non-negative (provided: %f)",
269 nominal_discharge_power_w);
270 xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
272 xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
274 xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
275 " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
276 xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
277 xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
280 /** @ingroup plugin_battery
281 * @param name The name of the Battery.
282 * @param state_of_charge The initial state of charge of the Battery [0,1].
283 * @param nominal_charge_power_w The maximum power delivered by the Battery in W (<= 0).
284 * @param nominal_discharge_power_w The maximum power absorbed by the Battery in W (>= 0).
285 * @param charge_efficiency The charge efficiency of the Battery [0,1].
286 * @param discharge_efficiency The discharge efficiency of the Battery [0,1].
287 * @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
288 * @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
289 * @return A BatteryPtr pointing to the new Battery.
291 BatteryPtr Battery::init(const std::string& name, double state_of_charge, double nominal_charge_power_w,
292 double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
293 double initial_capacity_wh, int cycles)
295 static bool plugin_inited = false;
296 if (not plugin_inited) {
298 plugin_inited = true;
300 auto battery = BatteryPtr(new Battery(name, state_of_charge, nominal_charge_power_w, nominal_discharge_power_w,
301 charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
302 battery_model_->add_battery(battery);
306 /** @ingroup plugin_battery
307 * @param name The name of the load
308 * @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
310 void Battery::set_load(const std::string& name, double power_w)
312 named_loads_[name] = power_w;
315 /** @ingroup plugin_battery
316 * @param host The Host to connect.
317 * @param active Status of the connected Host (default true).
318 * @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
319 from the Battery. To modify this status connect again the same Host with a different status.
320 @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
321 In this case all Batteries would have the full consumption from this Host.
323 void Battery::connect_host(s4u::Host* host, bool active)
325 host_loads_[host] = active;
328 /** @ingroup plugin_battery
329 * @return The state of charge of the battery.
331 double Battery::get_state_of_charge()
333 return energy_stored_j_ / (3600 * capacity_wh_);
336 /** @ingroup plugin_battery
337 * @return The state of health of the Battery.
339 double Battery::get_state_of_health()
342 ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
345 /** @ingroup plugin_battery
346 * @return The current capacity of the Battery.
348 double Battery::get_capacity()
353 /** @ingroup plugin_battery
354 * @return The energy provided by the Battery.
355 * @note It is the energy provided from an external point of view, after application of the discharge efficiency.
356 It means that the Battery lost more energy than it has provided.
358 double Battery::get_energy_provided()
360 return energy_provided_j_;
363 /** @ingroup plugin_battery
364 * @return The energy consumed by the Battery.
365 * @note It is the energy consumed from an external point of view, before application of the charge efficiency.
366 It means that the Battery consumed more energy than is has absorbed.
368 double Battery::get_energy_consumed()
370 return energy_consumed_j_;
373 /** @ingroup plugin_battery
374 * @param unit Valid units are J (default) and Wh.
375 * @return Energy stored in the Battery.
377 double Battery::get_energy_stored(std::string unit)
380 return energy_stored_j_;
381 else if (unit == "Wh")
382 return energy_stored_j_ / 3600;
384 xbt_die("Invalid unit. Valid units are J (default) or Wh.");
387 /** @ingroup plugin_battery
388 * @brief Schedule a new Handler.
389 * @param state_of_charge The state of charge at which the Handler will happen.
390 * @param flow The flow in which the Handler will happen, either when the Battery is charging or discharging.
391 * @param callback The callable to trigger when the Handler happen.
392 * @param Persistancy If the Handler is recurrent or unique.
393 * @return A shared pointer of the new Handler.
395 std::shared_ptr<Battery::Handler> Battery::schedule_handler(double state_of_charge, Flow flow, Handler::Persistancy p,
396 std::function<void()> callback)
398 auto handler = Handler::init(state_of_charge, flow, p, callback);
399 handlers_.push_back(handler);
403 /** @ingroup plugin_battery
404 * @return A vector containing the Handlers associated to the Battery.
406 std::vector<std::shared_ptr<Battery::Handler>> Battery::get_handlers()
411 /** @ingroup plugin_battery
412 * @brief Remove an Handler from the Battery.
414 void Battery::delete_handler(std::shared_ptr<Handler> handler)
416 handlers_.erase(std::remove_if(handlers_.begin(), handlers_.end(),
417 [&handler](std::shared_ptr<Handler> e) { return handler == e; }),
420 } // namespace simgrid::plugins