Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Added `BusSignalVoltageOut` bus model with voltage signal outlets and current signal inlets.
- Added `BusSignalVoltageIn` bus model with voltage signal inlets and current signal outlets.
- Added `enable` and `disable` solver events that put a branch in or out of service.

## v0.2

Expand Down
3 changes: 3 additions & 0 deletions GridKit/Model/PhasorDynamics/Branch/Branch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ namespace GridKit
virtual int evaluateJacobian() override final;
virtual int verify() const override final;

int setInService(bool in_service);

void setR(RealT R)
{
R_ = R;
Expand Down Expand Up @@ -195,6 +197,7 @@ namespace GridKit
RealT Bmag_{0.0};
RealT tap_{1.0};
RealT phase_{0.0};
RealT in_service_{1.0}; ///< 1 in service, 0 out of service
IdxT bus1_id_{0};
IdxT bus2_id_{0};

Expand Down
26 changes: 26 additions & 0 deletions GridKit/Model/PhasorDynamics/Branch/BranchImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,19 @@ namespace GridKit
return ret;
}

/// Put the branch in or out of service; out of service, it injects no current.
template <typename scalar_type, typename index_type>
int Branch<scalar_type, index_type>::setInService(bool in_service)
{
in_service_ = ZERO<RealT>;
if (in_service)
{
in_service_ = ONE<RealT>;
}
setDerivedParams();
return 0;
}

template <typename scalar_type, typename index_type>
__attribute__((always_inline)) inline void Branch<scalar_type, index_type>::addAdmittanceContribution(
const RealT G,
Expand Down Expand Up @@ -296,6 +309,9 @@ namespace GridKit
/**
* @brief Residual contribution of the branch is computed and pushed to the terminal buses.
*
* Out-of-service branches retain bus voltages but contribute no current.
* Empty isolated buses and floating series-only islands can make the
* system Jacobian singular; generator-supported islands need not do so.
*/
template <typename scalar_type, typename index_type>
int Branch<scalar_type, index_type>::evaluateResidual()
Expand Down Expand Up @@ -514,6 +530,16 @@ namespace GridKit

g22_ = g_diag - RealT{0.5} * G_;
b22_ = b_diag - RealT{0.5} * B_;

// An out-of-service branch has zero admittance
g11_ *= in_service_;
b11_ *= in_service_;
g12_ *= in_service_;
b12_ *= in_service_;
g21_ *= in_service_;
b21_ *= in_service_;
g22_ *= in_service_;
b22_ *= in_service_;
}

} // namespace PhasorDynamics
Expand Down
11 changes: 10 additions & 1 deletion GridKit/Model/PhasorDynamics/Branch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ contributions are oriented entering the adjacent buses.
bus 1; both shunts are added outside the $\mathbf{M}$ transformation.
- The branch has no solver-owned variables; it contributes current residuals
directly to the connected buses.
- Taking a branch out of service can leave bus voltages unconstrained and make
the system Jacobian singular. This can happen when an outage leaves a bus
with no other current contributions, or a floating island of series branches
without shunts. An island with generators may still be solvable. The model
does not detect or handle these singular cases.

## Model Parameters

