Constraint not working properly when optimizing with rolling horizon

93 views
Skip to first unread message

Matthew Smith

unread,
Mar 7, 2025, 4:14:11 AMMar 7
to pypsa
Dear PyPSA community,
 
Under certain PPAs, there are penalties for selling power to the market when the PPA capacity is not fully met. I have developed a constraint (with the kind help of Konstantinos) which prevents market sales while the PPA capacity is not met.
This works when I run the following:

m = network.optimize.create_model()
m = constrain_merchant_sell_below_PPA(m)
network.optimize.solve_model(solver_name="highs", solver_options={'log_to_console': False})

However, it takes too long to run this at full resolution. It is much faster if I split it up using a rolling horizon optimisation. Unfortunately, the solution is not correct when I do this. The constraint is implemented for all timeblocks except when the RE_to_PPA_p = 100.

This is how I run the model with rolling horizons.

def custom_constraints(network):

    #add custom constraints
    m = network.optimize.create_model()
    m = constrain_merchant_sell_below_PPA(m)
network.optimize.optimize_with_rolling_horizon(snapshots=network.snapshots, horizon=672, overlap=2, extra_functionality=custom_constraints(network), solver_name='highs', solver_options={'log_to_console': False})

I have also manually run a rolling horizon optimisation, but the outcome is the same as the use of optimize_with_rolling_horizon (works except for when RE_to_PPA = 100).

Does anyone have any thoughts on what could be causing the difference between the rolling horizon optimisation and the solve_model run?

This is the constraint I have implemented:

def constrain_merchant_sell_below_PPA(m)
        # Define the PPA offtake capacity      
         LOAD_PPA_MW = 200
        
# Define the variable time series of power to the PPA
        RE_to_PPA_p = m.variables["Link-p"].sel(Link=RE_to_PPA)
        
# define the variable of the time series of the link to sell power to merchant sales
        RE_to_MerchantSell_p = m.variables["Link-p"].sel(Link=RE_to_MerchantSell)
        
# Merchant_sell is the generators which buys power (flexible negative generator).
        # It has Commitable=TRUE, so we can get its status
        sel_dict = {'Generator-com':'Merchant_sell'}
        Merchant_sell_status = m.variables['Generator-status'].sel(sel_dict)

        # We want to prevent the merchant_sell generator from ever generating if the power to the
        #PPA is < 200MW
        lhs = Merchant_sell_status * LOAD_PPA_MW
        rhs = RE_to_PPA_p

        m.add_constraints(lhs <= rhs, name=f'Merchant_sell_below_ppa_{auction}')

    return(m)


Sketch of the network is attached.

PyPSA constraint challenge.png

Matthew Smith

unread,
Mar 10, 2025, 8:08:39 AMMar 10
to pypsa
Hi all,

While I wasn't able to solve the above issue, I found a way around it. I manually implemented rolling horizon for the create_model and solve_model functions. This properly implemented the constraint. No idea why the extra_functionality method didn't work.

# Manually implementing rolling horizon with overlap
overlap = 2
horizon = 146
increments = int((8760/resolution) / horizon)  # make sure this is an int which properly divides 8760/resolution

for i in range(0,increments):
    # network.optimize(network.snapshots[i * horizon : (i + 1) * horizon + overlap], solver_name="highs", solver_options={'log_to_console': False})
    m = network.optimize.create_model(snapshots=network.snapshots[i * horizon : (i + 1) * horizon + overlap])
    m = constrain_merchant_sell_below_PPA(m, network, control_f)

    network.optimize.solve_model(solver_name="highs", solver_options={'log_to_console': False})

Best regards,
Matt

Jonas Hörsch

unread,
Mar 11, 2025, 4:51:30 PMMar 11
to Matthew Smith, pypsa
Hi Matt,

your `extra_functionality` callback function should not call
`create_model`, that is done
for you within the optimize_with_rolling_horizon function.

ie. custom_constraints should be something like:

```python
def custom_constraints(network, snapshots):
constrain_merchant_sell_below_PPA(network.model, network, snapshots)
```

instead of what you tried.

Best,
Jonas
Reply all
Reply to author
Forward
0 new messages