PowerGrids / PowerGrids

This repository contains the code for PowerGrids, a Modelica library for electro-mechanical modelling of power systems.
Mozilla Public License 2.0
37 stars 17 forks source link

Introduce overconstrained connectors in PowerGrids #60

Closed casella closed 1 year ago

casella commented 1 year ago

State of the art

The electro-mechanical modelling of power systems with synchronous generators requires to share a common reference frequency for each synchronously connected sub-system. Libraries such as Modelica.Electrical.QuasiStatic.SinglePhase or PowerSystems share such reference by means of overconstrained connectors, where the overconstrained variable is a phase angle or a reference frequency.

When the PowerGrids project was started several years ago, we decided not to use this mechanism, because overconstrained connection graphs in the current Modelica Language Specification are static, so they cannot change at runtime.

In case a synchronous system is split into two (or more) synchronous systems because of breakers opening at the connection between different areas, one ends up with two systems that need a different frequency reference each. This is necessary for efficient variable step-size simulation: if the two new areas end up working at different frequencies, e.g. one at 49.5 Hz and one at 50.5 Hz, using only one reference, e.g. at 49.5 Hz, would mean that the phasors of the other sub-system would rotate at 1 Hz also in steady-state, thus requiring to shorten the time step very much below 1 s, and recomputing Jacobians all the time. This is not necessary if, after the splitting, each synchronously connected island gets its own frequency reference, because in that case, all phasors will become constant at steady-state.

PowerGrids 1.0.x was then designed to operate in three possible modes, set by the System object:

It turned out, programming the third choice, albeit technically possible with Modelica 3.5, is quite complicated: it requires an external object inside the system object with a lot of external C code to manage the dynamically changing topology at runtime, and also a lot of ugly connections to-from the system objects, with ugly things like connection numbers generated at initialization to manage the topology analyzer. A lot of imlementation work, and a not really object-oriented way do do things.

We recently prototype a new concept that allows to dynamically change the overconstrained connection topology at runtime, see #30 and references therein. The prototype works very well, it was written using OpenModelica.jl, but it can be easily ported into any Modelica tool. Most importantly, it requires really minimal and very straightforward changes to the library source code, compared to regular static overconstrained connections. The required change in the Modelica Language Definition is also minimal and does not require any new syntax, it just eliminates one constraint in the structure of if-equations.

Given this result, it is clear that the original adaptive reference generator concept will never be implemented, because it is so much simpler and so much more elegant to do it in a truly object-oriented way with dynamic overconstrained connectors. Even if that requires a (small) change in the Modelica language, and in the tools supporting it. We won't have an implementation of the adaptive reference generator anytime soon anyway, so I believe it's best to invest this time into getting dynamic overconstrained connectors into the language. BTW, this allow to also support other use cases that need dynamic overconstrained connectors, e.g., incompressible fluid networks.

Proposal

I would therefore propose that version 2.0.0 of PowerGrids scraps the current architecture, where the reference frequency is managed by the system object, and used overconstrained connectors instead, to carry around the reference frequency information. The way to do that is detailed in this paper and has already been tested successfully with the conceptual library DynamicOverconstrainedConnectors. The resulting code is actually more compact and nicer.

As a first step, we can re-implement the currently available options (nominal frequency for grids connected to one or more infinite buses, fixed reference generator for islanded grids) using regular overconstrained connectors. Thus, we get the same functionality, but with a much nicer object-oriented formulation. This works out of the box with current Modelica 3.5-compatible tools.

It is then possible to add the option of using dynamic overconstrained connectors to actually split the connection diagram at runtime when breaker opening form separate synchronous islands. This option could be triggered in the system object, and would of course only work with tools that implement this feature.

The changes to the library source code to implement this are really minimal: a few lines in the system object, and a couple of lines in the models including breakers. Note that this additional code does not prevent to use the library in tools that do not support this feature, as long as it is not activated in the system object.

casella commented 1 year ago

@ceraolo, @adriguir your comments are welcome.

AndreaBartolini commented 1 year ago

I'm starting to implement this feature together with the ones in the #54 and #62. I propose to set the default priority of the connections.potentialRoot of the generator equal to 1/Pnom, so that the biggest generator (by default) prescribes the frequency reference.

casella commented 1 year ago

I'm starting to implement this feature together with the ones in the #54 and #62. I propose to set the default priority of the connections.potentialRoot of the generator equal to 1/Pnom, so that the biggest generator (by default) prescribes the frequency reference.

Unfortunately the priority is defined as a non-negative Integer, so p = 1/Pnom doesn't work.

