Как добавить ограничение минимального значения в Pyomo

#python #optimization #pyomo

#python #оптимизация #pyomo

Вопрос:

Предыстория — я пытаюсь оптимизировать работу системы хранения энергии аккумулятора (BESS). Я использовал этот проект Github в качестве основы.

Проблема — Большинство систем BESS имеют ограничение глубины разряда. Т.Е. Для батареи мощностью 400 МВтч вы можете использовать только около 320 МВтч. Последние 20% по существу зарезервированы / непригодны для использования. Я пытался добавить это в качестве дополнительного ограничения, но мне не повезло

Что я пробовал

Я попытался установить ограничения для своего параметра хранения батареи, я попытался добавить ограничение (как показано ниже), но оба привели к ошибке «Нет значения для неинициализированного объекта NumericValue Ein [0]» (Ein является параметром для энергии в)

Попытка 1

 model.S = Var(model.T, bounds=(model.Smin, model.Smax))
  

Попытка 2

 def dod_limit(model, t):
    "Depth of Discharge limit"
    return model.Smin[t] <= model.S[t]

model.dod = Constraint(model.T, rule=dod_limit)
  

Запрошена справка

Кто-нибудь знает, как я могу написать ограничение для предела глубины разряда?

Полный код для полноты

 # ---------------------
# Main Functions
# ---------------------
def model_to_df(model, first_period, last_period):
    """
    Create a dataframe with hourly charge, discharge, state of charge, and
    price columns from a pyomo model. Only uses data from between the first
    (inclusive) and last (exclusive) hours.

    Parameters
    ----------
    model : pyomo model
        Model that has been solved

    first_period : int
        First hour of data to keep
    last_period: int
        The final hour of data to keep

    Returns
    -------
    dataframe

    """
    # Need to increase the first amp; last hour by 1 because of pyomo indexing
    # and add 1 to the value of last model hour because of the range
    # second model hour by 1
    periods = range(model.T[first_period   1], model.T[last_period   1]   1)
    Ein = [value(model.Ein[i]) for i in periods]
    Eout = [value(model.Eout[i]) for i in periods]
    rrp = [model.P.extract_values()[None][i] for i in periods]
    charge_state = [value(model.S[i]) for i in periods]

    df_dict = dict(
        period=periods,
        Ein=Ein,
        Eout=Eout,
        rrp=rrp,
        charge_state=charge_state
    )

    df = pd.DataFrame(df_dict)

    return df


