Skip to content
Merged
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
6 changes: 4 additions & 2 deletions docs/developer-guide/andes-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ its importer mapping and a numerical regression test are both present.
| `PV`, `Slack` | `AcBus` plus `Generator` | Implemented | Active-power and voltage targets are imported. |
| `Line` | `AcLine` | Partial | `r`, `x`, `b`, tap, phase shift, and status are imported. ANDES line-rated-voltage (`Vn1`/`Vn2`) base conversion, including its implicit 110 kV default, still needs an equivalent mixed-base implementation. |
| `Area` | `GridArea` | Partial | A GridDyn analogue exists; ANDES `Area` is not yet imported. |
| `Shunt` | fixed-admittance `ZipLoad` | Partial | The equivalent exists but needs a direct mapping and test. |
| `Shunt` | fixed-admittance `ZipLoad` | Implemented | Device `Sn`/`Vn` admittance-base conversion, status, conductance, and capacitive susceptance sign are mapped and numerically tested. |
| `ShuntSw` | `loads::Svd` | Partial | Both provide switched reactive support; block/control semantics need mapping and numerical tests. |
| `ShuntTD` | fixed shunt for power flow | Partial | Its steady-state behavior follows `Shunt`; its time-domain phase-voltage outputs are not represented. |
| `Jumper` | `links::ZBreaker` | Partial | Both merge buses through a zero-impedance connection; no importer mapping/test yet. |
| `Jumper` | `links::ZBreaker` | Implemented | End buses and status are imported; active jumpers merge the bus solutions. Network voltages and angles are numerically tested. ANDES jumper `p`/`q` transfer reporting has no direct `ZBreaker` output. |
| `Motor3`, `Motor5` | `MotorLoad3`, `MotorLoad5` | Partial | Candidate GridDyn models exist; parameter mapping and initialization comparisons remain. |
| `Fortescue` | none | No direct analogue | Requires a multi-terminal positive-/negative-/zero-sequence interface model. |
| `Node`, `Ground` | `DcBus` | Implemented | Ground is imported as a DC swing reference. |
Expand Down Expand Up @@ -95,4 +95,6 @@ it also needs native-input mapping, initialization, and a trajectory test.
| -------------------------- | ----------------------------------------------------------------------- |
| `andes_kundur_vsc_pflow` | 10-bus AC network, DC resistor, PQ/VQ VSC controls, and AC/DC coupling. |
| `andes_two_bus_pflow` | Minimal AC Slack/PQ/Line power flow. |
| `andes_shunt_pflow` | Fixed conductance/susceptance shunt with non-system `Sn`/`Vn` bases. |
| `andes_jumper_pflow` | Active and inactive zero-impedance jumpers in a loaded AC network. |
| `andes_vsc_resistor_pflow` | Minimal AC/DC `VSCShunt` plus DC resistance power flow. |
49 changes: 49 additions & 0 deletions src/fileInput/gridReadAndes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "griddyn/links/AcLine.h"
#include "griddyn/links/DcLink.h"
#include "griddyn/links/VSCShunt.h"
#include "griddyn/links/ZBreaker.h"
#include "griddyn/loads/ZipLoad.h"
#include "griddyn/primary/AcBus.h"
#include "griddyn/primary/DcBus.h"
Expand Down Expand Up @@ -118,6 +119,38 @@ bool loadAndesJson(CoreObject* parentObject, const std::string& fileName)
bus->second->add(load);
}
}
if (document.contains("Shunt") && document["Shunt"].is_array()) {
const auto parentBasePower = parentObject->get("basepower");
const auto systemBasePower = (parentBasePower > 0.0) ? parentBasePower : 100.0;
for (const auto& record : document["Shunt"]) {
const auto busIndex = indexKey(record, "bus");
const auto bus = acBuses.find(busIndex);
const auto baseVoltage = acBaseVoltages.find(busIndex);
if ((bus == acBuses.end()) || (baseVoltage == acBaseVoltages.end())) {
continue;
}

// ANDES marks g and b as y=True parameters. Convert from the
// device base to the system/bus base using Zb/Zn, then map the
// ANDES injection convention P=g*V^2, Q=-b*V^2 to ZipLoad.
const auto deviceBasePower = number(record, "Sn", 100.0);
const auto deviceBaseVoltage = number(record, "Vn", 110.0);
const auto busBaseVoltage = baseVoltage->second;
const auto admittanceScale =
(deviceBasePower > 0.0 && deviceBaseVoltage > 0.0 && busBaseVoltage > 0.0) ?
(deviceBasePower / systemBasePower) *
std::pow(busBaseVoltage / deviceBaseVoltage, 2) :
1.0;

auto* shunt = new ZipLoad(objectName(record, "Shunt"));
shunt->set("yp", number(record, "g") * admittanceScale);
shunt->set("yq", -number(record, "b") * admittanceScale);
if (number(record, "u", 1.0) == 0.0) {
shunt->disable();
}
bus->second->add(shunt);
}
}
if (document.contains("PV") && document["PV"].is_array()) {
for (const auto& record : document["PV"]) {
const auto bus = acBuses.find(indexKey(record, "bus"));
Expand Down Expand Up @@ -167,6 +200,22 @@ bool loadAndesJson(CoreObject* parentObject, const std::string& fileName)
parentObject->add(line);
}
}
if (document.contains("Jumper") && document["Jumper"].is_array()) {
for (const auto& record : document["Jumper"]) {
const auto first = acBuses.find(indexKey(record, "bus1"));
const auto second = acBuses.find(indexKey(record, "bus2"));
if ((first == acBuses.end()) || (second == acBuses.end())) {
continue;
}
auto* jumper = new links::ZBreaker(objectName(record, "Jumper"));
jumper->updateBus(first->second, 1);
jumper->updateBus(second->second, 2);
if (number(record, "u", 1.0) == 0.0) {
jumper->disable();
}
parentObject->add(jumper);
}
}

