DPsim
Loading...
Searching...
No Matches
SystemTopology.cpp
Go to the documentation of this file.
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
16
17using namespace CPS;
18
20 Matrix frequencies(1, 1);
21 frequencies << frequency;
22 return frequencies;
23}
24
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
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
47 for (auto topNode : topNodes)
48 addNode(topNode);
49}
50
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>
65 typename SimNode<VarType>::List simNodes) {
66 component->connect(simNodes);
67 for (auto simNode : simNodes)
68 mComponentsAtNode[simNode].push_back(component);
69}
70
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
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
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
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
247 for (auto it = mComponents.begin(); it != mComponents.end();) {
248 if ((*it)->name() == name) {
249 // Drop the component from the per-node lists as well, otherwise
250 // the power flow solvers can still pick it up via mComponentsAtNode
251 for (auto &[topoNode, comps] : mComponentsAtNode) {
252 comps.erase(std::remove(comps.begin(), comps.end(), *it), comps.end());
253 }
254 it = mComponents.erase(
255 it); // safe: returns next valid iterator when erasing
256 } else {
257 ++it;
258 }
259 }
260}
261
264static bool isConnectedTo(const IdentifiedObject::Ptr &comp,
265 const TopologicalNode::Ptr &node) {
266 auto powerComp = std::dynamic_pointer_cast<TopologicalPowerComp>(comp);
267 if (!powerComp)
268 return false;
269
270 // Terminals may not be connected yet
271 const auto terminals = powerComp->topologicalTerminals();
272 return std::any_of(terminals.begin(), terminals.end(),
273 [&node](const TopologicalTerminal::Ptr &terminal) {
274 return terminal && terminal->topologicalNodes() == node;
275 });
276}
277
278void SystemTopology::removeComponentsConnectedTo(
279 const TopologicalNode::Ptr &node) {
280 IdentifiedObject::List removedComponents;
281
282 for (auto it = mComponents.begin(); it != mComponents.end();) {
283 if (isConnectedTo(*it, node)) {
284 removedComponents.push_back(*it);
285 it = mComponents.erase(it);
286 } else {
287 ++it;
288 }
289 }
290
291 // A removed component may also be connected to other nodes,
292 // so drop it from the remaining per-node component lists as well
293 for (auto &[topoNode, comps] : mComponentsAtNode) {
294 for (const auto &removed : removedComponents) {
295 comps.erase(std::remove(comps.begin(), comps.end(), removed),
296 comps.end());
297 }
298 }
299
300 for (const auto &removed : removedComponents) {
301 mTearComponents.erase(
302 std::remove(mTearComponents.begin(), mTearComponents.end(), removed),
303 mTearComponents.end());
304 }
305}
306
308 for (auto it = mNodes.begin(); it != mNodes.end();) {
309 // The ground node is the network reference and must never be removed
310 if ((*it)->name() == name && !(*it)->isGround()) {
311 removeComponentsConnectedTo(*it);
312 mComponentsAtNode.erase(*it);
313 it = mNodes.erase(it);
314 } else {
315 ++it;
316 }
317 }
318}
319
320template <typename VarType>
321void SystemTopology::splitSubnets(std::vector<SystemTopology> &splitSystems) {
322 std::unordered_map<typename SimNode<VarType>::Ptr, int> subnet;
323 int numberSubnets = checkTopologySubnets<VarType>(subnet);
324 if (numberSubnets == 1) {
325 splitSystems.push_back(*this);
326 } else {
327 std::vector<IdentifiedObject::List> components(numberSubnets);
328 std::vector<TopologicalNode::List> nodes(numberSubnets);
329
330 // Split nodes into subnet groups
331 for (auto node : mNodes) {
332 auto pnode = std::dynamic_pointer_cast<SimNode<VarType>>(node);
333 if (!pnode || node->isGround())
334 continue;
335
336 nodes[subnet[pnode]].push_back(node);
337 }
338
339 // Split components into subnet groups
340 for (auto comp : mComponents) {
341 auto pcomp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(comp);
342 if (!pcomp) {
343 // TODO this should only be signal components.
344 // Proper solution would be to pass them to a different "solver"
345 // since they are actually independent of which solver we use
346 // for the electric part.
347 // Just adding them to an arbitrary solver for now has the same effect.
348 components[0].push_back(comp);
349 continue;
350 }
351 for (UInt nodeIdx = 0; nodeIdx < pcomp->terminalNumber(); nodeIdx++) {
352 if (!pcomp->node(nodeIdx)->isGround()) {
353 components[subnet[pcomp->node(nodeIdx)]].push_back(comp);
354 break;
355 }
356 }
357 }
358 for (int currentNet = 0; currentNet < numberSubnets; currentNet++) {
359 splitSystems.emplace_back(mSystemFrequency, nodes[currentNet],
360 components[currentNet]);
361 }
362 }
363}
364
365template <typename VarType>
367 std::unordered_map<typename SimNode<VarType>::Ptr, int> &subnet) {
368 std::unordered_map<typename SimNode<VarType>::Ptr,
369 typename SimNode<VarType>::List>
370 neighbours;
371
372 for (auto comp : mComponents) {
373 auto pcomp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(comp);
374 if (!pcomp)
375 continue;
376
377 for (UInt nodeIdx1 = 0; nodeIdx1 < pcomp->terminalNumberConnected();
378 nodeIdx1++) {
379 for (UInt nodeIdx2 = 0; nodeIdx2 < nodeIdx1; nodeIdx2++) {
380 auto node1 = pcomp->node(nodeIdx1);
381 auto node2 = pcomp->node(nodeIdx2);
382 if (node1->isGround() || node2->isGround())
383 continue;
384
385 neighbours[node1].push_back(node2);
386 neighbours[node2].push_back(node1);
387 }
388 }
389 }
390
391 int currentNet = 0;
392 size_t totalNodes = mNodes.size();
393 for (auto tnode : mNodes) {
394 auto node = std::dynamic_pointer_cast<SimNode<VarType>>(tnode);
395 if (!node || tnode->isGround()) {
396 totalNodes--;
397 }
398 }
399
400 while (subnet.size() != totalNodes) {
401 std::list<typename SimNode<VarType>::Ptr> nextSet;
402
403 for (auto tnode : mNodes) {
404 auto node = std::dynamic_pointer_cast<SimNode<VarType>>(tnode);
405 if (!node || tnode->isGround())
406 continue;
407
408 if (subnet.find(node) == subnet.end()) {
409 nextSet.push_back(node);
410 break;
411 }
412 }
413 while (!nextSet.empty()) {
414 auto node = nextSet.front();
415 nextSet.pop_front();
416
417 subnet[node] = currentNet;
418 for (auto neighbour : neighbours[node]) {
419 if (subnet.find(neighbour) == subnet.end())
420 nextSet.push_back(neighbour);
421 }
422 }
423 currentNet++;
424 }
425 return currentNet;
426}
427
428#ifdef WITH_GRAPHVIZ
429
430Graph::Graph SystemTopology::topologyGraph() {
431 Graph::Node *n, *c;
432
434
435 g.set("splines", "polyline");
436
437 for (auto node : mNodes) {
438 n = g.addNode(node->uid());
439
440 std::stringstream label, tooltip;
441
442 tooltip << node->uid();
443
444 label << "<FONT POINT-SIZE=\"12\"><B>" << node->name()
445 << "</B></FONT><BR/>";
446
447 double phase = 180.0 / M_PI * std::arg(node->initialSingleVoltage());
448 double mag = std::abs(node->initialSingleVoltage());
449
450 const char *suffixes[] = {"", "k", "M", "G"};
451
452 int s;
453 for (s = 0; s < 3 && mag > 1000; s++)
454 mag *= 1e-3;
455
456 if (node->initialSingleVoltage() != Complex(0, 0)) {
457 label << std::setprecision(2) << std::fixed;
458 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">";
459 label << "(" << mag << " " << suffixes[s] << "V &gt; " << phase << "°)";
460 label << "</FONT>";
461 }
462
463 n->set("xlabel", label.str(), true);
464 n->set("tooltip", tooltip.str(), true);
465 n->set("fillcolor", phase == 0 ? "red" : "black");
466 n->set("fixedsize", "true");
467 n->set("width", "0.15");
468 n->set("height", "0.15");
469 n->set("shape", "point");
470 }
471
472 std::map<String, String> compColorMap;
473
474 for (auto comp : mComponents) {
475 if (!comp) // TODO: this is a bug in the CIM::Reader!
476 continue;
477
479
480 if (!(topoComp = std::dynamic_pointer_cast<TopologicalPowerComp>(comp)))
481 continue;
482
483 c = g.addNode(topoComp->uid());
484
485 auto type = topoComp->type();
486 auto name = topoComp->name();
487
488 std::stringstream label, tooltip;
489
490 label << "<FONT POINT-SIZE=\"12\"><B>" << name << "</B></FONT><BR/>";
491 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">" << type
492 << "</FONT><BR/>";
493 if (topoComp->description() != "") {
494 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">"
495 << topoComp->description() << "</FONT>";
496 }
497
498 tooltip << "Attributes:";
499 for (auto it : topoComp->attributes()) {
500 tooltip << std::endl << it.first << ": " << it.second->toString();
501 }
502
503 if (compColorMap.find(type) != compColorMap.end()) {
504 compColorMap[type] =
505 String("/paired9/") + std::to_string(1 + compColorMap.size() % 9);
506 }
507
508 c->set("color", compColorMap[type]);
509 c->set("label", label.str(), true);
510 c->set("tooltip", tooltip.str(), true);
511 c->set("style", "rounded,filled,bold");
512
513 if (type.find("Line") == std::string::npos) {
514 c->set("shape", "rectangle");
515 c->set("fillcolor", "gray93");
516 } else {
517 c->set("shape", "plaintext");
518 c->set("fillcolor", "transparent");
519 }
520
521 for (auto term : topoComp->topologicalTerminals()) {
522 n = g.node(term->topologicalNodes()->uid());
523 if (!n)
524 continue;
525
526 g.addEdge(term->uid(), c, n);
527 }
528 }
529
530 return g;
531}
532
533String SystemTopology::render() {
534 auto graph = this->topologyGraph();
535 std::stringstream ss;
536 graph.render(ss, "neato", "svg");
537
538 return ss.str();
539}
540
541void SystemTopology::renderToFile(String filename) {
542 std::ofstream ofstr(filename);
543 this->topologyGraph().render(ofstr, "neato", "svg");
544}
545#endif
546
547// Explicit instantiation of template functions to be able to keep the definition in the cpp
548template void SystemTopology::multiplyPowerComps<Real>(Int numberCopies);
549template void SystemTopology::multiplyPowerComps<Complex>(Int numberCopies);
550template std::shared_ptr<TopologicalNode>
552template std::shared_ptr<TopologicalNode>
553SystemTopology::node<TopologicalNode>(std::string_view name);
554template std::shared_ptr<SimNode<Real>>
556template std::shared_ptr<SimNode<Complex>>
558template std::shared_ptr<SimNode<Real>>
559SystemTopology::node<SimNode<Real>>(std::string_view name);
560template std::shared_ptr<SimNode<Complex>>
561SystemTopology::node<SimNode<Complex>>(std::string_view name);
563 typename SimPowerComp<Real>::Ptr component,
564 typename SimNode<Real>::List simNodes);
566 typename SimPowerComp<Complex>::Ptr component,
567 typename SimNode<Complex>::List simNodes);
569 std::unordered_map<typename CPS::SimNode<Real>::Ptr, int> &subnet);
571 std::unordered_map<typename CPS::SimNode<Complex>::Ptr, int> &subnet);
573 std::vector<CPS::SystemTopology> &splitSystems);
575 std::vector<CPS::SystemTopology> &splitSystems);
static bool isConnectedTo(const IdentifiedObject::Ptr &comp, const TopologicalNode::Ptr &node)
void set(const String &key, const String &value, bool html=false)
Definition Graph.cpp:75
std::shared_ptr< IdentifiedObject > Ptr
std::vector< Ptr > List
std::shared_ptr< SimNode< VarType > > Ptr
Definition SimNode.h:32
std::vector< Ptr > List
Definition SimNode.h:33
Base class for all components that are transmitting power.
std::vector< Ptr > List
std::shared_ptr< SimPowerComp< VarType > > Ptr
void removeNode(const String &name)
Remove node and all components connected to it.
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.
std::map< String, String, std::less<> > listIdObjects() const
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.
Matrix initFrequency(Real frequency) const
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...
void splitSubnets(std::vector< CPS::SystemTopology > &splitSystems)
IdentifiedObject::List mTearComponents
SystemTopology()
Do not use this constructor.
void addTearComponents(const IdentifiedObject::List &components)
Add multiple components.
int checkTopologySubnets(std::unordered_map< typename CPS::SimNode< VarType >::Ptr, int > &subnet)
std::map< TopologicalNode::Ptr, TopologicalPowerComp::List > mComponentsAtNode
Map of network components connected to network nodes.
std::vector< Ptr > List
PhaseType phaseType() const
std::shared_ptr< TopologicalNode > Ptr
MatrixComp initialVoltage() const
std::shared_ptr< TopologicalPowerComp > Ptr
std::shared_ptr< TopologicalTerminal > Ptr
static std::shared_ptr< SimNode< VarType > > make(Args &&...args)
Definition PtrFactory.h:19
#define M_PI
Definition Definitions.h:41
Eigen::Matrix< Real, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > Matrix
Dense matrix for real numbers.
Definition Definitions.h:81
std::string String
Definition Definitions.h:65
double Real
Definition Definitions.h:62
int Int
Definition Definitions.h:61
std::complex< Real > Complex
Definition Definitions.h:63
unsigned int UInt
Definition Definitions.h:60