Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/grid_features/loadflow_validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ in susceptance, so that they are voltage dependent.

$targetP = 0$ MW

- If the regulation mode is `OFF`, then $targetQ$ is constant
- If the regulation is disabled, then $targetQ$ is constant
Comment thread
samirromdhani marked this conversation as resolved.
- If the regulation mode is `REACTIVE_POWER`, it behaves like a generator without voltage regulation
- If the regulation mode is `VOLTAGE`, it behaves like a generator with voltage regulation with the following bounds (dependent on the voltage, which is not the case for generators):
$minQ = - Bmax * V^2$ and $maxQ = - Bmin V^2$
Expand Down
49 changes: 33 additions & 16 deletions docs/user/itools/loadflow-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,18 @@ incomplete to go through the rest of the validation.
In this section, we go into more details about the checks performed by the validation feature of load-flow results available in PowSyBl.

### Buses
If all values are present, or if only one value is missing, the result is considered to be consistent.
The bus active and reactive power balances are considered consistent when:

$$
\begin{aligned}
\left| \sum_{load} P + \sum_{injections} P \right| \leq \epsilon \\
\left| \sum_{load} Q + \sum_{injections} Q \right| \leq \epsilon
\end{aligned}
$$

- `P injections` and `Q injections` are the sums of connected injections (generators, batteries, shunts, SVCs, VSC, lines, dangling lines, and transformers)
- `P load` and `Q load` are the sums of connected loads.

Note that if the result contains only the voltages (phase and angle), the PowSyBl validation provides a load-flow results completion feature.
It can be used to compute the flows from the voltages to ensure the result consistency, with the run-computation option of
the PowSyBl validation.
Expand All @@ -178,6 +189,8 @@ check more leniently.
In case the voltages are available but not the powers, the result completion feature of the PowSyBl validation
can be used to recompute them using the validation equations (meaning that the branch validation tests will always be OK, so that it allows performing the bus validation tests).

In case of disconnected branch, $P_i$ and $Q_i$ must be undefined or approximately equal to zero.

### Three-winding transformers
<span style="color: red">To be implemented, based on a conversion into 3 two-winding transformers.</span>

Expand Down Expand Up @@ -211,16 +224,18 @@ $$
\end{aligned}
$$

In the PowSyBl validation, there are a few tricks to handle special cases:
In the PowSyBl validation, there are a few tricks to handle special cases before applying the nominal active/reactive/voltage rules
- if `P` or `Q` is missing, validation fails if setpoints are defined and non-zero
- if $minQ > maxQ$, then the values are switched to recover a meaningful interval if `noRequirementIfReactiveBoundInversion = false`
- in case of a missing value, the corresponding test is OK
- $minQ$ and $maxQ$ are function of $P$. If $targetP$ is outside $[minP, maxP]$, no test is done.
- $minQ$ and $maxQ$ are function of $P$. If $targetP$ is outside $[minP, maxP]$, and `noRequirementIfSetpointOutsidePowerBounds = true`, generator validation checks are skipped.

### Loads
<span style="color: red">To be implemented, with tests similar to generators with voltage regulation.</span>

### Shunts
The two following conditions must be fulfilled in valid results:
### Shunt Compensator
#### Linear model
For connected shunts, the two following conditions must be fulfilled for valid results:

$$
\begin{aligned}
Expand All @@ -229,22 +244,24 @@ $$
\end{aligned}
$$

Additional condition for disconnected shunts:
- If the shunt is disconnected, `Q` must be undefined or equal to `0`

### Static VAR Compensators
The following conditions must be fulfilled in valid results:
$targetP = 0MW$
- If the regulation mode is `OFF`, then
Comment thread
samirromdhani marked this conversation as resolved.
The following conditions must be fulfilled for valid results:

$$\left| targetQ - Q \right| < \epsilon$$
- If the regulation is disabled, then $|Q| <= \epsilon$.
- Static VAR Compensators behave like generators producing zero active power: $|P - targetP| <= \epsilon$, with $targetP = 0$ MW.
- If `P` or `Q` is missing, then reactive power setpoint must be undefined equal to `0`
- If the regulation mode is `REACTIVE_POWER`, same checks as a generator without voltage regulation:

