HolegPowerFlow.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  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. import java.util.concurrent.Semaphore;
  15. public class HolegPowerFlow {
  16. private Random random = new Random();
  17. public GridSolverResult solve(Grid grid, PowerFlowSettings settings, HolegPowerFlowContext context) {
  18. if (grid == null)
  19. throw new IllegalArgumentException("grid is null");
  20. if (settings == null)
  21. throw new IllegalArgumentException("settings is null");
  22. List<Grid> islands = findIslands(grid);
  23. boolean anySolved = false;
  24. GridSolverResult sumResult = GridSolverResult.Solved;
  25. double minSlackPower = Double.MAX_VALUE;
  26. // Try to solve each island
  27. for (Grid island : islands) {
  28. // Check if we should stop
  29. if (Thread.interrupted())
  30. return GridSolverResult.Interrupted;
  31. boolean solved = false;
  32. // Calculate number of calculation tries
  33. int solveTryCount;
  34. if (island.getNodes().stream().anyMatch(n -> n.getTypeByDesign() == NodeType.Slack))
  35. solveTryCount = 1;
  36. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.MinimizeSlack)
  37. solveTryCount = island.getNodes().size();
  38. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomNode
  39. || settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer)
  40. solveTryCount = island.getNodes().size() / 2;
  41. else
  42. solveTryCount = 1;
  43. PowerFlowProblem bestProblem = null;
  44. SolverResult bestResult = null;
  45. // Build all problems
  46. PowerFlowProblem[] problems = new PowerFlowProblem[solveTryCount];
  47. SolverResult[] results = new SolverResult[solveTryCount];
  48. for (int i = 0; i < solveTryCount; i++) {
  49. // Check if we should stop
  50. if (Thread.interrupted())
  51. return GridSolverResult.Interrupted;
  52. // Build problem and solve it
  53. PowerFlowProblem problem = buildProblem(island, i, settings);
  54. // See if we can skip the problem
  55. if (problem.buses.length <= 1) {
  56. solved = true;
  57. break;
  58. }
  59. if (settings.skipGridsWithNoProducers && problem.getProducers().isEmpty()) {
  60. solved = true;
  61. break;
  62. }
  63. problems[i] = problem;
  64. results[i] = null;
  65. }
  66. // Create solver jobs
  67. if (!solved) {
  68. Semaphore semaphore = new Semaphore(0);
  69. Thread[] jobs = new Thread[problems.length];
  70. for (int i = 0; i < problems.length; i++) {
  71. int jobID = i;
  72. PowerFlowProblem problem = problems[jobID];
  73. jobs[jobID] = new Thread(() -> {
  74. Solver solver = new NewtonRaphsonSolver();
  75. SolverResult result = solver.solve(problem, settings.solverSettings);
  76. synchronized (results) {
  77. results[jobID] = result;
  78. }
  79. // Notify main thread that we are done
  80. semaphore.release();
  81. });
  82. jobs[jobID].setName("Solver Job " + Long.toHexString(jobs[jobID].getId()));
  83. }
  84. // Check if we should have stopped
  85. if (Thread.interrupted())
  86. return GridSolverResult.Interrupted;
  87. System.out.println(" ");
  88. // Check if actually we have jobs to run
  89. if (jobs.length > 0) {
  90. boolean[] resultUsed = new boolean[jobs.length];
  91. // Start maximum number of threads
  92. int jobThreadCount = Math.min(jobs.length, Math.max(1, settings.maxSolverThreads));
  93. for (int i = 0; i < jobThreadCount; i++)
  94. jobs[i].start();
  95. int nextJob = jobThreadCount;
  96. while (true) {
  97. // Wait for any job to finish
  98. try {
  99. semaphore.acquire();
  100. } catch (InterruptedException ignored) {
  101. for (Thread job : jobs) job.interrupt();
  102. return GridSolverResult.Interrupted;
  103. }
  104. // Check results of all jobs
  105. boolean allSolved = true;
  106. synchronized (results) {
  107. for (int i = 0; i < jobs.length; i++) {
  108. // Already checked?
  109. if (resultUsed[i])
  110. continue;
  111. // Check result of the job
  112. PowerFlowProblem problem = problems[i];
  113. SolverResult result = results[i];
  114. if (result != null) {
  115. // Debug message
  116. if (PowerFlowAnalysisMenu.getInstance().shouldShowDebug() && (result.solved || i == solveTryCount - 1))
  117. debugShowResultAsMessageBox(problem, result);
  118. // Solver was interrupted
  119. if (result.error == SolverError.Interrupted)
  120. return GridSolverResult.Interrupted;
  121. System.out.printf("Problem %d, solved: %s, slack: %.3f\n", i, result.solved ? "true" : "false", result.solved ? result.flowData.busInjection[i].real / problem.scalePower : 0);
  122. // Solver was successful?
  123. if (result.solved) {
  124. // Simply use the first solved result
  125. if (settings.slackNodePlacementStrategy != SlackNodePlacementStrategy.MinimizeSlack) {
  126. solved = true;
  127. bestProblem = problem;
  128. bestResult = result;
  129. break;
  130. }
  131. // Is this result better?
  132. double slackPower = result.flowData.busInjection[0].lenSquared();
  133. if (slackPower < minSlackPower) {
  134. minSlackPower = slackPower;
  135. bestProblem = problem;
  136. bestResult = result;
  137. }
  138. }
  139. resultUsed[i] = true;
  140. } else
  141. allSolved = false;
  142. }
  143. }
  144. // Either all are solved or we solved one job
  145. if (solved || allSolved)
  146. break;
  147. // Check if we should have stopped
  148. if (Thread.interrupted()) {
  149. for (Thread job : jobs) job.interrupt();
  150. return GridSolverResult.Interrupted;
  151. }
  152. // Start next thread
  153. if (nextJob < jobs.length)
  154. jobs[nextJob++].start();
  155. }
  156. // Stop every job
  157. for (Thread job : jobs) job.interrupt();
  158. }
  159. }
  160. // Update grid if solved
  161. if (solved || bestResult != null) {
  162. // Check limits
  163. boolean error = false;
  164. if (bestResult != null) {
  165. if (bestResult.flowData.busInjection[0].len() > settings.maxSlackPowerUntilInvalid)
  166. error = true;
  167. if (Arrays.stream(bestResult.voltages).anyMatch(u -> u.real < settings.minVoltageUntilInvalid))
  168. error = true;
  169. }
  170. // Update grid or set error
  171. if (!error) {
  172. if (bestProblem != null) {
  173. updateGrid(island, bestProblem, bestResult);
  174. // Save problem and result to context
  175. if (context != null) {
  176. synchronized (context.lock) {
  177. context.problem = bestProblem;
  178. context.result = bestResult;
  179. }
  180. }
  181. }
  182. anySolved = true;
  183. }
  184. else
  185. sumResult = GridSolverResult.Error;
  186. }
  187. else
  188. sumResult = GridSolverResult.Error;
  189. }
  190. // Check if we should have stopped
  191. if (Thread.interrupted())
  192. return GridSolverResult.Interrupted;
  193. // Check if some islands failed, but not all
  194. if (anySolved && sumResult == GridSolverResult.Error)
  195. return GridSolverResult.PartialFailure;
  196. return sumResult;
  197. }
  198. private List<Grid> findIslands(Grid grid) {
  199. List<Grid> islands = new ArrayList<>();
  200. List<GridNode> nodes = grid.getNodes();
  201. // Keeps track if the node at any index was already visited
  202. boolean[] visited = new boolean[nodes.size()];
  203. for (int i = 0; i < nodes.size(); i++) {
  204. if (visited[i])
  205. continue;
  206. // First node or node that is not connected to any other node -> new island
  207. Grid island = new Grid();
  208. Stack<Integer> nodesToVisit = new Stack<>();
  209. // Add first node
  210. nodesToVisit.push(i);
  211. // Depth first search algorithm
  212. while (!nodesToVisit.empty()) {
  213. int index = nodesToVisit.pop();
  214. // Check if already visited
  215. if (visited[index])
  216. continue;
  217. visited[index] = true;
  218. // Add node to island
  219. GridNode node = nodes.get(index);
  220. island.addNode(node);
  221. // Iterate over all edges
  222. List<GridEdge> edges = grid.getEdges(node);
  223. for (GridEdge edge : edges) {
  224. island.addEdge(edge);
  225. // Select other end of the edge for next visit
  226. if (edge.getFrom() == node)
  227. nodesToVisit.push(nodes.indexOf(edge.getTo()));
  228. else
  229. nodesToVisit.push(nodes.indexOf(edge.getFrom()));
  230. }
  231. }
  232. islands.add(island);
  233. }
  234. return islands;
  235. }
  236. private PowerFlowProblem buildProblem(Grid grid, int tryCount, PowerFlowSettings settings) {
  237. List<Bus> buses = new ArrayList<>();
  238. List<Line> lines = new ArrayList<>();
  239. // Convert from grid to power flow objects
  240. for (GridNode node : grid.getNodes())
  241. createGridNode(buses, node);
  242. // Scale voltages and scale power
  243. double scaleVoltage = settings.disableAutomaticScaling ? 1 : 1.0 / 230.0;
  244. double scalePower = settings.disableAutomaticScaling ? 1 : scalePower(buses);
  245. double scaleResistance = settings.disableAutomaticScaling ? 1 : 1.0 / ((scaleVoltage * scaleVoltage) / scalePower);
  246. for (GridEdge edge : grid.getEdges())
  247. createGridLine(grid, lines, edge, scaleResistance);
  248. // Find position for slack node
  249. addSlackNode(buses, lines, tryCount, settings);
  250. // Sort lines
  251. lines.sort(Comparator.comparingInt(a -> a.from));
  252. // Create problem
  253. return new PowerFlowProblem(buses.toArray(new Bus[0]), lines.toArray(new Line[0]), scaleVoltage, scalePower);
  254. }
  255. private void createGridNode(List<Bus> buses, GridNode node) {
  256. Bus bus = new Bus();
  257. bus.delta = 0;
  258. bus.voltage = 1;
  259. bus.Pl = Math.abs(node.getPowerConsumption().real);
  260. bus.Ql = Math.abs(node.getPowerConsumption().imaginary);
  261. bus.tag = node;
  262. if (node.getTypeByDesign() != NodeType.Bus) {
  263. switch(node.getTypeByDesign()) {
  264. case PV:
  265. bus.type = BusType.PV;
  266. break;
  267. case PQ:
  268. bus.type = BusType.PQ;
  269. break;
  270. case Slack:
  271. bus.type = BusType.Slack;
  272. break;
  273. }
  274. bus.Pg = Math.abs(node.getPowerGeneration().real);
  275. bus.Qg = Math.abs(node.getPowerGeneration().imaginary);
  276. }
  277. else if (node.getPowerGeneration().isZero()) {
  278. bus.Pg = 0;
  279. bus.Qg = 0;
  280. bus.type = BusType.PQ;
  281. }
  282. else {
  283. bus.Pg = Math.abs(node.getPowerGeneration().real);
  284. bus.Qg = Math.abs(node.getPowerGeneration().imaginary);
  285. bus.type = BusType.PV;
  286. }
  287. bus.Qmin = -1000000;
  288. bus.Qmax = 1000000;
  289. bus.index = buses.size();
  290. buses.add(bus);
  291. }
  292. private void createGridLine(Grid grid, List<Line> lines, GridEdge edge, double scaleResistance) {
  293. Line line = new Line();
  294. line.tag = edge;
  295. if (ComplexNumber.isNullOrZero(edge.getOverrideImpedance())) {
  296. // Calculate values for line based on sample datasheet
  297. line.R = 0.00642 * edge.getLengthKilometers() * scaleResistance;
  298. line.X = 0.0083 * edge.getLengthKilometers() * scaleResistance;
  299. line.B_2 = (0.0001 * edge.getLengthKilometers()) / scaleResistance;
  300. }
  301. else {
  302. line.R = edge.getOverrideImpedance().real;
  303. line.X = edge.getOverrideImpedance().imaginary;
  304. line.B_2 = edge.getOverrideShuntSusceptance();
  305. if (!edge.getOverrideInPerUnit()) {
  306. line.R *= scaleResistance;
  307. line.X *= scaleResistance;
  308. line.B_2 *= scaleResistance;
  309. }
  310. }
  311. line.a = 1;
  312. // Calculate from/to, swap if needed
  313. int from = grid.getNodes().indexOf(edge.getFrom());
  314. int to = grid.getNodes().indexOf(edge.getTo());
  315. line.reverse = from > to;
  316. line.from = Math.min(from, to);
  317. line.to = Math.max(from, to);
  318. lines.add(line);
  319. }
  320. private double scalePower(List<Bus> buses) {
  321. double decimal = 1;
  322. while(true) {
  323. boolean nextDecimal = false;
  324. for (Bus bus : buses) {
  325. if (Math.abs(bus.Pl) > decimal || Math.abs(bus.Pg) > decimal || Math.abs(bus.Ql) > decimal || Math.abs(bus.Qg) > decimal) {
  326. nextDecimal = true;
  327. break;
  328. }
  329. }
  330. if (!nextDecimal)
  331. break;
  332. decimal *= 10;
  333. }
  334. for (Bus bus : buses) {
  335. bus.Pl /= decimal;
  336. bus.Pg /= decimal;
  337. bus.Ql /= decimal;
  338. bus.Qg /= decimal;
  339. bus.Qmin /= decimal;
  340. bus.Qmax /= decimal;
  341. }
  342. return 1.0 / decimal;
  343. }
  344. private void addSlackNode(List<Bus> buses, List<Line> lines, int tryCount, PowerFlowSettings settings) {
  345. // Skip if have already slack node
  346. int slackNodePosition = -1;
  347. for (int i = 0; i < buses.size(); i++) {
  348. if (buses.get(i).type == BusType.Slack) {
  349. slackNodePosition = i;
  350. break;
  351. }
  352. }
  353. // Position
  354. int position;
  355. if (slackNodePosition >= 0)
  356. position = slackNodePosition;
  357. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.FirstNode)
  358. position = 0;
  359. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomNode
  360. || settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer) {
  361. // Find possible positions
  362. List<Integer> possiblePositions = new ArrayList<>();
  363. if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.RandomProducer) {
  364. for (int i = 0; i < buses.size(); i++)
  365. if (buses.get(i).isProducer())
  366. possiblePositions.add(i);
  367. }
  368. // Choose position in grid
  369. if (possiblePositions.size() == 0) // No generators or strategy not random producer? Add all positions
  370. position = random.nextInt(buses.size());
  371. else
  372. position = possiblePositions.get(random.nextInt(possiblePositions.size()));
  373. }
  374. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.LargestProducer) {
  375. position = 0;
  376. // Find producer with max Pg value
  377. double maxPower = -Double.MAX_VALUE;
  378. for (int i = 0; i < buses.size(); i++) {
  379. double power = buses.get(i).Pg;
  380. if (power > maxPower) {
  381. position = i;
  382. maxPower = power;
  383. }
  384. }
  385. }
  386. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.LargestConsumer) {
  387. position = 0;
  388. // Find producer with max Pl value
  389. double maxPower = -Double.MAX_VALUE;
  390. for (int i = 0; i < buses.size(); i++) {
  391. double power = buses.get(i).Pl;
  392. if (power > maxPower) {
  393. position = i;
  394. maxPower = power;
  395. }
  396. }
  397. }
  398. else if (settings.slackNodePlacementStrategy == SlackNodePlacementStrategy.MinimizeSlack) {
  399. position = tryCount;
  400. double power = buses.get(position).Pg + buses.get(position).Qg + buses.get(position).Pl + buses.get(position).Ql;
  401. if (Math.abs(power) < 0.01)
  402. return;
  403. }
  404. else
  405. throw new UnsupportedOperationException("SlackNodePlacementStrategy not implemented");
  406. // Already contains slack node or we should replace one node with a new slack node
  407. if (slackNodePosition >= 0 || settings.replaceNodeWithSlackNode) {
  408. Bus slack = buses.get(position);
  409. slack.type = BusType.Slack;
  410. if (position > 0) {
  411. // Swap position with first element
  412. Bus zeroNode = buses.get(0);
  413. buses.set(0, slack);
  414. buses.set(position, zeroNode);
  415. // Fix indices of lines (we changed the order in the list)
  416. for (Line line : lines) {
  417. if (line.from == position)
  418. line.from = 0;
  419. else if (line.from == 0)
  420. line.from = position;
  421. if (line.to == position)
  422. line.to = 0;
  423. else if (line.to == 0)
  424. line.to = position;
  425. // We need to swap from/to
  426. if (line.from > line.to) {
  427. int t = line.from;
  428. line.from = line.to;
  429. line.to = t;
  430. line.reverse = !line.reverse;
  431. }
  432. }
  433. }
  434. }
  435. else {
  436. // Create slack node
  437. Bus slack = new Bus();
  438. slack.voltage = 1;
  439. slack.delta = 0;
  440. slack.type = BusType.Slack;
  441. buses.add(0, slack);
  442. // Fix line indices
  443. for (Line line : lines) {
  444. line.from += 1;
  445. line.to += 1;
  446. }
  447. // Create short line
  448. Line line = new Line();
  449. line.from = position + 1;
  450. line.to = buses.indexOf(slack);
  451. line.R = 0.01938;
  452. line.X = 0.1;
  453. line.B_2 = 0.1;
  454. line.a = 1;
  455. lines.add(line);
  456. }
  457. }
  458. private void debugShowResultAsMessageBox(PowerFlowProblem problem, SolverResult result) {
  459. try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
  460. final String utf8 = StandardCharsets.UTF_8.name();
  461. try (PrintStream ps = new PrintStream(stream, true, utf8)) {
  462. result.printTo(problem, ps, "");
  463. }
  464. String data = stream.toString(utf8);
  465. JOptionPane.showMessageDialog(null, data,"Debug output", JOptionPane.INFORMATION_MESSAGE);
  466. } catch (IOException e) {
  467. e.printStackTrace();
  468. }
  469. }
  470. private void updateGrid(Grid grid, PowerFlowProblem problem, SolverResult result) {
  471. double currentScale = 1;
  472. for (int i = 0; i < problem.buses.length; i++) {
  473. GridNode node = (GridNode)problem.buses[i].tag;
  474. if (node == null)
  475. continue;
  476. node.setVoltage(result.voltages[i].len() / problem.scaleVoltage);
  477. node.setPhase(Math.toDegrees(result.voltages[i].angle()));
  478. node.setCurrent(result.flowData.busCurrent[i] / currentScale);
  479. node.setIndex(problem.buses[i].index);
  480. // Overwrite slack type
  481. if (problem.buses[i].type == BusType.Slack)
  482. node.setTypeSolved(NodeType.Slack);
  483. else
  484. node.setTypeSolved(node.getTypeByDesign());
  485. }
  486. for (int i = 0; i < problem.lines.length; i++) {
  487. GridEdge edge = (GridEdge)problem.lines[i].tag;
  488. if (edge == null)
  489. continue;
  490. double reverseFactor = problem.lines[i].reverse ? -1 : 1;
  491. edge.setPowerFlow(result.flowData.linePower[i].multiply(1 / problem.scalePower).multiply(reverseFactor));
  492. edge.setLineLoss(result.flowData.linePowerLoss[i].multiply(1 / problem.scalePower).multiply(reverseFactor));
  493. edge.setCurrent(result.flowData.lineCurrent[i] / currentScale * reverseFactor);
  494. }
  495. }
  496. }