optiwindnet 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/CHANGELOG.md +79 -0
  2. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/PKG-INFO +24 -10
  3. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/README.md +18 -7
  4. optiwindnet-0.2.2/_run_chain_tests.py +137 -0
  5. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/MILP/__init__.py +12 -6
  6. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/MILP/_core.py +49 -8
  7. optiwindnet-0.2.2/optiwindnet/MILP/_core.pyi +120 -0
  8. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/MILP/cplex.py +13 -2
  9. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/MILP/fscip.py +5 -3
  10. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/MILP/gurobi.py +39 -13
  11. optiwindnet-0.2.2/optiwindnet/MILP/ortools.py +597 -0
  12. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/MILP/pyomo.py +20 -13
  13. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/MILP/scip.py +4 -4
  14. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/__init__.py +2 -1
  15. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/api.py +127 -34
  16. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/api_utils.py +35 -25
  17. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/augmentation.py +48 -25
  18. optiwindnet-0.2.2/optiwindnet/baselines/_core.py +43 -0
  19. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/baselines/hgs.py +24 -43
  20. optiwindnet-0.2.2/optiwindnet/baselines/lkh.py +986 -0
  21. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/clustering.py +1 -2
  22. optiwindnet-0.2.2/optiwindnet/crossings.py +861 -0
  23. optiwindnet-0.2.2/optiwindnet/data/Amrumbank West.osm.pbf +0 -0
  24. optiwindnet-0.2.2/optiwindnet/data/Baltic 2.osm.pbf +0 -0
  25. optiwindnet-0.2.2/optiwindnet/data/Belwind.osm.pbf +0 -0
  26. optiwindnet-0.2.2/optiwindnet/data/Fecamp.osm.pbf +0 -0
  27. optiwindnet-0.2.2/optiwindnet/data/Gemini 1.osm.pbf +0 -0
  28. optiwindnet-0.2.2/optiwindnet/data/Gemini 2.osm.pbf +0 -0
  29. optiwindnet-0.2.2/optiwindnet/data/He Dreiht.osm.pbf +0 -0
  30. optiwindnet-0.2.2/optiwindnet/data/Hollandse Kust Zuid.osm.pbf +0 -0
  31. optiwindnet-0.2.2/optiwindnet/data/Humber Gateway.osm.pbf +0 -0
  32. optiwindnet-0.2.2/optiwindnet/data/Kaskasi.osm.pbf +0 -0
  33. optiwindnet-0.2.2/optiwindnet/data/Lillgrund.osm.pbf +0 -0
  34. optiwindnet-0.2.2/optiwindnet/data/Lincs.osm.pbf +0 -0
  35. optiwindnet-0.2.2/optiwindnet/data/Luchterduinen.osm.pbf +0 -0
  36. optiwindnet-0.2.2/optiwindnet/data/Mermaid.osm.pbf +0 -0
  37. optiwindnet-0.2.2/optiwindnet/data/Neart na Gaoithe.osm.pbf +0 -0
  38. optiwindnet-0.2.2/optiwindnet/data/Nordsee One.osm.pbf +0 -0
  39. optiwindnet-0.2.2/optiwindnet/data/Nordsee Ost.osm.pbf +0 -0
  40. optiwindnet-0.2.2/optiwindnet/data/Norther.osm.pbf +0 -0
  41. optiwindnet-0.2.2/optiwindnet/data/Northwind.osm.pbf +0 -0
  42. optiwindnet-0.2.2/optiwindnet/data/Nysted.osm.pbf +0 -0
  43. optiwindnet-0.2.2/optiwindnet/data/Rentel.osm.pbf +0 -0
  44. optiwindnet-0.2.2/optiwindnet/data/Sandbank.osm.pbf +0 -0
  45. optiwindnet-0.2.2/optiwindnet/data/Seagreen.osm.pbf +0 -0
  46. optiwindnet-0.2.2/optiwindnet/data/Veja Mate.osm.pbf +0 -0
  47. optiwindnet-0.2.2/optiwindnet/data/Westermost Rough.osm.pbf +0 -0
  48. optiwindnet-0.2.2/optiwindnet/data/Zhejiang Jiaxing 1.osm.pbf +0 -0
  49. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/db/__init__.py +9 -1
  50. optiwindnet-0.2.2/optiwindnet/db/migrate.py +168 -0
  51. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/db/model.py +31 -7
  52. optiwindnet-0.2.2/optiwindnet/db/model.pyi +78 -0
  53. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/db/storage.py +9 -29
  54. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/geometric.py +205 -133
  55. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/heuristics/ClassicEsauWilliams.py +8 -3
  56. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/heuristics/CrossingPreventingEW.py +9 -5
  57. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/heuristics/EW_presolver.py +7 -4
  58. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/heuristics/NonBranchingEW.py +9 -4
  59. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/heuristics/ObstacleBypassingEW.py +13 -5
  60. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/heuristics/__init__.py +1 -0
  61. optiwindnet-0.2.2/optiwindnet/heuristics/_deprecation.py +49 -0
  62. optiwindnet-0.2.2/optiwindnet/heuristics/constructor.py +798 -0
  63. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/heuristics/priorityqueue.py +6 -6
  64. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/importer.py +58 -41
  65. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/interarraylib.py +175 -6
  66. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/interface.py +21 -0
  67. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/mesh.py +260 -117
  68. optiwindnet-0.2.2/optiwindnet/pathfinding.py +2165 -0
  69. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/plotting.py +9 -7
  70. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/repair.py +1 -0
  71. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/svg.py +338 -44
  72. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/themes.py +2 -0
  73. optiwindnet-0.2.2/optiwindnet/version.py +24 -0
  74. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet.egg-info/PKG-INFO +24 -10
  75. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet.egg-info/SOURCES.txt +16 -11
  76. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet.egg-info/requires.txt +1 -2
  77. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/pyproject.toml +40 -28
  78. optiwindnet-0.2.2/tests/__init__.py +0 -0
  79. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/conftest.py +28 -17
  80. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/helpers.py +49 -17
  81. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/paths.py +3 -4
  82. optiwindnet-0.2.2/tests/solutions.pkl +0 -0
  83. optiwindnet-0.2.2/tests/test_MILP.py +235 -0
  84. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/test_api_WindFarmNetwork.py +13 -14
  85. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/test_augmentation.py +5 -3
  86. optiwindnet-0.2.2/tests/test_constructor.py +271 -0
  87. optiwindnet-0.2.2/tests/test_crossings.py +283 -0
  88. optiwindnet-0.2.2/tests/test_db.py +180 -0
  89. optiwindnet-0.2.2/tests/test_end_to_end.py +106 -0
  90. optiwindnet-0.2.2/tests/test_geometric.py +327 -0
  91. optiwindnet-0.2.2/tests/test_importer.py +147 -0
  92. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/test_interarraylib.py +73 -22
  93. optiwindnet-0.2.2/tests/test_lkh.py +340 -0
  94. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/test_mesh.py +70 -16
  95. optiwindnet-0.2.2/tests/test_pathfinding.py +294 -0
  96. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/test_repair.py +4 -3
  97. optiwindnet-0.2.2/tests/test_svg.py +212 -0
  98. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/test_themes.py +0 -1
  99. optiwindnet-0.2.2/tests/test_utils.py +95 -0
  100. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/tests/update_expected_values.py +76 -53
  101. optiwindnet-0.2.0/optiwindnet/MILP/ortools.py +0 -445
  102. optiwindnet-0.2.0/optiwindnet/baselines/lkh.py +0 -431
  103. optiwindnet-0.2.0/optiwindnet/crossings.py +0 -378
  104. optiwindnet-0.2.0/optiwindnet/data/Amrumbank West.osm.pbf +0 -0
  105. optiwindnet-0.2.0/optiwindnet/data/Baltic 2.osm.pbf +0 -0
  106. optiwindnet-0.2.0/optiwindnet/data/Belwind.osm.pbf +0 -0
  107. optiwindnet-0.2.0/optiwindnet/data/Fecamp.osm.pbf +0 -0
  108. optiwindnet-0.2.0/optiwindnet/data/Gemini 1.osm.pbf +0 -0
  109. optiwindnet-0.2.0/optiwindnet/data/Gemini 2.osm.pbf +0 -0
  110. optiwindnet-0.2.0/optiwindnet/data/He Dreiht.osm.pbf +0 -0
  111. optiwindnet-0.2.0/optiwindnet/data/Hollandse Kust Zuid.osm.pbf +0 -0
  112. optiwindnet-0.2.0/optiwindnet/data/Humber Gateway.osm.pbf +0 -0
  113. optiwindnet-0.2.0/optiwindnet/data/Kaskasi.osm.pbf +0 -0
  114. optiwindnet-0.2.0/optiwindnet/data/Lillgrund.osm.pbf +0 -0
  115. optiwindnet-0.2.0/optiwindnet/data/Lincs.osm.pbf +0 -0
  116. optiwindnet-0.2.0/optiwindnet/data/Luchterduinen.osm.pbf +0 -0
  117. optiwindnet-0.2.0/optiwindnet/data/Mermaid.osm.pbf +0 -0
  118. optiwindnet-0.2.0/optiwindnet/data/Neart na Gaoithe.osm.pbf +0 -0
  119. optiwindnet-0.2.0/optiwindnet/data/Nordsee One.osm.pbf +0 -0
  120. optiwindnet-0.2.0/optiwindnet/data/Nordsee Ost.osm.pbf +0 -0
  121. optiwindnet-0.2.0/optiwindnet/data/Norther.osm.pbf +0 -0
  122. optiwindnet-0.2.0/optiwindnet/data/Northwind.osm.pbf +0 -0
  123. optiwindnet-0.2.0/optiwindnet/data/Nysted.osm.pbf +0 -0
  124. optiwindnet-0.2.0/optiwindnet/data/Rentel.osm.pbf +0 -0
  125. optiwindnet-0.2.0/optiwindnet/data/Sandbank.osm.pbf +0 -0
  126. optiwindnet-0.2.0/optiwindnet/data/Seagreen.osm.pbf +0 -0
  127. optiwindnet-0.2.0/optiwindnet/data/Veja Mate.osm.pbf +0 -0
  128. optiwindnet-0.2.0/optiwindnet/data/Westermost Rough.osm.pbf +0 -0
  129. optiwindnet-0.2.0/optiwindnet/data/Zhejiang Jiaxing 1.osm.pbf +0 -0
  130. optiwindnet-0.2.0/optiwindnet/db/migrate.py +0 -159
  131. optiwindnet-0.2.0/optiwindnet/pathfinding.py +0 -923
  132. optiwindnet-0.2.0/optiwindnet/version.py +0 -34
  133. optiwindnet-0.2.0/tests/test_MILP.py +0 -56
  134. optiwindnet-0.2.0/tests/test_crossings.py +0 -70
  135. optiwindnet-0.2.0/tests/test_db.py +0 -152
  136. optiwindnet-0.2.0/tests/test_end_to_end.py +0 -81
  137. optiwindnet-0.2.0/tests/test_files/expected_end_to_end.dill +0 -0
  138. optiwindnet-0.2.0/tests/test_files/sites/Albatros.osm.pbf +0 -0
  139. optiwindnet-0.2.0/tests/test_files/sites/Borkum Riffgrund 2.yaml +0 -104
  140. optiwindnet-0.2.0/tests/test_files/sites/Cazzaro-2022G-210.yaml +0 -84
  141. optiwindnet-0.2.0/tests/test_files/sites/Hornsea One.yaml +0 -234
  142. optiwindnet-0.2.0/tests/test_files/sites/London Array.yaml +0 -204
  143. optiwindnet-0.2.0/tests/test_files/sites/Taylor-2023.yaml +0 -164
  144. optiwindnet-0.2.0/tests/test_files/sites/Yi-2019.yaml +0 -66
  145. optiwindnet-0.2.0/tests/test_geometric.py +0 -45
  146. optiwindnet-0.2.0/tests/test_svg.py +0 -14
  147. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/.github/workflows/release.yml +0 -0
  148. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/.zenodo.json +0 -0
  149. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/CITATION.cff +0 -0
  150. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/LICENSE.txt +0 -0
  151. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/baselines/__init__.py +0 -0
  152. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/baselines/utils.py +0 -0
  153. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Albatros.osm.pbf +0 -0
  154. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Anholt.yaml +0 -0
  155. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Arkona.osm.pbf +0 -0
  156. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/BARD Offshore 1.osm.pbf +0 -0
  157. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Baltic Eagle.osm.pbf +0 -0
  158. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Beatrice.osm.pbf +0 -0
  159. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Borkum Riffgrund 1.yaml +0 -0
  160. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Borkum Riffgrund 2.yaml +0 -0
  161. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Borkum Riffgrund 3.osm.pbf +0 -0
  162. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Borssele.yaml +0 -0
  163. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Butendiek.yaml +0 -0
  164. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/CECEP Yangjiang Nanpeng Island.osm.pbf +0 -0
  165. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/CGN Rudong Demonstration.osm.pbf +0 -0
  166. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Cazzaro-2022.yaml +0 -0
  167. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Cazzaro-2022G-140.yaml +0 -0
  168. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Cazzaro-2022G-210.yaml +0 -0
  169. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Coastal Virginia.osm.pbf +0 -0
  170. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/DanTysk.yaml +0 -0
  171. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Deutsche Bucht.osm.pbf +0 -0
  172. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Dogger Bank A.yaml +0 -0
  173. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Dogger Bank B.osm.pbf +0 -0
  174. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Dogger Bank C.osm.pbf +0 -0
  175. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Dudgeon.yaml +0 -0
  176. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/East Anglia ONE.yaml +0 -0
  177. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Galloper Inner.osm.pbf +0 -0
  178. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Global Tech 1.osm.pbf +0 -0
  179. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Gode Wind 1.yaml +0 -0
  180. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Greater Changhua 1.osm.pbf +0 -0
  181. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Greater Gabbard Inner.yaml +0 -0
  182. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Gwynt y Mor.yaml +0 -0
  183. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Hohe See.osm.pbf +0 -0
  184. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Horns Rev 1.yaml +0 -0
  185. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Horns Rev 2.yaml +0 -0
  186. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Horns Rev 3.yaml +0 -0
  187. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Hornsea One.yaml +0 -0
  188. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Hornsea Two West.yaml +0 -0
  189. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Huizhou Gangkou 1.osm.pbf +0 -0
  190. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Huizhou Gangkou 2.osm.pbf +0 -0
  191. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Inch Cape.osm.pbf +0 -0
  192. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Kriegers Flak A.osm.pbf +0 -0
  193. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Kriegers Flak B.osm.pbf +0 -0
  194. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Laoting Bodhi Island.osm.pbf +0 -0
  195. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/London Array.yaml +0 -0
  196. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Meerwind.osm.pbf +0 -0
  197. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Merkur.osm.pbf +0 -0
  198. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Moray East.yaml +0 -0
  199. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Moray West.yaml +0 -0
  200. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Noirmoutier.osm.pbf +0 -0
  201. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Ormonde.yaml +0 -0
  202. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Princess Amalia.osm.pbf +0 -0
  203. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Race Bank.yaml +0 -0
  204. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Rampion.yaml +0 -0
  205. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Riffgat.osm.pbf +0 -0
  206. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Robin Rigg.osm.pbf +0 -0
  207. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Rudong H10.osm.pbf +0 -0
  208. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Rudong H6.osm.pbf +0 -0
  209. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Rudong H8.osm.pbf +0 -0
  210. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/R/303/270dsand 2.yaml" +0 -0
  211. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/SPIC Binhai North H2.osm.pbf +0 -0
  212. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Saint-Brieuc.osm.pbf +0 -0
  213. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Saint-Nazaire.osm.pbf +0 -0
  214. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Shengsi 2.osm.pbf +0 -0
  215. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Sheringham Shoal.osm.pbf +0 -0
  216. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Sofia.yaml +0 -0
  217. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Taylor-2023.yaml +0 -0
  218. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Thanet.yaml +0 -0
  219. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Thor.osm.pbf +0 -0
  220. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Trianel Windpark Borkum.osm.pbf +0 -0
  221. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Triton Knoll.yaml +0 -0
  222. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Tr/303/251port.osm.pbf" +0 -0
  223. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Vineyard Wind 1.osm.pbf +0 -0
  224. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Walney 1.yaml +0 -0
  225. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Walney 2.yaml +0 -0
  226. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Walney Extension.yaml +0 -0
  227. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/West of Duddon Sands.yaml +0 -0
  228. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Wikinger.osm.pbf +0 -0
  229. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Yi-2019.yaml +0 -0
  230. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/data/Yunlin.osm.pbf +0 -0
  231. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/db/_core.py +0 -0
  232. /optiwindnet-0.2.0/tests/__init__.py → /optiwindnet-0.2.2/optiwindnet/py.typed +0 -0
  233. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/synthetic.py +0 -0
  234. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/utils.py +0 -0
  235. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet/weighting.py +0 -0
  236. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet.egg-info/dependency_links.txt +0 -0
  237. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/optiwindnet.egg-info/top_level.txt +0 -0
  238. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/setup.cfg +0 -0
  239. {optiwindnet-0.2.0 → optiwindnet-0.2.2}/sitecustomize.py +0 -0
  240. {optiwindnet-0.2.0/tests/test_files/sites → optiwindnet-0.2.2/tests/locations}/example_1ss_300wt.osm.pbf +0 -0
  241. {optiwindnet-0.2.0/tests/test_files/sites → optiwindnet-0.2.2/tests/locations}/example_4ss_300wt.osm.pbf +0 -0
  242. {optiwindnet-0.2.0/tests/test_files/sites → optiwindnet-0.2.2/tests/locations}/example_location.yaml +0 -0
