optiwindnet 0.2.2__tar.gz → 0.2.3__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.
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/CHANGELOG.md +37 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/PKG-INFO +1 -1
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/MILP/__init__.py +5 -4
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/MILP/_core.py +88 -11
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/MILP/_core.pyi +8 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/MILP/gurobi.py +5 -4
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/MILP/ortools.py +42 -59
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/MILP/pyomo.py +53 -72
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/MILP/scip.py +35 -54
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/api.py +107 -58
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/api_utils.py +30 -6
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/augmentation.py +37 -35
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/baselines/_core.py +3 -3
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/baselines/hgs.py +125 -27
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/baselines/lkh.py +41 -39
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/baselines/utils.py +17 -16
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/clustering.py +3 -3
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/crossings.py +19 -19
- optiwindnet-0.2.3/optiwindnet/data/East Anglia THREE.osm.pbf +0 -0
- optiwindnet-0.2.3/optiwindnet/data/Hollandse Kust Noord.osm.pbf +0 -0
- optiwindnet-0.2.3/optiwindnet/data/Hornsea 2.osm.pbf +0 -0
- optiwindnet-0.2.3/optiwindnet/data/Norfolk Vanguard West.osm.pbf +0 -0
- optiwindnet-0.2.3/optiwindnet/data/Revolution.osm.pbf +0 -0
- optiwindnet-0.2.3/optiwindnet/data/Sunrise.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/db/migrate.py +2 -2
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/db/model.py +87 -25
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/db/storage.py +14 -13
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/geometric.py +48 -257
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/heuristics/constructor.py +31 -26
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/heuristics/priorityqueue.py +7 -5
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/importer.py +76 -31
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/interarraylib.py +99 -80
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/mesh.py +20 -162
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/pathfinding.py +129 -72
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/plotting.py +12 -12
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/repair.py +18 -16
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/svg.py +13 -13
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/synthetic.py +3 -2
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/utils.py +0 -6
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/version.py +3 -3
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet.egg-info/PKG-INFO +1 -1
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet.egg-info/SOURCES.txt +12 -2
- optiwindnet-0.2.3/optiwindnet.egg-info/scm_file_list.json +276 -0
- optiwindnet-0.2.3/optiwindnet.egg-info/scm_version.json +8 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/pyproject.toml +1 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/conftest.py +16 -1
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/helpers.py +47 -1
- optiwindnet-0.2.3/tests/isolation.py +122 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/paths.py +1 -1
- optiwindnet-0.2.3/tests/test_MILP.py +400 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_api_WindFarmNetwork.py +74 -2
- optiwindnet-0.2.3/tests/test_baselines_utils.py +48 -0
- optiwindnet-0.2.3/tests/test_end_to_end.py +143 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_geometric.py +163 -0
- optiwindnet-0.2.3/tests/test_hgs.py +227 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_importer.py +17 -5
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_interarraylib.py +105 -2
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_mesh.py +0 -36
- optiwindnet-0.2.3/tests/test_plotting.py +125 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_repair.py +53 -0
- optiwindnet-0.2.3/tests/test_svg.py +391 -0
- optiwindnet-0.2.3/tests/test_themes.py +26 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_utils.py +1 -24
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/update_expected_values.py +31 -10
- optiwindnet-0.2.2/_run_chain_tests.py +0 -137
- optiwindnet-0.2.2/optiwindnet/weighting.py +0 -20
- optiwindnet-0.2.2/tests/test_MILP.py +0 -235
- optiwindnet-0.2.2/tests/test_end_to_end.py +0 -106
- optiwindnet-0.2.2/tests/test_svg.py +0 -212
- optiwindnet-0.2.2/tests/test_themes.py +0 -8
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/.github/workflows/release.yml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/.zenodo.json +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/CITATION.cff +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/LICENSE.txt +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/README.md +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/MILP/cplex.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/MILP/fscip.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/__init__.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/baselines/__init__.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Albatros.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Amrumbank West.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Anholt.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Arkona.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/BARD Offshore 1.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Baltic 2.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Baltic Eagle.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Beatrice.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Belwind.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Borkum Riffgrund 1.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Borkum Riffgrund 2.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Borkum Riffgrund 3.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Borssele.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Butendiek.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/CECEP Yangjiang Nanpeng Island.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/CGN Rudong Demonstration.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Cazzaro-2022.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Cazzaro-2022G-140.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Cazzaro-2022G-210.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Coastal Virginia.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/DanTysk.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Deutsche Bucht.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Dogger Bank A.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Dogger Bank B.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Dogger Bank C.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Dudgeon.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/East Anglia ONE.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Fecamp.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Galloper Inner.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Gemini 1.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Gemini 2.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Global Tech 1.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Gode Wind 1.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Greater Changhua 1.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Greater Gabbard Inner.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Gwynt y Mor.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/He Dreiht.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Hohe See.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Hollandse Kust Zuid.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Horns Rev 1.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Horns Rev 2.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Horns Rev 3.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Hornsea One.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Hornsea Two West.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Huizhou Gangkou 1.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Huizhou Gangkou 2.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Humber Gateway.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Inch Cape.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Kaskasi.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Kriegers Flak A.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Kriegers Flak B.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Laoting Bodhi Island.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Lillgrund.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Lincs.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/London Array.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Luchterduinen.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Meerwind.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Merkur.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Mermaid.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Moray East.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Moray West.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Neart na Gaoithe.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Noirmoutier.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Nordsee One.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Nordsee Ost.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Norther.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Northwind.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Nysted.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Ormonde.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Princess Amalia.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Race Bank.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Rampion.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Rentel.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Riffgat.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Robin Rigg.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Rudong H10.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Rudong H6.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Rudong H8.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/R/303/270dsand 2.yaml" +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/SPIC Binhai North H2.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Saint-Brieuc.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Saint-Nazaire.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Sandbank.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Seagreen.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Shengsi 2.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Sheringham Shoal.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Sofia.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Taylor-2023.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Thanet.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Thor.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Trianel Windpark Borkum.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Triton Knoll.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Tr/303/251port.osm.pbf" +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Veja Mate.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Vineyard Wind 1.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Walney 1.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Walney 2.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Walney Extension.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/West of Duddon Sands.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Westermost Rough.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Wikinger.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Yi-2019.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Yunlin.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/data/Zhejiang Jiaxing 1.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/db/__init__.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/db/_core.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/db/model.pyi +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/heuristics/ClassicEsauWilliams.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/heuristics/CrossingPreventingEW.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/heuristics/EW_presolver.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/heuristics/NonBranchingEW.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/heuristics/ObstacleBypassingEW.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/heuristics/__init__.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/heuristics/_deprecation.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/interface.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/py.typed +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet/themes.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet.egg-info/dependency_links.txt +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet.egg-info/requires.txt +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/optiwindnet.egg-info/top_level.txt +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/setup.cfg +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/sitecustomize.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/__init__.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/locations/example_1ss_300wt.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/locations/example_4ss_300wt.osm.pbf +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/locations/example_location.yaml +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/solutions.pkl +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_augmentation.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_constructor.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_crossings.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_db.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_lkh.py +0 -0
- {optiwindnet-0.2.2 → optiwindnet-0.2.3}/tests/test_pathfinding.py +0 -0
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
# v0.2.3
|
|
2
|
+
|
|
3
|
+
[Commit history since v0.2.2](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/-/compare/v0.2.2...v0.2.3)
|
|
4
|
+
|
|
5
|
+
Drop-in replacement for v0.2.2. The APIs deprecated in v0.2.2 are still available and still emit `DeprecationWarning`; they are scheduled for removal in v0.3.
|
|
6
|
+
|
|
7
|
+
## Important Changes
|
|
8
|
+
- **Balanced Subtrees Actually Balanced (MILP and HGS)**: `balanced=True` promises subtree loads differing at most by one unit, but both solver families only bounded those loads from below, letting a subtree grow up to `capacity`. This is now fixed consistently on both sides:
|
|
9
|
+
- *MILP*: the upper bound `ceil(T / feeders)` is now enforced across the pyomo, SCIP and OR-Tools model builders.
|
|
10
|
+
- *HGS*: `hgs_cvrp()` used to request more slack nodes than there were routes whenever `T < feeders * (capacity - 1)`, forcing the surplus through clipped `inf` arcs and inflating the reported objective. The balanced sub-problem is now solved at `capacity_effective = ceil(T / feeders)`, reported in `solver_details`.
|
|
11
|
+
- **Exact Feeder Count (MILP and HGS)**: a new, mirrored way to pin the feeder count instead of only bounding it from above. Because `balanced` is only expressible once the feeder count is pinned, this makes balanced solutions reachable above the minimum feeder count.
|
|
12
|
+
- *MILP*: `feeder_limit="exactly"` pins the feeder count to `max_feeders` (whereas `"specified"` remains an upper bound).
|
|
13
|
+
- *HGS*: `hgs_cvrp(vehicles=F, vehicles_exact=True)` — exposed as `HGSRouter(feeder_limit=F, feeder_exact=True)` — pins the feeder count to `F`, whereas `vehicles` alone remains an upper bound that HGS-CVRP normally undershoots. It currently requires `balanced=True` and a single substation; `F` must lie between `ceil(T / capacity)` and `T`.
|
|
14
|
+
- **Reversible lat/lon Coordinates**: `L_from_yaml()` and `L_from_pbf()` now project all coordinates into the single UTM zone holding the most turbines (instead of the zone of the first point), minimizing distortion for the bulk of the layout, and retain that zone as the graph attributes `utm_zone_number` and `utm_zone_letter`. This makes `VertexC` reversible back to lat/lon via `utm.to_latlon()`. Multi-zone `.yaml` input no longer raises an assertion.
|
|
15
|
+
- **Tunable `EWRouter`**: the `method` and `bias_margin` parameters of `heuristics.constructor()` are now exposed on `EWRouter`, giving access to the `esau_williams`, `biased_EW`, `rootlust` and `radial_EW` methods from the high-level API.
|
|
16
|
+
- **Concurrent HiGHS**: the pyomo HiGHS solver now runs a concurrent branch-and-bound tree search, in line with the other MILP backends. This speedup requires `highspy` v1.15 or newer; older versions accept the setting but run the search serially.
|
|
17
|
+
- **New Locations**: added Revolution, Sunrise, Hornsea 2, Norfolk Vanguard West, East Anglia 3 and Hollandse Kust Noord.
|
|
18
|
+
|
|
19
|
+
## Bug Fixes
|
|
20
|
+
- **Multi-Root Warmstart Eligibility**: `is_warmstart_eligible()` compared only the first root's feeder count against the feeder limit, while the model constrains the total across all roots.
|
|
21
|
+
- **`PathFinder` Malformed Chain**: two spanning fences of the same subtree meeting at a single chain-end vertex form a dead-end pocket, which could leave a chain short an access cone. Chain detection is now keyed on the subtree alone within a chain-end vertex.
|
|
22
|
+
- **`scaffolded()` Correctness**: fence hops and shortened-contour hops are now converted to primed edges (in both `PathFinder.scaffolded()` and `interarraylib.scaffolded()`), and a supertriangle/clone id collision was fixed.
|
|
23
|
+
- **Sexagesimal Coordinate Parsing**: `_translate_latlonstr()` failed to reset minutes/seconds between coordinates, corrupting the parsed values.
|
|
24
|
+
|
|
25
|
+
## Deprecated
|
|
26
|
+
- `WindFarmNetwork.from_yaml()` is renamed to `WindFarmNetwork.from_own_yaml()`; the old name still works and emits a `DeprecationWarning`.
|
|
27
|
+
- Reminder — the following remain available in this release and will be removed in v0.3. Users are advised to migrate now:
|
|
28
|
+
- Standalone EW heuristics (`ClassicEW`, `CPEW`, `NBEW`, `OBEW`, `EW_presolver`) → `heuristics.constructor()` (or the high-level `WindFarmNetwork`/`EWRouter`). See the [Legacy heuristics migration guide](https://optiwindnet.readthedocs.io/stable/notebooks/14-Legacy_heuristics.html), which pairs each legacy call with its `constructor()` equivalent.
|
|
29
|
+
- `optiwindnet.interface` (`heuristic_wrapper()`, `HeuristicFactory`) → `WindFarmNetwork`/`EWRouter`.
|
|
30
|
+
- HGS aliases `hgs_multiroot()` / `iterative_hgs_cvrp()` → `hgs_cvrp()`.
|
|
31
|
+
- LKH entry points `lkh()` / `iterative_lkh()` → `lkh3()`.
|
|
32
|
+
|
|
33
|
+
## Refactoring & Maintenance
|
|
34
|
+
- **Docstrings**: project-wide docstring formatting pass — standardized markup of string values for Sphinx rendering, unicode arrows, and docstrings for the database model.
|
|
35
|
+
- **Test Coverage**: expanded coverage for `geometric`, `interarraylib`, `plotting`, `svg`, `repair`, `themes` and `baselines.utils`.
|
|
36
|
+
- **CI**: isolated OR-Tools in a shared subprocess to resolve solver DLL conflicts, switched SCIP download to GitHub, bumped SCIPOptSuite, installed python tooling from conda, and added manual-dispatch pipeline targets.
|
|
37
|
+
|
|
1
38
|
# v0.2.2
|
|
2
39
|
|
|
3
40
|
[Commit history since v0.2.1](https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/-/compare/v0.2.1...v0.2.2)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: optiwindnet
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
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
|
|
@@ -33,14 +33,15 @@ __all__ = (
|
|
|
33
33
|
def solver_factory(solver_name: str) -> Solver:
|
|
34
34
|
"""Create a Solver object tied to the specified external MILP solver.
|
|
35
35
|
|
|
36
|
-
Note that the only solver that is a dependency of OptiWindNet is 'ortools'
|
|
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
|
|
39
|
+
Legacy compatibility: if ``solver_name == 'ortools'`` then the CP-SAT backend
|
|
40
|
+
is used.
|
|
40
41
|
|
|
41
42
|
Args:
|
|
42
|
-
solver_name: one of 'ortools.cp_sat'
|
|
43
|
-
'ortools.highs'
|
|
43
|
+
solver_name: one of ``'ortools.cp_sat'``, ``'ortools.gscip'``,
|
|
44
|
+
``'ortools.highs'``, ``'cplex'``, ``'gurobi'``, ``'cbc'``, ``'scip'``, ``'highs'``.
|
|
44
45
|
|
|
45
46
|
Returns:
|
|
46
47
|
Solver instance that can produce solutions for the cable routing problem.
|
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
import abc
|
|
5
5
|
import logging
|
|
6
|
+
import math
|
|
6
7
|
import os
|
|
7
8
|
import sys
|
|
8
9
|
from collections.abc import Mapping
|
|
9
10
|
from dataclasses import asdict, dataclass, field
|
|
10
11
|
from enum import StrEnum, auto
|
|
12
|
+
from inspect import cleandoc
|
|
11
13
|
from itertools import chain
|
|
12
14
|
from pathlib import Path
|
|
15
|
+
from textwrap import indent
|
|
13
16
|
from typing import Any
|
|
14
17
|
|
|
15
18
|
import networkx as nx
|
|
@@ -19,7 +22,7 @@ from ..interarraylib import G_from_S
|
|
|
19
22
|
from ..pathfinding import PathFinder
|
|
20
23
|
|
|
21
24
|
_lggr = logging.getLogger(__name__)
|
|
22
|
-
error, info = _lggr.error, _lggr.info
|
|
25
|
+
error, info, warn = _lggr.error, _lggr.info, _lggr.warning
|
|
23
26
|
|
|
24
27
|
|
|
25
28
|
def physical_core_count() -> int:
|
|
@@ -76,7 +79,7 @@ class Topology(StrEnum):
|
|
|
76
79
|
|
|
77
80
|
|
|
78
81
|
class FeederRoute(StrEnum):
|
|
79
|
-
|
|
82
|
+
"If feeder routes must be ``'straight'`` or can be detoured (``'segmented'``)."
|
|
80
83
|
|
|
81
84
|
STRAIGHT = auto()
|
|
82
85
|
SEGMENTED = auto()
|
|
@@ -84,11 +87,15 @@ class FeederRoute(StrEnum):
|
|
|
84
87
|
|
|
85
88
|
|
|
86
89
|
class FeederLimit(StrEnum):
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
'
|
|
90
|
+
"""Whether to limit the number of feeders. Both ``'specified'`` (an upper
|
|
91
|
+
bound) and ``'exactly'`` (an exact count) require the additional kwarg
|
|
92
|
+
``'max_feeders'``. Option ``'balanced'`` is only enforceable if the feeder
|
|
93
|
+
count is pinned to a single value, i.e. ``'minimum'``, ``'exactly'``, or
|
|
94
|
+
``'specified'`` with ``'max_feeders'`` at the minimum.
|
|
95
|
+
"""
|
|
90
96
|
|
|
91
97
|
UNLIMITED = auto()
|
|
98
|
+
EXACTLY = auto()
|
|
92
99
|
SPECIFIED = auto()
|
|
93
100
|
MINIMUM = auto()
|
|
94
101
|
MIN_PLUS1 = auto()
|
|
@@ -97,6 +104,74 @@ class FeederLimit(StrEnum):
|
|
|
97
104
|
DEFAULT = UNLIMITED
|
|
98
105
|
|
|
99
106
|
|
|
107
|
+
def feeder_and_load_bounds(
|
|
108
|
+
T: int,
|
|
109
|
+
capacity: int,
|
|
110
|
+
feeder_limit: FeederLimit,
|
|
111
|
+
max_feeders: int,
|
|
112
|
+
balanced: bool,
|
|
113
|
+
) -> tuple[int, int | None, int | None, int | None]:
|
|
114
|
+
"""Derive the feeder-count and feeder-load bounds a model must enforce.
|
|
115
|
+
|
|
116
|
+
The feeder count is bounded below by ``min_feeders = ceil(T/capacity)``
|
|
117
|
+
regardless of ``feeder_limit`` (a valid inequality). ``feeders_ub`` is
|
|
118
|
+
``None`` when the count is unbounded above; when it equals ``feeders_lb``,
|
|
119
|
+
the count is pinned and callers should emit an equality constraint.
|
|
120
|
+
|
|
121
|
+
Balanced subtrees (loads differing at most by one unit) are only expressible
|
|
122
|
+
with a pinned feeder count ``F``, in which case the loads must lie in
|
|
123
|
+
``{T // F, ceil(T / F)}``. A load bound of ``None`` means "do not emit":
|
|
124
|
+
either ``balanced`` is off, or it is not enforceable (a warning is issued),
|
|
125
|
+
or the bound is already implied by the flow variable's own bounds.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
``(feeders_lb, feeders_ub, load_lb, load_ub)``
|
|
129
|
+
"""
|
|
130
|
+
min_feeders = math.ceil(T / capacity)
|
|
131
|
+
if feeder_limit is FeederLimit.UNLIMITED:
|
|
132
|
+
feeders_lb, feeders_ub = min_feeders, None
|
|
133
|
+
elif feeder_limit is FeederLimit.MINIMUM:
|
|
134
|
+
feeders_lb = feeders_ub = min_feeders
|
|
135
|
+
elif feeder_limit is FeederLimit.EXACTLY:
|
|
136
|
+
if max_feeders < min_feeders:
|
|
137
|
+
raise ValueError('max_feeders is below the minimum necessary')
|
|
138
|
+
if max_feeders > T:
|
|
139
|
+
raise ValueError('max_feeders is above the number of terminals')
|
|
140
|
+
feeders_lb = feeders_ub = max_feeders
|
|
141
|
+
elif feeder_limit is FeederLimit.SPECIFIED:
|
|
142
|
+
if max_feeders < min_feeders:
|
|
143
|
+
raise ValueError('max_feeders is below the minimum necessary')
|
|
144
|
+
feeders_lb, feeders_ub = min_feeders, max_feeders
|
|
145
|
+
elif feeder_limit in (
|
|
146
|
+
FeederLimit.MIN_PLUS1,
|
|
147
|
+
FeederLimit.MIN_PLUS2,
|
|
148
|
+
FeederLimit.MIN_PLUS3,
|
|
149
|
+
):
|
|
150
|
+
plus = int(feeder_limit.value[-1])
|
|
151
|
+
feeders_lb, feeders_ub = min_feeders, min_feeders + plus
|
|
152
|
+
else:
|
|
153
|
+
raise NotImplementedError('Unknown value:', feeder_limit)
|
|
154
|
+
|
|
155
|
+
if not balanced:
|
|
156
|
+
return feeders_lb, feeders_ub, None, None
|
|
157
|
+
if feeders_lb != feeders_ub:
|
|
158
|
+
warn(
|
|
159
|
+
'Model option <balanced = True> requires a single possible feeder'
|
|
160
|
+
f' count, but <feeder_limit = {feeder_limit.value.upper()}> allows a'
|
|
161
|
+
' range: model will not enforce balanced subtrees.'
|
|
162
|
+
)
|
|
163
|
+
return feeders_lb, feeders_ub, None, None
|
|
164
|
+
F = feeders_lb
|
|
165
|
+
load_lb, load_ub = T // F, math.ceil(T / F)
|
|
166
|
+
# bounds at the extremes are already implied by the flow variable's bounds
|
|
167
|
+
return (
|
|
168
|
+
feeders_lb,
|
|
169
|
+
feeders_ub,
|
|
170
|
+
load_lb if load_lb > 1 else None,
|
|
171
|
+
load_ub if load_ub < capacity else None,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
100
175
|
class ModelOptions(dict):
|
|
101
176
|
"""Hold options for the modelling of the cable routing problem.
|
|
102
177
|
|
|
@@ -119,7 +194,8 @@ class ModelOptions(dict):
|
|
|
119
194
|
max_feeders=(
|
|
120
195
|
int,
|
|
121
196
|
0,
|
|
122
|
-
'
|
|
197
|
+
'Number of feeders: the maximum if <feeder_limit = "specified">, '
|
|
198
|
+
'the exact count if <feeder_limit = "exactly">',
|
|
123
199
|
),
|
|
124
200
|
)
|
|
125
201
|
|
|
@@ -149,13 +225,14 @@ class ModelOptions(dict):
|
|
|
149
225
|
@classmethod
|
|
150
226
|
def help(cls):
|
|
151
227
|
for k, v in cls.hints.items():
|
|
228
|
+
doc = indent(cleandoc(v.__doc__ or ''), ' ')
|
|
152
229
|
print(
|
|
153
230
|
f'{k} in {{'
|
|
154
231
|
+ ', '.join(
|
|
155
232
|
f'"{m}"' for n, m in v.__members__.items() if n != 'DEFAULT'
|
|
156
233
|
)
|
|
157
234
|
+ f'}} default: {cls.hints[k].DEFAULT.value}\n'
|
|
158
|
-
f'
|
|
235
|
+
f'{doc}\n'
|
|
159
236
|
)
|
|
160
237
|
for name, (kind, default, desc) in cls.simple.items():
|
|
161
238
|
print(f'{name} [{kind.__name__}] default: {default}\n {desc}\n')
|
|
@@ -242,11 +319,11 @@ class Solver(abc.ABC):
|
|
|
242
319
|
Args:
|
|
243
320
|
time_limit: maximum time (s) the solver is allowed to run.
|
|
244
321
|
mip_gap: relative difference from incumbent solution to lower bound
|
|
245
|
-
at which the search may be stopped before time_limit is reached.
|
|
322
|
+
at which the search may be stopped before ``time_limit`` is reached.
|
|
246
323
|
options: additional options to pass to solver (see solver manual).
|
|
247
324
|
|
|
248
325
|
Returns:
|
|
249
|
-
General information about the solution search (use get_solution() for
|
|
326
|
+
General information about the solution search (use ``get_solution()`` for
|
|
250
327
|
the actual solution).
|
|
251
328
|
"""
|
|
252
329
|
pass
|
|
@@ -289,7 +366,7 @@ class Solver(abc.ABC):
|
|
|
289
366
|
"""Create a topology graph from the solution to the MILP model.
|
|
290
367
|
|
|
291
368
|
Returns:
|
|
292
|
-
Graph topology
|
|
369
|
+
Graph topology ``S`` from the solution.
|
|
293
370
|
"""
|
|
294
371
|
metadata = self.metadata
|
|
295
372
|
S = nx.Graph(R=metadata.R, T=metadata.T)
|
|
@@ -342,7 +419,7 @@ class PoolHandler(abc.ABC):
|
|
|
342
419
|
|
|
343
420
|
@abc.abstractmethod
|
|
344
421
|
def _objective_at(self, index: int) -> float:
|
|
345
|
-
"Get objective value from solution pool at position
|
|
422
|
+
"Get objective value from solution pool at position ``index``"
|
|
346
423
|
pass
|
|
347
424
|
|
|
348
425
|
@abc.abstractmethod
|
|
@@ -11,6 +11,13 @@ from typing import Any
|
|
|
11
11
|
import networkx as nx
|
|
12
12
|
|
|
13
13
|
def physical_core_count() -> int: ...
|
|
14
|
+
def feeder_and_load_bounds(
|
|
15
|
+
T: int,
|
|
16
|
+
capacity: int,
|
|
17
|
+
feeder_limit: FeederLimit,
|
|
18
|
+
max_feeders: int,
|
|
19
|
+
balanced: bool,
|
|
20
|
+
) -> tuple[int, int | None, int | None, int | None]: ...
|
|
14
21
|
|
|
15
22
|
class OWNWarmupFailed(Exception): ...
|
|
16
23
|
class OWNSolutionNotFound(Exception): ...
|
|
@@ -27,6 +34,7 @@ class FeederRoute(StrEnum):
|
|
|
27
34
|
|
|
28
35
|
class FeederLimit(StrEnum):
|
|
29
36
|
UNLIMITED: str
|
|
37
|
+
EXACTLY: str
|
|
30
38
|
SPECIFIED: str
|
|
31
39
|
MINIMUM: str
|
|
32
40
|
MIN_PLUS1: str
|
|
@@ -28,10 +28,11 @@ error, info = _lggr.error, _lggr.info
|
|
|
28
28
|
|
|
29
29
|
class SolverGurobi(SolverPyomo, PoolHandler):
|
|
30
30
|
"""
|
|
31
|
-
For setting
|
|
32
|
-
its
|
|
31
|
+
For setting ``threadlimit``, the SolverGurobi instance must have this option in
|
|
32
|
+
its ``options`` attribute before the call to :meth:`set_problem`.
|
|
33
|
+
|
|
34
|
+
Example::
|
|
33
35
|
|
|
34
|
-
Example:
|
|
35
36
|
solver.options.update(threadlimit=8)
|
|
36
37
|
solver.set_problem(...)
|
|
37
38
|
solver.solve(...)
|
|
@@ -62,7 +63,7 @@ class SolverGurobi(SolverPyomo, PoolHandler):
|
|
|
62
63
|
warmstart: nx.Graph | None = None,
|
|
63
64
|
):
|
|
64
65
|
"""
|
|
65
|
-
This will keep the Gurobi license in use until a call to
|
|
66
|
+
This will keep the Gurobi license in use until a call to :meth:`get_solution`.
|
|
66
67
|
"""
|
|
67
68
|
solver = pyo.SolverFactory(
|
|
68
69
|
'gurobi_persistent',
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
# https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
|
-
import math
|
|
6
5
|
from datetime import timedelta
|
|
7
6
|
from itertools import chain
|
|
8
7
|
from typing import Any
|
|
@@ -24,6 +23,7 @@ from ._core import (
|
|
|
24
23
|
SolutionInfo,
|
|
25
24
|
Solver,
|
|
26
25
|
Topology,
|
|
26
|
+
feeder_and_load_bounds,
|
|
27
27
|
physical_core_count,
|
|
28
28
|
)
|
|
29
29
|
|
|
@@ -72,7 +72,8 @@ class SolverORTools(Solver, PoolHandler):
|
|
|
72
72
|
"""OR-Tools MathOpt wrapper using the selected backend.
|
|
73
73
|
|
|
74
74
|
This class wraps and changes the behavior of MathOpt in order to save all
|
|
75
|
-
solutions found to a pool. Meant to be used with
|
|
75
|
+
solutions found to a pool. Meant to be used with
|
|
76
|
+
:meth:`.PoolHandler._investigate_pool`.
|
|
76
77
|
"""
|
|
77
78
|
|
|
78
79
|
name: str
|
|
@@ -120,8 +121,8 @@ class SolverORTools(Solver, PoolHandler):
|
|
|
120
121
|
) -> SolutionInfo:
|
|
121
122
|
"""Wrapper for MathOpt solve() that saves all solutions.
|
|
122
123
|
|
|
123
|
-
This method uses a MIP_SOLUTION callback to fill a solution pool stored
|
|
124
|
-
in the attribute self.solutions
|
|
124
|
+
This method uses a ``MIP_SOLUTION`` callback to fill a solution pool stored
|
|
125
|
+
in the attribute ``self.solutions``.
|
|
125
126
|
"""
|
|
126
127
|
try:
|
|
127
128
|
model = self.model
|
|
@@ -315,13 +316,16 @@ def make_min_length_model(
|
|
|
315
316
|
Args:
|
|
316
317
|
A: graph with the available edges to choose from
|
|
317
318
|
capacity: maximum link flow capacity
|
|
318
|
-
topology: one of Topology.{BRANCHED, RADIAL}
|
|
319
|
+
topology: one of ``Topology.{BRANCHED, RADIAL}``
|
|
319
320
|
feeder_route:
|
|
320
|
-
FeederRoute.SEGMENTED
|
|
321
|
-
FeederRoute.STRAIGHT
|
|
322
|
-
feeder_limit: one of FeederLimit.{MINIMUM, UNLIMITED, SPECIFIED,
|
|
323
|
-
MIN_PLUS1, MIN_PLUS2, MIN_PLUS3}
|
|
324
|
-
|
|
321
|
+
``FeederRoute.SEGMENTED`` → feeder routes may be detoured around subtrees;
|
|
322
|
+
``FeederRoute.STRAIGHT`` → feeder routes must be straight, direct lines
|
|
323
|
+
feeder_limit: one of ``FeederLimit.{MINIMUM, UNLIMITED, EXACTLY, SPECIFIED,
|
|
324
|
+
MIN_PLUS1, MIN_PLUS2, MIN_PLUS3}``
|
|
325
|
+
balanced: enforce subtree loads differing at most by one unit (only
|
|
326
|
+
possible if ``feeder_limit`` pins the feeder count to a single value)
|
|
327
|
+
max_feeders: upper bound if ``feeder_limit`` is ``FeederLimit.SPECIFIED``,
|
|
328
|
+
exact count if it is ``FeederLimit.EXACTLY``, unused otherwise
|
|
325
329
|
"""
|
|
326
330
|
R = A.graph['R']
|
|
327
331
|
T = A.graph['T']
|
|
@@ -429,62 +433,41 @@ def make_min_length_model(
|
|
|
429
433
|
)
|
|
430
434
|
|
|
431
435
|
# feeder limits
|
|
432
|
-
|
|
436
|
+
feeders_lb, feeders_ub, load_lb, load_ub = feeder_and_load_bounds(
|
|
437
|
+
T, k, feeder_limit, max_feeders, balanced
|
|
438
|
+
)
|
|
439
|
+
if feeders_ub is not None and feeder_limit.name.startswith('MIN_PLUS'):
|
|
440
|
+
# derived from the minimum: surface it in the solution's metadata
|
|
441
|
+
max_feeders = feeders_ub
|
|
433
442
|
all_feeder_vars_sum = sum(link_[t, r] for r in _R for t in _T)
|
|
434
|
-
if
|
|
443
|
+
if feeders_lb == feeders_ub:
|
|
444
|
+
m.add_linear_constraint(
|
|
445
|
+
all_feeder_vars_sum == feeders_lb, name='feeder_limit_eq'
|
|
446
|
+
)
|
|
447
|
+
elif feeders_ub is None:
|
|
435
448
|
# valid inequality: number of feeders is at least the minimum
|
|
436
449
|
m.add_linear_constraint(
|
|
437
|
-
all_feeder_vars_sum >=
|
|
450
|
+
all_feeder_vars_sum >= feeders_lb, name='feeder_limit_lb'
|
|
438
451
|
)
|
|
439
|
-
if balanced:
|
|
440
|
-
warn(
|
|
441
|
-
'Model option <balanced = True> is incompatible with <feeder_limit'
|
|
442
|
-
' = UNLIMITED>: model will not enforce balanced subtrees.'
|
|
443
|
-
)
|
|
444
452
|
else:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
elif feeder_limit is FeederLimit.MIN_PLUS2:
|
|
456
|
-
max_feeders = min_feeders + 2
|
|
457
|
-
elif feeder_limit is FeederLimit.MIN_PLUS3:
|
|
458
|
-
max_feeders = min_feeders + 3
|
|
459
|
-
else:
|
|
460
|
-
raise NotImplementedError('Unknown value:', feeder_limit)
|
|
461
|
-
if is_equal_not_range:
|
|
453
|
+
m.add_linear_constraint(
|
|
454
|
+
expr=all_feeder_vars_sum,
|
|
455
|
+
lb=feeders_lb,
|
|
456
|
+
ub=feeders_ub,
|
|
457
|
+
name='feeder_limit_interval',
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# enforce balanced subtrees (subtree loads differ at most by one unit)
|
|
461
|
+
if load_lb is not None:
|
|
462
|
+
for t, r in stars:
|
|
462
463
|
m.add_linear_constraint(
|
|
463
|
-
|
|
464
|
+
flow_[t, r] >= link_[t, r] * load_lb, name=f'balanced_lb_{t}~r{-r}'
|
|
464
465
|
)
|
|
465
|
-
|
|
466
|
+
if load_ub is not None:
|
|
467
|
+
for t, r in stars:
|
|
466
468
|
m.add_linear_constraint(
|
|
467
|
-
|
|
468
|
-
lb=min_feeders,
|
|
469
|
-
ub=max_feeders,
|
|
470
|
-
name='feeder_limit_interval',
|
|
469
|
+
flow_[t, r] <= link_[t, r] * load_ub, name=f'balanced_ub_{t}~r{-r}'
|
|
471
470
|
)
|
|
472
|
-
# enforce balanced subtrees (subtree loads differ at most by one unit)
|
|
473
|
-
if balanced:
|
|
474
|
-
if is_equal_not_range:
|
|
475
|
-
feeder_min_load = T // min_feeders
|
|
476
|
-
if feeder_min_load < capacity:
|
|
477
|
-
for t, r in stars:
|
|
478
|
-
m.add_linear_constraint(
|
|
479
|
-
flow_[t, r] >= link_[t, r] * feeder_min_load,
|
|
480
|
-
name=f'balanced_{t}~r{-r}',
|
|
481
|
-
)
|
|
482
|
-
else:
|
|
483
|
-
warn(
|
|
484
|
-
'Model option <balanced = True> is incompatible with '
|
|
485
|
-
'having a range of possible feeder counts: model will '
|
|
486
|
-
'not enforce balanced subtrees.'
|
|
487
|
-
)
|
|
488
471
|
|
|
489
472
|
# radial or branched topology
|
|
490
473
|
if topology is Topology.RADIAL:
|
|
@@ -552,9 +535,9 @@ _make_min_length_model_fingerprint = fun_fingerprint(make_min_length_model)
|
|
|
552
535
|
def warmup_model(
|
|
553
536
|
model: mathopt.Model, metadata: ModelMetadata, S: nx.Graph
|
|
554
537
|
) -> mathopt.Model:
|
|
555
|
-
"""Set initial solution into
|
|
538
|
+
"""Set initial solution into ``model``.
|
|
556
539
|
|
|
557
|
-
Changes
|
|
540
|
+
Changes ``model`` and ``metadata`` in-place.
|
|
558
541
|
|
|
559
542
|
Args:
|
|
560
543
|
model: CP-SAT model to apply the solution to.
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
# https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
|
-
import math
|
|
6
5
|
from collections import namedtuple
|
|
7
6
|
from itertools import chain
|
|
8
7
|
from typing import Any
|
|
@@ -24,6 +23,7 @@ from ._core import (
|
|
|
24
23
|
SolutionInfo,
|
|
25
24
|
Solver,
|
|
26
25
|
Topology,
|
|
26
|
+
feeder_and_load_bounds,
|
|
27
27
|
physical_core_count,
|
|
28
28
|
)
|
|
29
29
|
|
|
@@ -68,7 +68,10 @@ _default_options = dict(
|
|
|
68
68
|
liftAndProjectCuts='off',
|
|
69
69
|
residualCapacityCuts='off',
|
|
70
70
|
),
|
|
71
|
-
highs=
|
|
71
|
+
highs=dict(
|
|
72
|
+
parallel='on',
|
|
73
|
+
# threads=0, # 0 means automatic and is HiGHS's default
|
|
74
|
+
),
|
|
72
75
|
scip={},
|
|
73
76
|
)
|
|
74
77
|
|
|
@@ -223,12 +226,17 @@ class SolverPyomoAppsi(Solver):
|
|
|
223
226
|
exc.args += ('.set_problem() must be called before .solve()',)
|
|
224
227
|
raise
|
|
225
228
|
applied_options = self.options | options
|
|
229
|
+
for key, value in applied_options.items():
|
|
230
|
+
if key in solver.config:
|
|
231
|
+
solver.config[key] = value
|
|
232
|
+
else:
|
|
233
|
+
solver._solver_options[key] = value
|
|
226
234
|
stopping = {
|
|
227
235
|
_optkey[name].time_limit: time_limit,
|
|
228
236
|
_optkey[name].mip_gap: mip_gap,
|
|
229
237
|
}
|
|
230
238
|
self.stopping = stopping
|
|
231
|
-
for k, v in
|
|
239
|
+
for k, v in stopping.items():
|
|
232
240
|
solver.config[k] = v
|
|
233
241
|
solver.config.load_solution = False
|
|
234
242
|
solver.config.stream_solver = verbose
|
|
@@ -290,13 +298,16 @@ def make_min_length_model(
|
|
|
290
298
|
Args:
|
|
291
299
|
A: graph with the available edges to choose from
|
|
292
300
|
capacity: maximum link flow capacity
|
|
293
|
-
topology: one of Topology.{BRANCHED, RADIAL}
|
|
301
|
+
topology: one of ``Topology.{BRANCHED, RADIAL}``
|
|
294
302
|
feeder_route:
|
|
295
|
-
FeederRoute.SEGMENTED
|
|
296
|
-
FeederRoute.STRAIGHT
|
|
297
|
-
feeder_limit: one of FeederLimit.{MINIMUM, UNLIMITED, SPECIFIED,
|
|
298
|
-
MIN_PLUS1, MIN_PLUS2, MIN_PLUS3}
|
|
299
|
-
|
|
303
|
+
``FeederRoute.SEGMENTED`` → feeder routes may be detoured around subtrees;
|
|
304
|
+
``FeederRoute.STRAIGHT`` → feeder routes must be straight, direct lines
|
|
305
|
+
feeder_limit: one of ``FeederLimit.{MINIMUM, UNLIMITED, EXACTLY, SPECIFIED,
|
|
306
|
+
MIN_PLUS1, MIN_PLUS2, MIN_PLUS3}``
|
|
307
|
+
balanced: enforce subtree loads differing at most by one unit (only
|
|
308
|
+
possible if ``feeder_limit`` pins the feeder count to a single value)
|
|
309
|
+
max_feeders: upper bound if ``feeder_limit`` is ``FeederLimit.SPECIFIED``,
|
|
310
|
+
exact count if it is ``FeederLimit.EXACTLY``, unused otherwise
|
|
300
311
|
"""
|
|
301
312
|
R = A.graph['R']
|
|
302
313
|
T = A.graph['T']
|
|
@@ -431,76 +442,46 @@ def make_min_length_model(
|
|
|
431
442
|
)
|
|
432
443
|
|
|
433
444
|
# feeder limits
|
|
434
|
-
|
|
435
|
-
|
|
445
|
+
feeders_lb, feeders_ub, load_lb, load_ub = feeder_and_load_bounds(
|
|
446
|
+
T, capacity, feeder_limit, max_feeders, balanced
|
|
447
|
+
)
|
|
448
|
+
if feeders_ub is not None and feeder_limit.name.startswith('MIN_PLUS'):
|
|
449
|
+
# derived from the minimum: surface it in the solution's metadata
|
|
450
|
+
max_feeders = feeders_ub
|
|
451
|
+
if feeders_lb == feeders_ub:
|
|
452
|
+
m.cons_feeder_limit_eq = pyo.Constraint(
|
|
453
|
+
rule=(lambda m: sum(m.link_[t, r] for r in _R for t in _T) == feeders_lb),
|
|
454
|
+
name='feeder_limit_eq',
|
|
455
|
+
)
|
|
456
|
+
else:
|
|
436
457
|
# valid inequality: number of feeders is at least the minimum
|
|
437
458
|
m.cons_feeder_limit_lb = pyo.Constraint(
|
|
438
|
-
rule=(lambda m: sum(m.link_[t, r] for r in _R for t in _T) >=
|
|
459
|
+
rule=(lambda m: sum(m.link_[t, r] for r in _R for t in _T) >= feeders_lb),
|
|
439
460
|
name='feeder_limit_lb',
|
|
440
461
|
)
|
|
441
|
-
if
|
|
442
|
-
warn(
|
|
443
|
-
'Model option <balanced = True> is incompatible with <feeder_limit'
|
|
444
|
-
' = UNLIMITED>: model will not enforce balanced subtrees.'
|
|
445
|
-
)
|
|
446
|
-
else:
|
|
447
|
-
is_equal_not_range = False
|
|
448
|
-
if feeder_limit is FeederLimit.SPECIFIED:
|
|
449
|
-
if max_feeders == min_feeders:
|
|
450
|
-
is_equal_not_range = True
|
|
451
|
-
elif max_feeders < min_feeders:
|
|
452
|
-
raise ValueError('max_feeders is below the minimum necessary')
|
|
453
|
-
elif feeder_limit is FeederLimit.MINIMUM:
|
|
454
|
-
is_equal_not_range = True
|
|
455
|
-
elif feeder_limit is FeederLimit.MIN_PLUS1:
|
|
456
|
-
max_feeders = min_feeders + 1
|
|
457
|
-
elif feeder_limit is FeederLimit.MIN_PLUS2:
|
|
458
|
-
max_feeders = min_feeders + 2
|
|
459
|
-
elif feeder_limit is FeederLimit.MIN_PLUS3:
|
|
460
|
-
max_feeders = min_feeders + 3
|
|
461
|
-
else:
|
|
462
|
-
raise NotImplementedError('Unknown value:', feeder_limit)
|
|
463
|
-
if is_equal_not_range:
|
|
464
|
-
m.cons_feeder_limit_eq = pyo.Constraint(
|
|
465
|
-
rule=(
|
|
466
|
-
lambda m: sum(m.link_[t, r] for r in _R for t in _T) == min_feeders
|
|
467
|
-
),
|
|
468
|
-
name='feeder_limit_eq',
|
|
469
|
-
)
|
|
470
|
-
else:
|
|
471
|
-
m.cons_feeder_limit_lb = pyo.Constraint(
|
|
472
|
-
rule=(
|
|
473
|
-
lambda m: sum(m.link_[t, r] for r in _R for t in _T) >= min_feeders
|
|
474
|
-
),
|
|
475
|
-
name='feeder_limit_lb',
|
|
476
|
-
)
|
|
462
|
+
if feeders_ub is not None:
|
|
477
463
|
m.cons_feeder_limit_ub = pyo.Constraint(
|
|
478
464
|
rule=(
|
|
479
|
-
lambda m: sum(m.link_[t, r] for r in _R for t in _T) <=
|
|
465
|
+
lambda m: sum(m.link_[t, r] for r in _R for t in _T) <= feeders_ub
|
|
480
466
|
),
|
|
481
467
|
name='feeder_limit_ub',
|
|
482
468
|
)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
warn(
|
|
500
|
-
'Model option <balanced = True> is incompatible with '
|
|
501
|
-
'having a range of possible feeder counts: model will '
|
|
502
|
-
'not enforce balanced subtrees.'
|
|
503
|
-
)
|
|
469
|
+
|
|
470
|
+
# enforce balanced subtrees (subtree loads differ at most by one unit)
|
|
471
|
+
if load_lb is not None:
|
|
472
|
+
m.cons_balanced_lb = pyo.Constraint(
|
|
473
|
+
m.T,
|
|
474
|
+
m.R,
|
|
475
|
+
rule=(lambda m, t, r: m.flow_[t, r] >= m.link_[t, r] * load_lb),
|
|
476
|
+
name='balanced_lb',
|
|
477
|
+
)
|
|
478
|
+
if load_ub is not None:
|
|
479
|
+
m.cons_balanced_ub = pyo.Constraint(
|
|
480
|
+
m.T,
|
|
481
|
+
m.R,
|
|
482
|
+
rule=(lambda m, t, r: m.flow_[t, r] <= m.link_[t, r] * load_ub),
|
|
483
|
+
name='balanced_ub',
|
|
484
|
+
)
|
|
504
485
|
|
|
505
486
|
# radial or branched topology
|
|
506
487
|
if topology is Topology.RADIAL:
|
|
@@ -581,9 +562,9 @@ _make_min_length_model_fingerprint = fun_fingerprint(make_min_length_model)
|
|
|
581
562
|
def warmup_model(
|
|
582
563
|
model: pyo.ConcreteModel, metadata: ModelMetadata, S: nx.Graph
|
|
583
564
|
) -> pyo.ConcreteModel:
|
|
584
|
-
"""Set initial solution into
|
|
565
|
+
"""Set initial solution into ``model``.
|
|
585
566
|
|
|
586
|
-
Changes
|
|
567
|
+
Changes ``model`` and ``metadata`` in-place.
|
|
587
568
|
|
|
588
569
|
Args:
|
|
589
570
|
model: pyomo model to apply the solution to.
|