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 // Idempotent: the map is a view derived from mComponents, so rebuild it
73 // from scratch instead of accumulating entries across calls (see #635)
74 mComponentsAtNode.clear();
75 for (auto comp : mComponents) {
76 auto powerComp = std::dynamic_pointer_cast<TopologicalPowerComp>(comp);
77 if (powerComp)
78 for (auto topoNode : powerComp->topologicalNodes())
79 mComponentsAtNode[topoNode].push_back(powerComp);
80 }
81}
82
84 for (auto comp : components)
85 addComponent(comp);
86}
87
89 CPS::Domain domain) {
90
91 for (auto nodePF : systemPF.mNodes) {
92 if (auto node = this->node<TopologicalNode>(nodePF->name())) {
93 //SPDLOG_LOGGER_INFO(mSLog, "Updating initial voltage of {} according to powerflow", node->name());
94 //SPDLOG_LOGGER_INFO(mSLog, "Former initial voltage: {}", node->initialSingleVoltage());
95 node->setInitialVoltage(
96 std::dynamic_pointer_cast<CPS::SimNode<CPS::Complex>>(nodePF)
97 ->singleVoltage());
98 //SPDLOG_LOGGER_INFO(mSLog, "Updated initial voltage: {}", node->initialSingleVoltage());
99 }
100 }
101
102 // set initial power of SG
103 for (auto compPF : systemPF.mComponents) {
104 if (auto genPF = std::dynamic_pointer_cast<CPS::SP::Ph1::SynchronGenerator>(
105 compPF)) {
106 if (domain == CPS::Domain::DP || domain == CPS::Domain::SP) {
107 auto comp = this->component<SimPowerComp<Complex>>(compPF->name());
108 auto terminal = comp->terminals()[0];
109 terminal->setPower(-genPF->getApparentPower());
110 } else if (domain == CPS::Domain::EMT) {
111 auto comp = this->component<SimPowerComp<Real>>(compPF->name());
112 auto terminal = comp->terminals()[0];
113 terminal->setPower(-genPF->getApparentPower());
114 }
115 //SPDLOG_LOGGER_INFO(mSLog, "Updated initial power of gen {}: {}", compPF->name(), genPF->getApparentPower());
116 }
117 }
118}
119
121 if (auto powerCompComplex =
122 std::dynamic_pointer_cast<SimPowerComp<Complex>>(component))
123 powerCompComplex->initialize(mFrequencies);
124
125 if (auto powerCompReal =
126 std::dynamic_pointer_cast<SimPowerComp<Real>>(component))
127 powerCompReal->initialize(mFrequencies);
128
129 mTearComponents.push_back(component);
130}
131
133 const IdentifiedObject::List &components) {
134 for (auto comp : components)
135 addTearComponent(comp);
136}
137
138template <typename Type>
139typename std::shared_ptr<Type> SystemTopology::node(UInt index) {
140 if (index < mNodes.size()) {
141 auto topoNode = mNodes[index];
142 auto node = std::dynamic_pointer_cast<Type>(topoNode);
143 if (node)
144 return node;
145 }
146
147 return nullptr;
148}
149
150template <typename Type>
151typename std::shared_ptr<Type> SystemTopology::node(std::string_view name) {
152 for (auto topoNode : mNodes) {
153 if (topoNode->name() == name) {
154 auto node = std::dynamic_pointer_cast<Type>(topoNode);
155 if (node)
156 return node;
157 else
158 return nullptr;
159 }
160 }
161 return nullptr;
162}
163
164std::map<String, String, std::less<>> SystemTopology::listIdObjects() const {
165 std::map<String, String, std::less<>> objTypeMap;
166
167 for (auto node : mNodes) {
168 objTypeMap[node->name()] = node->type();
169 }
170 for (auto comp : mComponents) {
171 objTypeMap[comp->name()] = comp->type();
172 }
173 return objTypeMap;
174}
175
176template <typename VarType>
177void SystemTopology::multiplyPowerComps(Int numberCopies) {
178 typename SimNode<VarType>::List newNodes;
179 typename SimPowerComp<VarType>::List newComponents;
180
181 for (int copy = 0; copy < numberCopies; copy++) {
182 std::unordered_map<typename SimNode<VarType>::Ptr,
183 typename SimNode<VarType>::Ptr>
184 nodeMap;
185 String copySuffix = "_" + std::to_string(copy + 2);
186
187 // copy nodes
188 typename SimNode<VarType>::Ptr nodePtr;
189 for (size_t nNode = 0; nNode < mNodes.size(); nNode++) {
190 auto nodePtr = this->node<SimNode<VarType>>(static_cast<UInt>(nNode));
191 if (!nodePtr)
192 continue;
193
194 // GND is not copied
195 if (nodePtr->isGround()) {
196 nodeMap[nodePtr] = nodePtr;
197 } else {
198 auto nodeCpy = SimNode<VarType>::make(nodePtr->name() + copySuffix,
199 nodePtr->phaseType());
200 nodeCpy->setInitialVoltage(nodePtr->initialVoltage());
201 nodeMap[nodePtr] = nodeCpy;
202 newNodes.push_back(nodeCpy);
203 }
204 }
205
206 // copy components
207 for (auto genComp : mComponents) {
208 auto comp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(genComp);
209 if (!comp)
210 continue;
211 auto copy = comp->clone(comp->name() + copySuffix);
212 if (!copy)
213 throw SystemError("copy() not implemented for " + comp->name());
214
215 // map the nodes to their new copies, creating new terminals
216 typename SimNode<VarType>::List nodeCopies;
217 for (UInt nNode = 0; nNode < comp->terminalNumber(); nNode++) {
218 nodeCopies.push_back(nodeMap[comp->node(nNode)]);
219 }
220 copy->connect(nodeCopies);
221
222 // update the terminal powers for powerflow initialization
223 for (UInt nTerminal = 0; nTerminal < comp->terminalNumber();
224 nTerminal++) {
225 copy->terminal(nTerminal)->setPower(comp->terminal(nTerminal)->power());
226 }
227 newComponents.push_back(copy);
228 }
229 }
230 for (auto node : newNodes)
231 addNode(node);
232 for (auto comp : newComponents)
233 addComponent(comp);
234}
235
237 // SimPowerComps should be all EMT or all DP anyway, but this way we don't have to look
238 multiplyPowerComps<Real>(numCopies);
239 multiplyPowerComps<Complex>(numCopies);
240}
241
244 // for (auto c : mComponents) {
245 // c->reset();
246 // }
247}
248
250 for (auto it = mComponents.begin(); it != mComponents.end();) {
251 if ((*it)->name() == name) {
252 // mComponentsAtNode needs no manual cleanup here: it is rebuilt from
253 // mComponents by componentsAtNodeList() wherever it is consumed (#635)
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 // mComponentsAtNode needs no manual cleanup here: it is rebuilt from
292 // mComponents by componentsAtNodeList() wherever it is consumed (#635)
293 for (const auto &removed : removedComponents) {
294 mTearComponents.erase(
295 std::remove(mTearComponents.begin(), mTearComponents.end(), removed),
296 mTearComponents.end());
297 }
298}
299
301 for (auto it = mNodes.begin(); it != mNodes.end();) {
302 // The ground node is the network reference and must never be removed
303 if ((*it)->name() == name && !(*it)->isGround()) {
304 removeComponentsConnectedTo(*it);
305 it = mNodes.erase(it);
306 } else {
307 ++it;
308 }
309 }
310}
311
312template <typename VarType>
313void SystemTopology::splitSubnets(std::vector<SystemTopology> &splitSystems) {
314 std::unordered_map<typename SimNode<VarType>::Ptr, int> subnet;
315 int numberSubnets = checkTopologySubnets<VarType>(subnet);
316 if (numberSubnets == 1) {
317 splitSystems.push_back(*this);
318 } else {
319 std::vector<IdentifiedObject::List> components(numberSubnets);
320 std::vector<TopologicalNode::List> nodes(numberSubnets);
321
322 // Split nodes into subnet groups
323 for (auto node : mNodes) {
324 auto pnode = std::dynamic_pointer_cast<SimNode<VarType>>(node);
325 if (!pnode || node->isGround())
326 continue;
327
328 nodes[subnet[pnode]].push_back(node);
329 }
330
331 // Split components into subnet groups
332 for (auto comp : mComponents) {
333 auto pcomp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(comp);
334 if (!pcomp) {
335 // TODO this should only be signal components.
336 // Proper solution would be to pass them to a different "solver"
337 // since they are actually independent of which solver we use
338 // for the electric part.
339 // Just adding them to an arbitrary solver for now has the same effect.
340 components[0].push_back(comp);
341 continue;
342 }
343 for (UInt nodeIdx = 0; nodeIdx < pcomp->terminalNumber(); nodeIdx++) {
344 if (!pcomp->node(nodeIdx)->isGround()) {
345 components[subnet[pcomp->node(nodeIdx)]].push_back(comp);
346 break;
347 }
348 }
349 }
350 for (int currentNet = 0; currentNet < numberSubnets; currentNet++) {
351 splitSystems.emplace_back(mSystemFrequency, nodes[currentNet],
352 components[currentNet]);
353 }
354 }
355}
356
357template <typename VarType>
359 std::unordered_map<typename SimNode<VarType>::Ptr, int> &subnet) {
360 std::unordered_map<typename SimNode<VarType>::Ptr,
361 typename SimNode<VarType>::List>
362 neighbours;
363
364 for (auto comp : mComponents) {
365 auto pcomp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(comp);
366 if (!pcomp)
367 continue;
368
369 for (UInt nodeIdx1 = 0; nodeIdx1 < pcomp->terminalNumberConnected();
370 nodeIdx1++) {
371 for (UInt nodeIdx2 = 0; nodeIdx2 < nodeIdx1; nodeIdx2++) {
372 auto node1 = pcomp->node(nodeIdx1);
373 auto node2 = pcomp->node(nodeIdx2);
374 if (node1->isGround() || node2->isGround())
375 continue;
376
377 neighbours[node1].push_back(node2);
378 neighbours[node2].push_back(node1);
379 }
380 }
381 }
382
383 int currentNet = 0;
384 size_t totalNodes = mNodes.size();
385 for (auto tnode : mNodes) {
386 auto node = std::dynamic_pointer_cast<SimNode<VarType>>(tnode);
387 if (!node || tnode->isGround()) {
388 totalNodes--;
389 }
390 }
391
392 while (subnet.size() != totalNodes) {
393 std::list<typename SimNode<VarType>::Ptr> nextSet;
394
395 for (auto tnode : mNodes) {
396 auto node = std::dynamic_pointer_cast<SimNode<VarType>>(tnode);
397 if (!node || tnode->isGround())
398 continue;
399
400 if (subnet.find(node) == subnet.end()) {
401 nextSet.push_back(node);
402 break;
403 }
404 }
405 while (!nextSet.empty()) {
406 auto node = nextSet.front();
407 nextSet.pop_front();
408
409 subnet[node] = currentNet;
410 for (auto neighbour : neighbours[node]) {
411 if (subnet.find(neighbour) == subnet.end())
412 nextSet.push_back(neighbour);
413 }
414 }
415 currentNet++;
416 }
417 return currentNet;
418}
419
420#ifdef WITH_GRAPHVIZ
421
422Graph::Graph SystemTopology::topologyGraph() {
423 Graph::Node *n, *c;
424
426
427 g.set("splines", "polyline");
428
429 for (auto node : mNodes) {
430 n = g.addNode(node->uid());
431
432 std::stringstream label, tooltip;
433
434 tooltip << node->uid();
435
436 label << "<FONT POINT-SIZE=\"12\"><B>" << node->name()
437 << "</B></FONT><BR/>";
438
439 double phase = 180.0 / M_PI * std::arg(node->initialSingleVoltage());
440 double mag = std::abs(node->initialSingleVoltage());
441
442 const char *suffixes[] = {"", "k", "M", "G"};
443
444 int s;
445 for (s = 0; s < 3 && mag > 1000; s++)
446 mag *= 1e-3;
447
448 if (node->initialSingleVoltage() != Complex(0, 0)) {
449 label << std::setprecision(2) << std::fixed;
450 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">";
451 label << "(" << mag << " " << suffixes[s] << "V &gt; " << phase << "°)";
452 label << "</FONT>";
453 }
454
455 n->set("xlabel", label.str(), true);
456 n->set("tooltip", tooltip.str(), true);
457 n->set("fillcolor", phase == 0 ? "red" : "black");
458 n->set("fixedsize", "true");
459 n->set("width", "0.15");
460 n->set("height", "0.15");
461 n->set("shape", "point");
462 }
463
464 std::map<String, String> compColorMap;
465
466 for (auto comp : mComponents) {
467 if (!comp) // TODO: this is a bug in the CIM::Reader!
468 continue;
469
471
472 if (!(topoComp = std::dynamic_pointer_cast<TopologicalPowerComp>(comp)))
473 continue;
474
475 c = g.addNode(topoComp->uid());
476
477 auto type = topoComp->type();
478 auto name = topoComp->name();
479
480 std::stringstream label, tooltip;
481
482 label << "<FONT POINT-SIZE=\"12\"><B>" << name << "</B></FONT><BR/>";
483 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">" << type
484 << "</FONT><BR/>";
485 if (topoComp->description() != "") {
486 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">"
487 << topoComp->description() << "</FONT>";
488 }
489
490 tooltip << "Attributes:";
491 for (auto it : topoComp->attributes()) {
492 tooltip << std::endl << it.first << ": " << it.second->toString();
493 }
494
495 if (compColorMap.find(type) != compColorMap.end()) {
496 compColorMap[type] =
497 String("/paired9/") + std::to_string(1 + compColorMap.size() % 9);
498 }
499
500 c->set("color", compColorMap[type]);
501 c->set("label", label.str(), true);
502 c->set("tooltip", tooltip.str(), true);
503 c->set("style", "rounded,filled,bold");
504
505 if (type.find("Line") == std::string::npos) {
506 c->set("shape", "rectangle");
507 c->set("fillcolor", "gray93");
508 } else {
509 c->set("shape", "plaintext");
510 c->set("fillcolor", "transparent");
511 }
512
513 for (auto term : topoComp->topologicalTerminals()) {
514 n = g.node(term->topologicalNodes()->uid());
515 if (!n)
516 continue;
517
518 g.addEdge(term->uid(), c, n);
519 }
520 }
521
522 return g;
523}
524
525String SystemTopology::render() {
526 auto graph = this->topologyGraph();
527 std::stringstream ss;
528 graph.render(ss, "neato", "svg");
529
530 return ss.str();
531}
532
533void SystemTopology::renderToFile(String filename) {
534 std::ofstream ofstr(filename);
535 this->topologyGraph().render(ofstr, "neato", "svg");
536}
537#endif
538
539// Explicit instantiation of template functions to be able to keep the definition in the cpp
540template void SystemTopology::multiplyPowerComps<Real>(Int numberCopies);
541template void SystemTopology::multiplyPowerComps<Complex>(Int numberCopies);
542template std::shared_ptr<TopologicalNode>
544template std::shared_ptr<TopologicalNode>
545SystemTopology::node<TopologicalNode>(std::string_view name);
546template std::shared_ptr<SimNode<Real>>
548template std::shared_ptr<SimNode<Complex>>
550template std::shared_ptr<SimNode<Real>>
551SystemTopology::node<SimNode<Real>>(std::string_view name);
552template std::shared_ptr<SimNode<Complex>>
553SystemTopology::node<SimNode<Complex>>(std::string_view name);
555 typename SimPowerComp<Real>::Ptr component,
556 typename SimNode<Real>::List simNodes);
558 typename SimPowerComp<Complex>::Ptr component,
559 typename SimNode<Complex>::List simNodes);
561 std::unordered_map<typename CPS::SimNode<Real>::Ptr, int> &subnet);
563 std::unordered_map<typename CPS::SimNode<Complex>::Ptr, int> &subnet);
565 std::vector<CPS::SystemTopology> &splitSystems);
567 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