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