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 it = mComponents.erase(
250 it); // safe: returns next valid iterator when erasing
251 } else {
252 ++it;
253 }
254 }
255}
256
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>
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
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
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);
517 std::unordered_map<typename CPS::SimNode<Real>::Ptr, int> &subnet);
519 std::unordered_map<typename CPS::SimNode<Complex>::Ptr, int> &subnet);
521 std::vector<CPS::SystemTopology> &splitSystems);
523 std::vector<CPS::SystemTopology> &splitSystems);
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)
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
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