HolegPowerFlow.java 16 KB

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