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 charge efficiency :math:`\eta_{charge}`, a discharge efficiency
28 :math:`\eta_{discharge}`, an initial capacity :math:`C_{initial}` and a number of cycle :math:`N`.
30 We distinguish the energy provided :math:`E_{provided}` / consumed :math:`E_{consumed}` from the energy lost
31 :math:`E_{lost}` / gained :math:`E_{gained}`. The energy provided / consumed shows the external point of view, and the
32 energy lost / gained shows the internal point of view:
36 E_{lost} = {E_{provided} \over \eta_{discharge}}
38 E_{gained} = E_{consumed} \times \eta_{charge}
40 For instance, if you apply a load of 100W to a battery for 10s with a discharge efficiency of 0.8, the energy provided
41 will be equal to 10kJ, and the energy lost will be equal to 12.5kJ.
43 Use the battery reduces its State of Health :math:`SoH` and its capacity :math:`C` linearly in consequence:
47 SoH = 1 - {E_{lost} + E_{gained} \over E_{budget}}
49 C = C_{initial} \times SoH
55 E_{budget} = C_{initial} \times N \times 2
57 Plotting the output of the example "battery-degradation" highlights the linear decrease of the :math:`SoH` due to a
58 continuous use of the battery alternating between charge and discharge:
60 .. image:: /img/battery_degradation.svg
63 The natural depletion of batteries over time is not taken into account.
68 You can add named loads to a battery. Those loads may be positive and consume energy from the battery, or negative and
69 add energy to the battery. You can also connect hosts to a battery. Theses hosts will consume their energy from the
70 battery until the battery is empty or until the connection between the hosts and the battery is set inactive.
75 You can create events that will happen at specific SoC of the battery and trigger a callback.
76 Theses events may be recurrent, for instance you may want to always set all loads to zero and deactivate all hosts
77 connections when the battery reaches 20% SoC.
81 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(Battery, kernel, "Logging specific to the battery plugin");
83 namespace simgrid::plugins {
87 BatteryModel::BatteryModel() : Model("BatteryModel") {}
89 void BatteryModel::add_battery(BatteryPtr b)
91 batteries_.push_back(b);
94 void BatteryModel::update_actions_state(double now, double delta)
96 for (auto battery : batteries_)
100 double BatteryModel::next_occurring_event(double now)
102 double time_delta = -1;
103 for (auto battery : batteries_) {
104 double time_delta_battery = battery->next_occurring_event();
105 time_delta = time_delta == -1 or time_delta_battery < time_delta ? time_delta_battery : time_delta;
112 Battery::Event::Event(double state_of_charge, Flow flow, std::function<void()> callback, bool repeat)
113 : state_of_charge_(state_of_charge), flow_(flow), callback_(callback), repeat_(repeat)
117 std::shared_ptr<Battery::Event> Battery::Event::init(double state_of_charge, Flow flow, std::function<void()> callback,
120 return std::make_shared<Battery::Event>(state_of_charge, flow, callback, repeat);
125 std::shared_ptr<BatteryModel> Battery::battery_model_;
127 void Battery::init_plugin()
129 auto model = std::make_shared<BatteryModel>();
130 simgrid::s4u::Engine::get_instance()->add_model(model);
131 Battery::battery_model_ = model;
134 void Battery::update()
136 kernel::actor::simcall_answered([this] {
137 double now = s4u::Engine::get_clock();
138 double time_delta_s = now - last_updated_;
141 if (time_delta_s <= 0)
144 // Calculate energy provided / consumed during this step
145 double provided_power_w = 0;
146 double consumed_power_w = 0;
147 for (auto const& [host, active] : host_loads_)
148 provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
149 for (auto const& [name, load] : named_loads_) {
151 provided_power_w += load;
153 consumed_power_w += -load;
155 double energy_lost_delta_j = provided_power_w / discharge_efficiency_ * time_delta_s;
156 double energy_gained_delta_j = consumed_power_w * charge_efficiency_ * time_delta_s;
158 // Check that the provided/consumed energy is valid
159 energy_lost_delta_j = std::min(energy_lost_delta_j, energy_stored_j_ + energy_gained_delta_j);
160 /* Charging deteriorate the capacity, but the capacity is used to evaluate the maximum charge so
161 we need to evaluate the theorethical new capacity in the worst case when we fully charge the battery */
162 double new_tmp_capacity_wh =
163 (initial_capacity_wh_ *
164 (1 - (energy_provided_j_ + energy_lost_delta_j * discharge_efficiency_ + energy_consumed_j_ -
165 (energy_stored_j_ + energy_lost_delta_j) / charge_efficiency_) /
167 (1 + 3600 * initial_capacity_wh_ / (charge_efficiency_ * energy_budget_j_));
168 energy_gained_delta_j =
169 std::min(energy_gained_delta_j, (3600 * new_tmp_capacity_wh) - energy_stored_j_ + energy_lost_delta_j);
172 energy_provided_j_ += energy_lost_delta_j * discharge_efficiency_;
173 energy_consumed_j_ += energy_gained_delta_j / charge_efficiency_;
175 initial_capacity_wh_ *
176 (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
177 energy_stored_j_ += energy_gained_delta_j - energy_lost_delta_j;
178 energy_stored_j_ = std::min(energy_stored_j_, 3600 * capacity_wh_);
181 std::vector<std::shared_ptr<Event>> to_delete = {};
182 for (auto event : events_) {
183 if (abs(event->time_delta_ - time_delta_s) < 0.000000001) {
186 event->time_delta_ = -1;
188 to_delete.push_back(event);
191 for (auto event : to_delete)
196 double Battery::next_occurring_event()
198 double provided_power_w = 0;
199 double consumed_power_w = 0;
200 for (auto const& [host, active] : host_loads_)
201 provided_power_w += active ? sg_host_get_current_consumption(host) : 0;
202 for (auto const& [name, load] : named_loads_) {
204 provided_power_w += load;
206 consumed_power_w += -load;
209 double time_delta = -1;
210 for (auto& event : events_) {
211 double lost_power_w = provided_power_w / discharge_efficiency_;
212 double gained_power_w = consumed_power_w * charge_efficiency_;
213 // Event cannot happen
214 if ((lost_power_w == gained_power_w) or (event->state_of_charge_ == energy_stored_j_ / (3600 * capacity_wh_)) or
215 (lost_power_w > gained_power_w and event->flow_ == Flow::CHARGE) or
216 (lost_power_w < gained_power_w and event->flow_ == Flow::DISCHARGE)) {
219 // Evaluate time until event happen
221 /* The time to reach a state of charge depends on the capacity, but charging and discharging deteriorate the
222 * capacity, so we need to evaluate the time considering a capacity that also depends on time
225 (3600 * event->state_of_charge_ * initial_capacity_wh_ *
226 (1 - (energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) /
229 (gained_power_w - lost_power_w +
230 3600 * event->state_of_charge_ * initial_capacity_wh_ * (gained_power_w + lost_power_w) / energy_budget_j_);
231 if ((time_delta == -1 or event->time_delta_ < time_delta) and abs(event->time_delta_) > 0.000000001)
232 time_delta = event->time_delta_;
238 Battery::Battery(const std::string& name, double state_of_charge, double charge_efficiency, double discharge_efficiency,
239 double initial_capacity_wh, int cycles)
241 , charge_efficiency_(charge_efficiency)
242 , discharge_efficiency_(discharge_efficiency)
243 , initial_capacity_wh_(initial_capacity_wh)
244 , energy_budget_j_(initial_capacity_wh * 3600 * cycles * 2)
245 , capacity_wh_(initial_capacity_wh)
246 , energy_stored_j_(state_of_charge * 3600 * initial_capacity_wh)
248 xbt_assert(state_of_charge >= 0 and state_of_charge <= 1, " : state of charge should be in [0, 1] (provided: %f)",
250 xbt_assert(charge_efficiency > 0 and charge_efficiency <= 1, " : charge efficiency should be in [0,1] (provided: %f)",
252 xbt_assert(discharge_efficiency > 0 and discharge_efficiency <= 1,
253 " : discharge efficiency should be in [0,1] (provided: %f)", discharge_efficiency);
254 xbt_assert(initial_capacity_wh > 0, " : initial capacity should be > 0 (provided: %f)", initial_capacity_wh);
255 xbt_assert(cycles > 0, " : cycles should be > 0 (provided: %d)", cycles);
258 /** @ingroup plugin_battery
259 * @param name The name of the Battery.
260 * @param state_of_charge The initial state of charge of the Battery [0,1].
261 * @param charge_efficiency The charge efficiency of the Battery [0,1].
262 * @param discharge_efficiency The discharge efficiency of the Battery [0,1].
263 * @param initial_capacity_wh The initial capacity of the Battery in Wh (>0).
264 * @param cycles The number of charge-discharge cycles until complete depletion of the Battery capacity.
265 * @return A BatteryPtr pointing to the new Battery.
267 BatteryPtr Battery::init(const std::string& name, double state_of_charge, double charge_efficiency,
268 double discharge_efficiency, double initial_capacity_wh, int cycles)
270 static bool plugin_inited = false;
271 if (not plugin_inited) {
273 plugin_inited = true;
275 auto battery = BatteryPtr(
276 new Battery(name, state_of_charge, charge_efficiency, discharge_efficiency, initial_capacity_wh, cycles));
277 battery_model_->add_battery(battery);
281 /** @ingroup plugin_battery
282 * @param name The name of the load
283 * @param power_w Power of the load in W. A positive value discharges the Battery while a negative value charges it.
285 void Battery::set_load(const std::string& name, double power_w)
287 named_loads_[name] = power_w;
290 /** @ingroup plugin_battery
291 * @param host The Host to connect.
292 * @param active Status of the connected Host (default true).
293 * @brief Connect a Host to the Battery with the status active. As long as the status is true the Host takes its energy
294 from the Battery. To modify this status connect again the same Host with a different status.
295 @warning Do NOT connect the same Host to multiple Batteries with the status true at the same time.
296 In this case all Batteries would have the full consumption from this Host.
298 void Battery::connect_host(s4u::Host* host, bool active)
300 host_loads_[host] = active;
303 /** @ingroup plugin_battery
304 * @return The state of charge of the battery.
306 double Battery::get_state_of_charge()
308 return energy_stored_j_ / (3600 * capacity_wh_);
311 /** @ingroup plugin_battery
312 * @return The state of health of the Battery.
314 double Battery::get_state_of_health()
317 ((energy_provided_j_ / discharge_efficiency_ + energy_consumed_j_ * charge_efficiency_) / energy_budget_j_);
320 /** @ingroup plugin_battery
321 * @return The current capacity of the Battery.
323 double Battery::get_capacity()
328 /** @ingroup plugin_battery
329 * @return The energy provided by the Battery.
330 * @note It is the energy provided from an external point of view, after application of the discharge efficiency.
331 It means that the Battery lost more energy than it has provided.
333 double Battery::get_energy_provided()
335 return energy_provided_j_;
338 /** @ingroup plugin_battery
339 * @return The energy consumed by the Battery.
340 * @note It is the energy consumed from an external point of view, before application of the charge efficiency.
341 It means that the Battery consumed more energy than is has absorbed.
343 double Battery::get_energy_consumed()
345 return energy_consumed_j_;
348 /** @ingroup plugin_battery
349 * @param unit Valid units are J (default) and Wh.
350 * @return Energy stored in the Battery.
352 double Battery::get_energy_stored(std::string unit)
355 return energy_stored_j_;
356 else if (unit == "Wh")
357 return energy_stored_j_ / 3600;
359 xbt_die("Invalid unit. Valid units are J (default) or Wh.");
362 /** @ingroup plugin_battery
363 * @brief Create a new Event.
364 * @param state_of_charge The state of charge at which the Event will happen.
365 * @param flow The flow in which the Event will happen, either when the Battery is charging or discharging.
366 * @param callback The callable to trigger when the Event happen.
367 * @param repeat If the Event is a recurrent Event or a single Event.
368 * @return A shared pointer of the new Event.
370 std::shared_ptr<Battery::Event> Battery::create_event(double state_of_charge, Flow flow, std::function<void()> callback,
373 auto event = Event::init(state_of_charge, flow, callback, repeat);
374 events_.push_back(event);
378 /** @ingroup plugin_battery
379 * @return A vector containing the Events associated to the Battery.
381 std::vector<std::shared_ptr<Battery::Event>> Battery::get_events()
386 /** @ingroup plugin_battery
387 * @brief Remove an Event from the Battery.
389 void Battery::delete_event(std::shared_ptr<Event> event)
392 std::remove_if(events_.begin(), events_.end(), [&event](std::shared_ptr<Event> e) { return event == e; }),
395 } // namespace simgrid::plugins