is there a way to use the constraint shared_limit for flows?
I have an energy system with two storages which are already linked via this constraint, but only for the storage_content. Since the two storages mimic one storage, the inputs and outputs have to share a maximum. At the moment it is possible for the storage to load and unload with more power than physically possible.
I tried to implement the shared_limit for the inputs with:
But I got an Error that inputs is no variable for the GenericStorageBlock.
I have already found the constraints equate_flows or limit_active_flow. From what I understand, they work differently to what I need.
I’d have to look what the Pyomo variable is, but I think there is an easier way. Recalling your diagram in Using an electricity storage for balancing energy, the following should be an easier solution for the same problem:
Thank you again @pschoen for your help.
I have never used the converter before. Does it separate one pair of input and output from another? Because I need to make sure that the electricity from the energy trading isn’t used for consumption and the electricity from the PV system isn’t sold on the other markets.
My understanding of the problem was that you have one battery that is used for different purposes. The right hand side models the economic view, the left hand side the electrical flows. I assumed you wanted to have both batteries at the same storage level all the time. Carefully re-reading the initial statement, I was wrong, sorry.
In your case, a Converter would not be the correct choice. Instead, I’d propose a bus and then add constraints that forces the flows at the LHS/RHS to be balanced. It would be a changed form of the equate_flows constraint, with the addition that the sum over a number of time steps needs to be equal between charging and discharging from one side. (This is easy when there are no losses in the storage or all losses are compensated from one side.)
PS: Unluckily, this solution is not compatible to the one in the other topic. Probably your initial idea really is the best one. I’ll have a look at the Pyomo variable name.
I like this solution better, because it fixes another problem. At the moment I am using three limit_active_flow_count_by_keyword constraints to avoid the loading and unloading of the storages at the same time. You can imagine how long the modelling time will be to optimize a whole year.
I’ts not compatible as the batteries for positive and negative FCR need to have a different SOC during the time the FCR is active. Especially, you will load in one scenario and unload in the other one. Thus, only one battery is not sufficient.
Why do you need to forbid loading and unloading at the same time? I see that people do that but typically it’s a sign of some other problem if it is happening.
The energy trading part of the energy system would just pass throught the electricity, because of the different prices on the different markets. E.g the mean price of 4 15min contracts could be lower than the price of the hour contract. Therfore the modell would just trade direct “without” the storage.
On the other hand, unloading for trading could take place while the storage in the left-hand system is being loaded from the PV system.
Maybe a mix of the two could work.
I could give the balancing energy part its own storage, as I have already done. For the rest of the system I use your idea of linking the economic and electrical flow parts together with a storage and an additional bus. Therefore I implement the modified equal_flows constraint for all inputs and outputs of each link. This way I only need one limit_active_flow constraint for the loading and unloading at the same time problem.
The only downside to this is that the storage for the FCR part is not included in the equate_flows constraints, but this is fine I guess.
Could you maybe help me for the change of the constraint? Do I have to implement it to the model like a custom constraint?
E.g the mean price of 4 15min contracts could be lower than the price of the hour contract. Therfore the modell would just trade direct “without” the storage.
If that’s the case
Why it is not done or should be suppressed?
If you really want to suppress it, working with duplicate storage and shared limits should optimise faster than using binary constraints (i.e. limit_active_flow_count).
limit_active_flow_count would still not forbid to buy a 15 minute contract and to sell at a one-hour contract.
Is there any requirement in the real world to provide constant power within the time of the contract or can you provide the energy at any time within the time frame?
Buying a 15 minute contract and sell the electricity on the hourly market is not forbidden.
I want to suppress the direct trading, which would be possible without a storage at all. Thats because I want to analyze the impact of trading with a storage.
I already use the duplicate storage and shared limits, but this doesn’t supress the simultaneosly loading and unloading.
I’m not quite sure what the requirements in the real world are. Normally if you sell 3 MWh on the hourly Market you provide 3MW for one houre. I think you could just provide 6 MW in half an hour, but I think that will be a problem for the equalisation of the “balancing group” (Bilanzkreis) and the power frequency.
OK, I have just compared the results of trading with and without the constraint. The difference isn’t that big. Without the constraint, the model makes 1.000 € more profit. The total is 38.000 €.
Back to the original topic:
I still need some kind of constraint that works like a shared_limit for flows. I also just realised, that the flows from the trading sources need to share a limit with eachother and with the normal power purchase on the left-hand-side of the energy system.
There are two ways to model this. One is a custom constraint the other is virtual flows. I tried to rewrite the constraint equate_flows accordingly:
def shared_flow_limit(
model pyomo.Model,
flows: list[solph.Flow],
weights: list[float],
shared_limit: float,
name="equate_flows": str,
):
def _limit_flow_groups_rule(m):
for p, ts in m.TIMEINDEX:
expr = shared_limit >= sum(
weight * m.flow[fi, fo, p, ts]
for weight, fi, fo in zip(weights, flows)
)
getattr(m, name).add((p, ts), expr)
setattr(
model,
name,
po.Constraint(model.TIMEINDEX, noruleinit=True),
)
setattr(
model,
name + "_build",
po.BuildAction(rule=_limit_flow_groups_rule),
)
return model
The virtual Flows are a bit easier to model: All of the according Flows get an additional Converter. All of these converters have on inflow from a shared common_limit Bus. The latter is fed with the shared limit by from a shared_limit Source.