fram-core 0.0.0__py3-none-any.whl → 0.1.0a2__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.
Files changed (103) hide show
  1. fram_core-0.1.0a2.dist-info/METADATA +42 -0
  2. fram_core-0.1.0a2.dist-info/RECORD +100 -0
  3. {fram_core-0.0.0.dist-info → fram_core-0.1.0a2.dist-info}/WHEEL +1 -2
  4. fram_core-0.1.0a2.dist-info/licenses/LICENSE.md +8 -0
  5. framcore/Base.py +142 -0
  6. framcore/Model.py +73 -0
  7. framcore/__init__.py +9 -0
  8. framcore/aggregators/Aggregator.py +153 -0
  9. framcore/aggregators/HydroAggregator.py +837 -0
  10. framcore/aggregators/NodeAggregator.py +495 -0
  11. framcore/aggregators/WindSolarAggregator.py +323 -0
  12. framcore/aggregators/__init__.py +13 -0
  13. framcore/aggregators/_utils.py +184 -0
  14. framcore/attributes/Arrow.py +305 -0
  15. framcore/attributes/ElasticDemand.py +90 -0
  16. framcore/attributes/ReservoirCurve.py +37 -0
  17. framcore/attributes/SoftBound.py +19 -0
  18. framcore/attributes/StartUpCost.py +54 -0
  19. framcore/attributes/Storage.py +146 -0
  20. framcore/attributes/TargetBound.py +18 -0
  21. framcore/attributes/__init__.py +65 -0
  22. framcore/attributes/hydro/HydroBypass.py +42 -0
  23. framcore/attributes/hydro/HydroGenerator.py +83 -0
  24. framcore/attributes/hydro/HydroPump.py +156 -0
  25. framcore/attributes/hydro/HydroReservoir.py +27 -0
  26. framcore/attributes/hydro/__init__.py +13 -0
  27. framcore/attributes/level_profile_attributes.py +714 -0
  28. framcore/components/Component.py +112 -0
  29. framcore/components/Demand.py +130 -0
  30. framcore/components/Flow.py +167 -0
  31. framcore/components/HydroModule.py +330 -0
  32. framcore/components/Node.py +76 -0
  33. framcore/components/Thermal.py +204 -0
  34. framcore/components/Transmission.py +183 -0
  35. framcore/components/_PowerPlant.py +81 -0
  36. framcore/components/__init__.py +22 -0
  37. framcore/components/wind_solar.py +67 -0
  38. framcore/curves/Curve.py +44 -0
  39. framcore/curves/LoadedCurve.py +155 -0
  40. framcore/curves/__init__.py +9 -0
  41. framcore/events/__init__.py +21 -0
  42. framcore/events/events.py +51 -0
  43. framcore/expressions/Expr.py +490 -0
  44. framcore/expressions/__init__.py +28 -0
  45. framcore/expressions/_get_constant_from_expr.py +483 -0
  46. framcore/expressions/_time_vector_operations.py +615 -0
  47. framcore/expressions/_utils.py +73 -0
  48. framcore/expressions/queries.py +423 -0
  49. framcore/expressions/units.py +207 -0
  50. framcore/fingerprints/__init__.py +11 -0
  51. framcore/fingerprints/fingerprint.py +293 -0
  52. framcore/juliamodels/JuliaModel.py +161 -0
  53. framcore/juliamodels/__init__.py +7 -0
  54. framcore/loaders/__init__.py +10 -0
  55. framcore/loaders/loaders.py +407 -0
  56. framcore/metadata/Div.py +73 -0
  57. framcore/metadata/ExprMeta.py +50 -0
  58. framcore/metadata/LevelExprMeta.py +17 -0
  59. framcore/metadata/Member.py +55 -0
  60. framcore/metadata/Meta.py +44 -0
  61. framcore/metadata/__init__.py +15 -0
  62. framcore/populators/Populator.py +108 -0
  63. framcore/populators/__init__.py +7 -0
  64. framcore/querydbs/CacheDB.py +50 -0
  65. framcore/querydbs/ModelDB.py +34 -0
  66. framcore/querydbs/QueryDB.py +45 -0
  67. framcore/querydbs/__init__.py +11 -0
  68. framcore/solvers/Solver.py +48 -0
  69. framcore/solvers/SolverConfig.py +272 -0
  70. framcore/solvers/__init__.py +9 -0
  71. framcore/timeindexes/AverageYearRange.py +20 -0
  72. framcore/timeindexes/ConstantTimeIndex.py +17 -0
  73. framcore/timeindexes/DailyIndex.py +21 -0
  74. framcore/timeindexes/FixedFrequencyTimeIndex.py +762 -0
  75. framcore/timeindexes/HourlyIndex.py +21 -0
  76. framcore/timeindexes/IsoCalendarDay.py +31 -0
  77. framcore/timeindexes/ListTimeIndex.py +197 -0
  78. framcore/timeindexes/ModelYear.py +17 -0
  79. framcore/timeindexes/ModelYears.py +18 -0
  80. framcore/timeindexes/OneYearProfileTimeIndex.py +21 -0
  81. framcore/timeindexes/ProfileTimeIndex.py +32 -0
  82. framcore/timeindexes/SinglePeriodTimeIndex.py +37 -0
  83. framcore/timeindexes/TimeIndex.py +90 -0
  84. framcore/timeindexes/WeeklyIndex.py +21 -0
  85. framcore/timeindexes/__init__.py +36 -0
  86. framcore/timevectors/ConstantTimeVector.py +135 -0
  87. framcore/timevectors/LinearTransformTimeVector.py +114 -0
  88. framcore/timevectors/ListTimeVector.py +123 -0
  89. framcore/timevectors/LoadedTimeVector.py +104 -0
  90. framcore/timevectors/ReferencePeriod.py +41 -0
  91. framcore/timevectors/TimeVector.py +94 -0
  92. framcore/timevectors/__init__.py +17 -0
  93. framcore/utils/__init__.py +36 -0
  94. framcore/utils/get_regional_volumes.py +369 -0
  95. framcore/utils/get_supported_components.py +60 -0
  96. framcore/utils/global_energy_equivalent.py +46 -0
  97. framcore/utils/isolate_subnodes.py +163 -0
  98. framcore/utils/loaders.py +97 -0
  99. framcore/utils/node_flow_utils.py +236 -0
  100. framcore/utils/storage_subsystems.py +107 -0
  101. fram_core-0.0.0.dist-info/METADATA +0 -5
  102. fram_core-0.0.0.dist-info/RECORD +0 -4
  103. fram_core-0.0.0.dist-info/top_level.txt +0 -1
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: fram-core
3
+ Version: 0.1.0a2
4
+ Summary:
5
+ License: LICENSE.md
6
+ License-File: LICENSE.md
7
+ Author: The Norwegian Water Resources and Energy Directorate
8
+ Author-email: fram@nve.no
9
+ Requires-Python: >=3.11,<4
10
+ Classifier: License :: Other/Proprietary License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Requires-Dist: juliacall (>=0.9.28,<0.10.0)
17
+ Requires-Dist: numexpr (>=2.10.2)
18
+ Requires-Dist: numpy (>=2.2.2)
19
+ Requires-Dist: pandas (>=2.2.3)
20
+ Requires-Dist: sympy (>=1.13.3)
21
+ Description-Content-Type: text/markdown
22
+
23
+ # fram-core
24
+
25
+ ## About
26
+
27
+ **fram-core** is the main package in **FRAM** modelling framework. The package contains essential features, interfaces and components for running energy market models in FRAM.
28
+
29
+ For package documentation see [fram-core](https://nve.github.io/fram-core){:target="_blank"}.
30
+
31
+ For FRAM documentation see [FRAM mainpage](https://nve.github.io/fram){:target="_blank"}.
32
+
33
+ ## Installation
34
+
35
+ To add the package to your project use:
36
+
37
+ pip install fram-core
38
+
39
+ With poetry:
40
+
41
+ poetry add fram-core
42
+
@@ -0,0 +1,100 @@
1
+ framcore/Base.py,sha256=2MP0T1HAjU3gqkSvdGnbvd3t1JZmdXGCcwueETIhrrs,5326
2
+ framcore/Model.py,sha256=TraN6dRQoJgm4veePEVszpdGAMGiTJA1dKSG4s_DtZg,2580
3
+ framcore/__init__.py,sha256=5Ryn1oUwtxaPX95dfleE5GVYo9_QSBAwMG8UiUNrg74,128
4
+ framcore/aggregators/Aggregator.py,sha256=n9jL4iAGk_N0An80BJNntkYYaXaQzGW1xGD3oFjUYzk,6473
5
+ framcore/aggregators/HydroAggregator.py,sha256=nsSzAqbTTwkifXyMeKXflMWSJWu6eNwICQuZVE20Qp0,47211
6
+ framcore/aggregators/NodeAggregator.py,sha256=Zz4D1H42EmDU0jgYJtAR4C0_eRDN6nacLA3guRt_r2M,20716
7
+ framcore/aggregators/WindSolarAggregator.py,sha256=3cHIxYZixTIH8kmkuR8GL6eROd8oU7z5txRVCMOvOrg,19620
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=7P5xpXD6MdlIuERGQn9f0ia4-tQVYNiSP6CfMbX3K7Q,10863
11
+ framcore/attributes/ElasticDemand.py,sha256=sG5wUTw17PQA_8YESaPMnRjUbr7nm6mSmkmuHoGlqJU,3024
12
+ framcore/attributes/ReservoirCurve.py,sha256=EVzCMPM5OELB9U9_BAYmDOEFF4Pd6LwwmDOzRpkRVME,813
13
+ framcore/attributes/SoftBound.py,sha256=7bW6KlSsKFvh8F6UPjC5PoLMGI7gClNo-dQrwAQHD5k,417
14
+ framcore/attributes/StartUpCost.py,sha256=WLZ8mcQWdSyyA0GKIJ2rhMbAU17pHAREBP1Tb-3_4rU,1831
15
+ framcore/attributes/Storage.py,sha256=x6njOhmkR3VvFI3Umn1Jjjd4F6yHWx-ZNNOYYv0lcZs,5335
16
+ framcore/attributes/TargetBound.py,sha256=DHl8CCPynhrcRqTHPhFo9HddhEt489mGqrqUHQPkPhg,433
17
+ framcore/attributes/__init__.py,sha256=SPzgyK4TQSMJ5tWOUmYfUpvWliCbQF8AVQkf6JeEGos,1552
18
+ framcore/attributes/hydro/HydroBypass.py,sha256=ZiAfOiukGvlckCRIlccFqjnAwLiGn1SnN1uqtSgN9H4,1342
19
+ framcore/attributes/hydro/HydroGenerator.py,sha256=ed0jMP808qchxkc4mXrVdtSO0LAGY6ihrjLN8xvdzN0,3207
20
+ framcore/attributes/hydro/HydroPump.py,sha256=Yi54d259CzKpcu44hhWoX3ZxTBqhzlWun1WC5pGfVKw,5915
21
+ framcore/attributes/hydro/HydroReservoir.py,sha256=7rYu530BTX9dJmTYrd_02uPddj290E-ziiZzjh_3Q3k,879
22
+ framcore/attributes/hydro/__init__.py,sha256=isOSLP2TdHOaPcr16EXIkdtx80LVe8ztFPiuGJPC4ao,381
23
+ framcore/attributes/level_profile_attributes.py,sha256=hZEBy_yjTJ4BIDM0qiNsQeO7Kcvr1CNMlv8Oyck7lkM,23340
24
+ framcore/components/Component.py,sha256=GT_scqmDwSE8eLB3ZHcDMGfenRbko7xj4cenm_6Io-Q,3820
25
+ framcore/components/Demand.py,sha256=vUP3hWobtk_Qe7ZGD2bXkLR_9zhaGrB8tKu3SvcvQp0,5399
26
+ framcore/components/Flow.py,sha256=wuqKZ76uIw-OfdEVC-55wok7PmoeRs7KEAR-o2wUC6A,5887
27
+ framcore/components/HydroModule.py,sha256=qpu0Fo4bFJs9kgwHtfwlInOX2UvwB1khdvRHWFWgmYQ,11352
28
+ framcore/components/Node.py,sha256=Kh8Glu0M0UjmEpJhlATXD9xqc7QZEd5HxFdE2EVT58s,2280
29
+ framcore/components/Thermal.py,sha256=MY0iOD870NSptZWpynKUEd8CuLt50s3_265GIiMG4Qc,7666
30
+ framcore/components/Transmission.py,sha256=DlVrkKT8rNTUyQXaO3375S1zKfE1OOkl3QYGB7BhhMU,6842
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=YsIPMZQEP81tijneFkeMEfprttu7lX7T5loVmSjSM90,2060
34
+ framcore/curves/Curve.py,sha256=89CsAJpCa_GkQtsBGclUcK8D3ttCyYhs583-_6zE0is,934
35
+ framcore/curves/LoadedCurve.py,sha256=8DiyLeJzydx6Q6MNQonxNGujl3zPma3vINDUN7E8_As,3891
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=I3m8cy-PNomN6p9088QQf1c4Egei5XEj7JoxwqZC0cY,20614
40
+ framcore/expressions/__init__.py,sha256=J8bNtWRfIpwIi6yZIjFazQXU9ymJbKsYLqPlMUoH028,628
41
+ framcore/expressions/_get_constant_from_expr.py,sha256=bqq-_xRhOrly6GB9Z868icgNqxrrUzobN1zXCq8B6lg,16596
42
+ framcore/expressions/_time_vector_operations.py,sha256=kIMmQYR-U2aAxB5KPWx-zaqLvjvlmiwa2yCnmeNvvRY,29031
43
+ framcore/expressions/_utils.py,sha256=mtruqnHcwpkIufbTFjJvT3iUfN98gN51SQ9e9hS9cUc,2123
44
+ framcore/expressions/queries.py,sha256=PLJVL0CJoJp3okIYyG1QOrR3s9HHfBdZdxuCzrCBHa0,16258
45
+ framcore/expressions/units.py,sha256=9X243DutWfkCbRNzy65frQNKN8tlLLzLvWYtiEyhHcg,6863
46
+ framcore/fingerprints/__init__.py,sha256=YpIj-t6DRrSOmdidchC4qjLHSoZwsdkf9980nHUsnsw,256
47
+ framcore/fingerprints/fingerprint.py,sha256=-e8aweHfPj8VURJqEra14xydVw3E1heB17LWcPFzIZo,9191
48
+ framcore/juliamodels/JuliaModel.py,sha256=S-W_t4Pv-xuzz6lrULmxNPu0No6Zs1CPpb9WW3ECZK0,6596
49
+ framcore/juliamodels/__init__.py,sha256=2ia9EGUQAZD0_f2o0NWdhD0d2z80K_bweohnGNfnpGA,124
50
+ framcore/loaders/__init__.py,sha256=iwlnauyXhH_BAuVIKVmX2vMvU94i_bXI57CE82eM7VA,209
51
+ framcore/loaders/loaders.py,sha256=EYqKeItQS5-zI-n-yOJf_7C_Ojo5VBk143f76joINYA,11254
52
+ framcore/metadata/Div.py,sha256=fIIB9W9fVJEUNzqIWXOJ9ZN3tWE8dB8ap2BdULT2fJw,2428
53
+ framcore/metadata/ExprMeta.py,sha256=zM5u7TXnImEIIVQbUGmcz74LbvNybMIkRYB4913cdiE,1556
54
+ framcore/metadata/LevelExprMeta.py,sha256=AcE4DfJLtMFY69A8H7hzKYSR454O6Z9JTpOQpNnqG1I,567
55
+ framcore/metadata/Member.py,sha256=7dcjqNWCcoGlOqXtqCBXYeN4z_5cIPBFUngcBVKAeas,1877
56
+ framcore/metadata/Meta.py,sha256=P9ESFJlrxxhcY9PNSnnccrsLBulu1v3Kir6agzSrv0I,1173
57
+ framcore/metadata/__init__.py,sha256=rf2oJ5SjlmdD4Fk0svPxHbNZlnzUdt-FPyjjhA8wbhc,350
58
+ framcore/populators/Populator.py,sha256=SraN4QOTsA3yXEVG4z9ohWfPKokssl3uvDJPGh56lrU,4077
59
+ framcore/populators/__init__.py,sha256=jE6tHIvTaPTWcD-yVpBOjDBxI3xFqJh0QASfQexBLCE,119
60
+ framcore/querydbs/CacheDB.py,sha256=vzLEJM8SsvQYMXDE_TdjzicHM6yfJJefzp6u-Qyneys,1707
61
+ framcore/querydbs/ModelDB.py,sha256=XD4jHlmRQHqF7L2euDIMrPZREhr3SXgv0UnqkADLzRw,1039
62
+ framcore/querydbs/QueryDB.py,sha256=IV96mtslktpJMrdxgrwQetVx4R1HAP1GxtKgx9WLPZA,1233
63
+ framcore/querydbs/__init__.py,sha256=oPX2sqAMoEMxWL9b_sRg4z4Q_b80iDm_jk-W_qmpEME,231
64
+ framcore/solvers/Solver.py,sha256=dtxguoJAYHQCFsDXU9bbCZJGEaFJhS4Bpk-JGvI2g_o,1302
65
+ framcore/solvers/SolverConfig.py,sha256=E181Jm9mJvEb2wMN7zE2AUzHwhQp_OLiKfSK6bEGF8s,10585
66
+ framcore/solvers/__init__.py,sha256=q9HLYJkRdvJeFgAM1NBGIaFI8APmsZCDTy1AkIKw_Zs,179
67
+ framcore/timeindexes/AverageYearRange.py,sha256=nAc-GxtPm9Hm-aEkDCkaKIfB4WC8jUJ_jgbPZjKi838,834
68
+ framcore/timeindexes/ConstantTimeIndex.py,sha256=mufAI-lqdKyRw7kOzYZW1RbH9MfMfCNKnJPK7Em_3jk,601
69
+ framcore/timeindexes/DailyIndex.py,sha256=SqKfnCisENPlt9gU7FTmjP7D_F3wNcLULqyyEoe5Cas,644
70
+ framcore/timeindexes/FixedFrequencyTimeIndex.py,sha256=7DI_TRfaVCr2o402Mz4Wd1oOjluE1Xfr1-ItUrhPGbg,31479
71
+ framcore/timeindexes/HourlyIndex.py,sha256=Xvqo41KZHIl1bsh0g1uSmH5TLG80UUt9vm3ZArNWa08,648
72
+ framcore/timeindexes/IsoCalendarDay.py,sha256=5zffRr-uIg_mrwraBUWjD1LwhiYNU0pAl6-oGgQgvl8,991
73
+ framcore/timeindexes/ListTimeIndex.py,sha256=XsYXkGG-wA6uPo-50zkp_R1_l9SII8dq7S_kGV060vY,8389
74
+ framcore/timeindexes/ModelYear.py,sha256=Yh8jKizsRLNJRkkjCGQbxMETkDOMZq2zPEXNSmbAbqc,699
75
+ framcore/timeindexes/ModelYears.py,sha256=ACNNNFeM6MycYNAQnYVVdXrjhqCgtdQ7DHyCXPimbzI,718
76
+ framcore/timeindexes/OneYearProfileTimeIndex.py,sha256=HzjILknONIbGdw6rN_Ro5_r4ywFDXMoMP1bEgVzGuHo,773
77
+ framcore/timeindexes/ProfileTimeIndex.py,sha256=Da_A-4NO8vgu9Z6Uax6rjKL0D03KnDCnroI5L90ZauU,1302
78
+ framcore/timeindexes/SinglePeriodTimeIndex.py,sha256=mM-dHDEHdEOgaRLHQ2Q6NbjDoz9wYl3rC46EkLeHQpI,1447
79
+ framcore/timeindexes/TimeIndex.py,sha256=kLQMiAi6ddQfzdKxyQbNRxmocwRmiLWz7SxUxCZ8wbw,2552
80
+ framcore/timeindexes/WeeklyIndex.py,sha256=fZr0OMDKc5yUlsMa--D7d8FxWg-fkeHH5oclXP_a4mI,648
81
+ framcore/timeindexes/__init__.py,sha256=2LmQFuMR1DffqHk5O2zd8o9g8QsazxSSykxOE7WGP4o,1351
82
+ framcore/timevectors/ConstantTimeVector.py,sha256=S9fMSna_xOzJwu_Q9B_wNJtm9QYlEC3DmbkutOtMofU,6012
83
+ framcore/timevectors/LinearTransformTimeVector.py,sha256=fcXQJ0Sn-m1K56j-5p78p1sFwxrEvgjq00x-THevTF4,4189
84
+ framcore/timevectors/ListTimeVector.py,sha256=rQyCY2o7BsutS6kSFnV-CVzgoJCIPccmUugoeo-cwoI,5324
85
+ framcore/timevectors/LoadedTimeVector.py,sha256=Igg045J6oFF8ocSUC1khbA98y_niww759FLFa-umzXk,4493
86
+ framcore/timevectors/ReferencePeriod.py,sha256=eB7DQYIsyq273D7jQEG_-b-x3S5mpY8pCkATik5DKE8,1456
87
+ framcore/timevectors/TimeVector.py,sha256=yH68GReHw19VjScOWBf9u0krRPlHzmOnbvDAyM-pLno,2598
88
+ framcore/timevectors/__init__.py,sha256=Wd3gXGatwpbe2yMKSCq7O9My5wbveuK23iYO2ykkUIk,603
89
+ framcore/utils/__init__.py,sha256=9M80LjgkJQ53Lbe5jiRro2tHUR4MQp2JPGiKF57wOr4,1222
90
+ framcore/utils/get_regional_volumes.py,sha256=QqQ3Ik8VIHFomEIA3krcGJkeyDZFecbaNRtIm7_n8KE,14956
91
+ framcore/utils/get_supported_components.py,sha256=F1AxTFuh4pLQTCwg8oYBhus0HOhKjkKYiRf9Ok1ttB4,1856
92
+ framcore/utils/global_energy_equivalent.py,sha256=R3I1XVIIaYkagr_BPuKaAj9eS7gNzaff5v1tk7hzQuY,2614
93
+ framcore/utils/isolate_subnodes.py,sha256=iRiqgTU_c6W1jLmIBGO7fQoK_abPA3N8beGJbroC2V8,6527
94
+ framcore/utils/loaders.py,sha256=8llunmOC6XkbqNXSxL1XVNipGj7OpjIZ-2AyJUj-5v8,3320
95
+ framcore/utils/node_flow_utils.py,sha256=4Z6eB4oUqhOWWeHFNYRZY0c0UQlJ8tM9duYvLzkJQ3k,7800
96
+ framcore/utils/storage_subsystems.py,sha256=PmkV9rhiVUrmPz1zsyKcEwjJzYUkXQUcGM511tttsXc,4158
97
+ fram_core-0.1.0a2.dist-info/METADATA,sha256=sCfBTgaBnPmQOiyYov3DDdMrC9tfVkpH12uVideaToo,1251
98
+ fram_core-0.1.0a2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
99
+ fram_core-0.1.0a2.dist-info/licenses/LICENSE.md,sha256=fxh4ZxuR8dM2HDs-pIUitPrJdxQ4fEFh1GvEYZA2m1E,1075
100
+ fram_core-0.1.0a2.dist-info/RECORD,,
@@ -1,5 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: poetry-core 2.2.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
-
@@ -0,0 +1,8 @@
1
+ The MIT License (MIT)
2
+ Copyright © 2025 NVE
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
framcore/Base.py ADDED
@@ -0,0 +1,142 @@
1
+ import contextlib
2
+ import inspect
3
+ from typing import Any
4
+
5
+ from framcore.events import (
6
+ send_debug_event,
7
+ send_error_event,
8
+ send_event,
9
+ send_info_event,
10
+ send_warning_event,
11
+ )
12
+ from framcore.fingerprints import Fingerprint
13
+
14
+ # TODO: Consider context dict | None in event-methods to support more info (e.g. process id)
15
+
16
+
17
+ class Base:
18
+ """Core base class to share methods."""
19
+
20
+ def _check_type(self, value, class_or_tuple) -> None: # noqa: ANN001
21
+ if not isinstance(value, class_or_tuple):
22
+ message = f"Expected {class_or_tuple} for {self}, got {type(value).__name__}"
23
+ raise TypeError(message)
24
+
25
+ def _ensure_float(self, value: object) -> float:
26
+ with contextlib.suppress(Exception):
27
+ return float(value)
28
+ message = f"Unable to convert {value} to float."
29
+ raise ValueError(message)
30
+
31
+ def _check_int(self, value: int, lower_bound: int | None, upper_bound: int | None) -> None:
32
+ if lower_bound is not None and value < lower_bound:
33
+ message = f"Value {value} is less than lower_bound {lower_bound}."
34
+ raise ValueError(message)
35
+ if upper_bound is not None and value > upper_bound:
36
+ message = f"Value {value} is greater than upper_bound {upper_bound}."
37
+ raise ValueError(message)
38
+
39
+ def _check_float(self, value: float, lower_bound: float | None, upper_bound: float | None) -> None:
40
+ if lower_bound is not None and value < lower_bound:
41
+ message = f"Value {value} is less than lower_bound {lower_bound}."
42
+ raise ValueError(message)
43
+ if upper_bound is not None and value > upper_bound:
44
+ message = f"Value {value} is greater than upper_bound {upper_bound}."
45
+ raise ValueError(message)
46
+
47
+ def _report_errors(self, errors: set[str]) -> None:
48
+ if errors:
49
+ n = len(errors)
50
+ s = "s" if n > 1 else ""
51
+ error_str = "\n".join(errors)
52
+ message = f"Found {n} error{s}:\n{error_str}"
53
+ raise RuntimeError(message)
54
+
55
+ def send_event(self, event_type: str, **kwargs: dict[str, Any]) -> None:
56
+ """All events in core should use this."""
57
+ send_event(sender=self, event_type=event_type, **kwargs)
58
+
59
+ def send_warning_event(self, message: str) -> None:
60
+ """Use this to send warning event."""
61
+ send_warning_event(sender=self, message=message)
62
+
63
+ def send_error_event(self, message: str, exception_type_name: str, traceback: str) -> None:
64
+ """Use this to send error event."""
65
+ send_error_event(sender=self, message=message, exception_type_name=exception_type_name, traceback=traceback)
66
+
67
+ def send_info_event(self, message: str) -> None:
68
+ """Use this to send info event."""
69
+ send_info_event(sender=self, message=message)
70
+
71
+ def send_debug_event(self, message: str) -> None:
72
+ """Use this to send debug event."""
73
+ send_debug_event(sender=self, message=message)
74
+
75
+ def get_fingerprint_default(
76
+ self,
77
+ refs: dict[str, str] | None = None,
78
+ excludes: set[str] | None = None,
79
+ ) -> Fingerprint:
80
+ """
81
+ Generate a Fingerprint for the object, optionally including references and excluding specified properties.
82
+
83
+ Parameters
84
+ ----------
85
+ refs : dict[str, str] | None, optional
86
+ Dictionary mapping property names to reference keys to include as references in the fingerprint.
87
+ excludes : set[str] | None, optional
88
+ Set of property names to exclude from the fingerprint.
89
+
90
+ Returns
91
+ -------
92
+ Fingerprint
93
+ The generated fingerprint for the object.
94
+
95
+ """
96
+ fingerprint = Fingerprint(source=self)
97
+
98
+ if refs:
99
+ for ref_prop, ref_key in refs.items():
100
+ if ref_key is not None:
101
+ fingerprint.add_ref(ref_prop, ref_key)
102
+
103
+ default_excludes = {"_parent"}
104
+
105
+ for prop_name, prop_value in self.__dict__.items():
106
+ if callable(prop_value) or (refs and prop_name in refs) or (excludes and prop_name in excludes) or prop_name in default_excludes:
107
+ continue
108
+
109
+ if prop_value is None:
110
+ continue
111
+
112
+ fingerprint.add(prop_name, prop_value)
113
+
114
+ return fingerprint
115
+
116
+ def _get_property_name(self, property_reference) -> str | None: # noqa: ANN001
117
+ for name, value in inspect.getmembers(self):
118
+ if value is property_reference:
119
+ return name
120
+ return None
121
+
122
+ def __repr__(self) -> str:
123
+ """Display type and non-None fields."""
124
+ type_name = type(self).__name__
125
+ value_fields = []
126
+ for k, v in vars(self).items():
127
+ display_value = self._get_attr_str(k, v)
128
+ if display_value is not None:
129
+ value_fields.append(f"{k}={display_value}")
130
+ value_fields = ", ".join(value_fields)
131
+ return f"{type_name}({value_fields})"
132
+
133
+ def _get_attr_str(self, key: str, value: object) -> str | None:
134
+ if value is None:
135
+ return None
136
+ if isinstance(value, int | float | str | bool):
137
+ return value
138
+ try:
139
+ return value._get_attr_str() # noqa: SLF001
140
+ except Exception:
141
+ pass
142
+ return type(value).__name__
framcore/Model.py ADDED
@@ -0,0 +1,73 @@
1
+ from collections import Counter
2
+ from typing import TYPE_CHECKING
3
+
4
+ from framcore import Base
5
+ from framcore.components import Component
6
+ from framcore.curves import Curve
7
+ from framcore.expressions import Expr
8
+ from framcore.timevectors import TimeVector
9
+
10
+ if TYPE_CHECKING:
11
+ from framcore.aggregators import Aggregator
12
+
13
+ class ModelDict(dict):
14
+ """Dict storing only values of type Component | Expr | TimeVector | Curve."""
15
+ def __setitem__(self, key, value):
16
+ if not isinstance(key, str):
17
+ message = f"Expected str for key {key}, got {type(key).__name__}"
18
+ raise TypeError(message)
19
+ if not isinstance(value, Component | Expr | TimeVector | Curve):
20
+ message = f"Expected Component | Expr | TimeVector | Curve for key {key}, got {type(value).__name__}"
21
+ raise TypeError(message)
22
+ return super().__setitem__(key, value)
23
+
24
+ class Model(Base):
25
+ """Definition of the Model class."""
26
+
27
+ def __init__(self) -> None:
28
+ """Create a new model instance."""
29
+ self._data = ModelDict()
30
+ self._aggregators: list[Aggregator] = []
31
+
32
+ def disaggregate(self) -> None:
33
+ """Undo all aggregations in LIFO order."""
34
+ while self._aggregators:
35
+ aggregator = self._aggregators.pop(-1) # last item
36
+ aggregator.disaggregate(self)
37
+
38
+ def get_data(self) -> ModelDict:
39
+ """Get internal data. Modify this with care."""
40
+ return self._data
41
+
42
+ def get_content_counts(self) -> dict[str, Counter]:
43
+ """Return number of objects stored in model organized into concepts and types."""
44
+ data_values = self.get_data().values()
45
+ counts = {
46
+ "components": Counter(),
47
+ "timevectors": Counter(),
48
+ "curves": Counter(),
49
+ "expressions": Counter(),
50
+ }
51
+ for obj in data_values:
52
+ if isinstance(obj, Component):
53
+ key = "components"
54
+ elif isinstance(obj, TimeVector):
55
+ key = "timevectors"
56
+ elif isinstance(obj, Curve):
57
+ key = "curves"
58
+ elif isinstance(obj, Expr):
59
+ key = "expressions"
60
+ else:
61
+ key = "unexpected"
62
+ if key not in counts:
63
+ counts[key] = Counter()
64
+ counts[key][type(obj).__name__] += 1
65
+
66
+ assert len(data_values) == sum(c.total() for c in counts.values())
67
+
68
+ counts["aggregators"] = Counter()
69
+ for a in self._aggregators:
70
+ counts["aggregators"][type(a).__name__] += 1
71
+
72
+ return counts
73
+
framcore/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ # framcore/__init__.py
2
+
3
+ from framcore.Base import Base
4
+ from framcore.Model import Model
5
+
6
+ __all__ = [
7
+ "Base",
8
+ "Model",
9
+ ]
@@ -0,0 +1,153 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections import defaultdict
5
+ from collections.abc import Iterable
6
+ from copy import deepcopy
7
+
8
+ from framcore.Base import Base
9
+ from framcore.components import Component
10
+ from framcore.curves import Curve
11
+ from framcore.expressions import Expr
12
+ from framcore.metadata import Member
13
+ from framcore.Model import Model
14
+ from framcore.timevectors import TimeVector
15
+
16
+
17
+ class Aggregator(Base, ABC):
18
+ """
19
+ Aggregator interface class.
20
+
21
+ Public API is the aggregate and disaggregate methods.
22
+
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
+ """
33
+
34
+ def __init__(self) -> None:
35
+ """Initialize the Aggregator with default state for aggregation tracking and data storage."""
36
+ self._is_last_call_aggregate = None
37
+ self._original_data: dict[str, Component | TimeVector | Curve | Expr] | None = None
38
+ self._aggregation_map: dict[str, set[str]] = None
39
+
40
+ def aggregate(self, model: Model) -> None:
41
+ """Aggregate model. Keep original data in case disaggregate is called."""
42
+ self._check_type(model, Model)
43
+
44
+ if self._is_last_call_aggregate is True:
45
+ message = f"Will overwrite existing aggregation."
46
+ self.send_warning_event(message)
47
+
48
+ self._original_data = deepcopy(model.get_data())
49
+ self._aggregate(model)
50
+ self._is_last_call_aggregate = True
51
+ if self in model._aggregators: # noqa: SLF001
52
+ message = f"{model} has already been aggregated with {self}. Cannot perform the same Aggregation more than once on a Model object."
53
+ raise ValueError(message)
54
+
55
+ # transfer_unambigous_memberships to aggregated components to support further aggregation
56
+ mapping = self.get_aggregation_map()
57
+ reversed_mapping = defaultdict(set)
58
+ new_data = model.get_data()
59
+ for member_id, group_ids in mapping.items():
60
+ self._check_type(group_ids, set)
61
+ for group_id in group_ids:
62
+ self._check_type(group_id, str)
63
+ member_component = self._original_data[member_id]
64
+ group_component = new_data[group_id]
65
+ reversed_mapping[group_component].add(member_component)
66
+ for group_component, member_components in reversed_mapping.items():
67
+ transfer_unambigous_memberships(group_component, member_components)
68
+
69
+ model._aggregators.append(deepcopy(self)) # noqa: SLF001
70
+
71
+ def disaggregate(self, model: Model) -> None:
72
+ """Disaggregate model back to pre-aggregate form. Move results into the disaggregated objects."""
73
+ self._check_type(model, Model)
74
+ self._check_is_aggregated()
75
+ self._disaggregate(model, self._original_data)
76
+ self._is_last_call_aggregate = False
77
+ self._original_data = None
78
+ self._aggregation_map = None
79
+
80
+ def get_aggregation_map(self) -> dict[str, set[str]]:
81
+ """
82
+ Return dictionary mapping from disaggregated to aggregated Component IDs.
83
+
84
+ The mapping should tell you which of the original Components were aggregated into which new Components.
85
+ Components which are left as is should not be in the mapping.
86
+ Components which are deleted without being aggregated are mapped to an empty set.
87
+ """
88
+ if self._aggregation_map is None:
89
+ message = f"{self} has not yet performed an aggregation or the aggregation map was not created during aggregation."
90
+ raise ValueError(message)
91
+ return self._aggregation_map
92
+
93
+ @abstractmethod
94
+ def _aggregate(self, model: Model) -> None:
95
+ """Modify model inplace. Replace components with aggregated components according to some method."""
96
+ pass
97
+
98
+ @abstractmethod
99
+ def _disaggregate(
100
+ self,
101
+ model: Model,
102
+ original_data: dict[str, Component | TimeVector | Curve | Expr],
103
+ ) -> None:
104
+ """
105
+ Modify model inplace. Restore from aggregated to original components.
106
+
107
+ Transfer any results from aggregated components to restored (disaggregated) components.
108
+
109
+ Implementers should document and handle changes in model instance between aggregation and disaggregation.
110
+ E.g. what to do if an aggregated component has been deleted prior to disaggregate call.
111
+ """
112
+ pass
113
+
114
+ def _check_is_aggregated(self) -> None:
115
+ if self._is_last_call_aggregate in [False, None]:
116
+ message = "Not aggregated. Must call aggregate and disaggregate in pairs."
117
+ raise RuntimeError(message)
118
+
119
+
120
+ def transfer_unambigous_memberships(group_component: Component, member_components: Iterable[Component]) -> None:
121
+ """
122
+ Transfer unambiguous membership metadata from member components to a group component.
123
+
124
+ Parameters
125
+ ----------
126
+ group_component : Component
127
+ The component to which unambiguous membership metadata will be transferred.
128
+ member_components : Iterable[Component]
129
+ The components from which membership metadata is collected.
130
+
131
+ Notes
132
+ -----
133
+ Only metadata keys with a single unique Member value among all member components are transferred.
134
+ Existing metadata on the group component is not overwritten.
135
+
136
+ """
137
+ d = defaultdict(set)
138
+ for member in member_components:
139
+ for key in member.get_meta_keys():
140
+ value = member.get_meta(key)
141
+ if not isinstance(value, Member):
142
+ continue
143
+ d[key].add(value)
144
+ for key, value_set in d.items():
145
+ test_value = group_component.get_meta(key)
146
+ if test_value is not None:
147
+ # don't overwrite if already set
148
+ continue
149
+ if len(value_set) != 1:
150
+ # ambigous membership
151
+ continue
152
+ value = next(iter(value_set))
153
+ group_component.add_meta(key, value)