fram-core 0.1.0a2__py3-none-any.whl → 0.1.1__py3-none-any.whl
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.
- {fram_core-0.1.0a2.dist-info → fram_core-0.1.1.dist-info}/METADATA +4 -4
- fram_core-0.1.1.dist-info/RECORD +100 -0
- {fram_core-0.1.0a2.dist-info → fram_core-0.1.1.dist-info}/WHEEL +1 -1
- framcore/Base.py +22 -3
- framcore/Model.py +26 -9
- framcore/__init__.py +2 -1
- framcore/aggregators/Aggregator.py +30 -11
- framcore/aggregators/HydroAggregator.py +35 -23
- framcore/aggregators/NodeAggregator.py +65 -30
- framcore/aggregators/WindSolarAggregator.py +22 -30
- framcore/attributes/Arrow.py +6 -4
- framcore/attributes/ElasticDemand.py +13 -13
- framcore/attributes/ReservoirCurve.py +3 -17
- framcore/attributes/SoftBound.py +2 -5
- framcore/attributes/StartUpCost.py +14 -3
- framcore/attributes/Storage.py +17 -5
- framcore/attributes/TargetBound.py +2 -4
- framcore/attributes/__init__.py +2 -4
- framcore/attributes/hydro/HydroBypass.py +9 -2
- framcore/attributes/hydro/HydroGenerator.py +24 -7
- framcore/attributes/hydro/HydroPump.py +32 -10
- framcore/attributes/hydro/HydroReservoir.py +4 -4
- framcore/attributes/level_profile_attributes.py +250 -53
- framcore/components/Component.py +27 -3
- framcore/components/Demand.py +18 -4
- framcore/components/Flow.py +26 -4
- framcore/components/HydroModule.py +45 -4
- framcore/components/Node.py +32 -9
- framcore/components/Thermal.py +12 -8
- framcore/components/Transmission.py +17 -2
- framcore/components/wind_solar.py +25 -10
- framcore/curves/LoadedCurve.py +0 -9
- framcore/expressions/Expr.py +137 -36
- framcore/expressions/__init__.py +3 -1
- framcore/expressions/_get_constant_from_expr.py +14 -20
- framcore/expressions/queries.py +121 -84
- framcore/expressions/units.py +30 -3
- framcore/fingerprints/fingerprint.py +0 -1
- framcore/juliamodels/JuliaModel.py +13 -3
- framcore/loaders/loaders.py +0 -2
- framcore/metadata/ExprMeta.py +13 -7
- framcore/metadata/LevelExprMeta.py +16 -1
- framcore/metadata/Member.py +7 -7
- framcore/metadata/__init__.py +1 -1
- framcore/querydbs/CacheDB.py +1 -1
- framcore/solvers/Solver.py +21 -6
- framcore/solvers/SolverConfig.py +4 -4
- framcore/timeindexes/AverageYearRange.py +9 -2
- framcore/timeindexes/ConstantTimeIndex.py +7 -2
- framcore/timeindexes/DailyIndex.py +14 -2
- framcore/timeindexes/FixedFrequencyTimeIndex.py +105 -53
- framcore/timeindexes/HourlyIndex.py +14 -2
- framcore/timeindexes/IsoCalendarDay.py +5 -3
- framcore/timeindexes/ListTimeIndex.py +103 -23
- framcore/timeindexes/ModelYear.py +8 -2
- framcore/timeindexes/ModelYears.py +11 -2
- framcore/timeindexes/OneYearProfileTimeIndex.py +10 -2
- framcore/timeindexes/ProfileTimeIndex.py +14 -3
- framcore/timeindexes/SinglePeriodTimeIndex.py +1 -1
- framcore/timeindexes/TimeIndex.py +16 -3
- framcore/timeindexes/WeeklyIndex.py +14 -2
- framcore/{expressions → timeindexes}/_time_vector_operations.py +76 -2
- framcore/timevectors/ConstantTimeVector.py +12 -16
- framcore/timevectors/LinearTransformTimeVector.py +20 -3
- framcore/timevectors/ListTimeVector.py +18 -14
- framcore/timevectors/LoadedTimeVector.py +1 -8
- framcore/timevectors/ReferencePeriod.py +13 -3
- framcore/timevectors/TimeVector.py +26 -12
- framcore/utils/__init__.py +0 -1
- framcore/utils/get_regional_volumes.py +21 -3
- framcore/utils/get_supported_components.py +1 -1
- framcore/utils/global_energy_equivalent.py +22 -5
- framcore/utils/isolate_subnodes.py +12 -3
- framcore/utils/loaders.py +7 -7
- framcore/utils/node_flow_utils.py +4 -4
- framcore/utils/storage_subsystems.py +3 -4
- fram_core-0.1.0a2.dist-info/RECORD +0 -100
- {fram_core-0.1.0a2.dist-info → fram_core-0.1.1.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fram-core
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary:
|
|
5
5
|
License: LICENSE.md
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -24,11 +24,11 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
|
|
25
25
|
## About
|
|
26
26
|
|
|
27
|
-
**fram-core** is the main package in **FRAM** modelling framework. The package
|
|
27
|
+
**fram-core** is the main package in the **FRAM** modelling framework. The package holds the functionality used to describe and manipulate the energy system, handle time series operations, and hold the definition of key interfaces in FRAM.
|
|
28
28
|
|
|
29
|
-
For package documentation see [fram-core](https://nve.github.io/fram-core)
|
|
29
|
+
For package documentation see [fram-core](https://nve.github.io/fram-core).
|
|
30
30
|
|
|
31
|
-
For FRAM documentation see [FRAM mainpage](https://nve.github.io/fram)
|
|
31
|
+
For FRAM documentation see [FRAM mainpage](https://nve.github.io/fram).
|
|
32
32
|
|
|
33
33
|
## Installation
|
|
34
34
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
framcore/Base.py,sha256=WAc8GBeuLxPpLFHbHwyYzd3HCv6RWo17iw0ItCRFXlE,6027
|
|
2
|
+
framcore/Model.py,sha256=S8Tdfl7Ynhhhk1KbOjCl0W4oZRNaroVGwjomwKZi9yU,3654
|
|
3
|
+
framcore/__init__.py,sha256=TvgesX44gH4OCopUSGY05BFK32oafI7ZDhctD6yQ57k,182
|
|
4
|
+
framcore/aggregators/Aggregator.py,sha256=sDlMZyCVwF-NhQPjMwcSm8xHH-SerBqlIP5ckbJ0C5A,8226
|
|
5
|
+
framcore/aggregators/HydroAggregator.py,sha256=XmaLPMe3xD1Vs2s7KsrSEy_zwLmDtCxMsslaCX5aJfg,47321
|
|
6
|
+
framcore/aggregators/NodeAggregator.py,sha256=QUPdXURkRIPymZAxHyavCzo1cGrnBJ3ahZNEmT9kwxE,23651
|
|
7
|
+
framcore/aggregators/WindSolarAggregator.py,sha256=5Fko0-FLRfUPMZQjpkWmpTwqzgW0WGm04zrUZ_-_Ypk,16465
|
|
8
|
+
framcore/aggregators/__init__.py,sha256=ZoBqilfv0XhFkYVROBPQhJHzSY1IcvhJmJ_FOo_Z5Pw,426
|
|
9
|
+
framcore/aggregators/_utils.py,sha256=EoRLeGmPC2ds-egPdvDewtaYbRozi6Psyf9DD1PRajk,8337
|
|
10
|
+
framcore/attributes/Arrow.py,sha256=bodWoKLG3g2bHt6FygwOJP1n3VKfAdrRfYCOveogrmk,11204
|
|
11
|
+
framcore/attributes/ElasticDemand.py,sha256=1sNXLAw1C-pGeQU2kgyoZF_-LTb3NCTOY5hOJwd7mrU,3073
|
|
12
|
+
framcore/attributes/ReservoirCurve.py,sha256=la4bgXNtR0DIdnQ1Iu37ydqr-sWRZN5yOVZ6qJ8rOZY,644
|
|
13
|
+
framcore/attributes/SoftBound.py,sha256=ooScf39EmqyWUIRafGiWEKGIvr6kMbV9JF4CIDARKBM,405
|
|
14
|
+
framcore/attributes/StartUpCost.py,sha256=tKT4hK23yQtjXWvrNz2EPC1oOxBO19SRfecxy77frU4,2107
|
|
15
|
+
framcore/attributes/Storage.py,sha256=c2p5rhaXpjB4PiPi6FCL7SB5X_DAu91bVSsRWE7-h1o,6284
|
|
16
|
+
framcore/attributes/TargetBound.py,sha256=5IcLwi1ciLm7SK3V9ZNzMDBeUKF62LfYr36ARQqJHQk,400
|
|
17
|
+
framcore/attributes/__init__.py,sha256=50viQ-upiArFHZpeUjEh9xqvuh-ZRGtIVwZNEtSGlI8,1530
|
|
18
|
+
framcore/attributes/hydro/HydroBypass.py,sha256=MdF51LNugwZhtpadBjiRI0zPA77Cnku5c8hTn42AJc8,1683
|
|
19
|
+
framcore/attributes/hydro/HydroGenerator.py,sha256=2uohj1QkZl52qNAR8nIywnLW-j_ujnxX-ITV2t9MRrU,4186
|
|
20
|
+
framcore/attributes/hydro/HydroPump.py,sha256=jKeJJk9kzz2dsxXfppqDcVf8_NHtwl6Yzlrv28k6z3M,7506
|
|
21
|
+
framcore/attributes/hydro/HydroReservoir.py,sha256=PEogXuSrdQ08uk5R_Re7G-Ue5N8cphPCEVvQhBIiy_U,879
|
|
22
|
+
framcore/attributes/hydro/__init__.py,sha256=isOSLP2TdHOaPcr16EXIkdtx80LVe8ztFPiuGJPC4ao,381
|
|
23
|
+
framcore/attributes/level_profile_attributes.py,sha256=feyIQ9lSnQGXdo45lU9kPCsKB1SNFrTfRaViFeNxlMI,33636
|
|
24
|
+
framcore/components/Component.py,sha256=z7ApRmaTPIZLwKJdbczrM8buLQnPJhBAgOixTLYG_zc,5581
|
|
25
|
+
framcore/components/Demand.py,sha256=c8N0R_f2GR58zt4pAXVmC-KcqLP5yeAuwQyEYQtHAtA,6241
|
|
26
|
+
framcore/components/Flow.py,sha256=slbVLlZsIxcEgilAJTe3eib4-xi2DTSAGHbpkh3KgFg,7391
|
|
27
|
+
framcore/components/HydroModule.py,sha256=5svph2fVlAYnMqKywdQv9hPsMLbHqSOdFIGwmkf8lMY,14302
|
|
28
|
+
framcore/components/Node.py,sha256=L_CDjjRHyxbiPCTjjj89dNQ9Z7VZjYp_dTHVodJN-DM,3661
|
|
29
|
+
framcore/components/Thermal.py,sha256=mOIIybvIDGhwsWN5ZCIch1Lkz8M1513S6uBI5bOil8s,7895
|
|
30
|
+
framcore/components/Transmission.py,sha256=u5opwBKWGylwDYYBELvGl8omHrBmMk4u4O5rE2hsdkA,7889
|
|
31
|
+
framcore/components/_PowerPlant.py,sha256=zDJ5LtYQN2GLa__BaqCLz54mZJil3dOYjICMPurh-U8,2787
|
|
32
|
+
framcore/components/__init__.py,sha256=0N4UMrYi7cYYaZHMIS6HSOli2jEbtct6LsZ0JcwGz5A,568
|
|
33
|
+
framcore/components/wind_solar.py,sha256=MSKr8MacjhDZLfKgWZf1NVyzurcMrGVjJhXfQzafzjQ,2311
|
|
34
|
+
framcore/curves/Curve.py,sha256=89CsAJpCa_GkQtsBGclUcK8D3ttCyYhs583-_6zE0is,934
|
|
35
|
+
framcore/curves/LoadedCurve.py,sha256=mW4OsG9WPEcm3rgPHZRMnMjqdf_PzYH2rYusN5PNGwQ,3639
|
|
36
|
+
framcore/curves/__init__.py,sha256=BnzdAZVjPwJcjduramRPPX6NJag-g6aKXPho2PwmGco,170
|
|
37
|
+
framcore/events/__init__.py,sha256=O3lOZukd_ixwbkoOmf2ei_--lKJLVXvyhSHjUDfaXEE,401
|
|
38
|
+
framcore/events/events.py,sha256=mNcHUjWraKyb-gwxeKr5ryOtcN8dCKptRWIYgcxM1p4,1683
|
|
39
|
+
framcore/expressions/Expr.py,sha256=qPbRGZxaWpn3y70dENth68DqkF3HP9dN-BVBp87yZWw,25554
|
|
40
|
+
framcore/expressions/__init__.py,sha256=Fn6a9aVCSU-R0QeGmZB7QuHkdLzP7Fvj_prnDLfwsrs,750
|
|
41
|
+
framcore/expressions/_get_constant_from_expr.py,sha256=3891ls8NQzVhTsvzvMsP7TG9Q5bE9jnOlBgA-N_Upjo,16429
|
|
42
|
+
framcore/expressions/_utils.py,sha256=mtruqnHcwpkIufbTFjJvT3iUfN98gN51SQ9e9hS9cUc,2123
|
|
43
|
+
framcore/expressions/queries.py,sha256=Hx3zOQPYR54cwttr1_NoS1i4HQRFpROfeBRqBLUWIF8,18077
|
|
44
|
+
framcore/expressions/units.py,sha256=RAcNTKbkuIs0svkIk6L7hPwA7-G8yHAaByB5BIku1kI,7715
|
|
45
|
+
framcore/fingerprints/__init__.py,sha256=YpIj-t6DRrSOmdidchC4qjLHSoZwsdkf9980nHUsnsw,256
|
|
46
|
+
framcore/fingerprints/fingerprint.py,sha256=GnvzaLquT15lXPGnWe103plCNMNgLzlY0ve1LUY8W5A,9179
|
|
47
|
+
framcore/juliamodels/JuliaModel.py,sha256=7USkw5Y4HKrSZP-Hmm-omoNXHqkow4NQqr0vuZOxIFU,7135
|
|
48
|
+
framcore/juliamodels/__init__.py,sha256=2ia9EGUQAZD0_f2o0NWdhD0d2z80K_bweohnGNfnpGA,124
|
|
49
|
+
framcore/loaders/__init__.py,sha256=iwlnauyXhH_BAuVIKVmX2vMvU94i_bXI57CE82eM7VA,209
|
|
50
|
+
framcore/loaders/loaders.py,sha256=-lMVdp1SDuUJ_kIGaEZTnl-_7keMCk0TOmSX0d-1efI,11118
|
|
51
|
+
framcore/metadata/Div.py,sha256=fIIB9W9fVJEUNzqIWXOJ9ZN3tWE8dB8ap2BdULT2fJw,2428
|
|
52
|
+
framcore/metadata/ExprMeta.py,sha256=nbpEBB2lkwRP1VqD9ohSKfGj99cVIX1g6hdVAocEXXQ,1679
|
|
53
|
+
framcore/metadata/LevelExprMeta.py,sha256=H_sqVFDP2NF6y4e1eq0ANH0Cq7ZdpSBuYaODtfWnO_4,1103
|
|
54
|
+
framcore/metadata/Member.py,sha256=w41UYW97BIXPr_yk514aaHmiYiYS8_Voh0bbE0qCleA,1715
|
|
55
|
+
framcore/metadata/Meta.py,sha256=P9ESFJlrxxhcY9PNSnnccrsLBulu1v3Kir6agzSrv0I,1173
|
|
56
|
+
framcore/metadata/__init__.py,sha256=8GfRSOVEb-051QiMCsgwYgqh-KA8UwSVdKpqtz1a3ZA,350
|
|
57
|
+
framcore/populators/Populator.py,sha256=SraN4QOTsA3yXEVG4z9ohWfPKokssl3uvDJPGh56lrU,4077
|
|
58
|
+
framcore/populators/__init__.py,sha256=jE6tHIvTaPTWcD-yVpBOjDBxI3xFqJh0QASfQexBLCE,119
|
|
59
|
+
framcore/querydbs/CacheDB.py,sha256=VZxNuZ9jX3a0ejDrUqAaZgBmFMTJG64v470-Imtl9i4,1738
|
|
60
|
+
framcore/querydbs/ModelDB.py,sha256=XD4jHlmRQHqF7L2euDIMrPZREhr3SXgv0UnqkADLzRw,1039
|
|
61
|
+
framcore/querydbs/QueryDB.py,sha256=IV96mtslktpJMrdxgrwQetVx4R1HAP1GxtKgx9WLPZA,1233
|
|
62
|
+
framcore/querydbs/__init__.py,sha256=oPX2sqAMoEMxWL9b_sRg4z4Q_b80iDm_jk-W_qmpEME,231
|
|
63
|
+
framcore/solvers/Solver.py,sha256=taiprQFC75pelr2koSmmxnJOeYtl7HsEi-0zFT6SR1k,2255
|
|
64
|
+
framcore/solvers/SolverConfig.py,sha256=QkowQHTfcB2f7v3_8U1zfkSewizzY7BHjT8LhSu8Bx0,10597
|
|
65
|
+
framcore/solvers/__init__.py,sha256=q9HLYJkRdvJeFgAM1NBGIaFI8APmsZCDTy1AkIKw_Zs,179
|
|
66
|
+
framcore/timeindexes/AverageYearRange.py,sha256=EDEkBxQNzTTQ2LtFubsTp9fWtBFWwlDyhPnaN91GJSY,1094
|
|
67
|
+
framcore/timeindexes/ConstantTimeIndex.py,sha256=eg8vsApymy1PkNEtHhx50L0eWFo9DB1xv4YXWbB35JU,787
|
|
68
|
+
framcore/timeindexes/DailyIndex.py,sha256=nORCghZjLvsuObRbC9SbR-NMBTng_DmjqULW2eJKh0M,1127
|
|
69
|
+
framcore/timeindexes/FixedFrequencyTimeIndex.py,sha256=BhIilAo6nG7-UEqPwJLS1T5ONA7g9RLrTIBIVapIk9E,33601
|
|
70
|
+
framcore/timeindexes/HourlyIndex.py,sha256=hdvoXmzzbATxjgO4uSyjo0v_GySkasmvs5_D6HzKrPc,1131
|
|
71
|
+
framcore/timeindexes/IsoCalendarDay.py,sha256=tm9XC5XEn6C_O5vrT94TNIm30XyMBpf7nCTVc0D9i-c,1144
|
|
72
|
+
framcore/timeindexes/ListTimeIndex.py,sha256=UjTTOiwLJ4D_BJPmuROQ6-mp01OV7aJ2sxnkn2iqcZI,11848
|
|
73
|
+
framcore/timeindexes/ModelYear.py,sha256=oq1Ef1gD-j6cteuQzx7eR8Ew9ZBL2QuDF0e1GVnu730,869
|
|
74
|
+
framcore/timeindexes/ModelYears.py,sha256=92UGocvqPtI6I01uJc6PKOInRVmYpZX3FcUnQtM2lxs,958
|
|
75
|
+
framcore/timeindexes/OneYearProfileTimeIndex.py,sha256=yKaan34cHSOypzzt_Q3nzn2ZZFy0R1bg7lChUEu--tQ,1154
|
|
76
|
+
framcore/timeindexes/ProfileTimeIndex.py,sha256=FDXXXEaXoImsiLSWUxYAFFOCzQpUb170sPk9hUuLkMs,1831
|
|
77
|
+
framcore/timeindexes/SinglePeriodTimeIndex.py,sha256=h9lkfIOlXuY-6OHULaVekTbtrSVCYgM5AjW0xTa1b1k,1452
|
|
78
|
+
framcore/timeindexes/TimeIndex.py,sha256=HPcyuJNoBrdvS7jAsQLLUIEYF9WvtT9cPL7dH2QX2a4,3515
|
|
79
|
+
framcore/timeindexes/WeeklyIndex.py,sha256=KiSQouztEW6dwphZwxYa8BsNpFQKs6FMWjrmneYnsz8,1160
|
|
80
|
+
framcore/timeindexes/__init__.py,sha256=2LmQFuMR1DffqHk5O2zd8o9g8QsazxSSykxOE7WGP4o,1351
|
|
81
|
+
framcore/timeindexes/_time_vector_operations.py,sha256=05HqseGkRqwztNk5rEI6p5VXFtXqmwArWuir88OFbnk,31301
|
|
82
|
+
framcore/timevectors/ConstantTimeVector.py,sha256=fp7Bu6nP8iIl0Pn9JLD0sMDMkldZ44d8bQNWWl2RvHQ,5651
|
|
83
|
+
framcore/timevectors/LinearTransformTimeVector.py,sha256=tmPICcT5mlGn60JDpQsQ5p3FXZyRbs5wehAGbH1sj2o,5435
|
|
84
|
+
framcore/timevectors/ListTimeVector.py,sha256=g8v4ym5NzjRJpHW9awbPKGQA4XidYPhBT_u8iSd5744,5429
|
|
85
|
+
framcore/timevectors/LoadedTimeVector.py,sha256=EmCSYwvvjzb7yQae2Y1C_QdY4sHsy4RARQfd06odGmU,4018
|
|
86
|
+
framcore/timevectors/ReferencePeriod.py,sha256=dnstzGIbK6Y2nxZqBEBueIGH44QmWva-HZ7-HTUctis,1712
|
|
87
|
+
framcore/timevectors/TimeVector.py,sha256=D8YOwdNo4TcXHVaTXCc1GiEJXrU85ve-v7QGcFMKeN8,3454
|
|
88
|
+
framcore/timevectors/__init__.py,sha256=Wd3gXGatwpbe2yMKSCq7O9My5wbveuK23iYO2ykkUIk,603
|
|
89
|
+
framcore/utils/__init__.py,sha256=qmmfcTWo6hrNyZn07m-A-6pcHwAUw5Y9It9ihapiiKY,1221
|
|
90
|
+
framcore/utils/get_regional_volumes.py,sha256=gTiXfKBPz4j7t-vyh49jBOQoszrBOMp8ZJViaOSZrA4,16041
|
|
91
|
+
framcore/utils/get_supported_components.py,sha256=yjLMmNwaHT8N9ZueQjw4v6z_rt5_aQVVtb7WAJ1-mzg,1885
|
|
92
|
+
framcore/utils/global_energy_equivalent.py,sha256=2k3PbORz6nwTdCriye7tJf4Ht5v6F1ZIetuSGmkfvnE,3445
|
|
93
|
+
framcore/utils/isolate_subnodes.py,sha256=Em1dc2shtWiF0IqxGj0CVjFF2A_6Ou64lsp88uAEcK8,6917
|
|
94
|
+
framcore/utils/loaders.py,sha256=pWOyRLSV6PikZHuUomKnZOL6c6VzXERysaMC_Q3mvfg,3201
|
|
95
|
+
framcore/utils/node_flow_utils.py,sha256=qnWelrqslc1FDBw-NdkVVQ1nk0l_EEvO8cDgObZnmJQ,7781
|
|
96
|
+
framcore/utils/storage_subsystems.py,sha256=LTKt6j3bCY7rvmOY3dUGcBP711CPXv5PS5XRb2DUmWM,4144
|
|
97
|
+
fram_core-0.1.1.dist-info/METADATA,sha256=7JuS8ef0hHwFatbcxvmbtSKPTmebjofuRj02SAxKnjs,1277
|
|
98
|
+
fram_core-0.1.1.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
|
|
99
|
+
fram_core-0.1.1.dist-info/licenses/LICENSE.md,sha256=fxh4ZxuR8dM2HDs-pIUitPrJdxQ4fEFh1GvEYZA2m1E,1075
|
|
100
|
+
fram_core-0.1.1.dist-info/RECORD,,
|
framcore/Base.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import inspect
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from framcore.events import (
|
|
@@ -18,9 +19,7 @@ class Base:
|
|
|
18
19
|
"""Core base class to share methods."""
|
|
19
20
|
|
|
20
21
|
def _check_type(self, value, class_or_tuple) -> None: # noqa: ANN001
|
|
21
|
-
|
|
22
|
-
message = f"Expected {class_or_tuple} for {self}, got {type(value).__name__}"
|
|
23
|
-
raise TypeError(message)
|
|
22
|
+
check_type(value, class_or_tuple, caller=self)
|
|
24
23
|
|
|
25
24
|
def _ensure_float(self, value: object) -> float:
|
|
26
25
|
with contextlib.suppress(Exception):
|
|
@@ -140,3 +139,23 @@ class Base:
|
|
|
140
139
|
except Exception:
|
|
141
140
|
pass
|
|
142
141
|
return type(value).__name__
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# could not place this in utils and use __init__ as modules in utils also import queries, if queries then import via utils __init__ we get circular imports.
|
|
145
|
+
def check_type(value: object, expected: type | tuple[type], caller: Callable | None = None) -> None:
|
|
146
|
+
"""
|
|
147
|
+
Check a value matches expected type(s).
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
value (object): value being checked.
|
|
151
|
+
expected (type | tuple[type]): Expected types.
|
|
152
|
+
caller (Callable): The origin of the check.
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
TypeError: When value does not match expected types.
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
if not isinstance(value, expected):
|
|
159
|
+
message = f"{expected}, got {type(value).__name__}"
|
|
160
|
+
message = "Expected " + message if caller is None else f"{caller} expected " + message
|
|
161
|
+
raise TypeError(message)
|
framcore/Model.py
CHANGED
|
@@ -10,9 +10,12 @@ from framcore.timevectors import TimeVector
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from framcore.aggregators import Aggregator
|
|
12
12
|
|
|
13
|
+
|
|
13
14
|
class ModelDict(dict):
|
|
14
15
|
"""Dict storing only values of type Component | Expr | TimeVector | Curve."""
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
def __setitem__(self, key: str, value: Component | Expr | TimeVector | Curve) -> None:
|
|
18
|
+
"""Set item with type checking."""
|
|
16
19
|
if not isinstance(key, str):
|
|
17
20
|
message = f"Expected str for key {key}, got {type(key).__name__}"
|
|
18
21
|
raise TypeError(message)
|
|
@@ -21,24 +24,39 @@ class ModelDict(dict):
|
|
|
21
24
|
raise TypeError(message)
|
|
22
25
|
return super().__setitem__(key, value)
|
|
23
26
|
|
|
27
|
+
|
|
24
28
|
class Model(Base):
|
|
25
|
-
"""
|
|
29
|
+
"""
|
|
30
|
+
Model stores the representation of the energy system with Components, TimeVectors, Expression, and the Aggregators applied to the Model.
|
|
31
|
+
|
|
32
|
+
- Components describe the main elements in the energy system. Can have additional Attributes.
|
|
33
|
+
- TimeVector and Curve hold the time series data.
|
|
34
|
+
- Expressions for data manipulation of TimeVectors and Curves. Can be queried.
|
|
35
|
+
- Aggregators handle aggregation and disaggregation of Components. Aggregators are added to Model when used (Aggregator.aggregate(model)),
|
|
36
|
+
and can be undone in LIFO order with disaggregate().
|
|
37
|
+
|
|
38
|
+
Methods:
|
|
39
|
+
get_data(): Get dict of Components, Expressions, TimeVectors and Curves stored in the Model. Can be modified.
|
|
40
|
+
disaggregate(): Undo all aggregations applied to Model in LIFO order.
|
|
41
|
+
get_content_counts(): Return number of objects stored in model organized into concepts and types.
|
|
42
|
+
|
|
43
|
+
"""
|
|
26
44
|
|
|
27
45
|
def __init__(self) -> None:
|
|
28
|
-
"""Create a new model instance."""
|
|
46
|
+
"""Create a new model instance with empty data and no aggregators."""
|
|
29
47
|
self._data = ModelDict()
|
|
30
48
|
self._aggregators: list[Aggregator] = []
|
|
31
49
|
|
|
50
|
+
def get_data(self) -> ModelDict:
|
|
51
|
+
"""Get dict of Components, Expressions, TimeVectors and Curves stored in the Model. Can be modified."""
|
|
52
|
+
return self._data
|
|
53
|
+
|
|
32
54
|
def disaggregate(self) -> None:
|
|
33
|
-
"""Undo all aggregations in LIFO order."""
|
|
55
|
+
"""Undo all aggregations applied to Model in LIFO order."""
|
|
34
56
|
while self._aggregators:
|
|
35
57
|
aggregator = self._aggregators.pop(-1) # last item
|
|
36
58
|
aggregator.disaggregate(self)
|
|
37
59
|
|
|
38
|
-
def get_data(self) -> ModelDict:
|
|
39
|
-
"""Get internal data. Modify this with care."""
|
|
40
|
-
return self._data
|
|
41
|
-
|
|
42
60
|
def get_content_counts(self) -> dict[str, Counter]:
|
|
43
61
|
"""Return number of objects stored in model organized into concepts and types."""
|
|
44
62
|
data_values = self.get_data().values()
|
|
@@ -70,4 +88,3 @@ class Model(Base):
|
|
|
70
88
|
counts["aggregators"][type(a).__name__] += 1
|
|
71
89
|
|
|
72
90
|
return counts
|
|
73
|
-
|
framcore/__init__.py
CHANGED
|
@@ -18,17 +18,36 @@ class Aggregator(Base, ABC):
|
|
|
18
18
|
"""
|
|
19
19
|
Aggregator interface class.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Aggregators handles aggregation and disaggregation of Components.
|
|
22
|
+
- The general approach for aggregation is to group Components, aggregate Components in the same group to (a) new Component(s),
|
|
23
|
+
delete the detailed Components, and add the mapping to self._aggregation_map.
|
|
24
|
+
- The general approach for disaggregation is to restore the detailed Components, move results from aggregated
|
|
25
|
+
Components to detailed Components, and delete the aggregated Components.
|
|
26
|
+
|
|
27
|
+
Concrete Aggregators must implement the abstract methods _aggregate() and _disaggregate().
|
|
28
|
+
|
|
29
|
+
Some rules for using Aggregators:
|
|
30
|
+
1. Disaggragate can only be called after aggregate has been called.
|
|
31
|
+
2. Not allowed to call aggregate twice. Must call disaggregate before aggregate can be called again.
|
|
32
|
+
3. Aggregators are stored in Model when aggregate is called. Disaggregate by calling Model.disaggregate(),
|
|
33
|
+
which will disaggregate all Aggregators in LIFO order.
|
|
34
|
+
4. At the moment we allow changes to the aggregated Components, which is ignored during disaggregation. TODO: Handle this
|
|
35
|
+
5. It is recommended to only use the same Aggregator type once on the same components of a Model.
|
|
36
|
+
If you want to go from one aggregation level to another, it is better to use Model.disaggregate first and then aggregate again.
|
|
37
|
+
This is to keep the logic simple and avoid complex expressions.
|
|
38
|
+
|
|
39
|
+
Some design notes:
|
|
40
|
+
- Levels and profiles are aggregated separately and then combined into attributes.
|
|
41
|
+
- We have chosen to eagerly evaluate weights for aggregation (weighted averages) and disaggregation of levels and profiles.
|
|
42
|
+
This approach supports any form of aggregation by varying the weights, and complex weights can be created by eagerly evaluating
|
|
43
|
+
expressions and using the result to compute those weights.
|
|
44
|
+
- This is a balance between eagerly evaluating everything and setting up complex expressions.
|
|
45
|
+
Eagerly evaluating everything would require setting up new TimeVectors after evaluation, which is not ideal.
|
|
46
|
+
While setting up complex expressions gives expressions that are harder to work with and slower to query from.
|
|
47
|
+
- This trade-off simplifies adding logic that recognises if result expressions come from aggregations or disaggregations.
|
|
48
|
+
When aggregating or disaggregating these, we can go back to the original results rather than setting up complex expressions
|
|
49
|
+
that for examples aggregates the disaggregated results.
|
|
22
50
|
|
|
23
|
-
These methods come with the folloing calling rules:
|
|
24
|
-
1. Not allowed to call aggregate twice. Must call disaggregate before aggregate can be called again.
|
|
25
|
-
2. Disaggragate can only be called after aggregate has been called.
|
|
26
|
-
|
|
27
|
-
Implementations should implement _aggregate and _disaggregate.
|
|
28
|
-
- The general approach for aggregation is to group components, aggregated components in the same group, delete the detailed components,
|
|
29
|
-
and add the mapping to self._aggregation_map.
|
|
30
|
-
- The general approach for disaggregation is to restore the detailed components, move results from aggregated components to detailed components,
|
|
31
|
-
and delete the aggregated components.
|
|
32
51
|
"""
|
|
33
52
|
|
|
34
53
|
def __init__(self) -> None:
|
|
@@ -42,7 +61,7 @@ class Aggregator(Base, ABC):
|
|
|
42
61
|
self._check_type(model, Model)
|
|
43
62
|
|
|
44
63
|
if self._is_last_call_aggregate is True:
|
|
45
|
-
message =
|
|
64
|
+
message = "Will overwrite existing aggregation."
|
|
46
65
|
self.send_warning_event(message)
|
|
47
66
|
|
|
48
67
|
self._original_data = deepcopy(model.get_data())
|
|
@@ -27,9 +27,10 @@ if TYPE_CHECKING:
|
|
|
27
27
|
|
|
28
28
|
class HydroAggregator(Aggregator):
|
|
29
29
|
"""
|
|
30
|
-
Aggregate
|
|
30
|
+
Aggregate HydroModules into two equivalent modules based on the regulation factor, into one regulated and one unregulated module per area.
|
|
31
31
|
|
|
32
32
|
Aggregation steps (self._aggregate):
|
|
33
|
+
|
|
33
34
|
1. Group modules based on their power nodes (self._group_modules_by_power_node)
|
|
34
35
|
- Modules with generators are grouped based on their power nodes. You can choose to only group modules for certain power nodes by giving
|
|
35
36
|
self._power_node_members alone or together with self._metakey_power_node. NB! Watershed that crosses power nodes should not be aggregated in two
|
|
@@ -59,7 +60,9 @@ class HydroAggregator(Aggregator):
|
|
|
59
60
|
3b. Make new hydro module and delete original modules from model data.
|
|
60
61
|
4. Add mapping from detailed to aggregated modules to self._aggregation_map.
|
|
61
62
|
|
|
63
|
+
|
|
62
64
|
Disaggregation steps (self._disaggregate):
|
|
65
|
+
|
|
63
66
|
1. Restore original modules from self._original_data. NB! Changes to aggregated modules are lost except for results (TODO)
|
|
64
67
|
2. Move production and filling results from aggregated modules to detailed modules, weighted based on production capacity and reservoir capacity.
|
|
65
68
|
- TODO: Water values, spill, bypass and pumping results are currently ignored in the disaggregation.
|
|
@@ -69,15 +72,7 @@ class HydroAggregator(Aggregator):
|
|
|
69
72
|
from the model after the first aggregation. Reservoirs will also be assigned to the power node which has the highest cumulative energy equivalent, so
|
|
70
73
|
this aggregator does not work well for reservoirs that are upstream of multiple power nodes.
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
- It is recommended to only use the same aggregator type once on the same components of a model. If you want to go from one aggregation level to
|
|
74
|
-
another, it is better to use model.disaggregate first and then aggregate again. This is to keep the logic simple and avoid complex expressions.
|
|
75
|
-
We have also logic that recognises if result expressions come from aggregations or disaggregations. When aggregating or disaggregating these,
|
|
76
|
-
we can go back to the original results rather than setting up complex expressions that for examples aggregates the disaggregated results.
|
|
77
|
-
- Levels and profiles are aggregated separately, and then combined into attributes.
|
|
78
|
-
- We have chosen to eagerly evaluate weights for aggregation and disaggregation of levels and profiles. This is a balance between eagerly evaluating
|
|
79
|
-
everything, and setting up complex expressions. Eagerly evaluating everything would require setting up new timevectors after eager evaluation, which
|
|
80
|
-
is not ideal. While setting up complex expressions gives expressions that are harder to work with and slower to query from.
|
|
75
|
+
See Aggregator for general design notes and rules to follow when using Aggregators.
|
|
81
76
|
|
|
82
77
|
Attributes:
|
|
83
78
|
_metakey_energy_eq_downstream (str): Metadata key for energy equivalent downstream.
|
|
@@ -93,6 +88,7 @@ class HydroAggregator(Aggregator):
|
|
|
93
88
|
_release_capacity_profile (TimeVector | None): If given, use this profile for all aggregated modules' release capacities.
|
|
94
89
|
|
|
95
90
|
Parent Attributes (see framcore.aggregators.Aggregator):
|
|
91
|
+
|
|
96
92
|
_is_last_call_aggregate (bool | None): Tracks whether the last operation was an aggregation.
|
|
97
93
|
_original_data (dict[str, Component | TimeVector | Curve | Expr] | None): Original detailed data before aggregation.
|
|
98
94
|
_aggregation_map (dict[str, set[str]] | None): Maps aggregated components to their detailed components. detailed to agg
|
|
@@ -128,13 +124,15 @@ class HydroAggregator(Aggregator):
|
|
|
128
124
|
super().__init__()
|
|
129
125
|
self._check_type(metakey_energy_eq_downstream, str)
|
|
130
126
|
self._check_type(ror_threshold, float)
|
|
131
|
-
assert ror_threshold >= 0, ValueError(f"ror_threshold must be non-negative, got {ror_threshold}.")
|
|
132
127
|
self._check_type(data_dim, SinglePeriodTimeIndex)
|
|
133
128
|
self._check_type(scen_dim, FixedFrequencyTimeIndex)
|
|
134
129
|
self._check_type(metakey_power_node, (str, type(None)))
|
|
135
130
|
self._check_type(power_node_members, (list, type(None)))
|
|
136
|
-
if
|
|
137
|
-
|
|
131
|
+
if ror_threshold < 0:
|
|
132
|
+
msg = f"ror_threshold must be non-negative, got {ror_threshold}."
|
|
133
|
+
raise ValueError(msg)
|
|
134
|
+
if metakey_power_node is not None and len(power_node_members) <= 0:
|
|
135
|
+
raise ValueError("If metakey_power_node is given, power_node_members must also be given.")
|
|
138
136
|
|
|
139
137
|
self._metakey_energy_eq_downstream = metakey_energy_eq_downstream
|
|
140
138
|
self._ror_threshold = ror_threshold
|
|
@@ -183,7 +181,7 @@ class HydroAggregator(Aggregator):
|
|
|
183
181
|
for dd in d:
|
|
184
182
|
if dd not in self._aggregation_map:
|
|
185
183
|
self._aggregation_map[dd] = set([a])
|
|
186
|
-
elif not data[dd].get_reservoir(): #
|
|
184
|
+
elif not (data[dd].get_reservoir() and data[a].get_reservoir()): # reservoir modules can only be mapped to one aggregated reservoir module
|
|
187
185
|
self._aggregation_map[dd].add(a)
|
|
188
186
|
self.send_debug_event(f"add generator modules to _aggregation_map time: {round(time() - t, 3)} seconds")
|
|
189
187
|
|
|
@@ -450,7 +448,7 @@ class HydroAggregator(Aggregator):
|
|
|
450
448
|
(
|
|
451
449
|
mm,
|
|
452
450
|
get_level_value(
|
|
453
|
-
data[mm].get_generator().
|
|
451
|
+
data[mm].get_generator().get_energy_equivalent().get_level() * data[mm].get_release_capacity().get_level(),
|
|
454
452
|
model,
|
|
455
453
|
"MW",
|
|
456
454
|
self._data_dim,
|
|
@@ -466,7 +464,7 @@ class HydroAggregator(Aggregator):
|
|
|
466
464
|
|
|
467
465
|
return ignore_production_capacity_modules
|
|
468
466
|
|
|
469
|
-
def _aggregate_groups( # noqa: C901
|
|
467
|
+
def _aggregate_groups( # noqa: C901, PLR0915
|
|
470
468
|
self,
|
|
471
469
|
model: Model,
|
|
472
470
|
upstream_topology: dict[str, list[str]],
|
|
@@ -487,10 +485,10 @@ class HydroAggregator(Aggregator):
|
|
|
487
485
|
|
|
488
486
|
generator = HydroGenerator(
|
|
489
487
|
power_node=data[generator_module_names[0]].get_generator().get_power_node(),
|
|
490
|
-
|
|
488
|
+
energy_equivalent=Conversion(level=ConstantTimeVector(1.0, "kWh/m3", is_max_level=True)),
|
|
491
489
|
production=sum_production,
|
|
492
490
|
)
|
|
493
|
-
energy_eq = generator.
|
|
491
|
+
energy_eq = generator.get_energy_equivalent().get_level()
|
|
494
492
|
|
|
495
493
|
# Release capacity
|
|
496
494
|
release_capacities = [data[m].get_release_capacity() for m in generator_module_names if m not in ignore_capacity]
|
|
@@ -501,9 +499,16 @@ class HydroAggregator(Aggregator):
|
|
|
501
499
|
release_capacities = deepcopy(release_capacities)
|
|
502
500
|
for rc in release_capacities:
|
|
503
501
|
rc.set_profile(self._release_capacity_profile)
|
|
504
|
-
generator_energy_eqs = [data[m].get_generator().
|
|
502
|
+
generator_energy_eqs = [data[m].get_generator().get_energy_equivalent() for m in generator_module_names if m not in ignore_capacity]
|
|
505
503
|
release_capacity_levels = [rc.get_level() * ee.get_level() for rc, ee in zip(release_capacities, generator_energy_eqs, strict=True)]
|
|
506
|
-
|
|
504
|
+
|
|
505
|
+
release_capacity_profile = None
|
|
506
|
+
if any(rc.get_profile() for rc in release_capacities):
|
|
507
|
+
one_profile_max = Expr(src=ConstantTimeVector(1.0, is_zero_one_profile=False), is_profile=True)
|
|
508
|
+
weights = [get_level_value(rcl, model, "MW", self._data_dim, self._scen_dim, is_max=True) for rcl in release_capacity_levels]
|
|
509
|
+
profiles = [rc.get_profile() if rc.get_profile() else one_profile_max for rc in release_capacities]
|
|
510
|
+
release_capacity_profile = _aggregate_weighted_expressions(profiles, weights)
|
|
511
|
+
release_capacity = MaxFlowVolume(level=sum(release_capacity_levels) / energy_eq, profile=release_capacity_profile)
|
|
507
512
|
|
|
508
513
|
# Inflow level
|
|
509
514
|
upstream_inflow_levels = defaultdict(list)
|
|
@@ -513,7 +518,7 @@ class HydroAggregator(Aggregator):
|
|
|
513
518
|
if inflow:
|
|
514
519
|
upstream_inflow_levels[m].append(inflow.get_level())
|
|
515
520
|
inflow_level_energy = sum(
|
|
516
|
-
sum(upstream_inflow_levels[m]) * data[m].get_generator().
|
|
521
|
+
sum(upstream_inflow_levels[m]) * data[m].get_generator().get_energy_equivalent().get_level()
|
|
517
522
|
for m in generator_module_names
|
|
518
523
|
if len(upstream_inflow_levels[m]) > 0
|
|
519
524
|
)
|
|
@@ -524,7 +529,7 @@ class HydroAggregator(Aggregator):
|
|
|
524
529
|
inflow_profile_to_energyinflow = defaultdict(list)
|
|
525
530
|
inflow_level_to_value = dict()
|
|
526
531
|
for m in generator_module_names:
|
|
527
|
-
m_energy_eq = data[m].get_generator().
|
|
532
|
+
m_energy_eq = data[m].get_generator().get_energy_equivalent().get_level()
|
|
528
533
|
m_energy_eq_value = get_level_value(
|
|
529
534
|
m_energy_eq,
|
|
530
535
|
db=model,
|
|
@@ -592,6 +597,13 @@ class HydroAggregator(Aggregator):
|
|
|
592
597
|
"""Aggregate reservoir fillings if all fillings are not None."""
|
|
593
598
|
sum_filling = None
|
|
594
599
|
if all(filling.get_level() for filling in fillings):
|
|
600
|
+
if any(not filling.get_profile() for filling in fillings):
|
|
601
|
+
missing = [member for member, filling in zip(members, fillings, strict=False) if not filling.get_profile()]
|
|
602
|
+
message = (
|
|
603
|
+
"Some reservoir fillings in grouped modules have no profile. Cannot aggregate profiles.",
|
|
604
|
+
f"Group: '{group_id}', missing profile for {missing}.",
|
|
605
|
+
)
|
|
606
|
+
raise ValueError(message)
|
|
595
607
|
level, profiles, weights = self._get_level_profiles_weights_fillings(model, fillings, energy_eq_downstreams, energy_eq, weight_unit)
|
|
596
608
|
profile = _aggregate_weighted_expressions(profiles, weights)
|
|
597
609
|
sum_filling = StockVolume(level=level, profile=profile)
|
|
@@ -755,7 +767,7 @@ class HydroAggregator(Aggregator):
|
|
|
755
767
|
for det in detailed_keys:
|
|
756
768
|
det_module = data[det]
|
|
757
769
|
release_capacity_level = det_module.get_release_capacity().get_level()
|
|
758
|
-
generator_energy_eq = det_module.get_generator().
|
|
770
|
+
generator_energy_eq = det_module.get_generator().get_energy_equivalent().get_level()
|
|
759
771
|
production_weight = get_level_value(
|
|
760
772
|
release_capacity_level * generator_energy_eq,
|
|
761
773
|
db=model,
|