Skip to contents

The functions get_demand() and get_occupancy() have time-series calculations that are repeated on a daily basis. A 24-hour period is considered an independent optimization window, thus the corresponding time-series calculations can be done in parallel.

At the same time, in computer science, a common practice to reduce computation times is parallel processing when the CPU of the computer has multiple cores that can work independently. We can discover the number of cores in our computer CPU using function parallel::detectCores():

n_cores <- parallel::detectCores()
print(n_cores)
## [1] 14

In this case, the CPU used is the Apple M4 Pro, which features 10 performance CPU cores (P-core) running at up to 4.5 GHz along with 4 efficient cores (E-core) running at up to 2.6 GHz. So, when parallel::detectCores() returns 14, macOS is exposing 14 logical cores — but not all of them are equal in speed.

In evsim, parallel processing is used inside get_demand() and get_occupancy() functions, using the mirai package and its mirai::daemons() function to set the number of cores that we want to work in parallel. Below you will find an example about how to use this functionality.

Time-series EV demand calculation

Package evsim provides a sample data set of California EV sessions from October 2018 to September 2021. If we filter sessions corresponding to 2019, we have >15.000 sessions, with an average of 70 charging sessions during working days.

sessions_2019 <- evsim::california_ev_sessions_profiles %>% 
  filter(year(ConnectionStartDateTime) == 2019)

Let’s use these real charging sessions to calculate their time-series demand with get_demand() function, using multiple cores (with mirai::daemons()) but also different values of number of days (optimization windows):

n_days_seq <- c(3, 7, 15, 30, 120, 365) # Days in a year
n_cores_seq <- c(1, seq(2, 10, 2)) # 10 performance cores (P-core)
cores_time <- tibble(
  days = rep(n_days_seq, each = length(n_cores_seq)),
  cores = rep(n_cores_seq, length(n_days_seq)), 
  time = 0
)

for (nd in n_days_seq) {
  message(nd, " days ---------------- ")
  sessions <- sessions_2019 %>%
    filter(date(ConnectionStartDateTime) <= dmy(01012019)+days(nd)) %>% 
    evsim::adapt_charging_features(time_resolution = 15)

  for (mcc in cores_time$cores[cores_time$days == nd]) {
    message(mcc, " cores")
    
    # Define daemons
    if (mcc > 1) {
      mirai::daemons(mcc)
    }
    # Execute calculations
    results <- system.time(
      sessions %>% 
        get_demand(by = "Profile", resolution = 15)
    )
    # Reset daemons
    mirai::daemons(0)
    
    cores_time$time[
      cores_time$cores == mcc & cores_time$days == nd
    ] <- as.numeric(results[3])
  }
}

# Adapt variables for a better plot
cores_time <- cores_time %>% 
    mutate(
      days = factor(
        paste(days, "days"), 
        levels = factor(paste(n_days_seq, "days"))
      ), 
      cores = factor(cores)
    )

The plot below shows that:

  1. Parallel processing is worth it from more than 1-month data sets (at least for this case with ~70 charging sessions per day)
  2. From 6 cores the reduction in computation time is not very relevant
cores_time %>%
  ggplot(aes(x = cores, y = time)) +
  geom_col() + 
  facet_wrap(vars(days), scales = "free", nrow = 2) + 
  labs(x = "Number of cores", y = "Computation time (seconds)")

Computation time for battery optimization using multiple cores