@@ -1,3 +1,82 @@
1
+ # v0.2.2
2
+
3
+ [Commit history since v0.2.1](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/-/compare/v0.2.1...v0.2.2)
4
+
5
+ ## Breaking Changes
6
+ - **Advanced API Cleanups**: Helper functions for root-assignment and link-blockage moved from `optiwindnet.geometric` to `optiwindnet.interarraylib`. Users should import `add_terminal_closest_root()`, `add_link_blockmap()`, and `add_link_cosines()` from `optiwindnet.interarraylib`.
7
+
8
+ ## Important Changes
9
+ - **Default Vector SVG Plotting**: High-level `WindFarmNetwork` plotting methods (`plot()`, `plot_location()`, `plot_available_links()`, `plot_navigation_mesh()`, and `plot_selected_links()`) now use a modern, interactive vector SVG plotting backend (`svgplot`/`svgpplot`) by default. This delivers clean, high-resolution inline displays in Jupyter notebooks. The legacy Matplotlib-based backend remains fully accessible by passing an explicit `ax` argument (including `ax=None` to dynamically instantiate Matplotlib figures).
10
+ - **svgplot() matches gplot()'s features**: SVG plots now support node labeling, boundary/obstacle vertex tagging, and figure legend.
11
+ - **Informative String Representations**: Added descriptive, debugger-safe string representations (`__repr__`) for `WindFarmNetwork` and `Router` subclasses (`EWRouter`, `HGSRouter`, `MILPRouter`) displaying key configuration parameters and solved network metrics.
12
+ - **Shorter Substation Labels**: Pre-packaged offshore wind farm datasets (.osm.pbf format) have been updated with short, human-readable substation abbreviations (such as "Alpha", "Beta", "OSS") to fit cleanly in visualization labels.
13
+ - **New Fused Heuristic**: Added `heuristics.constructor()` with `esau_williams`, `biased_EW`, `rootlust`, and `radial_EW` methods, unifying the constructive routing heuristics. The high-level `EWRouter` now uses this path, offering radial topology and the performant rootlust method.
14
+ - **LKH-3 Solver Parity**: Added `lkh3()` as the preferred LKH entry point, bringing it to feature parity with the HGS solver. It supports single- and multi-root configurations, per-root clustering, warm starts, capacity-violation retries, crossing repair, and improved solver metadata.
15
+ - **Expanded Crossing Diagnostics**: Added Shapely-based `find_geometric_crossings()` for geometry-first validation of arbitrary routesets, including detours, contour clones, shared-run overlap crossings, and branch-split cases.
16
+ - **Robust PathFinder Detours**: Major robustness improvements when routing detours among cable routes that follow boundaries or exclusion zones, significantly reducing cable use on sites with many obstacles.
17
+
18
+ ## Deprecated
19
+ - Standalone EW heuristics (`ClassicEW`, `CPEW`, `NBEW`, `OBEW`, and `EW_presolver`) are deprecated and will be removed in v0.3. They are superseded by the new unified `heuristics.constructor()`. Note that `constructor` expects the available-links graph `A`, not the location graph `L`.
20
+ - The legacy `optiwindnet.interface` module (`heuristic_wrapper()`, `HeuristicFactory`) is deprecated and will be removed in v0.3; use `WindFarmNetwork`/`EWRouter` instead.
21
+
22
+ ## Fixes
23
+ - **Pathfinder Robustness**: Resolved fatal crashes (`KeyError` and triangulation flip failures) when constructing detours.
24
+ - **Diagonal Mesh Exclusion**: Prevented invalid diagonal paths by skipping edges in the site's boundary polygon during navigation mesh generation.
25
+ - **Logging & Diagnostics**: Replaced all remaining raw `print()` statements across the API and utility modules with standard Python logging.
26
+ - **LKH and Heuristic Repairs**: Fixed LKH warm-start tour construction (indexing, walk order within clusters) and aligned HGS/LKH repair behavior for capacity-violating and crossing routes.
27
+ - **Overflow Prevention**: Added checks for LKH weight-matrix construction with clear guidance when inputs need normalization.
28
+ - **Crossing Detection**: Fixed shared-route overlap crossing detection and added geometric handling for route intersections not expressible as available-edge crossings.
29
+
30
+ ## Refactoring & Maintenance
31
+ - **Python 3.11–3.14 Support**: Explicitly declared support for Python 3.11 through 3.14 with standard Trove classifiers on PyPI.
32
+ - **Updated OR-Tools Floor**: Aligned OR-Tools requirements in `pyproject.toml` to `>=9.14.6206` for consistency across development and production environments.
33
+ - **Strict Deprecation Testing**: Test suite configured to treat `DeprecationWarning` as errors to guarantee API health.
34
+ - **Linting & Code Quality**: Enforced strict Ruff linting and formatting rules via continuous integration.
35
+ - **Performance Optimizations**: Optimized pathfinding sector lookups and precomputed chain-end topologies to speed up execution.
36
+
37
+ # v0.2.1
38
+
39
+ [Commit history since v0.2.0](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/-/compare/v0.2.0...v0.2.1)
40
+
41
+ ## Breaking Changes
42
+ - `optiwindnet.db` module only: **RouteSet schema slimmed (v4)**: `num_gates` was renamed to `feeders_per_root`; the unused `valid`, `is_normalized`, and `stuntC` columns were removed. The `python -m optiwindnet.db.migrate` script now writes the v4 schema and accepts both v2 (Pony ORM) and v3 (Peewee) source databases.
43
+
44
+ ## Important Changes
45
+ - **OR-Tools MILP backend switched to MathOpt API** (replacing `cp_model`), enabling multiple backends through a unified wrapper. No impact on use through either API.
46
+ - **Pyomo CPLEX/Gurobi solvers switched to the persistent interfaces** (`cplex_persistent`, `gurobi_persistent`). Relevant for successive calls to solver.solve().
47
+ - **`PathFinder`**: `A` is now a mandatory argument, it is relied upon to inform about tentative feeder crossings (saves the repeated check done before); default options were updated; search heuristics improved.
48
+
49
+ ## Features
50
+ - LKH improvements: iterations are also triggered on capacity violations, new `warmstart` argument.
51
+ - Better estimation of obstructed feeder lengths pre-optimization (make_planar_embedding).
52
+ - Default thread count for MathOpt solvers set to the number of physical cores; non-OR-Tools solvers also use `physical_core_count()`.
53
+ - New context-managed database connection API; `open_database()` and `database_connection()` accept a `timeout` argument.
54
+ - `.osm.pbf` parsing now accepts locations without borders.
55
+ - Added typing stubs and improved type annotations.
56
+ - Informative string repr for `SvgRepr`.
57
+
58
+ ## Fixes
59
+ - Multiple PathFinder robustness fixes: collinear vertices in funnel apex update, expansion of `P_paths` shortcuts when building contour clones, shortcut provenance tracking for barriers, cumulative turning check for dropping traversers, and `bad_streak` decay on first arrival.
60
+ - `make_planar_embedding` fixes: constraint checks and line-of-sight tagging now use Shapely's `STRtree`, proper handling of diagonal promotion conflicts in concave meshes, string-pulling skipped when only one border vertex is on the path (enabled by STRtree check).
61
+ - `validate_routeset()`: corrected detour index range; touchpoint set as bunch-split corner apex.
62
+ - LKH: replaced stale `_add_link_blockage` call with `add_link_blockmap`.
63
+ - Removed WAL mode from the SQLite open pragma (caused issues on shared clusters).
64
+ - Stunt vertices are no longer placed in `G` (regression since a70b575).
65
+ - Gracefully handle repeated extents' vertices in .yaml input files.
66
+ - Fixed `migrate.py` ImportError.
67
+
68
+ ## Refactoring & Maintenance
69
+ - Replaced `dill` with `pickle` everywhere it was used in tests.
70
+ - Removed `stuntC` from the routeset saving path.
71
+ - CI now runs a test matrix covering Python 3.11–3.14 (default bumped to 3.14); release requires passing on all versions.
72
+ - Increased test coverage and added topology-aware routeset comparison to prevent spurious failures.
73
+
74
+ ## Documentation
75
+ - Major refactor of the Topfarm integration example (now including substation trajectory); several notebook updates.
76
+ - Added links to TOPFARM and Ard, updated preamble with Jupyter tutorial links.
77
+ - Acknowledged the DFF grant in README and doc index.
78
+ - Improved docstrings and setup instructions.
79
+
1
80
  # v0.2.0
