HolegPowerFlow.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. package holeg;
  2. import holeg.model.Grid;
  3. import holeg.model.GridEdge;
  4. import holeg.model.GridNode;
  5. import holeg.model.NodeType;
  6. import holeg.power_flow.*;
  7. import holeg.ui.PowerFlowAnalysisMenu;
  8. import javax.swing.*;
  9. import java.io.ByteArrayOutputStream;
  10. import java.io.IOException;
  11. import java.io.PrintStream;
  12. import java.nio.charset.StandardCharsets;
  13. import java.util.*;
  14. public class HolegPowerFlow {
  15. private Random random = new Random();
  16. public GridSolverResult solve(Grid grid, PowerFlowSettings settings) {
  17. if (grid == null)
  18. throw new IllegalArgumentException("grid is null");
  19. if (settings == null)
  20. throw new IllegalArgumentException("settings is null");
  21. List<Grid> islands = findIslands(grid);
  22. boolean anySolved = false;
  23. GridSolverResult sumResult = GridSolverResult.Solved;
  24. double minSlackPower = Double.MAX_VALUE;
  25. // Try to solve each island
  26. for (Grid island : islands) {
  27. // Check if we should stop
  28. if (Thread.interrupted())
  29. return GridSolverResult.Interrupted;
  30. boolean solved = false;
  31. // Calculate number of calculation tries
  32. int solveTryCount;
  33. if (island.getNodes().stream().anyMatch(n -> n.getTypeByDesign() == NodeType.Slack))
  34. solveTryCount = 1;
  35. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.MinimizeSlack)
  36. solveTryCount = island.getNodes().size();
  37. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomNode
  38. || settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer)
  39. solveTryCount = island.getNodes().size() / 2;
  40. else
  41. solveTryCount = 1;
  42. PowerFlowProblem bestProblem = null;
  43. SolverResult bestResult = null;
  44. for (int i = 0; i < solveTryCount; i++) {
  45. // Check if we should stop
  46. if (Thread.interrupted())
  47. return GridSolverResult.Interrupted;
  48. // Build problem and solve it
  49. PowerFlowProblem problem = buildProblem(island, i, settings);
  50. // See if we can skip the problem
  51. if (problem.buses.length <= 1) {
  52. solved = true;
  53. break;
  54. }
  55. if (settings.skipGridsWithNoProducers && problem.getProducers().isEmpty()) {
  56. solved = true;
  57. break;
  58. }
  59. Solver solver = new NewtonRaphsonSolver();
  60. SolverResult result = solver.solve(problem, settings.solverSettings);
  61. // Solver was interrupted
  62. if (result.error == SolverError.Interrupted)
  63. return GridSolverResult.Interrupted;
  64. // Debug message
  65. if (PowerFlowAnalysisMenu.getInstance().shouldShowDebug() && (result.solved || i == solveTryCount - 1))
  66. debugShowResultAsMessageBox(problem, result);
  67. if (result.solved) {
  68. solved = true;
  69. // Simply use the first solved result
  70. if (settings.slackNodePlacementStrategy != SlackNodePlacementStrategy.MinimizeSlack) {
  71. bestProblem = problem;
  72. bestResult = result;
  73. break;
  74. }
  75. // Is this result better?
  76. double slackPower = result.flowData.busInjection[0].lenSquared();
  77. if (slackPower < minSlackPower) {
  78. minSlackPower = slackPower;
  79. bestProblem = problem;
  80. bestResult = result;
  81. }
  82. }
  83. }
  84. // Update grid if solved
  85. if (solved) {
  86. // Check limits
  87. boolean error = false;
  88. if (bestResult != null) {
  89. if (bestResult.flowData.busInjection[0].len() > settings.maxSlackPowerUntilInvalid)
  90. error = true;
  91. if (Arrays.stream(bestResult.voltages).anyMatch(u -> u.real < settings.minVoltageUntilInvalid))
  92. error = true;
  93. }
  94. // Update grid or set error
  95. if (!error) {
  96. if (bestProblem != null)
  97. updateGrid(island, bestProblem, bestResult);
  98. anySolved = true;
  99. }
  100. else
  101. sumResult = GridSolverResult.Error;
  102. }
  103. else
  104. sumResult = GridSolverResult.Error;
  105. }
  106. // Check if we should have stopped
  107. if (Thread.interrupted())
  108. return GridSolverResult.Interrupted;
  109. // Check if some islands failed, but not all
  110. if (anySolved && sumResult == GridSolverResult.Error)
  111. return GridSolverResult.PartialFailure;
  112. return sumResult;
  113. }
  114. private List<Grid> findIslands(Grid grid) {
  115. List<Grid> islands = new ArrayList<>();
  116. List<GridNode> nodes = grid.getNodes();
  117. // Keeps track if the node at any index was already visited
  118. boolean[] visited = new boolean[nodes.size()];
  119. for (int i = 0; i < nodes.size(); i++) {
  120. if (visited[i])
  121. continue;
  122. // First node or node that is not connected to any other node -> new island
  123. Grid island = new Grid();
  124. Stack<Integer> nodesToVisit = new Stack<>();
  125. // Add first node
  126. nodesToVisit.push(i);
  127. // Depth first search algorithm
  128. while (!nodesToVisit.empty()) {
  129. int index = nodesToVisit.pop();
  130. // Check if already visited
  131. if (visited[index])
  132. continue;
  133. visited[index] = true;
  134. // Add node to island
  135. GridNode node = nodes.get(index);
  136. island.addNode(node);
  137. // Iterate over all edges
  138. List<GridEdge> edges = grid.getEdges(node);
  139. for (GridEdge edge : edges) {
  140. island.addEdge(edge);
  141. // Select other end of the edge for next visit
  142. if (edge.getFrom() == node)
  143. nodesToVisit.push(nodes.indexOf(edge.getTo()));
  144. else
  145. nodesToVisit.push(nodes.indexOf(edge.getFrom()));
  146. }
  147. }
  148. islands.add(island);
  149. }
  150. return islands;
  151. }
  152. private PowerFlowProblem buildProblem(Grid grid, int tryCount, PowerFlowSettings settings) {
  153. List<Bus> buses = new ArrayList<>();
  154. List<Line> lines = new ArrayList<>();
  155. // Convert from grid to power flow objects
  156. for (GridNode node : grid.getNodes())
  157. createGridNode(buses, node);
  158. for (GridEdge edge : grid.getEdges())
  159. createGridLine(grid, lines, edge);
  160. // Scale voltages and scale power
  161. double scaleVoltage = 1.0 / 230.0;
  162. double scalePower = scalePower(buses);
  163. // Find position for slack node
  164. addSlackNode(buses, lines, tryCount, settings);
  165. // Sort lines
  166. lines.sort(Comparator.comparingInt(a -> a.from));
  167. // Create problem
  168. return new PowerFlowProblem(buses.toArray(new Bus[0]), lines.toArray(new Line[0]), scaleVoltage, scalePower);
  169. }
  170. private void createGridNode(List<Bus> buses, GridNode node) {
  171. Bus bus = new Bus();
  172. bus.delta = 0;
  173. bus.voltage = 1;
  174. bus.Pl = Math.abs(node.getPowerConsumption().real);
  175. bus.Ql = Math.abs(node.getPowerConsumption().imaginary);
  176. bus.tag = node;
  177. if (node.getTypeByDesign() == NodeType.Slack)
  178. bus.type = BusType.Slack;
  179. else if (node.getPowerGeneration().isZero()) {
  180. bus.Pg = 0;
  181. bus.Qg = 0;
  182. bus.type = BusType.PQ;
  183. }
  184. else {
  185. bus.Pg = Math.abs(node.getPowerGeneration().real);
  186. bus.Qg = Math.abs(node.getPowerGeneration().imaginary);
  187. bus.type = BusType.PV;
  188. }
  189. bus.Qmin = -1000000;
  190. bus.Qmax = 1000000;
  191. buses.add(bus);
  192. }
  193. private void createGridLine(Grid grid, List<Line> lines, GridEdge edge) {
  194. Line line = new Line();
  195. line.tag = edge;
  196. if (ComplexNumber.isNullOrZero(edge.getOverrideImpedance())) {
  197. // Calculate values for line based on sample datasheet
  198. line.R = 0.000194 * edge.getLengthKilometers();
  199. line.X = 0.000592 * edge.getLengthKilometers();
  200. line.B_2 = 0.000528;
  201. }
  202. else {
  203. line.R = edge.getOverrideImpedance().real;
  204. line.X = edge.getOverrideImpedance().imaginary;
  205. line.B_2 = edge.getOverrideShuntSusceptance();
  206. }
  207. line.a = 1;
  208. // Calculate from/to, swap if needed
  209. int from = grid.getNodes().indexOf(edge.getFrom());
  210. int to = grid.getNodes().indexOf(edge.getTo());
  211. line.reverse = from > to;
  212. line.from = Math.min(from, to);
  213. line.to = Math.max(from, to);
  214. lines.add(line);
  215. }
  216. private double scalePower(List<Bus> buses) {
  217. double decimal = 1;
  218. while(true) {
  219. boolean nextDecimal = false;
  220. for (Bus bus : buses) {
  221. if (Math.abs(bus.Pl) > decimal || Math.abs(bus.Pg) > decimal || Math.abs(bus.Ql) > decimal || Math.abs(bus.Qg) > decimal) {
  222. nextDecimal = true;
  223. break;
  224. }
  225. }
  226. if (!nextDecimal)
  227. break;
  228. decimal *= 10;
  229. }
  230. for (Bus bus : buses) {
  231. bus.Pl /= decimal;
  232. bus.Pg /= decimal;
  233. bus.Ql /= decimal;
  234. bus.Qg /= decimal;
  235. bus.Qmin /= decimal;
  236. bus.Qmax /= decimal;
  237. }
  238. return 1.0 / decimal;
  239. }
  240. private void addSlackNode(List<Bus> buses, List<Line> lines, int tryCount, PowerFlowSettings settings) {
  241. // Skip if have already slack node
  242. int slackNodePosition = -1;
  243. for (int i = 0; i < buses.size(); i++) {
  244. if (buses.get(i).type == BusType.Slack) {
  245. slackNodePosition = i;
  246. break;
  247. }
  248. }
  249. // Position
  250. int position;
  251. if (slackNodePosition >= 0)
  252. position = slackNodePosition;
  253. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.FirstNode)
  254. position = 0;
  255. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomNode
  256. || settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer) {
  257. // Find possible positions
  258. List<Integer> possiblePositions = new ArrayList<>();
  259. if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer) {
  260. for (int i = 0; i < buses.size(); i++)
  261. if (buses.get(i).isProducer())
  262. possiblePositions.add(i);
  263. }
  264. // Choose position in grid
  265. if (possiblePositions.size() == 0) // No generators or strategy not random producer? Add all positions
  266. position = random.nextInt(buses.size());
  267. else
  268. position = possiblePositions.get(random.nextInt(possiblePositions.size()));
  269. }
  270. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.LargestProducer) {
  271. position = 0;
  272. // Find producer with max Pg value
  273. double maxPower = -Double.MAX_VALUE;
  274. for (int i = 0; i < buses.size(); i++) {
  275. double power = buses.get(i).Pg;
  276. if (power > maxPower) {
  277. position = i;
  278. maxPower = power;
  279. }
  280. }
  281. }
  282. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.LargestConsumer) {
  283. position = 0;
  284. // Find producer with max Pl value
  285. double maxPower = -Double.MAX_VALUE;
  286. for (int i = 0; i < buses.size(); i++) {
  287. double power = buses.get(i).Pl;
  288. if (power > maxPower) {
  289. position = i;
  290. maxPower = power;
  291. }
  292. }
  293. }
  294. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.MinimizeSlack) {
  295. position = tryCount;
  296. double power = buses.get(position).Pg + buses.get(position).Qg + buses.get(position).Pl + buses.get(position).Ql;
  297. if (Math.abs(power) < 0.01)
  298. return;
  299. }
  300. else
  301. throw new UnsupportedOperationException("SlackNodePlacementStrategy not implemented");
  302. // Already contains slack node or we should replace one node with a new slack node
  303. if (slackNodePosition >= 0 || settings.replaceNodeWithSlackNode) {
  304. Bus slack = buses.get(position);
  305. slack.type = BusType.Slack;
  306. if (position > 0) {
  307. // Swap position with first element
  308. Bus zeroNode = buses.get(0);
  309. buses.set(0, slack);
  310. buses.set(position, zeroNode);
  311. // Fix indices of lines (we changed the order in the list)
  312. for (Line line : lines) {
  313. if (line.from == position)
  314. line.from = 0;
  315. else if (line.from == 0)
  316. line.from = position;
  317. if (line.to == position)
  318. line.to = 0;
  319. else if (line.to == 0)
  320. line.to = position;
  321. // We need to swap from/to
  322. if (line.from > line.to) {
  323. int t = line.from;
  324. line.from = line.to;
  325. line.to = t;
  326. line.reverse = !line.reverse;
  327. }
  328. }
  329. }
  330. }
  331. else {
  332. // Create slack node
  333. Bus slack = new Bus();
  334. slack.voltage = 1;
  335. slack.delta = 0;
  336. slack.type = BusType.Slack;
  337. buses.add(0, slack);
  338. // Fix line indices
  339. for (Line line : lines) {
  340. line.from += 1;
  341. line.to += 1;
  342. }
  343. // Create short line
  344. Line line = new Line();
  345. line.from = position + 1;
  346. line.to = buses.indexOf(slack);
  347. line.R = 0.01938;
  348. line.X = 0.1;
  349. line.B_2 = 0.1;
  350. line.a = 1;
  351. lines.add(line);
  352. }
  353. }
  354. private void debugShowResultAsMessageBox(PowerFlowProblem problem, SolverResult result) {
  355. try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
  356. final String utf8 = StandardCharsets.UTF_8.name();
  357. try (PrintStream ps = new PrintStream(stream, true, utf8)) {
  358. result.printTo(problem, ps);
  359. }
  360. String data = stream.toString(utf8);
  361. JOptionPane.showMessageDialog(null, data,"Debug output", JOptionPane.INFORMATION_MESSAGE);
  362. } catch (IOException e) {
  363. e.printStackTrace();
  364. }
  365. }
  366. private void updateGrid(Grid grid, PowerFlowProblem problem, SolverResult result) {
  367. double currentScale = 1;
  368. for (int i = 0; i < problem.buses.length; i++) {
  369. GridNode node = (GridNode)problem.buses[i].tag;
  370. if (node == null)
  371. continue;
  372. node.setVoltage(result.voltages[i].len() / problem.scaleVoltage);
  373. node.setPhase(Math.toDegrees(result.voltages[i].angle()));
  374. node.setCurrent(result.flowData.busCurrent[i] / currentScale);
  375. // Overwrite slack type
  376. if (problem.buses[i].type == BusType.Slack)
  377. node.setTypeSolved(NodeType.Slack);
  378. else
  379. node.setTypeSolved(node.getTypeByDesign());
  380. }
  381. for (int i = 0; i < problem.lines.length; i++) {
  382. GridEdge edge = (GridEdge)problem.lines[i].tag;
  383. if (edge == null)
  384. continue;
  385. double reverseFactor = problem.lines[i].reverse ? -1 : 1;
  386. edge.setPowerFlow(result.flowData.linePower[i].multiply(1 / problem.scalePower).multiply(reverseFactor));
  387. edge.setLineLoss(result.flowData.linePowerLoss[i].multiply(1 / problem.scalePower).multiply(reverseFactor));
  388. edge.setCurrent(result.flowData.lineCurrent[i] / currentScale * reverseFactor);
  389. }
  390. }
  391. }