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 create events that will happen at specific SoC of the battery and trigger a callback.
84 Theses events 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_event();
113 time_delta = time_delta == -1 or time_delta_battery < time_delta ? time_delta_battery : time_delta;
120 Battery::Event::Event(double state_of_charge, Flow flow, std::function<void()> callback, bool repeat)
121 : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), repeat_(repeat)
125 std::shared_ptr<Battery::Event> Battery::Event::init(double state_of_charge, Flow flow, std::function<void()> callback,
128 return std::make_shared<Battery::Event>(state_of_charge, flow, callback, repeat);
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<Event>> to_delete = {};
193 for (auto event : events_) {
194 if (abs(event->time_delta_ - time_delta_s) < 0.000000001) {
197 event->time_delta_ = -1;
199 to_delete.push_back(event);
202 for (auto event : to_delete)
207 double Battery::next_occurring_event()
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& event : events_) {
225 double lost_power_w = provided_power_w / discharge_efficiency_;
226 double gained_power_w = consumed_power_w * charge_efficiency_;
227 // Event cannot happen
228 if ((lost_power_w == gained_power_w) or (event->state_of_charge_ == energy_stored_j_ / (3600 * capacity_wh_)) or
229 (lost_power_w > gained_power_w and event->flow_ == Flow::CHARGE) or
230 (lost_power_w < gained_power_w and event->flow_ == Flow::DISCHARGE)) {
233 // Evaluate time until event 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
239 (3600 * event->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 * event->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) / energy_budget_j_);
245 if ((time_delta == -1 or event->time_delta_ < time_delta) and abs(event->time_delta_) > 0.000000001)
246 time_delta = event->time_delta_;
252 Battery::Battery(const std::string& name, double state_of_charge, double nominal_charge_power_w,
253 double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
254 double initial_capacity_wh, int cycles)
256 , nominal_charge_power_w_(nominal_charge_power_w)
257 , nominal_discharge_power_w_(nominal_discharge_power_w)
258 , charge_efficiency_(charge_efficiency)
259 , discharge_efficiency_(discharge_efficiency)
260 , initial_capacity_wh_(initial_capacity_wh)
261 , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
262 , capacity_wh_(initial_capacity_wh)
263 , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
265 xbt_assert(nominal_charge_power_w <= 0, " : nominal charge power must be non-negative (provided: %f)",
266 nominal_charge_power_w);
267 xbt_assert(nominal_discharge_power_w >= 0, " : nominal discharge power must be non-negative (provided: %f)",
268 nominal_discharge_power_w);
269 xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
271 xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
273 xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
274 " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
275 xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
276 xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
279 /** @ingroup plugin_battery
280 * @param name The name of the Battery.
281 * @param state_of_charge The initial state of charge of the Battery [0,1].
282 * @param nominal_charge_power_w The maximum power delivered by the Battery in W (<= 0).
283 * @param nominal_discharge_power_w The maximum power absorbed by the Battery in W (>= 0).
284 * @param charge_efficiency The charge efficiency of the Battery [0,1].
285 * @param discharge_efficiency The discharge efficiency of the Battery [0,1].
286 * @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
287 * @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
288 * @return A BatteryPtr pointing to the new Battery.
290 BatteryPtr Battery::init(const std::string& name, double state_of_charge, double nominal_charge_power_w,
291 double nominal_discharge_power_w, double charge_efficiency, double discharge_efficiency,
292 double initial_capacity_wh, int cycles)
294 static bool plugin_inited = false;
295 if (not plugin_inited) {
297 plugin_inited = true;
299 auto battery = BatteryPtr(new Battery(name, state_of_charge, nominal_charge_power_w, nominal_discharge_power_w,
300 charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
301 battery_model_->add_battery(battery);
305 /** @ingroup plugin_battery
306 * @param name The name of the load
307 * @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
309 void Battery::set_load(const std::string& name, double power_w)
311 named_loads_[name] = power_w;
314 /** @ingroup plugin_battery
315 * @param host The Host to connect.
316 * @param active Status of the connected Host (default true).
317 * @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
318 from the Battery. To modify this status connect again the same Host with a different status.
319 @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
320 In this case all Batteries would have the full consumption from this Host.
322 void Battery::connect_host(s4u::Host* host, bool active)
324 host_loads_[host] = active;
327 /** @ingroup plugin_battery
328 * @return The state of charge of the battery.
330 double Battery::get_state_of_charge()
332 return energy_stored_j_ / (3600 * capacity_wh_);
335 /** @ingroup plugin_battery
336 * @return The state of health of the Battery.
338 double Battery::get_state_of_health()
341 ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
344 /** @ingroup plugin_battery
345 * @return The current capacity of the Battery.
347 double Battery::get_capacity()
352 /** @ingroup plugin_battery
353 * @return The energy provided by the Battery.
354 * @note It is the energy provided from an external point of view, after application of the discharge efficiency.
355 It means that the Battery lost more energy than it has provided.
357 double Battery::get_energy_provided()
359 return energy_provided_j_;
362 /** @ingroup plugin_battery
363 * @return The energy consumed by the Battery.
364 * @note It is the energy consumed from an external point of view, before application of the charge efficiency.
365 It means that the Battery consumed more energy than is has absorbed.
367 double Battery::get_energy_consumed()
369 return energy_consumed_j_;
372 /** @ingroup plugin_battery
373 * @param unit Valid units are J (default) and Wh.
374 * @return Energy stored in the Battery.
376 double Battery::get_energy_stored(std::string unit)
379 return energy_stored_j_;
380 else if (unit == "Wh")
381 return energy_stored_j_ / 3600;
383 xbt_die("Invalid unit. Valid units are J (default) or Wh.");
386 /** @ingroup plugin_battery
387 * @brief Create a new Event.
388 * @param state_of_charge The state of charge at which the Event will happen.
389 * @param flow The flow in which the Event will happen, either when the Battery is charging or discharging.
390 * @param callback The callable to trigger when the Event happen.
391 * @param repeat If the Event is a recurrent Event or a single Event.
392 * @return A shared pointer of the new Event.
394 std::shared_ptr<Battery::Event> Battery::create_event(double state_of_charge, Flow flow, std::function<void()> callback,
397 auto event = Event::init(state_of_charge, flow, callback, repeat);
398 events_.push_back(event);
402 /** @ingroup plugin_battery
403 * @return A vector containing the Events associated to the Battery.
405 std::vector<std::shared_ptr<Battery::Event>> Battery::get_events()
410 /** @ingroup plugin_battery
411 * @brief Remove an Event from the Battery.
413 void Battery::delete_event(std::shared_ptr<Event> event)
416 std::remove_if(events_.begin(), events_.end(), [&event](std::shared_ptr<Event> e) { return event == e; }),
419 } // namespace simgrid::plugins