std::unordered_map<std::string, DcBus*> dcBuses;
for (const auto& record : document["Node"]) {
Expand Down
4 changes: 4 additions & 0 deletions src/griddyn/links/ZBreaker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,16 @@ void ZBreaker::merge()
{
B1->mergeBus(B2);
merged = true;
B1->alert(this, STATE_COUNT_CHANGE);
B2->alert(this, STATE_COUNT_CHANGE);
}

void ZBreaker::unmerge()
{
B1->unmergeBus(B2);
merged = false;
B1->alert(this, STATE_COUNT_CHANGE);
B2->alert(this, STATE_COUNT_CHANGE);
}

int ZBreaker::fixRealPower(double /*power*/,
Expand Down
68 changes: 68 additions & 0 deletions test/libraryTests/testJsonReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "formatInterpreters/jsonReaderElement.h"
#include "griddyn/links/DcLink.h"
#include "griddyn/links/VSCShunt.h"
#include "griddyn/links/ZBreaker.h"
#include "griddyn/loads/ZipLoad.h"
#include "griddyn/primary/AcBus.h"
#include "griddyn/primary/DcBus.h"
#include <array>
Expand Down Expand Up @@ -375,6 +377,72 @@ TEST(AndesPowerFlowTests, MatchesCapturedTwoBusReference)
}
}

TEST(AndesPowerFlowTests, MatchesCapturedShuntReference)
{
std::ifstream input(makeAndesTestPath("andes_shunt_pflow_reference.json"));
ASSERT_TRUE(input.is_open());
nlohmann::json reference;
input >> reference;
const auto tolerance = reference.at("tolerance").get<double>();

auto simulation = std::make_unique<griddyn::GridDynSimulation>();
griddyn::loadFile(simulation.get(), makeAndesTestPath("andes_shunt_pflow.json"));
ASSERT_EQ(simulation->powerflow(), 0);

const std::array<std::string, 2> busNames{"slack_bus", "shunt_bus"};
for (std::size_t index = 0; index < busNames.size(); ++index) {
auto* bus = dynamic_cast<griddyn::AcBus*>(simulation->find(busNames[index]));
ASSERT_NE(bus, nullptr);
EXPECT_NEAR(bus->get("voltage"), reference["bus_voltage"][index].get<double>(), tolerance);
EXPECT_NEAR(bus->get("angle"), reference["bus_angle"][index].get<double>(), tolerance);
}

auto* shunt = dynamic_cast<griddyn::ZipLoad*>(simulation->find("shunt_bus::fixed_shunt"));
ASSERT_NE(shunt, nullptr);
EXPECT_NEAR(shunt->get("yp"), reference["shunt_yp"].get<double>(), tolerance);
EXPECT_NEAR(shunt->get("yq"), reference["shunt_yq"].get<double>(), tolerance);
EXPECT_NEAR(shunt->getRealPower(), reference["shunt_p"].get<double>(), tolerance);
EXPECT_NEAR(shunt->getReactivePower(), reference["shunt_q"].get<double>(), tolerance);
}