Expand Down Expand Up @@ -94,19 +99,23 @@ The off-nominal transformer transformation uses bus 1 as the tap side:
\end{aligned}
```

The magnetizing and line shunts are added outside the transformation:
The magnetizing and line shunts are added outside the transformation, and the
service status $u$ scales the whole branch:

```math
\begin{aligned}
\mathbf{Y}
&=
u
\left(
\mathbf{M}^{\dagger}
\mathbf{Y}_0
\mathbf{M}
+
\mathbf{Y}_\mathrm{mag}
+
\mathbf{Y}_\mathrm{sh}
\right)
\end{aligned}
```

Expand Down
7 changes: 7 additions & 0 deletions GridKit/Model/PhasorDynamics/SystemModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <map>
#include <memory>
#include <string>

#include <GridKit/Model/PhasorDynamics/Component.hpp>
#include <GridKit/Model/PhasorDynamics/SignalNode/SignalNodeSet.hpp>
Expand All @@ -25,6 +26,9 @@ namespace GridKit
template <typename scalar_type, typename index_type>
class BusFault;

template <typename scalar_type, typename index_type>
class Branch;

template <typename scalar_type, typename index_type>
class SignalNode;

Expand Down Expand Up @@ -105,6 +109,7 @@ namespace GridKit
BusT* getBus(IdxT bus_id);
SignalNodeT* getSignalNode(IdxT signal_id);
ComponentT* getComponent(IdxT gridkit_component_id);
Branch<ScalarT, IdxT>* getBranch(const std::string& id);
BusFault<ScalarT, IdxT>* getBusFault(IdxT fault_id);

private:
Expand All @@ -115,6 +120,8 @@ namespace GridKit
std::map<IdxT, IdxT> gridkit_bus_indices_; ///< Map between gridkit_bus_id and bus_id
std::map<IdxT, IdxT> gridkit_fault_indices_; ///< Map between fault_id and component_id

std::map<std::string, IdxT> gridkit_branch_indices_; ///< Map between branch id and gridkit_component_id

bool owns_components_{false};

/// Variable monitor
Expand Down
13 changes: 13 additions & 0 deletions GridKit/Model/PhasorDynamics/SystemModelImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ namespace GridKit
auto* branch = new Branch<ScalarT, IdxT>(getBus(bus1_index),
getBus(bus2_index),
branchdata);

gridkit_branch_indices_[branchdata.disambiguation_string] = static_cast<IdxT>(components_.size());
addComponent(branch);
}

Expand Down Expand Up @@ -830,6 +832,17 @@ namespace GridKit
return components_[gridkit_component_id];
}

/**
* @brief Return pointer to a branch by case-file `id`
*/
template <typename scalar_type, typename index_type>
Branch<scalar_type, index_type>*
SystemModel<scalar_type, index_type>::getBranch(const std::string& id)
{
// Should fail if user-provided id is incorrect
return dynamic_cast<Branch<ScalarT, IdxT>*>(components_[gridkit_branch_indices_.at(id)]);
}

/**
* @brief Return pointer to a bus fault model
*
Expand Down
10 changes: 8 additions & 2 deletions application/PhasorDynamics/AnalysisUtilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <magic_enum/magic_enum.hpp>
#include <nlohmann/json.hpp>

#include <GridKit/Constants.hpp>
#include <GridKit/Model/PhasorDynamics/SystemModelData.hpp>
#include <GridKit/Solver/Dynamic/Ida.hpp>
#include <GridKit/Testing/TestHelpers.hpp>
Expand All @@ -34,7 +35,9 @@ namespace GridKit
enum class Type
{
FAULT_ON,
FAULT_OFF
FAULT_OFF,
IN_SERVICE,
OUT_OF_SERVICE
};

/// Time event takes place
Expand All @@ -43,6 +46,8 @@ namespace GridKit
Type type;
/// ID of element used in event (e.g., bus fault id)
std::size_t element_id;
/// Case-file `id` of the branch to put in or out of service
std::string device;
};

/**
Expand Down Expand Up @@ -130,7 +135,8 @@ namespace GridKit
{
auto& event = c.events.emplace_back();
raw_event.at("time").get_to(event.time);
raw_event.at("element_id").get_to(event.element_id);
event.element_id = raw_event.value("element_id", INVALID_INDEX<std::size_t>);
event.device = raw_event.value("device", std::string{});

auto type_str = raw_event.at("type").get<std::string>();
using EventType = SystemEvent::Type;
Expand Down
7 changes: 7 additions & 0 deletions application/PhasorDynamics/ContingencyAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <omp.h>
#endif

#include <GridKit/Model/PhasorDynamics/Branch/Branch.hpp>
#include <GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp>
#include <GridKit/Model/PhasorDynamics/SystemModel.hpp>
#include <GridKit/Solver/Dynamic/Ida.hpp>
Expand Down Expand Up @@ -64,6 +65,12 @@ TestStatus runStudy(StudyData study_data)
case EventType::FAULT_OFF:
sys.getBusFault(event.element_id)->setStatus(false);
break;
case EventType::IN_SERVICE:
sys.getBranch(event.device)->setInService(true);
break;
case EventType::OUT_OF_SERVICE:
sys.getBranch(event.device)->setInService(false);
break;
}

// Re-initialize simulation at event time
Expand Down
7 changes: 7 additions & 0 deletions application/PhasorDynamics/DynamicSimulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <filesystem>
#include <fstream>

#include <GridKit/Model/PhasorDynamics/Branch/Branch.hpp>
#include <GridKit/Model/PhasorDynamics/BusFault/BusFault.hpp>
#include <GridKit/Model/PhasorDynamics/SystemModel.hpp>
#include <GridKit/Solver/Dynamic/Ida.hpp>
Expand Down Expand Up @@ -63,6 +64,12 @@ int runApplication(int argc, const char* argv[])
case EventType::FAULT_OFF:
sys.getBusFault(event.element_id)->setStatus(false);
break;
case EventType::IN_SERVICE:
sys.getBranch(event.device)->setInService(true);
break;
case EventType::OUT_OF_SERVICE:
sys.getBranch(event.device)->setInService(false);
break;
}

// Re-initialize simulation at event time
Expand Down
5 changes: 3 additions & 2 deletions application/PhasorDynamics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ Each event group describes a system event that occurs at a given time point
Name | Value
--------------------|-------------------------------------------------------
`time` | A floating point value for time event occurs
`type` | Event type (one of { "fault_on", "fault_off" })
`element_id` | An integer value referencing the element associated with the event (e.g., bus fault id)
`type` | Event type (one of { "fault_on", "fault_off", "in_service", "out_of_service" })
`element_id` | An integer value referencing the element associated with the event (e.g., bus fault id)
`device` | `id` of the branch to put in service ("enable") or take out of service ("disable")
1 change: 1 addition & 0 deletions examples/PhasorDynamics/DynamicSimulation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ inputs.

| Example | Description |
| --- | --- |
| [ThreeBusBasic](Toy/ThreeBusBasic/README.md) | A three-bus line opened and reclosed. |
| [ThreeBusConstantSource](Toy/ThreeBusConstantSource/README.md) | A three-bus constant signal source example. |
| [ACTIVSg10k](ACTIVSg10k/README.md) | A short simulation without disturbances using the reusable ACTIVSg10k case. |
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(ThreeBusBasic)
add_subdirectory(ThreeBusConstantSource)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
gridkit_example_add_file(${CMAKE_SOURCE_DIR}/cases/PhasorDynamics/Toy/ThreeBusBasic.case.json)
gridkit_example_add_file(ThreeBusBasic.solver.json)

add_test(
NAME ThreeBusBasic_line_switching
COMMAND DynamicSimulation ThreeBusBasic.solver.json
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# ThreeBusBasic Line Switching

This study uses the reusable
[ThreeBusBasic case](../../../../../cases/PhasorDynamics/Toy/README.md).
Branch `BR_1_2` is taken out of service at 1 s and put back in service at 5 s.

![Bus voltage magnitude](figures/ThreeBusBasic.Vm.png)
![Generator speed](figures/ThreeBusBasic.speed.png)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"system_model_file": "ThreeBusBasic.case.json",
"output_file": "ThreeBusBasic.csv",
"dt_monitor": 0.004166666666666667,
"tmax": 10.0,
"events": [
{ "time": 1.0, "type": "out_of_service", "device": "BR_1_2" },
{ "time": 5.0, "type": "in_service", "device": "BR_1_2" }
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -234,5 +234,15 @@ int main()
error_set->display();
success *= error_set->total_error.max_value < 1e-4;

// Check that the generator-supported island remains solvable.
success *= branch56.setInService(false) == 0;
success *= ida.initializeSimulation(10.0) == 0;
success *= ida.runSimulation(10.1) == 0;
success *= sys.evaluateResidual() == 0;
for (index_type i = 0; i < sys.size(); ++i)
{
success *= isEqual(sys.getResidual().getData()[i], real_type{0.0}, 1e-5);
}

return success.report("TenGenClassical");
}
66 changes: 66 additions & 0 deletions tests/UnitTests/PhasorDynamics/BranchTests.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include <complex>
#include <iomanip>
#include <iostream>
#include <vector>

#include <GridKit/AutomaticDifferentiation/DependencyTracking/Variable.hpp>
#include <GridKit/Definitions.hpp>
#include <GridKit/Model/PhasorDynamics/Branch/Branch.hpp>
#include <GridKit/Model/PhasorDynamics/Bus/Bus.hpp>
#include <GridKit/Model/PhasorDynamics/Bus/BusInfinite.hpp>
Expand Down Expand Up @@ -243,6 +245,70 @@ namespace GridKit
return success.report(__func__);
}

#ifdef GRIDKIT_ENABLE_ENZYME
TestOutcome outOfServiceJacobian()
{
// Verify zero currents, fixed sparsity, and restoration after switching.
TestStatus success = true;

PhasorDynamics::Bus<ScalarT, IdxT> bus1(10.0, 20.0);
PhasorDynamics::Bus<ScalarT, IdxT> bus2(30.0, 40.0);
bus1.allocate();
bus2.allocate();
bus1.initialize();
bus2.initialize();
for (IdxT i = 0; i < 2; ++i)
{
bus1.setVariableIndex(i, i);
bus1.setResidualIndex(i, i);
bus2.setVariableIndex(i, i + 2);
bus2.setResidualIndex(i, i + 2);
}

PhasorDynamics::Branch<ScalarT, IdxT> branch(&bus1, &bus2, 2.0, 4.0, 0.2, 1.2);
branch.allocate();
branch.evaluateJacobian();
const IdxT nnz = branch.nnz();
success *= nnz > 0;
auto* jacobian = branch.getCooJacobian();
const std::vector<IdxT> rows(jacobian->getRowData(), jacobian->getRowData() + nnz);
const std::vector<IdxT> cols(jacobian->getColData(), jacobian->getColData() + nnz);
const std::vector<RealT> values(jacobian->getValues(), jacobian->getValues() + nnz);

success *= branch.setInService(false) == 0;
bus1.evaluateResidual();
bus2.evaluateResidual();
branch.evaluateResidual();
success *= isEqual(bus1.Ir(), ScalarT{0.0});
success *= isEqual(bus1.Ii(), ScalarT{0.0});
success *= isEqual(bus2.Ir(), ScalarT{0.0});
success *= isEqual(bus2.Ii(), ScalarT{0.0});
branch.evaluateJacobian();
success *= branch.nnz() == nnz;

jacobian = branch.getCooJacobian();
for (IdxT i = 0; i < nnz; ++i)
{
success *= jacobian->getRowData()[i] == rows[i];
success *= jacobian->getColData()[i] == cols[i];
success *= isEqual(jacobian->getValues()[i], RealT{0.0});
}

success *= branch.setInService(true) == 0;
branch.evaluateJacobian();
success *= branch.nnz() == nnz;
jacobian = branch.getCooJacobian();
for (IdxT i = 0; i < nnz; ++i)
{
success *= jacobian->getRowData()[i] == rows[i];
success *= jacobian->getColData()[i] == cols[i];
success *= isEqual(jacobian->getValues()[i], values[i]);
}

return success.report(__func__);
}
#endif

TestOutcome parameterSetters()
{
// Verifies parameter setters refresh derived admittance values.
Expand Down
Loading
Loading