colnames(data) <- c("DATE", "Reservoir_level", "Reservoir_volume", "Rainfall", "Inflow", "Withdrawals", "Demand")
# Pre-processing
data <- data %>%
mutate(
DATE = dmy(DATE),
Reservoir_volume = as.numeric(gsub(",", ".", Reservoir_volume)),
Inflow = as.numeric(gsub(",", ".", Inflow)),
Month = month(DATE),
Year = year(DATE)
) %>%
filter(Year == 2022)
# Parameters
min_withdrawals_l_sec <- c(499, 569, 517, 486, 769, 974, 1374, 1325, 1015, 659, 532, 525)
data$min_daily_withdrawal <- min_withdrawals_l_sec[data$Month] * 86.4 # Convert l/s to m³/day
min_volume <- 7500000 # 7.5 million m³
max_volume <- 33000000 # 33 million m³
max_daily_withdrawal <- 190080 # maximum daily (m³)
initial_volume <- data$Reservoir_volume[1]
n_days <- nrow(data)
max_demand <- max(data$Demand, na.rm = TRUE)
# Fitness function
reservoir_fitness <- function(x, inflow, initial_volume, min_volume, max_volume,
min_withdrawal, demand, months, penalty_coef = 1e6) {
n <- length(x)
volume <- numeric(n)
actual_withdrawals <- numeric(n)
penalty <- 0
# Day 1: initial volume
volume[1] <- initial_volume
actual_withdrawals[1] <- x[1]
# Penalties for constraints violation
if (volume[1] < min_volume) penalty <- penalty + penalty_coef * (min_volume - volume[1])
if (volume[1] > max_volume) penalty <- penalty + penalty_coef * (volume[1] - max_volume)
if (actual_withdrawals[1] < min_withdrawal[1]) penalty <- penalty + penalty_coef * (min_withdrawal[1] - actual_withdrawals[1])
if (actual_withdrawals[1] > max_daily_withdrawal) penalty <- penalty + penalty_coef * (actual_withdrawals[1] - max_daily_withdrawal)
# Subsequent days
for (t in 2:n) {
provisional_volume <- volume[t - 1] + inflow[t] - x[t]
if (provisional_volume < min_volume) {
actual_withdrawals[t] <- 0
penalty <- penalty + penalty_coef * (min_volume - provisional_volume)
} else if (provisional_volume > max_volume) {
actual_withdrawals[t] <- x[t]
penalty <- penalty + penalty_coef * (provisional_volume - max_volume)
} else {
actual_withdrawals[t] <- x[t]
}
# Withdrawal penalties
if (actual_withdrawals[t] < min_withdrawal[t]) {
penalty <- penalty + penalty_coef * (min_withdrawal[t] - actual_withdrawals[t])
}
if (actual_withdrawals[t] > max_daily_withdrawal) {
penalty <- penalty + penalty_coef * (actual_withdrawals[t] - max_daily_withdrawal)
}
volume[t] <- volume[t - 1] + inflow[t] - actual_withdrawals[t]
}
# Seasonal weighting
seasonal_weight <- ifelse(months %in% 3:6, 1.5, 1)
# Objective function
normalized_error <- sum(((actual_withdrawals - demand) / max_demand)^2 * seasonal_weight)
smoothness <- 0.1 * sum(diff(actual_withdrawals)^2)
fitness <- normalized_error + penalty + smoothness
return(-fitness) # GA maximizes
}
# Run genetic algorithm
GA_result <- ga(
type = "real-valued",
fitness = reservoir_fitness,
inflow = data$Inflow,
initial_volume = initial_volume,
min_volume = min_volume,
max_volume = max_volume,
min_withdrawal = data$min_daily_withdrawal,
demand = data$Demand,
months = data$Month,
lower = data$min_daily_withdrawal,
upper = rep(max_daily_withdrawal, n_days),
popSize = 80,
maxiter = 300,
run = 100,
parallel = TRUE,
seed = 123
)
# Output
optimal_withdrawals <- GA_result@solution[1, ]
volume <- numeric(n_days)
volume[1] <- initial_volume
for (t in 2:n_days) {
volume[t] <- volume[t - 1] + data$Inflow[t] - optimal_withdrawals[t]
volume[t] <- max(min(volume[t], max_volume), min_volume)
}
# Results
results <- data.frame(
Date = data$DATE,
Optimal_withdrawals = optimal_withdrawals,
Volume = volume,
Demand = data$Demand,
Min_withdrawal = data$min_daily_withdrawal
)