#python #iteration #pyomo #battery #chunks
#питон #итерация #pyomo #батарея #фрагменты
Вопрос:
У меня есть ежегодные данные о ценах на электроэнергию под названием «HOEP». С помощью моей модели pyomo я хочу определить поведение батареи в течение всего года, но с временным горизонтом 365 часов (потребление энергии = Ein и расход энергии = Eout). Другими словами, я хочу, чтобы мой алгоритм выполнялся в течение первых 365 часов, затем снова запускался в течение следующих 365 часов с начальным состоянием батареи, равным последнему часу предыдущего периода временного горизонта.
Я попытался разделить свои годовые данные на блоки (24 блока по 365 часов в году). С помощью df_list = np.vsplit(dfa, 24) я создаю список блоков и преобразую их в 24 разных фрейма данных. Затем я использую для idx, df в enumerate([df0, df1, df2]), (здесь только 3 фрагмента для тестирования), прежде чем моя модель будет перебирать данные. Однако, когда я смотрю на свои результаты, кажется, что модель оптимизируется только для последнего аргумента enumerate([df0, df1, df2]), который равен df2.
Кто-нибудь знает, почему это не работает для 3 блоков? Или как я мог бы сделать это по-другому?
Заранее благодарим вас за помощь!
Вот отредактированная версия моего кода, которая работает сейчас, но я знаю, что это, возможно, не самый питонический способ сделать это.
import numpy as np
import pandas as pd
from typing import List
from itertools import chain
from pyomo.environ import *
output = []
for idx, df in enumerate([df0,df1,df2]):
model = ConcreteModel()
# Variables of the model
model.T = Set(initialize=df.hour.tolist(), ordered=True)
model.Rmax = Param(initialize=1, within=Any)
model.Smax = Param(initialize=5, within=Any)
model.Dmax = Param(initialize=5, within=Any)
model.Ein = Var(model.T, domain=NonNegativeReals)
model.Eout = Var(model.T, domain=NonNegativeReals)
model.Z = Var(model.T, domain=NonNegativeReals)
model.L = Var(model.T, domain=NonNegativeReals)
model.NES = Var(model.T)
# Constraints
def storage_state(model, t):
if t == model.T.first():
return model.Z[t] == 0
else:
return (model.Z[t] == (model.Z[t-1] (model.Ein[t]) - (model.Eout[t])))
model.charge_state = Constraint(model.T, rule=storage_state)
def discharge_constraint(model, t):
return model.Eout[t] <= model.Rmax
model.discharge = Constraint(model.T, rule=discharge_constraint)
def charge_constraint(model, t):
return model.Ein[t] <= model.Rmax
model.charge = Constraint(model.T, rule=charge_constraint)
def positive_charge(model, t):
return model.Eout[t] <= model.Z[t]
model.positive_charge = Constraint(model.T, rule=positive_charge)
def max_SOC(model, t):
return model.Z[t] <= model.Smax
model.max_SOC = Constraint(model.T, rule=max_SOC)
def demand_constraint(model, t):
return (model.L[t] == (df.loc[t, 'MktDemand'] (model.Ein[t]) - (model.Eout[t])))
model.demand_constraint = Constraint(model.T, rule=demand_constraint)
def discharge_limit(model, t):
max_t = model.T.last()
if t < max_t - 24:
return sum(model.Eout[i] for i in range(t, t 24)) <= model.Dmax
else:
return Constraint.Skip
model.limit_disch_out = Constraint(model.T, rule=discharge_limit)
def charge_limit(model, t):
max_t = model.T.last()
if t < max_t - 24:
return sum(model.Ein[i] for i in range(t, t 24)) <= model.Dmax
else:
return Constraint.Skip
model.limit_charg_out = Constraint(model.T, rule=charge_limit)
def Net_energy_sold(model, t):
return model.NES[t] == ((model.Eout[t] - model.Ein[t]) / model.Rmax * 100)
model.net_energy = Constraint(model.T, rule=Net_energy_sold)
# Objective function and optimization
income = sum(df.loc[t,'HOEP'] * model.Eout[t] for t in model.T)
expenses = sum(df.loc[t,'HOEP'] * model.Ein[t] for t in model.T)
profits = (income - expenses)
model.objective = Objective(expr=profits, sense=maximize)
# Solve model
solver = SolverFactory('glpk')
solver.solve(model)
# Extract model output in list
Date = list(df['Date'])
output.append([Date, model.Ein.get_values().values(), model.Eout.get_values().values(),
model.Z.get_values().values(), model.NES.get_values().values(),
model.L.get_values().values()])
df_results = pd.DataFrame(output)
df_results.rename(columns = {0: 'Date', 1: 'Ein', 2:'Eout', 3:'Z', 4:'NES', 5:'Load'}, inplace = True)
df_results
# Present final results in dataframe
d = ein = eout = z = l = nes = []
for i in list(df_results.index):
d = d list(df_results.loc[i,'Date'])
ein = ein list(df_results.loc[i,'Ein'])
eout = eout list(df_results.loc[i,'Eout'])
z = z list(df_results.loc[i,'Z'])
nes = nes list(df_results.loc[i,'NES'])
l = l list(df_results.loc[i,'Load'])
results = pd.DataFrame(zip(d, ein, eout, z, nes, l), columns = ['Date','Ein','Eout','SOC','NES','Load'])
results
# Returned dataframe
Date Ein Eout SOC NES Load
0 2019-01-01 0.0 0.00 0.00 0.0 16231.00
1 2019-01-01 0.0 0.00 0.00 0.0 16051.00
2 2019-01-01 1.0 0.00 1.00 -100.0 15806.00
3 2019-01-01 1.0 0.00 2.00 -100.0 15581.00
...
Комментарии:
1. где
dfb
определено? Он используется при созданииlist_of_series
, но, похоже, нигде не инициализируется. Это опечатка?2. есть ли конкретная причина, по которой вы хотите оптимизировать на основе 365 часов? например, подойдет ли вам оптимизация на основе недель или месяцев? просто спрашиваю, потому что очень легко группировать по неделям или месяцам, а високосные годы не кратны 365 часам.
3. Я выбрал 365 часов только потому, что было бы легко иметь блоки одинакового размера. Но кусок из 14 дней тоже может сработать!
4. Я отредактировал код, так что теперь dfb определен выше.
Ответ №1:
Почему это не работает
(отказ от ответственности: это одна проблема, которую я вижу, могут быть и другие).
На каждой итерации цикла for list_of_series
определяется с нуля, поэтому все результаты, полученные на предыдущих итерациях, теряются.
Я бы также проверил, что df.hour
это «час года» или «час с начала данных», а не «час дня» (если это последнее, это также приведет к ошибке).
Устранение проблемы
(очевидно, есть несколько решений) на каждой итерации цикла for превратите list_of_series
в pd.DataFrame
, и добавьте фрейм данных в список. В конце цикла for (после запуска модели для каждого фрагмента данных) объедините список фреймов данных.
from typing import List
...
# find a better name, variable names shouldn't specify the type
list_of_dataframes: List[pd.DataFrame] = []
for ...: # for each chunk of data
... # create model, solve
list_of_series = ...
list_of_dataframes.append(pd.DataFrame(list_of_series))
results = pd.concat(list_of_dataframes, axis=0) # use `ignore_index=True` if needed
Несколько советов
- Разбейте свой код на функции. Создайте функцию, которая определяет модель. Это подчеркивает, какие входные и выходные данные, делает цикл for более читаемым, позволяет использовать его в других контекстах и, возможно, тестировать его.
- (самоуверенный) установите свои «данные» в качестве параметров модели, вместо того, чтобы использовать их непосредственно для построения ограничений и целевой функции. Это позволяет вам иметь единственное место, где каждая часть данных попадает в модель, создает внутреннюю согласованность в модели и позволяет извлекать результаты исключительно на основе оптимизированной модели.
- отделите ввод-вывод (чтение / запись в файл) от остальной части кода. Если ваш источник данных изменит формат или тип файла, вы сможете изменить это, не изменяя остальную часть кода.
def main(input_data: pd.DataFrame) -> pd.DataFrame:
# group by week, month, or any applicable resolution
# this assumes the index is a `pd.DatetimeIndex`
# `MS` is "Month Start" - watch out with weeks because `freq="w"` starts
# on Mondays, and your data might start on a different weekday.
# If you want split into chunks of some number of days,
# use e.g. `freq="14d"`
grouped = df.groupby(pd.Grouper(freq="MS"))
results_list: List[pd.DataFrame] = []
for month, group in grouped:
model = create_model(group)
optimization_results = SolverFactory('glpk').solve(model)
results_list.append(extract_results(model)) # pass `group` if needed
results = pd.concat(results_list, axis=0, ignore_index=True)
return results
def create_model(df: pd.DataFrame) -> ConcreteModel:
# NOTE: instead of hard-coding parameters such as battery capacity,
# pass them as inputs to the function.
model = ConcreteModel()
...
return model
def extract_results(model: ConcreteModel) -> pd.DataFrame:
...
def load_data(filename) -> pd.DataFrame:
...
if __name__ == "__main__":
input_data = load_data(...)
results = main(input_data)
results.to_csv(...)
Комментарии:
1. Привет, Джорджио, спасибо за ваш комментарий. Итак, в вашей части кода «Устранение проблемы» for …: (цикл for) должен быть в конце моего начального цикла for для фреймов данных, и должен ли он снова быть циклом for для фрейма данных, например, так? « для idx, df в enumerate([df0, df1, df2]):
2. Это ваш цикл for
for idx, df in enumerate([df0, df1, df2]): ...
. Итак, в нижней части (каждой итерации) вашего существующего цикла for создайте фрейм данных и добавьте его в список фреймов данных. После завершения всего цикла for объедините список фреймов данных в один фрейм данных.3. Не совсем. Возможно, это потому, что я недостаточно четко изложил код, но результат определяется как результат целевой модели « lresults = SolverFactory(‘glpk’). решите (модель) « таким образом, это объект pyomo, и его нельзя рассматривать как список, который я добавляю. Мне удалось создать список с результатами оптимизации, но при их объединении я получал вывод dataframe с 3 строками (по одной для каждого элемента цикла for) и несколькими значениями в каждой ячейке этого фрейма данных.
4. Я обновил код в вопросе тем, что работает до сих пор
5. Я не понял
results
, что уже используется. Я исправил опечатку: вы должны добавлять кlist_of_dataframes
(опять же, это неправильное имя переменной). Затем я вызываюresults
«финал»results
. Помимо деталей реализации, ясна ли идея?