2
81
 
3
82
  [Commit history since v0.1.6](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/-/compare/v0.1.6...v0.2.0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: optiwindnet
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Tool for designing and optimizing the electrical cable network of offshore wind farms
5
5
  Author-email: DTU Wind Energy <mikf@dtu.dk>, Mauricio Souza de Alencar <ma___@dtu.dk>, Amir Arasteh <am___@dtu.dk>
6
6
  License-Expression: MIT
@@ -15,6 +15,10 @@ Classifier: Development Status :: 4 - Beta
15
15
  Classifier: Intended Audience :: Developers
16
16
  Classifier: Intended Audience :: Science/Research
17
17
  Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
18
22
  Classifier: Operating System :: OS Independent
19
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
24
  Classifier: Topic :: Scientific/Engineering
@@ -32,7 +36,7 @@ Requires-Dist: matplotlib>=3.10.1
32
36
  Requires-Dist: networkx>=3.6
33
37
  Requires-Dist: numba>=0.62.1
34
38
  Requires-Dist: numpy>=2.0
35
- Requires-Dist: ortools>=9.12.4544
39
+ Requires-Dist: ortools>=9.14.6206
36
40
  Requires-Dist: peewee>=3.17
37
41
  Requires-Dist: Pyomo>=6.9.5
38
42
  Requires-Dist: PyYAML>=6.0.2
@@ -46,7 +50,6 @@ Requires-Dist: gurobipy; extra == "solvers"
46
50
  Requires-Dist: highspy; extra == "solvers"
47
51
  Requires-Dist: pyscipopt; extra == "solvers"
48
52
  Provides-Extra: test
49
- Requires-Dist: dill; extra == "test"
50
53
  Requires-Dist: coverage; extra == "test"
51
54
  Requires-Dist: pytest; extra == "test"
52
55
  Requires-Dist: pytest-cov; extra == "test"
@@ -69,11 +72,11 @@ Requires-Dist: pypandoc; extra == "docs"
69
72
  Requires-Dist: setuptools-scm; extra == "docs"
70
73
  Dynamic: license-file
71
74
 
72
- [![pipeline status](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/badges/main/pipeline.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/commits/main)
73
75
  [![PyPi](https://img.shields.io/pypi/v/optiwindnet)](https://pypi.org/project/optiwindnet/)
74
76
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.18388438.svg)](https://doi.org/10.5281/zenodo.18388438)
75
- [![License](https://img.shields.io/pypi/l/optiwindnet)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/blob/main/LICENSE)
76
- [![coverage report](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/badges/main/coverage.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/commits/main)
77
+ [![License](https://img.shields.io/pypi/l/optiwindnet)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/blob/main/LICENSE.txt)
78
+ [![pipeline status](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/badges/main/pipeline.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/-/pipelines)
79
+ [![coverage report](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/badges/main/coverage.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/-/jobs)
77
80
  <!---
78
81
  [![DOI](https://zenodo.org/badge/164115313.svg)](https://zenodo.org/badge/latestdoi/164115313)
79
82
  -->
@@ -113,7 +116,7 @@ Detailed instructions in [Installation](https://optiwindnet.readthedocs.io/stabl
113
116
  Requirements
114
117
  ------------
115
118
 
116
- Python version 3.11 or 3.12 is recommended to run *OptiWindNet*. Python 3.13+ may cause issues with the `optiwindnet.db` module, but all other features work fine. The last version to support Python 3.10 was v0.1.0.
119
+ *OptiWindNet* requires Python version 3.11+ (tested with 3.11-14). The last version to support Python 3.10 was v0.0.6.
117
120
 
118
121
  The use of a Python virtual environment is recommended. OptiWindNet's dependencies (except for MILP solvers, see docs) will be installed automatically when using `pip install optiwindnet` or `conda install optiwindnet`.
119
122
 
@@ -146,11 +149,22 @@ European Journal of Operational Research,
146
149
  }
147
150
  ```
148
151
 
152
+ Citing OptiWindNet
153
+ ------------------
154
+
155
+ The OptiWindNet software package can be cited (unversioned) as:
156
+ > Souza de Alencar, M., Arasteh, A., & Friis-Møller, M. (2026). OptiWindNet by DTU Wind Energy. Zenodo. https://doi.org/10.5281/zenodo.18388438
157
+
158
+ To cite a specific version, get the version-specific DOI at [OptiWindNet's entry at Zenodo](https://doi.org/10.5281/zenodo.18388438). Select the desired version on the right column and use one of the ready-to-use citation formats available at the bottom right of that page.
159
+
160
+
149
161
  Acknowledgements
150
162
  ----------------
151
163
 
152
- The heuristics implemented in this repository (release 0.0.1) are presented and analyzed in the MSc thesis [Optimization heuristics for offshore wind power plant collection systems design](https://fulltext-gateway.cvt.dk/oafilestore?oid=62dddf809a5e7116caf943f3&targetid=62dddf80a41ba354e4ed35bc) (DTU Wind - Technical University of Denmark, July 4, 2022)
164
+ The development of OptiWindNet was carried out as part of a Ph.D. project at the Technical University of Denmark (DTU Wind), financially supported by the Independent Research Fund Denmark / Danmarks Frie Forskningsfond (DFF) under grant no. 1127-00188B, project _Integrated Design of Offshore Wind Power Plants_.
165
+
166
+ The heuristics implemented in this repository (release 0.0.1) are presented and analyzed in the MSc thesis [Optimization heuristics for offshore wind power plant collection systems design](https://fulltext-gateway.cvt.dk/oafilestore?oid=62dddf809a5e7116caf943f3&targetid=62dddf80a41ba354e4ed35bc) (DTU Wind - Technical University of Denmark, July 4, 2022).
153
167
 
154
- The meta-heuristic used is [vidalt/HGS-CVRP: Modern implementation of the hybrid genetic search (HGS) algorithm specialized to the capacitated vehicle routing problem (CVRP). This code also includes an additional neighborhood called SWAP\*.](https://github.com/vidalt/HGS-CVRP) via its Python bindings [chkwon/PyHygese: A Python wrapper for the Hybrid Genetic Search algorithm for Capacitated Vehicle Routing Problems (HGS-CVRP)](https://github.com/chkwon/PyHygese).
168
+ The meta-heuristic used is [vidalt/HGS-CVRP](https://github.com/vidalt/HGS-CVRP) a modern implementation of the hybrid genetic search (HGS) algorithm specialized to the capacitated vehicle routing problem (CVRP), including an additional neighborhood called SWAP\* via its Python bindings [mdealencar/HybGenSea](https://github.com/mdealencar/HybGenSea).
155
169
 
156
- The cable routing relies on a navigation mesh generated by the library [artem-ogre/CDT: Constrained Delaunay Triangulation (C++)](https://github.com/artem-ogre/CDT) via its Python bindings - [artem-ogre/PythonCDT: Constrained Delaunay Triangulation (Python)](https://github.com/artem-ogre/PythonCDT).
170
+ The cable routing relies on a navigation mesh generated by the library [artem-ogre/CDT](https://github.com/artem-ogre/CDT) (Constrained Delaunay Triangulation, C++) via its Python bindings [artem-ogre/PythonCDT](https://github.com/artem-ogre/PythonCDT).
@@ -1,8 +1,8 @@
1
- [![pipeline status](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/badges/main/pipeline.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/commits/main)
2
1
  [![PyPi](https://img.shields.io/pypi/v/optiwindnet)](https://pypi.org/project/optiwindnet/)
3
2
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.18388438.svg)](https://doi.org/10.5281/zenodo.18388438)
4
- [![License](https://img.shields.io/pypi/l/optiwindnet)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/blob/main/LICENSE)
5
- [![coverage report](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/badges/main/coverage.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/commits/main)
3
+ [![License](https://img.shields.io/pypi/l/optiwindnet)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/blob/main/LICENSE.txt)
4
+ [![pipeline status](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/badges/main/pipeline.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/-/pipelines)
5
+ [![coverage report](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/badges/main/coverage.svg)](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/-/jobs)
6
6
  <!---
7
7
  [![DOI](https://zenodo.org/badge/164115313.svg)](https://zenodo.org/badge/latestdoi/164115313)
8
8
  -->
@@ -42,7 +42,7 @@ Detailed instructions in [Installation](https://optiwindnet.readthedocs.io/stabl
42
42
  Requirements
43
43
  ------------
44
44
 
45
- Python version 3.11 or 3.12 is recommended to run *OptiWindNet*. Python 3.13+ may cause issues with the `optiwindnet.db` module, but all other features work fine. The last version to support Python 3.10 was v0.1.0.
45
+ *OptiWindNet* requires Python version 3.11+ (tested with 3.11-14). The last version to support Python 3.10 was v0.0.6.
46
46
 
47
47
  The use of a Python virtual environment is recommended. OptiWindNet's dependencies (except for MILP solvers, see docs) will be installed automatically when using `pip install optiwindnet` or `conda install optiwindnet`.
48
48
 
@@ -75,11 +75,22 @@ European Journal of Operational Research,
75
75
  }
76
76
  ```
77
77
 
78
+ Citing OptiWindNet
79
+ ------------------
80
+
81
+ The OptiWindNet software package can be cited (unversioned) as:
82
+ > Souza de Alencar, M., Arasteh, A., & Friis-Møller, M. (2026). OptiWindNet by DTU Wind Energy. Zenodo. https://doi.org/10.5281/zenodo.18388438
83
+
84
+ To cite a specific version, get the version-specific DOI at [OptiWindNet's entry at Zenodo](https://doi.org/10.5281/zenodo.18388438). Select the desired version on the right column and use one of the ready-to-use citation formats available at the bottom right of that page.
85
+
86
+
78
87
  Acknowledgements
79
88
  ----------------
80
89
 
81
- The heuristics implemented in this repository (release 0.0.1) are presented and analyzed in the MSc thesis [Optimization heuristics for offshore wind power plant collection systems design](https://fulltext-gateway.cvt.dk/oafilestore?oid=62dddf809a5e7116caf943f3&targetid=62dddf80a41ba354e4ed35bc) (DTU Wind - Technical University of Denmark, July 4, 2022)
90
+ The development of OptiWindNet was carried out as part of a Ph.D. project at the Technical University of Denmark (DTU Wind), financially supported by the Independent Research Fund Denmark / Danmarks Frie Forskningsfond (DFF) under grant no. 1127-00188B, project _Integrated Design of Offshore Wind Power Plants_.
91
+
92
+ The heuristics implemented in this repository (release 0.0.1) are presented and analyzed in the MSc thesis [Optimization heuristics for offshore wind power plant collection systems design](https://fulltext-gateway.cvt.dk/oafilestore?oid=62dddf809a5e7116caf943f3&targetid=62dddf80a41ba354e4ed35bc) (DTU Wind - Technical University of Denmark, July 4, 2022).
82
93
 
83
- The meta-heuristic used is [vidalt/HGS-CVRP: Modern implementation of the hybrid genetic search (HGS) algorithm specialized to the capacitated vehicle routing problem (CVRP). This code also includes an additional neighborhood called SWAP\*.](https://github.com/vidalt/HGS-CVRP) via its Python bindings [chkwon/PyHygese: A Python wrapper for the Hybrid Genetic Search algorithm for Capacitated Vehicle Routing Problems (HGS-CVRP)](https://github.com/chkwon/PyHygese).
94
+ The meta-heuristic used is [vidalt/HGS-CVRP](https://github.com/vidalt/HGS-CVRP) a modern implementation of the hybrid genetic search (HGS) algorithm specialized to the capacitated vehicle routing problem (CVRP), including an additional neighborhood called SWAP\* via its Python bindings [mdealencar/HybGenSea](https://github.com/mdealencar/HybGenSea).
84
95
 
85
- The cable routing relies on a navigation mesh generated by the library [artem-ogre/CDT: Constrained Delaunay Triangulation (C++)](https://github.com/artem-ogre/CDT) via its Python bindings - [artem-ogre/PythonCDT: Constrained Delaunay Triangulation (Python)](https://github.com/artem-ogre/PythonCDT).
96
+ The cable routing relies on a navigation mesh generated by the library [artem-ogre/CDT](https://github.com/artem-ogre/CDT) (Constrained Delaunay Triangulation, C++) via its Python bindings [artem-ogre/PythonCDT](https://github.com/artem-ogre/PythonCDT).
@@ -0,0 +1,137 @@
1
+ """Validation driver for the chain-sleeve pathfinding feature.
2
+
3
+ Loads pickled G_db routesets for four RIDs and re-runs PathFinder, checking
4
+ that the expected feeder paths and total length are produced.
5
+
6
+ Usage:
7
+ python _run_chain_tests.py
8
+ """
9
+
10
+ import math
11
+ import pickle
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ from optiwindnet.interarraylib import G_from_S, L_from_G, S_from_G
16
+ from optiwindnet.mesh import make_planar_embedding
17
+ from optiwindnet.pathfinding import PathFinder
18
+
19
+ EXPECTED = {
20
+ 22822: {
21
+ 'length': 9.4484,
22
+ 'feeders': [[-1, 46, 70, 35], [-1, 46, 70, 26]],
23
+ },
24
+ 33303: {
25
+ 'length': 9.5741,
26
+ 'feeders': [[-1, 46, 70, 35], [-1, 46, 70, 26]],
27
+ },
28
+ 33372: {
29
+ 'length': 11.1059,
30
+ 'feeders': [[-1, 40, 57, 44], [-1, 40, 57, 58, 59, 5]],
31
+ },
32
+ 35875: {
33
+ 'length': None,
34
+ 'feeders': [[-1, 60, 75, 76, 77, 8]],
35
+ },
36
+ }
37
+
38
+
39
+ def _feeder_paths(G):
40
+ """Extract every -1->...->terminal path through the routeset, mapped to primes."""
41
+ R = G.graph['R']
42
+ T = G.graph['T']
43
+ fnT = G.graph.get('fnT')
44
+
45
+ def prime(n):
46
+ if n < 0:
47
+ return n
48
+ if fnT is not None and n < len(fnT):
49
+ return int(fnT[n])
50
+ return n
51
+
52
+ feeders = []
53
+ # walk every root->...->terminal path through detour clones and contour
54
+ # helpers; emit one feeder per terminal-anchored path that starts at root
55
+ for r in range(-R, 0):
56
+ for first in G.neighbors(r):
57
+ # DFS to find paths root → ... → terminal (prime in [0, T))
58
+ stack = [(first, [r, first], {r, first})]
59
+ while stack:
60
+ cur, path, visited = stack.pop()
61
+ # Terminate on actual terminal node (not a clone whose prime is
62
+ # a terminal — those happen along contour helpers).
63
+ if 0 <= cur < T:
64
+ feeders.append([prime(x) for x in path])
65
+ continue
66
+ for nxt in G.neighbors(cur):
67
+ if nxt in visited:
68
+ continue
69
+ stack.append((nxt, path + [nxt], visited | {nxt}))
70
+ return feeders
71
+
72
+
73
+ def _run_one(rid):
74
+ p = Path(__file__).parent / 'tests' / 'locations' / f'G_{rid}.pkl'
75
+ with open(p, 'rb') as f:
76
+ G_db = pickle.load(f)
77
+
78
+ # reconstruct site → planar+A → topology → tentative → PathFinder
79
+ L = L_from_G(G_db)
80
+ L.graph['border'] = G_db.graph['border']
81
+ L.graph['VertexC'] = G_db.graph['VertexC'][
82
+ : G_db.graph['T'] + G_db.graph['B'] - G_db.graph.get('D', 0)
83
+ ] if False else G_db.graph['VertexC'][
84
+ : G_db.graph['T'] + G_db.graph['B']
85
+ ].copy()
86
+ # Stitch the actual VertexC together: terminals + borders + roots
87
+ import numpy as np
88
+ R, T, B = G_db.graph['R'], G_db.graph['T'], G_db.graph['B']
89
+ VertexC = G_db.graph['VertexC']
90
+ L.graph['VertexC'] = np.vstack((VertexC[: T + B], VertexC[-R:]))
91
+ L.graph['B'] = B
92
+ L.graph['R'] = R
93
+ L.graph['T'] = T
94
+
95
+ P, A = make_planar_embedding(L)
96
+ S = S_from_G(G_db)
97
+ G_tent = G_from_S(S, A)
98
+ pf = PathFinder(G_tent, planar=P, A=A)
99
+ G_out = pf.create_detours()
100
+ length = G_out.size(weight='length')
101
+ feeders = _feeder_paths(G_out)
102
+ return length, feeders
103
+
104
+
105
+ def main():
106
+ failures = 0
107
+ for rid, expected in EXPECTED.items():
108
+ try:
109
+ length, feeders = _run_one(rid)
110
+ except Exception as e:
111
+ print(f' RID {rid}: ERROR {type(e).__name__}: {e}')
112
+ failures += 1
113
+ continue
114
+ ok_len = (
115
+ expected['length'] is None
116
+ or math.isclose(length, expected['length'], abs_tol=1e-3)
117
+ )
118
+ # Match expected feeders as multisets of prime-vertex sequences
119
+ got_set = {tuple(f) for f in feeders}
120
+ want_set = {tuple(f) for f in expected['feeders']}
121
+ missing = want_set - got_set
122
+ ok_feeders = not missing
123
+ status = 'OK' if (ok_len and ok_feeders) else 'FAIL'
124
+ if status == 'FAIL':
125
+ failures += 1
126
+ print(
127
+ f'RID {rid}: {status} length={length:.4f}'
128
+ + (f' (expected {expected["length"]})' if expected['length'] else '')
129
+ )
130
+ if missing:
131
+ print(f' missing feeders: {sorted(missing)}')
132
+ print(f' got feeders: {sorted(got_set)}')
133
+ return failures
134
+
135
+
136
+ if __name__ == '__main__':
137
+ sys.exit(main())
@@ -9,8 +9,8 @@ from ._core import (
9
9
  FeederRoute,
10
10
  ModelMetadata,
11
11
  ModelOptions,
12
- OWNWarmupFailed,
13
12
  OWNSolutionNotFound,
13
+ OWNWarmupFailed,
14
14
  SolutionInfo,
15
15
  Solver,
16
16
  Topology,
@@ -36,18 +36,22 @@ def solver_factory(solver_name: str) -> Solver:
36
36
  Note that the only solver that is a dependency of OptiWindNet is 'ortools'.
37
37
  Check OptiWindNet's documentation on how to install optional solvers.
38
38
 
39
+ Legacy compatibility: if solver_name == 'ortools' then the CP-SAT backend is used.
40
+
39
41
  Args:
40
- solver_name: one of 'ortools', 'cplex', 'gurobi', 'cbc', 'scip', 'highs'.
42
+ solver_name: one of 'ortools.cp_sat', 'ortools.gscip',
43
+ 'ortools.highs', 'cplex', 'gurobi', 'cbc', 'scip', 'highs'.
41
44
 
42
45
  Returns:
43
46
  Solver instance that can produce solutions for the cable routing problem.
44
47
  """
48
+ solver_name, *backend = solver_name.split('.', maxsplit=1)
45
49
  match solver_name:
46
50
  case 'ortools':
47
51
  if find_spec('ortools'):
48
52
  from .ortools import SolverORTools
49
53
 
50
- return SolverORTools()
54
+ return SolverORTools(backend[0] if backend else 'cp_sat')
51
55
  raise ModuleNotFoundError(
52
56
  "Package 'ortools' not found. Try 'pip install ortools'."
53
57
  )
@@ -95,8 +99,9 @@ def solver_factory(solver_name: str) -> Solver:
95
99
  return SolverFSCIP()
96
100
  else:
97
101
  raise FileNotFoundError(
98
- "Executable 'fscip' not found. Ensure the system PATH includes the "
99
- "path to 'fscip' (part of scipoptsuite from https://www.scipopt.org)."
102
+ "Executable 'fscip' not found. Ensure the system PATH includes"
103
+ " the path to 'fscip' (part of scipoptsuite from"
104
+ ' https://www.scipopt.org).'
100
105
  )
101
106
  else:
102
107
  raise ModuleNotFoundError(
@@ -105,9 +110,10 @@ def solver_factory(solver_name: str) -> Solver:
105
110
  )
106
111
  case 'highs':
107
112
  if find_spec('highspy'):
108
- from .pyomo import SolverPyomoAppsi
109
113
  from pyomo.contrib.appsi.solvers.highs import Highs
110
114
 
115
+ from .pyomo import SolverPyomoAppsi
116
+
111
117
  return SolverPyomoAppsi(solver_name, Highs)
112
118
  raise ModuleNotFoundError(
113
119
  "Package 'highspy' not found. Try 'pip install highspy' or "
@@ -3,11 +3,14 @@
3
3
 
4
4
  import abc
5
5
  import logging
6
- from dataclasses import asdict, dataclass
7
-
6
+ import os
7
+ import sys
8
+ from collections.abc import Mapping
9
+ from dataclasses import asdict, dataclass, field
8
10
  from enum import StrEnum, auto
9
11
  from itertools import chain
10
- from typing import Any, Mapping
12
+ from pathlib import Path
13
+ from typing import Any
11
14
 
12
15
  import networkx as nx
13
16
  from makefun import with_signature
@@ -19,6 +22,37 @@ _lggr = logging.getLogger(__name__)
19
22
  error, info = _lggr.error, _lggr.info
20
23
 
21
24
 
25
+ def physical_core_count() -> int:
26
+ """Count physical cores available to this process (cross-platform).
27
+
28
+ On Linux, reads sysfs topology to count physical cores within the
29
+ process affinity set. On other platforms, falls back to psutil.
30
+ """
31
+ if sys.platform == 'linux':
32
+ try:
33
+ affinity = os.sched_getaffinity(0)
34
+ except OSError:
35
+ affinity = None
36
+ if affinity is not None:
37
+ physical_cores = set()
38
+ for cpu_id in affinity:
39
+ topo = Path(f'/sys/devices/system/cpu/cpu{cpu_id}/topology')
40
+ try:
41
+ pkg = (topo / 'physical_package_id').read_text().strip()
42
+ core = (topo / 'core_id').read_text().strip()
43
+ physical_cores.add((pkg, core))
44
+ except OSError:
45
+ # sysfs unavailable (e.g. container), fall through
46
+ physical_cores = None
47
+ break
48
+ if physical_cores is not None:
49
+ return len(physical_cores)
50
+ # Windows, macOS, or Linux without sysfs
51
+ import psutil
52
+
53
+ return psutil.cpu_count(logical=False) or os.cpu_count() or 1
54
+
55
+
22
56
  def _identifier_from_class_name(c: type) -> str:
23
57
  "Convert a camel-case class name to a snake-case identifier"
24
58
  s = c.__name__
@@ -50,7 +84,9 @@ class FeederRoute(StrEnum):
50
84
 
51
85
 
52
86
  class FeederLimit(StrEnum):
53
- 'Whether to limit the maximum number of feeders, if set to "specified", additional kwarg "max_feeders" must be given.'
87
+ 'Whether to limit the maximum number of feeders, if set to "specified",'
88
+
89
+ ' additional kwarg "max_feeders" must be given.'
54
90
 
55
91
  UNLIMITED = auto()
56
92
  SPECIFIED = auto()
@@ -125,16 +161,21 @@ class ModelOptions(dict):
125
161
  print(f'{name} [{kind.__name__}] default: {default}\n {desc}\n')
126
162
 
127
163
 
164
+ _Link = tuple[int, int]
165
+
166
+
128
167
  @dataclass(slots=True)
129
168
  class ModelMetadata:
130
169
  R: int
131
170
  T: int
132
171
  capacity: int
133
- linkset: tuple
134
- link_: Mapping
135
- flow_: Mapping
136
- model_options: dict
172
+ linkset: tuple[_Link, ...]
173
+ link_: Mapping[_Link, Any]
174
+ flow_: Mapping[_Link, Any]
175
+ model_options: dict[str, Any]
137
176
  fun_fingerprint: dict[str, str | bytes]
177
+ weight_: tuple[float, ...] = ()
178
+ solution_hint: dict[Any, float] = field(default_factory=dict)
138
179
  warmed_by: str = ''
139
180
 
140
181
 
@@ -0,0 +1,120 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Typing stubs for optiwindnet.MILP._core
3
+ # Surfaces the dynamically generated ModelOptions.__init__ signature.
4
+
5
+ import abc
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass, field
8
+ from enum import StrEnum
9
+ from typing import Any
10
+
11
+ import networkx as nx
12
+
13
+ def physical_core_count() -> int: ...
14
+
15
+ class OWNWarmupFailed(Exception): ...
16
+ class OWNSolutionNotFound(Exception): ...
17
+
18
+ class Topology(StrEnum):
19
+ RADIAL: str
20
+ BRANCHED: str
21
+ DEFAULT: str
22
+
23
+ class FeederRoute(StrEnum):
24
+ STRAIGHT: str
25
+ SEGMENTED: str
26
+ DEFAULT: str
27
+
28
+ class FeederLimit(StrEnum):
29
+ UNLIMITED: str
30
+ SPECIFIED: str
31
+ MINIMUM: str
32
+ MIN_PLUS1: str
33
+ MIN_PLUS2: str
34
+ MIN_PLUS3: str
35
+ DEFAULT: str
36
+
37
+ _Link = tuple[int, int]
38
+
39
+ class ModelOptions(dict[str, Any]):
40
+ """Hold options for the modelling of the cable routing problem."""
41
+
42
+ hints: dict[str, type[StrEnum]]
43
+ simple: dict[str, tuple[type, Any, str]]
44
+
45
+ def __init__(
46
+ self,
47
+ *,
48
+ topology: Topology | str = ...,
49
+ feeder_route: FeederRoute | str = ...,
50
+ feeder_limit: FeederLimit | str = ...,
51
+ balanced: bool = ...,
52
+ max_feeders: int = ...,
53
+ ) -> None: ...
54
+ @classmethod
55
+ def help(cls) -> None: ...
56
+
57
+ @dataclass(slots=True)
58
+ class ModelMetadata:
59
+ R: int
60
+ T: int
61
+ capacity: int
62
+ linkset: tuple[_Link, ...]
63
+ link_: Mapping[_Link, Any]
64
+ flow_: Mapping[_Link, Any]
65
+ model_options: dict[str, Any]
66
+ fun_fingerprint: dict[str, str | bytes]
67
+ weight_: tuple[float, ...] = ()
68
+ solution_hint: dict[Any, float] = field(default_factory=dict)
69
+ warmed_by: str = ''
70
+
71
+ @dataclass(slots=True)
72
+ class SolutionInfo:
73
+ runtime: float
74
+ bound: float
75
+ objective: float
76
+ relgap: float
77
+ termination: str
78
+
79
+ class Solver(abc.ABC):
80
+ name: str
81
+ metadata: ModelMetadata
82
+ solver: Any
83
+ options: dict[str, Any]
84
+ stopping: dict[str, Any]
85
+ solution_info: SolutionInfo
86
+ applied_options: dict[str, Any]
87
+
88
+ @abc.abstractmethod
89
+ def _link_val(self, var: Any) -> int | bool: ...
90
+ @abc.abstractmethod
91
+ def _flow_val(self, var: Any) -> int: ...
92
+ @abc.abstractmethod
93
+ def set_problem(
94
+ self,
95
+ P: nx.PlanarEmbedding,
96
+ A: nx.Graph,
97
+ capacity: int,
98
+ model_options: ModelOptions,
99
+ warmstart: nx.Graph | None = None,
100
+ ) -> None: ...
101
+ @abc.abstractmethod
102
+ def solve(
103
+ self,
104
+ time_limit: float,
105
+ mip_gap: float,
106
+ options: dict[str, Any] = ...,
107
+ verbose: bool = False,
108
+ ) -> SolutionInfo: ...
109
+ @abc.abstractmethod
110
+ def get_solution(self, A: nx.Graph | None = None) -> tuple[nx.Graph, nx.Graph]: ...
111
+
112
+ class PoolHandler(abc.ABC):
113
+ name: str
114
+ num_solutions: int
115
+ model_options: ModelOptions
116
+
117
+ @abc.abstractmethod
118
+ def _objective_at(self, index: int) -> float: ...
119
+ @abc.abstractmethod
120
+ def _topology_from_mip_pool(self) -> nx.Graph: ...