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