def optimize_year(df, mwh, mw, ef, soc, first_model_period, last_model_period):
    """
    Optimize the charge/discharge behavior of a battery storage unit over a
    full year. Assume perfect foresight of electricity prices. The battery
    has a discharge constraint equal to its storage capacity and round-trip
    efficiency of 85%.

    Parameters
    ----------
    df : dataframe
        dataframe with columns of hourly LBMP and the hour of the year
    mwh : int
        size of bess
    mw : int
        storage capacity of bess
    ef  : double
        round trip efficiency of bess
    soc  : double
        State of Charge
    first_model_period : int, optional
        Set the first hour of the year to be considered in the optimization
        (the default is 0)
    last_model_period : int, optional
        Set the last hour of the year to be considered in the optimization (the
        default is 8759)

    Returns
    -------
    dataframe
        hourly state of charge, charge/discharge behavior, lbmp, and time stamp
    """

    # Filter the data
    df = df.loc[first_model_period:last_model_period, :]

    model = ConcreteModel()

    # Define model parameters
    model.T = Set(initialize=df.period.tolist(), doc='periods of year', ordered=True)
    model.Rmax = Param(initialize=mw / 2, doc='Max rate of power flow (MW) in or out', within=Any)
    model.Smax = Param(initialize=mwh, doc='Max storage (MWh)', within=Any)
    model.Smin = Param(initialize=mwh * 0.2, doc='Min storage (MWh)', within=Any)
    model.Dmax = Param(initialize=mwh * np.sqrt(ef) * 0.8, doc='Max discharge in 24 hour period', within=Any)
    model.P = Param(initialize=df.rrp.tolist(), doc='price for each period', within=Any)
    eta = ef  # Round trip storage efficiency

    # Charge, discharge, and state of charge
    # Could use bounds for the first 2 instead of constraints
    model.Ein = Var(model.T, domain=NonNegativeReals)
    model.Eout = Var(model.T, domain=NonNegativeReals)
    model.S = Var(model.T, bounds=(model.Smin, model.Smax))

    # Set all constraints
    def storage_state(model, t):
        'Storage changes with flows in/out and efficiency losses'
        # Set first period state of charge to 0
        if t == model.T.first():
            return model.S[t] == soc
        else:
            return (model.S[t] == (model.S[t - 1]
                                     (model.Ein[t - 1] * np.sqrt(eta))
                                   - (model.Eout[t - 1] / np.sqrt(eta))))

    model.charge_state = Constraint(model.T, rule=storage_state)

    def dod_limit(model, t):
        "Depth of Discharge limit"
        return model.Smin[t] <= model.S[t]

    model.dod = Constraint(model.T, rule=dod_limit)

    def discharge_constraint(model, t):
        "Maximum dischage within a single period"
        return model.Eout[t] <= model.Rmax

    model.discharge = Constraint(model.T, rule=discharge_constraint)

    def charge_constraint(model, t):
        "Maximum charge within a single period"
        return model.Ein[t] <= model.Rmax

    model.charge = Constraint(model.T, rule=charge_constraint)

    # Without a constraint the model would discharge in the final period
    # even when SOC was 0.
    def positive_charge(model, t):
        'Limit discharge to the amount of charge in battery, including losses'
        return model.Eout[t] <= model.S[t] * np.sqrt(eta)

    model.positive_charge = Constraint(model.T, rule=positive_charge)

    def discharge_limit(model, t):
        "Limit on discharge within a 24 hour period"
        max_t = model.T.last()

        # Check all t until the last 24 hours
        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_out = Constraint(model.T, rule=discharge_limit)

    # Define the battery income, expenses, and profit
    income = sum(df.loc[t, 'rrp'] * model.Eout[t] for t in model.T)
    expenses = sum(df.loc[t, 'rrp'] * model.Ein[t] for t in model.T)
    profit = income - expenses
    model.objective = Objective(expr=profit, sense=maximize)

    # Solve the model
    solver = SolverFactory('glpk')
    solver.solve(model)

    results_df = model_to_df(model, first_period=first_model_period, last_period=last_model_period)
    results_df['local_time'] = df.loc[:, 'local_time']

    return results_df
  

Ответ №1:

Любой из этих 2 подходов должен работать. Ваша модель не воспроизводима без некоторой работы, поэтому ее не так быстро проверить.

При вашем втором подходе и том, что у вас есть в вашем коде, поймите, что ваш model.Smin не проиндексирован. Это просто константа. Итак, у вас здесь ошибка:

 def dod_limit(model, t):
    "Depth of Discharge limit"
    return model.Smin[t] <= model.S[t]
  

Это должно быть просто:

     return model.Smin <= model.S[t]  # note model.Smin is not indexed
  

Я обеспокоен тем, что ошибка, которую вы показываете, связана с чем-то другим. Если это исправление выше ничего не исправляет, можете ли вы отредактировать свой пост с полной трассировкой стека?

Ответ №2:

понял это. Два метода, которые я описал выше в моем вопросе, действительно работают, мне просто нужно было установить начальные значения для моих переменных.

 model.Ein = Var(model.T, bounds=(0, model.Rmax), initialize=0)
model.Eout = Var(model.T, bounds=(0, model.Rmax), initialize=0)
model.S = Var(model.T, bounds=(model.Smin, model.Smax), initialize=soc)