I would suggest to use something like integer(10 - log10(Pnom)), this gives max priority p = 0 for 10 GW, then p = 1 for 1 GW, p = 2 for 100 MW, p = 3 for 10 MW, p = 4 for 1 MW, p = 5 for 100 kW, p = 6 for 10 kW, p = 7 for 1 kW, etc.

If you want less quantization, you could useinteger(100 - 10*log10(Pnom)). This gives p = 0 for 10 GW, p = 10 for 1 GW, p = 20 for 100 MW, p = 30 for 10 MW, p = 40 for 1 MW, p = 50 for 100 kW, p = 60 for 10 kW, p = 70 for 1 kW, with all the intermediate values, e.g. p = 1 for 8 GW, p = 2 for 6 GW, p = 3 for 5 GW, etc.

AndreaBartolini commented 1 year ago

Maybe the best solution is to put a replaceable function in the system object that produces the priority value for each generator starting from its Pnom value (passed as formal parameter). By this way it's easy to change the priority calculation rule for all the generators in the network (it's enough to redeclare the function where the system object is instantiated)... the default function to be used maybe the second one proposed by @casella.

AndreaBartolini commented 1 year ago

The code that introduces the overconstrained connectors has been implemented and is currently available on the following branch

https://github.com/PowerGrids/PowerGrids/tree/Issue%2360_IntroduceOverconstrainedConnectors

It works properly in OpenModelica but there is a problem with Dymola, that up to now doesn't support the potential root priority given as parameter (it accepts only constants), therefore the current implementation that calculates said priority starting from the PNom parameter produces the following error in Dymola:

immagine

This means that if we merge this feature into PowerGrids 2.0.0 the Dymola users will not be able to use it until Dymola will solve this problem.

In my opinion this point should be discussed before to make a PR and merge the feature in the PowerGrids 2.0.0.

casella commented 1 year ago

@AndreaBartolini I'm not sure if the previous comment was written before or after our conversation. If priority is a parameter with a binding equation (not a function call) and annotation(Evaluate = true), Dymola could accept it.

casella commented 1 year ago

Anyway, good that it works, at least in OMC 😄

We should decide how to demonstrate the technique in the Tutorial, and document it properly.

My suggestion is that models in the tutorial should mostly rely on the built-in power flow, because it's easier to manage for small systems that are handled with OMEdit. Only one or two test cases should be added to demonstrate the direct setting of UStart, PStart, and QStart obtained from a separate power flow.

@ceraolo, @AndreaBartolini what do you think?

AndreaBartolini commented 1 year ago

@AndreaBartolini I'm not sure if the previous comment was written before or after our conversation. If priority is a parameter with a binding equation (not a function call) and annotation(Evaluate = true), Dymola could accept it.

The comment refers exactly to the solution above, unfortunately the parameter with the binding equation doesn't work in Dymola, also with the annotation Evaluate=true.

The only way that works in Dymola is to directly put the priority calculation inside the potential root declaration

Connections.potentialRoot(terminalAC.omegaRefPu, integer(100 - 10*log10(PNom))); 

but this makes impossible the change of the priority by the user.

AndreaBartolini commented 1 year ago

Anyway, good that it works, at least in OMC 😄

We should decide how to demonstrate the technique in the Tutorial, and document it properly.

My suggestion is that models in the tutorial should mostly rely on the built-in power flow, because it's easier to manage for small systems that are handled with OMEdit. Only one or two test cases should be added to demonstrate the direct setting of UStart, PStart, and QStart obtained from a separate power flow.

@ceraolo, @AndreaBartolini what do you think?

It seems to me that this is matter of the issue #54 (not yet implemented because I'm waiting the decision about the merge of this one), in any case I agree with @casella proposal about the tutorial.

casella commented 1 year ago

The only way that works in Dymola is to directly put the priority calculation inside the potential root declaration

Connections.potentialRoot(terminalAC.omegaRefPu, integer(100 - 10*log10(PNom))); 

I would suggest to keep it this way for the time being, open a ticket with Dassault Systèmes, and open a ticket to improve this later in version 2.1.0 or something. As I see it, changing the priority of generators is not a critical feature, I would try as much as possible to make sure that PowerGrids works with all Modelica tools, not only with OMC, unless this requires serious design compromises.

AndreaBartolini commented 1 year ago

Good news, I found a workaround that works in Dymola (in the sense that now the model TwoGeneratorsOneReferenceGenerator is checked without problems in Dymola, then fails the initialization (in Dymola) but this is another story...). It's enough to add a reduntant typecasting as following:

Connections.potentialRoot(terminalAC.omegaRefPu, integer(priority));

so I'll proceed with the workaround and then I'll open a ticket to Dassault Systémes.

AndreaBartolini commented 1 year ago

implemented in PR #80

AndreaBartolini commented 1 year ago

bug solved in PR #84