如何在 Pyomo 中添加最小值约束

How to add a minimum value constraint in Pyomo

背景 - 我正在尝试优化电池储能系统 (BESS) 的运行。我使用 This Github Project 作为基础。

问题 - 大多数 BESS 系统都有放电深度限制。 IE。对于 400 MWh 的电池,您只能使用大约 320 MWh。最后 20% 基本上是 reserved/unusable。我一直在尝试将其添加为附加约束,但我没有运气

我试过的

我试过为我的电池存储参数设置界限,我试过添加约束(如下所示),但两者都导致了错误 'No value for uninitialized NumericValue object Ein[0]'(Ein 是 Energy In 的参数)

尝试 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 & 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

这两种方法中的任何一种都应该有效。如果不做一些工作,你的模型是不可复制的,所以不要快速验证。

关于你的第二种方法,以及你在代码中的内容,意识到你的 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

我担心您显示的错误是由于其他原因造成的。如果上面的这个修复没有解决问题,你能用完整的堆栈跟踪编辑你的 post 吗?

想通了。我在上面的问题中描述的两种方法确实有效,我只需要为我的变量设置初始值。

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)