TEST(AndesPowerFlowTests, MatchesCapturedJumperReference)
{
std::ifstream input(makeAndesTestPath("andes_jumper_pflow_reference.json"));
ASSERT_TRUE(input.is_open());
nlohmann::json reference;
input >> reference;
const auto tolerance = reference.at("tolerance").get<double>();

auto simulation = std::make_unique<griddyn::GridDynSimulation>();
griddyn::loadFile(simulation.get(), makeAndesTestPath("andes_jumper_pflow.json"));
ASSERT_EQ(simulation->powerflow(), 0);

const std::array<std::string, 3> busNames{"slack_bus", "jumper_bus", "load_bus"};
for (std::size_t index = 0; index < busNames.size(); ++index) {
auto* bus = dynamic_cast<griddyn::AcBus*>(simulation->find(busNames[index]));
ASSERT_NE(bus, nullptr);
EXPECT_NEAR(bus->get("voltage"), reference["bus_voltage"][index].get<double>(), tolerance);
EXPECT_NEAR(bus->get("angle"), reference["bus_angle"][index].get<double>(), tolerance);
}

auto* active = dynamic_cast<griddyn::links::ZBreaker*>(simulation->find("active_jumper"));
ASSERT_NE(active, nullptr);
EXPECT_TRUE(active->isEnabled());
ASSERT_NE(active->getBus(1), nullptr);
ASSERT_NE(active->getBus(2), nullptr);
EXPECT_EQ(active->getBus(1)->getName(), "slack_bus");
EXPECT_EQ(active->getBus(2)->getName(), "jumper_bus");
EXPECT_NEAR(active->getBus(1)->get("voltage"), active->getBus(2)->get("voltage"), tolerance);
EXPECT_NEAR(active->getBus(1)->get("angle"), active->getBus(2)->get("angle"), tolerance);

auto* inactive = dynamic_cast<griddyn::links::ZBreaker*>(simulation->find("inactive_jumper"));
ASSERT_NE(inactive, nullptr);
EXPECT_FALSE(inactive->isEnabled());
EXPECT_GT(std::abs(reference["bus_voltage"][0].get<double>() -
reference["bus_voltage"][2].get<double>()),
tolerance);
}

TEST(AndesPowerFlowTests, MatchesCapturedVscResistorReference)
{
std::ifstream input(makeAndesTestPath("andes_vsc_resistor_pflow_reference.json"));
Expand Down
38 changes: 38 additions & 0 deletions test/test_files/andes_tests/andes_jumper_pflow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"Bus": [
{ "idx": 1, "name": "slack_bus", "Vn": 230.0, "v0": 1.0, "a0": 0.0 },
{ "idx": 2, "name": "jumper_bus", "Vn": 230.0, "v0": 1.0, "a0": 0.0 },
{ "idx": 3, "name": "load_bus", "Vn": 230.0, "v0": 0.98, "a0": -0.1 }
],
"Slack": [{ "idx": 1, "bus": 1, "p0": 0.0, "q0": 0.0, "v0": 1.0, "a0": 0.0 }],
"PQ": [{ "idx": "PQ_1", "bus": 3, "p0": 1.0, "q0": 0.3 }],
"Line": [
{
"idx": "Line_1",
"bus1": 2,
"bus2": 3,
"Vn1": 230.0,
"Vn2": 230.0,
"r": 0.01,
"x": 0.1,
"b": 0.02
}
],
"Jumper": [
{
"idx": "Jumper_1",
"name": "active_jumper",
"bus1": 1,
"bus2": 2,
"u": 1
},
{
"idx": "Jumper_2",
"name": "inactive_jumper",
"bus1": 1,
"bus2": 3,
"u": 0
}
],
"Node": []
}
10 changes: 10 additions & 0 deletions test/test_files/andes_tests/andes_jumper_pflow_reference.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"source": "ANDES pflow: andes_jumper_pflow.json (single worker)",
"tolerance": 1e-6,
"bus_voltage": [1.0000000000000588, 1.0000000000001175, 0.9538230766212176],
"bus_angle": [
5.872574183726748e-15, 1.1745148367455111e-14, -0.10196800834017775
],
"jumper_p": [1.011921860424372, 0.0],
"jumper_q": [0.4001207123321315, 0.0]
}
33 changes: 33 additions & 0 deletions test/test_files/andes_tests/andes_shunt_pflow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"Bus": [
{ "idx": 1, "name": "slack_bus", "Vn": 230.0, "v0": 1.0, "a0": 0.0 },
{ "idx": 2, "name": "shunt_bus", "Vn": 230.0, "v0": 0.98, "a0": -0.08 }
],
"Slack": [{ "idx": 1, "bus": 1, "p0": 0.0, "q0": 0.0, "v0": 1.0, "a0": 0.0 }],
"PQ": [{ "idx": "PQ_1", "bus": 2, "p0": 0.8, "q0": 0.3 }],
"Shunt": [
{
"idx": "Shunt_1",
"name": "fixed_shunt",
"bus": 2,
"Sn": 50.0,
"Vn": 115.0,
"g": 0.02,
"b": 0.1,
"u": 1
}
],
"Line": [
{
"idx": "Line_1",
"bus1": 1,
"bus2": 2,
"Vn1": 230.0,
"Vn2": 230.0,
"r": 0.01,
"x": 0.1,
"b": 0.02
}
],
"Node": []
}
10 changes: 10 additions & 0 deletions test/test_files/andes_tests/andes_shunt_pflow_reference.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"source": "ANDES pflow: andes_shunt_pflow.json (single worker)",
"tolerance": 1e-6,
"bus_voltage": [1.000000000002938, 0.9776771355574213],
"bus_angle": [2.7532649205730654e-13, -0.08482362610684113],
"shunt_yp": 0.04,
"shunt_yq": -0.2,
"shunt_p": 0.03823410325567057,
"shunt_q": -0.19117051627835288
}