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