Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Distributed slack management in sensitivity analysis #178

Merged
merged 29 commits into from
Dec 17, 2020
Merged

Conversation

Djazouli
Copy link
Contributor

@Djazouli Djazouli commented Dec 3, 2020

Signed-off-by: Gael Macherel gael.macherel@artelys.com

Please check if the PR fulfills these requirements (please use '[x]' to check the checkboxes, or submit the PR and then click the checkboxes)

  • The commit message follows our guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Create a feature allowing the user to use distributed slack in sensitivity analysis

Does this PR introduce a breaking change or deprecate an API? If yes, check the following:

  • The migration guide has been updated in the github wiki (What changes might users need to make in their application due to this PR?)

Other information:

This is currently WIP, I opened the PR to request feedback

@Djazouli Djazouli requested review from geofjamg and annetill December 3, 2020 17:37
@Djazouli Djazouli self-assigned this Dec 3, 2020
@@ -30,11 +30,11 @@
*/
private static final double SLACK_P_RESIDUE_EPS = Math.pow(10, -5);

protected static class ParticipatingElement<T> {
public static class ParticipatingElement<T> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really want to get rid of these "public" changes, but I need to get those values somewhere else in the code
Should I just create a protected method that returns something understandable by the rest of the code ?
(returning Pair<String, Number> instead of participatingElement for example)

@@ -131,6 +145,38 @@ void testDc4buses() {
assertEquals(0.25d, getValue(result, "g4", "l13"), LoadFlowAssert.DELTA_POWER);
}

@Test
void testDc4busesDistributed() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test does not pass at the moment, it's [WIP]

if (loadFlowParameters.isDistributedSlack()) {
switch (loadFlowParameters.getBalanceType()) {
case PROPORTIONAL_TO_GENERATION_P_MAX:
DistributedSlackOnGenerationOuterLoop outerLoop = new DistributedSlackOnGenerationOuterLoop(openLoadFlowParameters.isThrowsExceptionInCaseOfSlackDistributionFailure());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is were I need all this outer loop stuff to be Public, I would love to avoid this

* @param oldFactors
* @return
*/
private List<SensitivityFactor> getDistributedFactors(Network network, LfNetwork lfNetwork,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method uses network to get generators. But lfNetwork may not contain all these generators (because it is a connected component of network), so I would love to use only lfNetwork, but I do not know how to access generators from LfNetwork

* @param sensitivityValues
* @param remainder
* @return
*/
Copy link
Contributor Author

@Djazouli Djazouli Dec 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hardly understandable, needs refactoring

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, it is the part of the code that is the less understable for me.

List<SensitivityValue> sensitivityValues = calculateSensitivityValues(lfNetwork, equationSystem, factorsByVarConfig, states);

if (!lfParameters.isDistributedSlack()) {
return sensitivityValues;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a return in the middle of the function makes it harder to understand, I need to find a better way to distinguish the distributed/ not distributed case

Base automatically changed from sensi to master December 4, 2020 10:20
@annetill annetill changed the title [WIP] first commit for distributed slack in sensitivity analysis [WIP] Distributed slack management in sensitivity analysis Dec 4, 2020
}

switch (balanceType) {
case PROPORTIONAL_TO_GENERATION_P_MAX:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Djazouli Geoffory prefers that we do not use factors that whould remain in the API. So we have to fix that.

* @param loadFlowParameters
* @return
*/
private List<Pair<String, Number>> getParticipatingElements(LfNetwork lfNetwork, LoadFlowParameters loadFlowParameters) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here we just have to have as argument the balance type.

Copy link
Member

@annetill annetill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests have to be added. I think that this PR should not have more features but should be tested and improved before review.

}

// compute sensitivities corrected from the slack distribution effects
for (SensitivityValue sensitivityValue : sensitivityValues) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here a fix is needed because I don't have sort the sensitivity values to give only those that have been ask in the API. @Djazouli your code was functionaly right but it was to remove the use of factors.

@Djazouli Djazouli linked an issue Dec 9, 2020 that may be closed by this pull request
assertEquals(0.275d, getValue(result, "g1", "l12"), LoadFlowAssert.DELTA_POWER);
assertEquals(-0.125d, getValue(result, "g1", "l23"), LoadFlowAssert.DELTA_POWER);
assertEquals(0.025d, getValue(result, "g1", "l34"), LoadFlowAssert.DELTA_POWER);
assertEquals(0.15d, getValue(result, "g1", "l13"), LoadFlowAssert.DELTA_POWER);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Todo:
add tests:

  • where we are injecting on a load node that has no generator
  • Where the slack bus is participating in the compensation
  • Where the balance type is PROPORTIONAL_TO_LOAD

@Djazouli
Copy link
Contributor Author

Djazouli commented Dec 9, 2020

We still need to test balance type : PROPORTIONAL_TO_LOAD to improve coverage

Djazouli and others added 9 commits December 10, 2020 16:54
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
…rs required for distribution

Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Improve variable naming.

Signed-off-by: Anne Tilloy <anne.tilloy@rte-france.com>
Signed-off-by: Anne Tilloy <anne.tilloy@rte-france.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
annetill and others added 2 commits December 14, 2020 13:30
Signed-off-by: Anne Tilloy <anne.tilloy@rte-france.com>
@annetill annetill changed the title [WIP] Distributed slack management in sensitivity analysis Distributed slack management in sensitivity analysis Dec 14, 2020
@annetill annetill marked this pull request as ready for review December 14, 2020 13:53
Signed-off-by: Anne Tilloy <anne.tilloy@rte-france.com>
Signed-off-by: Anne Tilloy <anne.tilloy@rte-france.com>
Copy link
Member

@annetill annetill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe some slight variable names have to be changed to improve readability. I have a question about injection increase for a generator or a load that is connected to the slack bus: for the moment, we throw an exception. Is it the final behaviour that we aim ?

Map<SensitivityVariableConfiguration, SensitivityFactorGroup> factorsByVarConfig = new LinkedHashMap<>(factors.size());
Map<String, Number> participationMap = getParticipationMap(network, lfNetwork, loadFlowParameters); // empty if slack is not distributed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe participationFactorMap ?

Map<SensitivityVariableConfiguration, SensitivityFactorGroup> factorsByVarConfig = new LinkedHashMap<>(factors.size());
Map<String, Number> participationMap = getParticipationMap(network, lfNetwork, loadFlowParameters); // empty if slack is not distributed
participationMap.remove(lfNetwork.getSlackBus().getId()); // the injection on the slack bus will not appear in the rhs
participationMap.replaceAll((key, value) -> -value.doubleValue()); // the compensation on a bus will be the opposite of its participation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compensation -> slack distribution

SensitivityVariableConfiguration varConfig = new SensitivityVariableConfiguration(Collections.singleton(bus.getId()), Collections.emptySet());
Map<String, Number> busCompensation = new HashMap<>(participationMap);
// add 1 where we are making the injection
busCompensation.put(bus.getId(), busCompensation.getOrDefault(bus.getId(), 0).doubleValue() + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

busCompensation is not the good name choice indeed.

} else if (participatingElement.getElement() instanceof LfBus) {
busId = ((LfBus) participatingElement.getElement()).getId();
} else {
throw new UnsupportedOperationException("Only buses can have an impact in slack distribution");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed I am not sure that you can cover easily this line.

annetill and others added 6 commits December 15, 2020 08:42
Signed-off-by: Anne Tilloy <anne.tilloy@rte-france.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Anne Tilloy <anne.tilloy@rte-france.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
@@ -142,7 +142,7 @@ private DenseMatrix initRhs(LfNetwork lfNetwork, EquationSystem equationSystem,
for (Map.Entry<String, Number> busAndInjection : configuration.busInjectionById().entrySet()) {
LfBus lfBus = lfNetwork.getBusById(busAndInjection.getKey());
if (lfBus.isSlack()) {
throw new PowsyblException("Cannot analyse sensitivity of slack bus");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure to get why this line is not cover by our test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is because we never reach this line, the slack bus will never be inside the businjectionbyId. But the exception here stays for the safety of future developments

participatingElements = step.getParticipatingElements(lfNetwork);
ParticipatingElement.normalizeParticipationFactors(participatingElements, "bus");

participatingElements.forEach(participatingElement -> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can directly create the map like:

Map<String, Number> participationFactorByBusMap = participatingElements.stream().collect(Collectors.toMap(..));

// run DC load
Map<String, Double> functionReferenceByBranch = new HashMap<>();
DcLoadFlowParameters dcLoadFlowParameters = new DcLoadFlowParameters(lfParametersExt.getSlackBusSelector(),
new SparseMatrixFactory(), true, lfParametersExt.isDcUseTransformerRatio(), lfParameters.isDistributedSlack(), lfParameters.getBalanceType());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MatrixFactory should be taken from parameters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, but tp be checked by @Djazouli !

* @param loadFlowParameters
* @return
*/
private Map<String, Number> getParticipationFactorByBus(Network network, LfNetwork lfNetwork, LoadFlowParameters loadFlowParameters) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map<String, Double> ?

@@ -36,17 +39,17 @@

static class SensitivityVariableConfiguration {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should remove SensitivityVariableConfiguration? I added it to avoid computing same sensibility multiple times but there is no reason for that to happen frequently (user input mistake?) and it adds useless complexity.
So instead of Map<SensitivityVariableConfiguration, SensitivityFactorGroup> factorsByVarConfig we would only have List<SensitivityFactor<?, ?>>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like SensitivityVariableConfigurations are useful when the user gives a provider that contains all the generators of the network. Not sure if this is considered as an "user input mistake". But in this case, the SensitivityVariableConfiguration is useful to remove duplicate (generators connected on the same bus).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so let s keep it

Map<String, Double> functionReferenceByBranch = new HashMap<>();
DcLoadFlowParameters dcLoadFlowParameters = new DcLoadFlowParameters(lfParametersExt.getSlackBusSelector(),
new SparseMatrixFactory(), true, lfParametersExt.isDcUseTransformerRatio(), lfParameters.isDistributedSlack(), lfParameters.getBalanceType());
DcLoadFlowResult dcLoadFlowResult = new DcLoadFlowEngine(lfNetworks, dcLoadFlowParameters).run();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we calculate sensitivity only on first component do we really need to run the initial DC loadflow on all components?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, in the DCLoadFlowEngine, we explicitely compute the LF just on the first connected component. So I leave it like that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

participatingElements.forEach(participatingElement -> {
String busId;
if (participatingElement.getElement() instanceof LfGenerator) {
busId = network.getGenerator(((LfGenerator) participatingElement.getElement()).getId()).getTerminal().getBusView().getBus().getId();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid searching generator into IIDM model. We could just get LfBus from LfGenerator.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed there is no existing method, you mean that we have to implement the getter on all LfGenerator or there is something easier?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried something in the latest commit, tell me if this works for you:

Map<String, Double> functionReferenceByBranch = new HashMap<>();
DcLoadFlowParameters dcLoadFlowParameters = new DcLoadFlowParameters(lfParametersExt.getSlackBusSelector(),
new SparseMatrixFactory(), true, lfParametersExt.isDcUseTransformerRatio(), lfParameters.isDistributedSlack(), lfParameters.getBalanceType());
DcLoadFlowResult dcLoadFlowResult = new DcLoadFlowEngine(lfNetworks, dcLoadFlowParameters).run();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for later but running a DC LF using DcLoadFlowEngine is not efficient as we later need to build the same equation system and jacobian for sensitivity analysis.

annetill and others added 3 commits December 16, 2020 07:27
Signed-off-by: Anne Tilloy <anne.tilloy@rte-france.com>
… factor map

Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
Signed-off-by: Gael Macherel <gael.macherel@artelys.com>
@Djazouli Djazouli requested a review from geofjamg December 16, 2020 12:10
Signed-off-by: Geoffroy Jamgotchian <geoffroy.jamgotchian@rte-france.com>
@sonarqubecloud
Copy link

Copy link
Member

@annetill annetill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @Djazouli and @Hadrien-Godard for this great PR !

@annetill annetill merged commit 8b32eff into master Dec 17, 2020
@annetill annetill deleted the sensi_compense branch December 17, 2020 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Manage distributedSlack in Sensitivity Analysis
3 participants