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
257void SystemTopology::removeNode(const String &name) {
258 // TODO: Check if any components are connected to the node and remove them as well
259 for (auto it = mNodes.begin(); it != mNodes.end();) {
260 if ((*it)->name() == name) {
261 it = mNodes.erase(it);
262 } else {
263 ++it;
264 }
265 }
266}
267
268template <typename VarType>
269void SystemTopology::splitSubnets(std::vector<SystemTopology> &splitSystems) {
270 std::unordered_map<typename SimNode<VarType>::Ptr, int> subnet;
271 int numberSubnets = checkTopologySubnets<VarType>(subnet);
272 if (numberSubnets == 1) {
273 splitSystems.push_back(*this);
274 } else {
275 std::vector<IdentifiedObject::List> components(numberSubnets);
276 std::vector<TopologicalNode::List> nodes(numberSubnets);
277
278 // Split nodes into subnet groups
279 for (auto node : mNodes) {
280 auto pnode = std::dynamic_pointer_cast<SimNode<VarType>>(node);
281 if (!pnode || node->isGround())
282 continue;
283
284 nodes[subnet[pnode]].push_back(node);
285 }
286
287 // Split components into subnet groups
288 for (auto comp : mComponents) {
289 auto pcomp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(comp);
290 if (!pcomp) {
291 // TODO this should only be signal components.
292 // Proper solution would be to pass them to a different "solver"
293 // since they are actually independent of which solver we use
294 // for the electric part.
295 // Just adding them to an arbitrary solver for now has the same effect.
296 components[0].push_back(comp);
297 continue;
298 }
299 for (UInt nodeIdx = 0; nodeIdx < pcomp->terminalNumber(); nodeIdx++) {
300 if (!pcomp->node(nodeIdx)->isGround()) {
301 components[subnet[pcomp->node(nodeIdx)]].push_back(comp);
302 break;
303 }
304 }
305 }
306 for (int currentNet = 0; currentNet < numberSubnets; currentNet++) {
307 splitSystems.emplace_back(mSystemFrequency, nodes[currentNet],
308 components[currentNet]);
309 }
310 }
311}
312
313template <typename VarType>
314int SystemTopology::checkTopologySubnets(
315 std::unordered_map<typename SimNode<VarType>::Ptr, int> &subnet) {
316 std::unordered_map<typename SimNode<VarType>::Ptr,
317 typename SimNode<VarType>::List>
318 neighbours;
319
320 for (auto comp : mComponents) {
321 auto pcomp = std::dynamic_pointer_cast<SimPowerComp<VarType>>(comp);
322 if (!pcomp)
323 continue;
324
325 for (UInt nodeIdx1 = 0; nodeIdx1 < pcomp->terminalNumberConnected();
326 nodeIdx1++) {
327 for (UInt nodeIdx2 = 0; nodeIdx2 < nodeIdx1; nodeIdx2++) {
328 auto node1 = pcomp->node(nodeIdx1);
329 auto node2 = pcomp->node(nodeIdx2);
330 if (node1->isGround() || node2->isGround())
331 continue;
332
333 neighbours[node1].push_back(node2);
334 neighbours[node2].push_back(node1);
335 }
336 }
337 }
338
339 int currentNet = 0;
340 size_t totalNodes = mNodes.size();
341 for (auto tnode : mNodes) {
342 auto node = std::dynamic_pointer_cast<SimNode<VarType>>(tnode);
343 if (!node || tnode->isGround()) {
344 totalNodes--;
345 }
346 }
347
348 while (subnet.size() != totalNodes) {
349 std::list<typename SimNode<VarType>::Ptr> nextSet;
350
351 for (auto tnode : mNodes) {
352 auto node = std::dynamic_pointer_cast<SimNode<VarType>>(tnode);
353 if (!node || tnode->isGround())
354 continue;
355
356 if (subnet.find(node) == subnet.end()) {
357 nextSet.push_back(node);
358 break;
359 }
360 }
361 while (!nextSet.empty()) {
362 auto node = nextSet.front();
363 nextSet.pop_front();
364
365 subnet[node] = currentNet;
366 for (auto neighbour : neighbours[node]) {
367 if (subnet.find(neighbour) == subnet.end())
368 nextSet.push_back(neighbour);
369 }
370 }
371 currentNet++;
372 }
373 return currentNet;
374}
375
376#ifdef WITH_GRAPHVIZ
377
378Graph::Graph SystemTopology::topologyGraph() {
379 Graph::Node *n, *c;
380
381 Graph::Graph g("topology", Graph::Type::undirected);
382
383 g.set("splines", "polyline");
384
385 for (auto node : mNodes) {
386 n = g.addNode(node->uid());
387
388 std::stringstream label, tooltip;
389
390 tooltip << node->uid();
391
392 label << "<FONT POINT-SIZE=\"12\"><B>" << node->name()
393 << "</B></FONT><BR/>";
394
395 double phase = 180.0 / M_PI * std::arg(node->initialSingleVoltage());
396 double mag = std::abs(node->initialSingleVoltage());
397
398 const char *suffixes[] = {"", "k", "M", "G"};
399
400 int s;
401 for (s = 0; s < 3 && mag > 1000; s++)
402 mag *= 1e-3;
403
404 if (node->initialSingleVoltage() != Complex(0, 0)) {
405 label << std::setprecision(2) << std::fixed;
406 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">";
407 label << "(" << mag << " " << suffixes[s] << "V &gt; " << phase << "°)";
408 label << "</FONT>";
409 }
410
411 n->set("xlabel", label.str(), true);
412 n->set("tooltip", tooltip.str(), true);
413 n->set("fillcolor", phase == 0 ? "red" : "black");
414 n->set("fixedsize", "true");
415 n->set("width", "0.15");
416 n->set("height", "0.15");
417 n->set("shape", "point");
418 }
419
420 std::map<String, String> compColorMap;
421
422 for (auto comp : mComponents) {
423 if (!comp) // TODO: this is a bug in the CIM::Reader!
424 continue;
425
426 TopologicalPowerComp::Ptr topoComp;
427
428 if (!(topoComp = std::dynamic_pointer_cast<TopologicalPowerComp>(comp)))
429 continue;
430
431 c = g.addNode(topoComp->uid());
432
433 auto type = topoComp->type();
434 auto name = topoComp->name();
435
436 std::stringstream label, tooltip;
437
438 label << "<FONT POINT-SIZE=\"12\"><B>" << name << "</B></FONT><BR/>";
439 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">" << type
440 << "</FONT><BR/>";
441 if (topoComp->description() != "") {
442 label << "<FONT POINT-SIZE=\"10\" COLOR=\"gray28\">"
443 << topoComp->description() << "</FONT>";
444 }
445
446 tooltip << "Attributes:";
447 for (auto it : topoComp->attributes()) {
448 tooltip << std::endl << it.first << ": " << it.second->toString();
449 }
450
451 if (compColorMap.find(type) != compColorMap.end()) {
452 compColorMap[type] =
453 String("/paired9/") + std::to_string(1 + compColorMap.size() % 9);
454 }
455
456 c->set("color", compColorMap[type]);
457 c->set("label", label.str(), true);
458 c->set("tooltip", tooltip.str(), true);
459 c->set("style", "rounded,filled,bold");
460
461 if (type.find("Line") == std::string::npos) {
462 c->set("shape", "rectangle");
463 c->set("fillcolor", "gray93");
464 } else {
465 c->set("shape", "plaintext");
466 c->set("fillcolor", "transparent");
467 }
468
469 for (auto term : topoComp->topologicalTerminals()) {
470 n = g.node(term->topologicalNodes()->uid());
471 if (!n)
472 continue;
473
474 g.addEdge(term->uid(), c, n);
475 }
476 }
477
478 return g;
479}
480
481String SystemTopology::render() {
482 auto graph = this->topologyGraph();
483 std::stringstream ss;
484 graph.render(ss, "neato", "svg");
485
486 return ss.str();
487}
488
489void SystemTopology::renderToFile(String filename) {
490 std::ofstream ofstr(filename);
491 this->topologyGraph().render(ofstr, "neato", "svg");
492}
493#endif
494
495// Explicit instantiation of template functions to be able to keep the definition in the cpp
496template void SystemTopology::multiplyPowerComps<Real>(Int numberCopies);
497template void SystemTopology::multiplyPowerComps<Complex>(Int numberCopies);
498template std::shared_ptr<TopologicalNode>
500template std::shared_ptr<TopologicalNode>
501SystemTopology::node<TopologicalNode>(std::string_view name);
502template std::shared_ptr<SimNode<Real>>
504template std::shared_ptr<SimNode<Complex>>
506template std::shared_ptr<SimNode<Real>>
507SystemTopology::node<SimNode<Real>>(std::string_view name);
508template std::shared_ptr<SimNode<Complex>>
509SystemTopology::node<SimNode<Complex>>(std::string_view name);
511 typename SimPowerComp<Real>::Ptr component,
512 typename SimNode<Real>::List simNodes);
514 typename SimPowerComp<Complex>::Ptr component,
515 typename SimNode<Complex>::List simNodes);
516template int SystemTopology::checkTopologySubnets<Real>(
517 std::unordered_map<typename CPS::SimNode<Real>::Ptr, int> &subnet);
518template int SystemTopology::checkTopologySubnets<Complex>(
519 std::unordered_map<typename CPS::SimNode<Complex>::Ptr, int> &subnet);
520template void SystemTopology::splitSubnets<Real>(
521 std::vector<CPS::SystemTopology> &splitSystems);
522template void SystemTopology::splitSubnets<Complex>(
523 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.