- If the regulation mode is `REACTIVE_POWER`, same checks as a generator without voltage regulation
$|Q - reactivePowerSetpoint| <= \epsilon$.
- If the regulation mode is `VOLTAGE`, same checks as a generator with voltage regulation with the following bounds:

$$
\begin{aligned}
minQ = - B_{max} * V^2 \\
maxQ = - B_{min} * V^2
\end{aligned}
$$
$minQ = -Bmax * V^2$ and $maxQ = -Bmin * V^2$.
- If $V < voltageSetpoint$, then `Q` must match `maxQ`.
- If $V > voltageSetpoint$, then `Q` must match `minQ`.
- If $|V - voltageSetpoint| <= \epsilon$, then `Q` must be within `[minQ, maxQ]`.

### HVDC lines
<span style="color: red">To be done.</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

/**
* A bus is a set of equipments connected together through a closed switch.
* <p>It could be a configured object ot a result of a computation depending of the
* <p>It could be a configured object ot a result of a computation depending on the
* context.</p>
*
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@

import com.powsybl.loadflow.validation.io.ValidationWriter;

import static com.powsybl.loadflow.validation.ValidationUtils.*;

/**
*
* @author Massimo Ferraro {@literal <massimo.ferraro@techrain.eu>}
*
* Rule for valid results :<br/>
* |incomingP + loadP| <= threshold and |incomingQ + loadQ| <= threshold
*/
public final class BusesValidation {

Expand Down Expand Up @@ -157,15 +162,11 @@ public boolean checkBuses(String id, double loadP, double loadQ, double genP, do

double incomingP = genP + batP + shuntP + svcP + vscCSP + lineP + boundaryLineP + t2wtP + t3wtP;
double incomingQ = genQ + batQ + shuntQ + svcQ + vscCSQ + lineQ + boundaryLineQ + t2wtQ + t3wtQ;
if (ValidationUtils.isMainComponent(config, mainComponent)) {
if (ValidationUtils.areNaN(config, incomingP, loadP) || Math.abs(incomingP + loadP) > config.getThreshold()) {
LOGGER.warn("{} {}: {} P {} {}", ValidationType.BUSES, ValidationUtils.VALIDATION_ERROR, id, incomingP, loadP);
validated = false;
}
if (ValidationUtils.areNaN(config, incomingQ, loadQ) || Math.abs(incomingQ + loadQ) > config.getThreshold()) {
LOGGER.warn("{} {}: {} Q {} {}", ValidationType.BUSES, ValidationUtils.VALIDATION_ERROR, id, incomingQ, loadQ);
validated = false;
}
if (isMainComponent(config, mainComponent)) {
// |incomingP + loadP| <= threshold
validated &= validatePowerBalance(id, "P", incomingP, loadP, config);
// |incomingQ + loadQ| <= threshold
validated &= validatePowerBalance(id, "Q", incomingQ, loadQ, config);
}
try {
busesWriter.write(id, incomingP, incomingQ, loadP, loadQ, genP, genQ, batP, batQ, shuntP, shuntQ, svcP, svcQ, vscCSP, vscCSQ,
Expand All @@ -175,4 +176,17 @@ public boolean checkBuses(String id, double loadP, double loadQ, double genP, do
}
return validated;
}

private boolean validatePowerBalance(String id, String balanceType, double incomingPower, double loadPower, ValidationConfig config) {
if (!areNaN(config, incomingPower, loadPower) && !isBalanceInconsistent(incomingPower, loadPower, config.getThreshold())) {
return true;
}
LOGGER.warn("{} {}: {} {} {} {}", ValidationType.BUSES, ValidationUtils.VALIDATION_ERROR, id, balanceType, incomingPower, loadPower);
return false;
}

private static boolean isBalanceInconsistent(double incomingP, double loadP, double threshold) {
return isOutsideTolerance(incomingP, -loadP, threshold);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@
import com.powsybl.iidm.network.util.BranchData;
import com.powsybl.loadflow.validation.io.ValidationWriter;

import static com.powsybl.loadflow.validation.ValidationUtils.*;

/**
*
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*
* Rules for valid results: <br/>
* Rule 1: checks disconnected terminal: P and Q must be undefined or ~0 <br/>
* Rule 2: checks connected terminal: |P - Pcalc| <= ε and |Q - Qcalc| <= ε
*/
public final class FlowsValidation {

Expand All @@ -46,7 +52,7 @@ public boolean checkFlows(BranchData branch, ValidationConfig config, Writer wri
Objects.requireNonNull(config);
Objects.requireNonNull(writer);

try (ValidationWriter flowsWriter = ValidationUtils.createValidationWriter(branch.getId(), config, writer, ValidationType.FLOWS)) {
try (ValidationWriter flowsWriter = createValidationWriter(branch.getId(), config, writer, ValidationType.FLOWS)) {
return checkFlows(branch, config, flowsWriter);
} catch (IOException e) {
throw new UncheckedIOException(e);
Expand All @@ -61,15 +67,15 @@ public boolean checkFlows(BranchData branch, ValidationConfig config, Validation
boolean validated = true;

if (!branch.isConnected1()) {
validated &= checkDisconnectedTerminal(branch.getId(), "1", branch.getP1(), branch.getComputedP1(), branch.getQ1(), branch.getComputedQ1(), config);
validated &= checkDisconnectedTerminal(branch.getId(), "1", branch.getP1(), branch.getComputedP1(), branch.getQ1(), branch.getComputedQ1(), config.getThreshold());
}
if (!branch.isConnected2()) {
validated &= checkDisconnectedTerminal(branch.getId(), "2", branch.getP2(), branch.getComputedP2(), branch.getQ2(), branch.getComputedQ2(), config);
validated &= checkDisconnectedTerminal(branch.getId(), "2", branch.getP2(), branch.getComputedP2(), branch.getQ2(), branch.getComputedQ2(), config.getThreshold());
}
if (branch.isConnected1() && ValidationUtils.isMainComponent(config, branch.isMainComponent1())) {
if (branch.isConnected1() && isMainComponent(config, branch.isMainComponent1())) {
validated &= checkConnectedTerminal(branch.getId(), "1", branch.getP1(), branch.getComputedP1(), branch.getQ1(), branch.getComputedQ1(), config);
}
if (branch.isConnected2() && ValidationUtils.isMainComponent(config, branch.isMainComponent2())) {
if (branch.isConnected2() && isMainComponent(config, branch.isMainComponent2())) {
validated &= checkConnectedTerminal(branch.getId(), "2", branch.getP2(), branch.getComputedP2(), branch.getQ2(), branch.getComputedQ2(), config);
}
try {
Expand All @@ -90,27 +96,27 @@ public boolean checkFlows(BranchData branch, ValidationConfig config, Validation
return validated;
}

private static boolean checkDisconnectedTerminal(String id, String terminalNumber, double p, double pCalc, double q, double qCalc, ValidationConfig config) {
private static boolean checkDisconnectedTerminal(String id, String terminalNumber, double p, double pCalc, double q, double qCalc, double threshold) {
boolean validated = true;
if (!Double.isNaN(p) && Math.abs(p) > config.getThreshold()) {
LOGGER.warn("{} {}: {} disconnected P{} {} {}", ValidationType.FLOWS, ValidationUtils.VALIDATION_ERROR, id, terminalNumber, p, pCalc);
if (!Double.isNaN(p) && isOutsideTolerance(p, 0.0, threshold)) {
LOGGER.warn("{} {}: {} disconnected P{} {} {}", ValidationType.FLOWS, VALIDATION_ERROR, id, terminalNumber, p, pCalc);
validated = false;
}
if (!Double.isNaN(q) && Math.abs(q) > config.getThreshold()) {
LOGGER.warn("{} {}: {} disconnected Q{} {} {}", ValidationType.FLOWS, ValidationUtils.VALIDATION_ERROR, id, terminalNumber, q, qCalc);
if (!Double.isNaN(q) && isOutsideTolerance(q, 0.0, threshold)) {
LOGGER.warn("{} {}: {} disconnected Q{} {} {}", ValidationType.FLOWS, VALIDATION_ERROR, id, terminalNumber, q, qCalc);
validated = false;
}
return validated;
}

private static boolean checkConnectedTerminal(String id, String terminalNumber, double p, double pCalc, double q, double qCalc, ValidationConfig config) {
boolean validated = true;
if (ValidationUtils.areNaN(config, pCalc) || Math.abs(p - pCalc) > config.getThreshold()) {
LOGGER.warn("{} {}: {} P{} {} {}", ValidationType.FLOWS, ValidationUtils.VALIDATION_ERROR, id, terminalNumber, p, pCalc);
if (areNaN(config, pCalc) || isOutsideTolerance(p, pCalc, config.getThreshold())) {
LOGGER.warn("{} {}: {} P{} {} {}", ValidationType.FLOWS, VALIDATION_ERROR, id, terminalNumber, p, pCalc);
validated = false;
}
if (ValidationUtils.areNaN(config, qCalc) || Math.abs(q - qCalc) > config.getThreshold()) {
LOGGER.warn("{} {}: {} Q{} {} {}", ValidationType.FLOWS, ValidationUtils.VALIDATION_ERROR, id, terminalNumber, q, qCalc);
if (areNaN(config, qCalc) || isOutsideTolerance(q, qCalc, config.getThreshold())) {
LOGGER.warn("{} {}: {} Q{} {} {}", ValidationType.FLOWS, VALIDATION_ERROR, id, terminalNumber, q, qCalc);
validated = false;
}
return validated;
Expand All @@ -121,7 +127,7 @@ public boolean checkFlows(Line l, ValidationConfig config, Writer writer) {
Objects.requireNonNull(config);
Objects.requireNonNull(writer);

try (ValidationWriter flowsWriter = ValidationUtils.createValidationWriter(l.getId(), config, writer, ValidationType.FLOWS)) {
try (ValidationWriter flowsWriter = createValidationWriter(l.getId(), config, writer, ValidationType.FLOWS)) {
return checkFlows(l, config, flowsWriter);
} catch (IOException e) {
throw new UncheckedIOException(e);
Expand All @@ -142,7 +148,7 @@ public boolean checkFlows(TwoWindingsTransformer twt, ValidationConfig config, W
Objects.requireNonNull(config);
Objects.requireNonNull(writer);

try (ValidationWriter flowsWriter = ValidationUtils.createValidationWriter(twt.getId(), config, writer, ValidationType.FLOWS)) {
try (ValidationWriter flowsWriter = createValidationWriter(twt.getId(), config, writer, ValidationType.FLOWS)) {
return checkFlows(twt, config, flowsWriter);
} catch (IOException e) {
throw new UncheckedIOException(e);
Expand All @@ -168,7 +174,7 @@ public boolean checkFlows(TieLine tl, ValidationConfig config, Writer writer) {
Objects.requireNonNull(config);
Objects.requireNonNull(writer);

try (ValidationWriter flowsWriter = ValidationUtils.createValidationWriter(tl.getId(), config, writer, ValidationType.FLOWS)) {
try (ValidationWriter flowsWriter = createValidationWriter(tl.getId(), config, writer, ValidationType.FLOWS)) {
return checkFlows(tl, config, flowsWriter);
} catch (IOException e) {
throw new UncheckedIOException(e);
Expand All @@ -189,7 +195,7 @@ public boolean checkFlows(Network network, ValidationConfig config, Writer write
Objects.requireNonNull(config);
Objects.requireNonNull(writer);

try (ValidationWriter flowsWriter = ValidationUtils.createValidationWriter(network.getId(), config, writer, ValidationType.FLOWS)) {
try (ValidationWriter flowsWriter = createValidationWriter(network.getId(), config, writer, ValidationType.FLOWS)) {
return checkFlows(network, config, flowsWriter);
} catch (IOException e) {
throw new UncheckedIOException(e);
Expand Down
Loading
Loading