HolegPowerFlow.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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.getTypeByDesign() == 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.getTypeByDesign() == 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. if (ComplexNumber.isNullOrZero(edge.getOverrideImpedance())) {
  185. // Calculate values for line based on sample datasheet
  186. line.R = 0.000194 * edge.getLengthKilometers();
  187. line.X = 0.000592 * edge.getLengthKilometers();
  188. line.B_2 = 0.000528;
  189. }
  190. else {
  191. line.R = edge.getOverrideImpedance().real;
  192. line.X = edge.getOverrideImpedance().imaginary;
  193. line.B_2 = edge.getOverrideShuntSusceptance();
  194. }
  195. line.a = 1;
  196. // Calculate from/to, swap if needed
  197. int from = grid.getNodes().indexOf(edge.getFrom());
  198. int to = grid.getNodes().indexOf(edge.getTo());
  199. line.reverse = from > to;
  200. line.from = Math.min(from, to);
  201. line.to = Math.max(from, to);
  202. lines.add(line);
  203. }
  204. private double scalePower(List<Bus> buses) {
  205. double decimal = 1;
  206. while(true) {
  207. boolean nextDecimal = false;
  208. for (Bus bus : buses) {
  209. if (Math.abs(bus.Pl) > decimal || Math.abs(bus.Pg) > decimal || Math.abs(bus.Ql) > decimal || Math.abs(bus.Qg) > decimal) {
  210. nextDecimal = true;
  211. break;
  212. }
  213. }
  214. if (!nextDecimal)
  215. break;
  216. decimal *= 10;
  217. }
  218. for (Bus bus : buses) {
  219. bus.Pl /= decimal;
  220. bus.Pg /= decimal;
  221. bus.Ql /= decimal;
  222. bus.Qg /= decimal;
  223. bus.Qmin /= decimal;
  224. bus.Qmax /= decimal;
  225. }
  226. return 1.0 / decimal;
  227. }
  228. private void addSlackNode(List<Bus> buses, List<Line> lines, int tryCount, PowerFlowSettings settings) {
  229. // Skip if have already slack node
  230. int slackNodePosition = -1;
  231. for (int i = 0; i < buses.size(); i++) {
  232. if (buses.get(i).type == BusType.Slack) {
  233. slackNodePosition = i;
  234. break;
  235. }
  236. }
  237. // Position
  238. int position;
  239. if (slackNodePosition >= 0)
  240. position = slackNodePosition;
  241. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.FirstNode)
  242. position = 0;
  243. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomNode
  244. || settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer) {
  245. // Find possible positions
  246. List<Integer> possiblePositions = new ArrayList<>();
  247. if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer) {
  248. for (int i = 0; i < buses.size(); i++)
  249. if (buses.get(i).isProducer())
  250. possiblePositions.add(i);
  251. }
  252. // Choose position in grid
  253. if (possiblePositions.size() == 0) // No generators or strategy not random producer? Add all positions
  254. position = random.nextInt(buses.size());
  255. else
  256. position = possiblePositions.get(random.nextInt(possiblePositions.size()));
  257. }
  258. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.LargestProducer) {
  259. position = 0;
  260. // Find producer with max Pg value
  261. double maxPower = -Double.MAX_VALUE;
  262. for (int i = 0; i < buses.size(); i++) {
  263. double power = buses.get(i).Pg;
  264. if (power > maxPower) {
  265. position = i;
  266. maxPower = power;
  267. }
  268. }
  269. }
  270. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.LargestConsumer) {
  271. position = 0;
  272. // Find producer with max Pl value
  273. double maxPower = -Double.MAX_VALUE;
  274. for (int i = 0; i < buses.size(); i++) {
  275. double power = buses.get(i).Pl;
  276. if (power > maxPower) {
  277. position = i;
  278. maxPower = power;
  279. }
  280. }
  281. }
  282. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.MinimizeSlack) {
  283. position = tryCount;
  284. double power = buses.get(position).Pg + buses.get(position).Qg + buses.get(position).Pl + buses.get(position).Ql;
  285. if (Math.abs(power) < 0.01)
  286. return;
  287. }
  288. else
  289. throw new UnsupportedOperationException("SlackNodePlacementStrategy not implemented");
  290. // Already contains slack node or we should replace one node with a new slack node
  291. if (slackNodePosition >= 0 || settings.replaceNodeWithSlackNode) {
  292. Bus slack = buses.get(position);
  293. slack.type = BusType.Slack;
  294. if (position > 0) {
  295. // Swap position with first element
  296. Bus zeroNode = buses.get(0);
  297. buses.set(0, slack);
  298. buses.set(position, zeroNode);
  299. // Fix indices of lines (we changed the order in the list)
  300. for (Line line : lines) {
  301. if (line.from == position)
  302. line.from = 0;
  303. else if (line.from == 0)
  304. line.from = position;
  305. if (line.to == position)
  306. line.to = 0;
  307. else if (line.to == 0)
  308. line.to = position;
  309. // We need to swap from/to
  310. if (line.from > line.to) {
  311. int t = line.from;
  312. line.from = line.to;
  313. line.to = t;
  314. line.reverse = !line.reverse;
  315. }
  316. }
  317. }
  318. }
  319. else {
  320. // Create slack node
  321. Bus slack = new Bus();
  322. slack.voltage = 1;
  323. slack.delta = 0;
  324. slack.type = BusType.Slack;
  325. buses.add(0, slack);
  326. // Fix line indices
  327. for (Line line : lines) {
  328. line.from += 1;
  329. line.to += 1;
  330. }
  331. // Create short line
  332. Line line = new Line();
  333. line.from = position + 1;
  334. line.to = buses.indexOf(slack);
  335. line.R = 0.01938;
  336. line.X = 0.1;
  337. line.B_2 = 0.1;
  338. line.a = 1;
  339. lines.add(line);
  340. }
  341. }
  342. private void debugShowResultAsMessageBox(PowerFlowProblem problem, SolverResult result) {
  343. try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
  344. final String utf8 = StandardCharsets.UTF_8.name();
  345. try (PrintStream ps = new PrintStream(stream, true, utf8)) {
  346. result.printTo(problem, ps);
  347. }
  348. String data = stream.toString(utf8);
  349. JOptionPane.showMessageDialog(null, data,"Debug output", JOptionPane.INFORMATION_MESSAGE);
  350. } catch (IOException e) {
  351. e.printStackTrace();
  352. }
  353. }
  354. private void updateGrid(Grid grid, PowerFlowProblem problem, SolverResult result) {
  355. double currentScale = 1;
  356. for (int i = 0; i < problem.buses.length; i++) {
  357. GridNode node = (GridNode)problem.buses[i].tag;
  358. if (node == null)
  359. continue;
  360. node.setVoltage(result.voltages[i].len() / problem.scaleVoltage);
  361. node.setPhase(Math.toDegrees(result.voltages[i].angle()));
  362. node.setCurrent(result.flowData.busCurrent[i] / currentScale);
  363. // Overwrite slack type
  364. if (problem.buses[i].type == BusType.Slack)
  365. node.setTypeSolved(NodeType.Slack);
  366. else
  367. node.setTypeSolved(node.getTypeByDesign());
  368. }
  369. for (int i = 0; i < problem.lines.length; i++) {
  370. GridEdge edge = (GridEdge)problem.lines[i].tag;
  371. if (edge == null)
  372. continue;
  373. double reverseFactor = problem.lines[i].reverse ? -1 : 1;
  374. edge.setPowerFlow(result.flowData.linePower[i].multiply(1 / problem.scalePower).multiply(reverseFactor));
  375. edge.setLineLoss(result.flowData.linePowerLoss[i].multiply(1 / problem.scalePower).multiply(reverseFactor));
  376. edge.setCurrent(result.flowData.lineCurrent[i] / currentScale * reverseFactor);
  377. }
  378. }
  379. }