DPsim
Loading...
Searching...
No Matches
SystemTopology.cpp
1/* Copyright 2017-2021 Institute for Automation of Complex Power Systems,
2 * EONERC, RWTH Aachen University
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7 *********************************************************************************/
8
9#include <fstream>
10#include <iomanip>
11#include <iostream>
12#include <unordered_map>
13
14#include <dpsim-models/SP/SP_Ph1_SynchronGenerator.h>
15#include <dpsim-models/SystemTopology.h>
16
17using namespace CPS;
18
19Matrix SystemTopology::initFrequency(Real frequency) const {
20 Matrix frequencies(1, 1);
21 frequencies << frequency;
22 return frequencies;
23}
24
25void SystemTopology::addNode(TopologicalNode::Ptr topNode) {
26 if (auto nodeComplex = std::dynamic_pointer_cast<SimNode<Complex>>(topNode))
27 nodeComplex->initialize(mFrequencies);
28 if (auto nodeReal = std::dynamic_pointer_cast<SimNode<Real>>(topNode))
29 nodeReal->initialize(mFrequencies);
30
31 mNodes.push_back(topNode);
32}
33
34void SystemTopology::addNodeAt(TopologicalNode::Ptr topNode, UInt index) {
35 if (auto node = std::dynamic_pointer_cast<SimNode<Complex>>(topNode))
36 node->initialize(mFrequencies);
37 if (auto nodeReal = std::dynamic_pointer_cast<SimNode<Real>>(topNode))
38 nodeReal->initialize(mFrequencies);
39
40 if (index > mNodes.capacity())
41 mNodes.resize(index + 1);
42
43 mNodes[index] = topNode;
44}
45
46void SystemTopology::addNodes(const TopologicalNode::List &topNodes) {
47 for (auto topNode : topNodes)
48 addNode(topNode);
49}
50
51void SystemTopology::addComponent(IdentifiedObject::Ptr component) {
52 if (auto powerCompComplex =
53 std::dynamic_pointer_cast<SimPowerComp<Complex>>(component))
54 powerCompComplex->initialize(mFrequencies);
55 if (auto powerCompReal =
56 std::dynamic_pointer_cast<SimPowerComp<Real>>(component))
57 powerCompReal->initialize(mFrequencies);
58
59 mComponents.push_back(component);
60}
61
62template <typename VarType>
64 typename SimPowerComp<VarType>::Ptr component,
65 typename SimNode<VarType>::List simNodes) {
66 component->connect(simNodes);
67 for (auto simNode : simNodes)
68 mComponentsAtNode[simNode].push_back(component);
69}
70
71void SystemTopology::componentsAtNodeList() {
72 for (auto comp : mComponents) {
73 auto powerComp = std::dynamic_pointer_cast<TopologicalPowerComp>(comp);
74 if (powerComp)
75 for (auto topoNode : powerComp->topologicalNodes())
76 mComponentsAtNode[topoNode].push_back(powerComp);
77 }
78}
79
80void SystemTopology::addComponents(const IdentifiedObject::List &components) {
81 for (auto comp : components)
82 addComponent(comp);
83}
84
86 CPS::Domain domain) {
87
88 for (auto nodePF : systemPF.mNodes) {
89 if (auto node = this->node<TopologicalNode>(nodePF->name())) {
90 //SPDLOG_LOGGER_INFO(mSLog, "Updating initial voltage of {} according to powerflow", node->name());
91 //SPDLOG_LOGGER_INFO(mSLog, "Former initial voltage: {}", node->initialSingleVoltage());
92 node->setInitialVoltage(
93 std::dynamic_pointer_cast<CPS::SimNode<CPS::Complex>>(nodePF)
94 ->singleVoltage());
95 //SPDLOG_LOGGER_INFO(mSLog, "Updated initial voltage: {}", node->initialSingleVoltage());
96 }
97 }
98
99 // set initial power of SG
100 for (auto compPF : systemPF.mComponents) {
101 if (auto genPF = std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
102 compPF)) {
103 if (domain == CPS::Domain::DP || domain == CPS::Domain::SP) {
104 auto comp = this->component<SimPowerComp<Complex>>(compPF->name());
105 auto terminal = comp->terminals()[0];
106 terminal->setPower(-genPF->getApparentPower());
107 } else if (domain == CPS::Domain::EMT) {
108 auto comp = this->component<SimPowerComp<Real>>(compPF->name());
109 auto terminal = comp->terminals()[0];
110 terminal->setPower(-genPF->getApparentPower());
111 }
112 //SPDLOG_LOGGER_INFO(mSLog, "Updated initial power of gen {}: {}", compPF->name(), genPF->getApparentPower());
113 }
114 }
115}
116
117void SystemTopology::addTearComponent(IdentifiedObject::Ptr component) {
118 if (auto powerCompComplex =
119 std::dynamic_pointer_cast<SimPowerComp<Complex>>(component))
120 powerCompComplex->initialize(mFrequencies);
121
122 if (auto powerCompReal =
123 std::dynamic_pointer_cast<SimPowerComp<Real>>(component))
124 powerCompReal->initialize(mFrequencies);
125
126 mTearComponents.push_back(component);
127}
128
130 const IdentifiedObject::List &components) {
131 for (auto comp : components)
132 addTearComponent(comp);
133}
134
135template <typename Type>
136typename std::shared_ptr<Type> SystemTopology::node(UInt index) {
137 if (index < mNodes.size()) {
138 auto topoNode = mNodes[index];
139 auto node = std::dynamic_pointer_cast<Type>(topoNode);
140 if (node)
141 return node;
142 }
143
144 return nullptr;
145}
146
147template <typename Type>
148typename std::shared_ptr<Type> SystemTopology::node(std::string_view name) {
149 for (auto topoNode : mNodes) {
150 if (topoNode->name() == name) {
151 auto node = std::dynamic_pointer_cast<Type>(topoNode);
152 if (node)
153 return node;
154 else
155 return nullptr;
156 }
157 }
158 return nullptr;
159}
160
161std::map<String, String, std::less<>> SystemTopology::listIdObjects() const {
162 std::map<String, String, std::less<>> objTypeMap;
163
164 for (auto node : mNodes) {
165 objTypeMap[node->name()] = node->type();
166 }
167 for (auto comp : mComponents) {
168 objTypeMap[comp->name()] = comp->type();
169 }
170 return objTypeMap;
171}
172
173template <typename VarType>
174void SystemTopology::multiplyPowerComps(Int numberCopies) {
175 typename SimNode<VarType>::List newNodes;
176 typename SimPowerComp<VarType>::List newComponents;
177
178 for (int copy = 0; copy < numberCopies; copy++) {
179 std::unordered_map<typename SimNode<VarType>::Ptr,
180 typename SimNode<VarType>::Ptr>
181 nodeMap;
182 String copySuffix = "_" + std::to_string(copy + 2);
183
184 // copy nodes
185 typename SimNode<VarType>::Ptr nodePtr;
186 for (size_t nNode = 0; nNode < mNodes.size(); nNode++) {
187 auto nodePtr = this->node<SimNode<VarType>>(static_cast<UInt>(nNode));
188 if (!nodePtr)
189 continue;
190
191 // GND is not copied
192 if (nodePtr->isGround()) {
193 nodeMap[nodePtr] = nodePtr;
194 } else {
195 auto nodeCpy = SimNode<VarType>::make(nodePtr->name() + copySuffix,
196 nodePtr->phaseType());
197 nodeCpy->setInitialVoltage(nodePtr->initialVoltage());
198 nodeMap[nodePtr] = nodeCpy;
199 newNodes.push_back(nodeCpy);
200 }
201 }
202
203 // copy components
204 for (auto genComp : mComponents) {
205 auto comp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(genComp);
206 if (!comp)
207 continue;
208 auto copy = comp->clone(comp->name() + copySuffix);
209 if (!copy)
210 throw SystemError("copy() not implemented for " + comp->name());
211
212 // map the nodes to their new copies, creating new terminals
213 typename SimNode<VarType>::List nodeCopies;
214 for (UInt nNode = 0; nNode < comp->terminalNumber(); nNode++) {
215 nodeCopies.push_back(nodeMap[comp->node(nNode)]);
216 }
217 copy->connect(nodeCopies);
218
219 // update the terminal powers for powerflow initialization
220 for (UInt nTerminal = 0; nTerminal < comp->terminalNumber();
221 nTerminal++) {
222 copy->terminal(nTerminal)->setPower(comp->terminal(nTerminal)->power());
223 }
224 newComponents.push_back(copy);
225 }
226 }
227 for (auto node : newNodes)
228 addNode(node);
229 for (auto comp : newComponents)
230 addComponent(comp);
231}
232
233void SystemTopology::multiply(Int numCopies) {
234 // SimPowerComps should be all EMT or all DP anyway, but this way we don't have to look
235 multiplyPowerComps<Real>(numCopies);
236 multiplyPowerComps<Complex>(numCopies);
237}
238
241 // for (auto c : mComponents) {
242 // c->reset();
243 // }
244}
245
246void SystemTopology::removeComponent(const String &name) {
247 for (auto it = mComponents.begin(); it != mComponents.end();) {
248 if ((*it)->name() == name) {
249 it = mComponents.erase(
250 it); // safe: returns next valid iterator when erasing
251 } else {
252 ++it;
253 }
254 }
255}
256
257template <typename VarType>
258void SystemTopology::splitSubnets(std::vector<SystemTopology> &splitSystems) {
259 std::unordered_map<typename SimNode<VarType>::Ptr, int> subnet;
260 int numberSubnets = checkTopologySubnets<VarType>(subnet);
261 if (numberSubnets == 1) {
262 splitSystems.push_back(*this);
263 } else {
264 std::vector<IdentifiedObject::List> components(numberSubnets);
265 std::vector<TopologicalNode::List> nodes(numberSubnets);
266
267 // Split nodes into subnet groups
268 for (auto node : mNodes) {
269 auto pnode = std::dynamic_pointer_cast<SimNode<VarType>>(node);
270 if (!pnode || node->isGround())
271 continue;
272
273 nodes[subnet[pnode]].push_back(node);
274 }
275
276 // Split components into subnet groups
277 for (auto comp : mComponents) {
278 auto pcomp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(comp);
279 if (!pcomp) {
280 // TODO this should only be signal components.
281 // Proper solution would be to pass them to a different "solver"
282 // since they are actually independent of which solver we use
283 // for the electric part.
284 // Just adding them to an arbitrary solver for now has the same effect.
285 components[0].push_back(comp);
286 continue;
287 }
288 for (UInt nodeIdx = 0; nodeIdx < pcomp->terminalNumber(); nodeIdx++) {
289 if (!pcomp->node(nodeIdx)->isGround()) {
290 components[subnet[pcomp->node(nodeIdx)]].push_back(comp);
291 break;
292 }
293 }
294 }
295 for (int currentNet = 0; currentNet < numberSubnets; currentNet++) {
296 splitSystems.emplace_back(mSystemFrequency, nodes[currentNet],
297 components[currentNet]);
298 }
299 }
300}
301
302template <typename VarType>
303int SystemTopology::checkTopologySubnets(
304 std::unordered_map<typename SimNode<VarType>::Ptr, int> &subnet) {
305 std::unordered_map<typename SimNode<VarType>::Ptr,
306 typename SimNode<VarType>::List>
307 neighbours;
308
309 for (auto comp : mComponents) {
310 auto pcomp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(comp);
311 if (!pcomp)
312 continue;
313
314 for (UInt nodeIdx1 = 0; nodeIdx1 < pcomp->terminalNumberConnected();
315 nodeIdx1++) {
316 for (UInt nodeIdx2 = 0; nodeIdx2 < nodeIdx1; nodeIdx2++) {
317 auto node1 = pcomp->node(nodeIdx1);
318 auto node2 = pcomp->node(nodeIdx2);
319 if (node1->isGround() || node2->isGround())
320 continue;
321
322 neighbours[node1].push_back(node2);
323 neighbours[node2].push_back(node1);
324 }
325 }
326 }
327
328 int currentNet = 0;
329 size_t totalNodes = mNodes.size();
330 for (auto tnode : mNodes) {
331 auto node = std::dynamic_pointer_cast<SimNode<VarType>>(tnode);
332 if (!node || tnode->isGround()) {
333 totalNodes--;
334 }
335 }
336
337 while (subnet.size() != totalNodes) {
338 std::list<typename SimNode<VarType>::Ptr> nextSet;
339
340 for (auto tnode : mNodes) {
341 auto node = std::dynamic_pointer_cast<SimNode<VarType>>(tnode);
342 if (!node || tnode->isGround())
343 continue;
344
345 if (subnet.find(node) == subnet.end()) {
346 nextSet.push_back(node);
347 break;
348 }
349 }
350 while (!nextSet.empty()) {
351 auto node = nextSet.front();
352 nextSet.pop_front();
353
354 subnet[node] = currentNet;
355 for (auto neighbour : neighbours[node]) {
356 if (subnet.find(neighbour) == subnet.end())
357 nextSet.push_back(neighbour);
358 }
359 }
360 currentNet++;
361 }
362 return currentNet;
363}
364
365#ifdef WITH_GRAPHVIZ
366
367Graph::Graph SystemTopology::topologyGraph() {
368 Graph::Node *n, *c;
369
370 Graph::Graph g("topology", Graph::Type::undirected);
371
372 g.set("splines", "polyline");
373
374 for (auto node : mNodes) {
375 n = g.addNode(node->uid());
376
377 std::stringstream label, tooltip;
378
379 tooltip << node->uid();
380
381 label << "<FONT POINT-SIZE=\"12\"><B>" << node->name()
382 << "</B></FONT><BR/>";
383
384 double phase = 180.0 / M_PI * std::arg(node->initialSingleVoltage());
385 double mag = std::abs(node->initialSingleVoltage());
386
387 const char *suffixes[] = {"", "k", "M", "G"};
388
389 int s;
390 for (s = 0; s < 3 && mag > 1000; s++)
391 mag *= 1e-3;
392
393 if (node->initialSingleVoltage() != Complex(0, 0)) {
394 label << std::setprecision(2) << std::fixed;
395 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">";
396 label << "(" << mag << " " << suffixes[s] << "V &gt; " << phase << "°)";
397 label << "</FONT>";
398 }
399
400 n->set("xlabel", label.str(), true);
401 n->set("tooltip", tooltip.str(), true);
402 n->set("fillcolor", phase == 0 ? "red" : "black");
403 n->set("fixedsize", "true");
404 n->set("width", "0.15");
405 n->set("height", "0.15");
406 n->set("shape", "point");
407 }
408
409 std::map<String, String> compColorMap;
410
411 for (auto comp : mComponents) {
412 if (!comp) // TODO: this is a bug in the CIM::Reader!
413 continue;
414
415 TopologicalPowerComp::Ptr topoComp;
416
417 if (!(topoComp = std::dynamic_pointer_cast<TopologicalPowerComp>(comp)))
418 continue;
419
420 c = g.addNode(topoComp->uid());
421
422 auto type = topoComp->type();
423 auto name = topoComp->name();
424
425 std::stringstream label, tooltip;
426
427 label << "<FONT POINT-SIZE=\"12\"><B>" << name << "</B></FONT><BR/>";
428 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">" << type
429 << "</FONT><BR/>";
430 if (topoComp->description() != "") {
431 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">"
432 << topoComp->description() << "</FONT>";
433 }
434
435 tooltip << "Attributes:";
436 for (auto it : topoComp->attributes()) {
437 tooltip << std::endl << it.first << ": " << it.second->toString();
438 }
439
440 if (compColorMap.find(type) != compColorMap.end()) {
441 compColorMap[type] =
442 String("/paired9/") + std::to_string(1 + compColorMap.size() % 9);
443 }
444
445 c->set("color", compColorMap[type]);
446 c->set("label", label.str(), true);
447 c->set("tooltip", tooltip.str(), true);
448 c->set("style", "rounded,filled,bold");
449
450 if (type.find("Line") == std::string::npos) {
451 c->set("shape", "rectangle");
452 c->set("fillcolor", "gray93");
453 } else {
454 c->set("shape", "plaintext");
455 c->set("fillcolor", "transparent");
456 }
457
458 for (auto term : topoComp->topologicalTerminals()) {
459 n = g.node(term->topologicalNodes()->uid());
460 if (!n)
461 continue;
462
463 g.addEdge(term->uid(), c, n);
464 }
465 }
466
467 return g;
468}
469
470String SystemTopology::render() {
471 auto graph = this->topologyGraph();
472 std::stringstream ss;
473 graph.render(ss, "neato", "svg");
474
475 return ss.str();
476}
477
478void SystemTopology::renderToFile(String filename) {
479 std::ofstream ofstr(filename);
480 this->topologyGraph().render(ofstr, "neato", "svg");
481}
482#endif
483
484// Explicit instantiation of template functions to be able to keep the definition in the cpp
485template void SystemTopology::multiplyPowerComps<Real>(Int numberCopies);
486template void SystemTopology::multiplyPowerComps<Complex>(Int numberCopies);
487template std::shared_ptr<TopologicalNode>
489template std::shared_ptr<TopologicalNode>
490SystemTopology::node<TopologicalNode>(std::string_view name);
491template std::shared_ptr<SimNode<Real>>
493template std::shared_ptr<SimNode<Complex>>
495template std::shared_ptr<SimNode<Real>>
496SystemTopology::node<SimNode<Real>>(std::string_view name);
497template std::shared_ptr<SimNode<Complex>>
498SystemTopology::node<SimNode<Complex>>(std::string_view name);
500 typename SimPowerComp<Real>::Ptr component,
501 typename SimNode<Real>::List simNodes);
503 typename SimPowerComp<Complex>::Ptr component,
504 typename SimNode<Complex>::List simNodes);
505template int SystemTopology::checkTopologySubnets<Real>(
506 std::unordered_map<typename CPS::SimNode<Real>::Ptr, int> &subnet);
507template int SystemTopology::checkTopologySubnets<Complex>(
508 std::unordered_map<typename CPS::SimNode<Complex>::Ptr, int> &subnet);
509template void SystemTopology::splitSubnets<Real>(
510 std::vector<CPS::SystemTopology> &splitSystems);
511template void SystemTopology::splitSubnets<Complex>(
512 std::vector<CPS::SystemTopology> &splitSystems);
Base class for all components that are transmitting power.
void addNodes(const TopologicalNode::List &topNodes)
Add multiple nodes.
Real mSystemFrequency
System frequency.
IdentifiedObject::List mComponents
List of network components.
std::shared_ptr< Type > node(UInt index)
Returns TopologicalNode by index in node list.
void initWithPowerflow(const SystemTopology &systemPF, CPS::Domain domain)
Initialize nodes and SG power from PowerFlow.
void removeComponent(const String &name)
Remove system component.
void addNode(TopologicalNode::Ptr topNode)
Adds node and initializes frequencies.
void addTearComponent(IdentifiedObject::Ptr component)
Adds component and initializes frequencies.
std::shared_ptr< Type > component(const String &name)
Returns Component by name.
TopologicalNode::List mNodes
List of network nodes.
void reset()
Reset state of components.
void addNodeAt(TopologicalNode::Ptr topNode, UInt index)
Adds node at specified position and initializes frequencies.
void addComponents(const IdentifiedObject::List &components)
Add multiple components.
Matrix mFrequencies
List of considered network frequencies.
void connectComponentToNodes(typename SimPowerComp< VarType >::Ptr component, typename SimNode< VarType >::List simNodes)
Connect component to simNodes.
void addComponent(IdentifiedObject::Ptr component)
Adds component and initializes frequencies.
void multiply(Int numberCopies)
Copy the whole topology the given number of times and add the resulting components and nodes to the t...
IdentifiedObject::List mTearComponents
SystemTopology()
Do not use this constructor.
void addTearComponents(const IdentifiedObject::List &components)
Add multiple components.
std::map< TopologicalNode::Ptr, TopologicalPowerComp::List > mComponentsAtNode
Map of network components connected to network nodes.