fram-core 0.0.0__py3-none-any.whl → 0.1.0__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.0.dist-info/METADATA +42 -0
  2. fram_core-0.1.0.dist-info/RECORD +100 -0
  3. {fram_core-0.0.0.dist-info → fram_core-0.1.0.dist-info}/WHEEL +1 -2
  4. fram_core-0.1.0.dist-info/licenses/LICENSE.md +8 -0
  5. framcore/Base.py +161 -0
  6. framcore/Model.py +90 -0
  7. framcore/__init__.py +10 -0
  8. framcore/aggregators/Aggregator.py +172 -0
  9. framcore/aggregators/HydroAggregator.py +849 -0
  10. framcore/aggregators/NodeAggregator.py +530 -0
  11. framcore/aggregators/WindSolarAggregator.py +315 -0
  12. framcore/aggregators/__init__.py +13 -0
  13. framcore/aggregators/_utils.py +184 -0
  14. framcore/attributes/Arrow.py +307 -0
  15. framcore/attributes/ElasticDemand.py +90 -0
  16. framcore/attributes/ReservoirCurve.py +23 -0
  17. framcore/attributes/SoftBound.py +16 -0
  18. framcore/attributes/StartUpCost.py +65 -0
  19. framcore/attributes/Storage.py +158 -0
  20. framcore/attributes/TargetBound.py +16 -0
  21. framcore/attributes/__init__.py +63 -0
  22. framcore/attributes/hydro/HydroBypass.py +49 -0
  23. framcore/attributes/hydro/HydroGenerator.py +100 -0
  24. framcore/attributes/hydro/HydroPump.py +178 -0
  25. framcore/attributes/hydro/HydroReservoir.py +27 -0
  26. framcore/attributes/hydro/__init__.py +13 -0
  27. framcore/attributes/level_profile_attributes.py +911 -0
  28. framcore/components/Component.py +136 -0
  29. framcore/components/Demand.py +144 -0
  30. framcore/components/Flow.py +189 -0
  31. framcore/components/HydroModule.py +371 -0
  32. framcore/components/Node.py +99 -0
  33. framcore/components/Thermal.py +208 -0
  34. framcore/components/Transmission.py +198 -0
  35. framcore/components/_PowerPlant.py +81 -0
  36. framcore/components/__init__.py +22 -0
  37. framcore/components/wind_solar.py +82 -0
  38. framcore/curves/Curve.py +44 -0
  39. framcore/curves/LoadedCurve.py +146 -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 +591 -0
  44. framcore/expressions/__init__.py +30 -0
  45. framcore/expressions/_get_constant_from_expr.py +477 -0
  46. framcore/expressions/_utils.py +73 -0
  47. framcore/expressions/queries.py +416 -0
  48. framcore/expressions/units.py +227 -0
  49. framcore/fingerprints/__init__.py +11 -0
  50. framcore/fingerprints/fingerprint.py +292 -0
  51. framcore/juliamodels/JuliaModel.py +171 -0
  52. framcore/juliamodels/__init__.py +7 -0
  53. framcore/loaders/__init__.py +10 -0
  54. framcore/loaders/loaders.py +405 -0
  55. framcore/metadata/Div.py +73 -0
  56. framcore/metadata/ExprMeta.py +56 -0
  57. framcore/metadata/LevelExprMeta.py +32 -0
  58. framcore/metadata/Member.py +55 -0
  59. framcore/metadata/Meta.py +44 -0
  60. framcore/metadata/__init__.py +15 -0
  61. framcore/populators/Populator.py +108 -0
  62. framcore/populators/__init__.py +7 -0
  63. framcore/querydbs/CacheDB.py +50 -0
  64. framcore/querydbs/ModelDB.py +34 -0
  65. framcore/querydbs/QueryDB.py +45 -0
  66. framcore/querydbs/__init__.py +11 -0
  67. framcore/solvers/Solver.py +63 -0
  68. framcore/solvers/SolverConfig.py +272 -0
  69. framcore/solvers/__init__.py +9 -0
  70. framcore/timeindexes/AverageYearRange.py +27 -0
  71. framcore/timeindexes/ConstantTimeIndex.py +22 -0
  72. framcore/timeindexes/DailyIndex.py +33 -0
  73. framcore/timeindexes/FixedFrequencyTimeIndex.py +814 -0
  74. framcore/timeindexes/HourlyIndex.py +33 -0
  75. framcore/timeindexes/IsoCalendarDay.py +33 -0
  76. framcore/timeindexes/ListTimeIndex.py +277 -0
  77. framcore/timeindexes/ModelYear.py +23 -0
  78. framcore/timeindexes/ModelYears.py +27 -0
  79. framcore/timeindexes/OneYearProfileTimeIndex.py +29 -0
  80. framcore/timeindexes/ProfileTimeIndex.py +43 -0
  81. framcore/timeindexes/SinglePeriodTimeIndex.py +37 -0
  82. framcore/timeindexes/TimeIndex.py +103 -0
  83. framcore/timeindexes/WeeklyIndex.py +33 -0
  84. framcore/timeindexes/__init__.py +36 -0
  85. framcore/timeindexes/_time_vector_operations.py +689 -0
  86. framcore/timevectors/ConstantTimeVector.py +131 -0
  87. framcore/timevectors/LinearTransformTimeVector.py +131 -0
  88. framcore/timevectors/ListTimeVector.py +127 -0
  89. framcore/timevectors/LoadedTimeVector.py +97 -0
  90. framcore/timevectors/ReferencePeriod.py +51 -0
  91. framcore/timevectors/TimeVector.py +108 -0
  92. framcore/timevectors/__init__.py +17 -0
  93. framcore/utils/__init__.py +35 -0
  94. framcore/utils/get_regional_volumes.py +387 -0
  95. framcore/utils/get_supported_components.py +60 -0
  96. framcore/utils/global_energy_equivalent.py +63 -0
  97. framcore/utils/isolate_subnodes.py +172 -0
  98. framcore/utils/loaders.py +97 -0
  99. framcore/utils/node_flow_utils.py +236 -0
  100. framcore/utils/storage_subsystems.py +106 -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,131 @@
1
+ import numpy as np
2
+ from numpy.typing import NDArray
3
+
4
+ from framcore.fingerprints import Fingerprint
5
+ from framcore.timeindexes import ConstantTimeIndex
6
+ from framcore.timevectors import ReferencePeriod
7
+ from framcore.timevectors.TimeVector import TimeVector # NB! full import path needed for inheritance to work
8
+
9
+
10
+ class ConstantTimeVector(TimeVector):
11
+ """ConstantTimeVector class for TimeVectors that are constant over time. Subclass of TimeVector."""
12
+
13
+ def __init__(
14
+ self,
15
+ scalar: float,
16
+ unit: str | None = None,
17
+ is_max_level: bool | None = None,
18
+ is_zero_one_profile: bool | None = None,
19
+ reference_period: ReferencePeriod | None = None,
20
+ ) -> None:
21
+ """
22
+ Initialize the ConstantTimeVector class.
23
+
24
+ Args:
25
+ scalar (float): Constant float value of the TimeVector.
26
+ unit (str | None): Unit of the value in the vector.
27
+ is_max_level (bool | None): Whether the vector represents the maximum level, average level given a
28
+ reference period, or not a level at all.
29
+ is_zero_one_profile (bool | None): Whether the vector represents a profile with values between 0 and 1, a
30
+ profile with values averaging to 1 over a given reference period, or is
31
+ not a profile.
32
+ reference_period (ReferencePeriod | None, optional): Given reference period if the vector represents average
33
+ level or mean one profile. Defaults to None.
34
+
35
+ Raises:
36
+ ValueError: When both is_max_level and is_zero_one_profile is not None. This would mean the TimeVector
37
+ represents both a level and a profile, which is not allowed.
38
+
39
+ """
40
+ self._scalar = float(scalar)
41
+ self._unit = unit
42
+ self._is_max_level = is_max_level
43
+ self._is_zero_one_profile = is_zero_one_profile
44
+ self._reference_period = reference_period
45
+
46
+ self._check_type(scalar, (float, np.float32)) # TODO: Accept np.float32 elsewhere aswell
47
+ self._check_type(unit, (str, type(None)))
48
+ self._check_type(is_max_level, (bool, type(None)))
49
+ self._check_type(is_zero_one_profile, (bool, type(None)))
50
+ self._check_type(reference_period, (ReferencePeriod, type(None)))
51
+
52
+ self._check_is_level_or_profile()
53
+
54
+ def __repr__(self) -> str:
55
+ """Return the string representation of the ConstantTimeVector."""
56
+ ref_period = None
57
+ if self._reference_period is not None:
58
+ start_year = self._reference_period.get_start_year()
59
+ num_years = self._reference_period.get_num_years()
60
+ ref_period = f"{start_year}-{start_year + num_years - 1}"
61
+ unit = f", unit={self._unit}" if self._unit is not None else ""
62
+ ref_period = f", reference_period={ref_period}" if ref_period is not None else ""
63
+ is_max_level = f", is_max_level={self._is_max_level}"
64
+ return f"ConstantTimeVector({self._scalar}{unit}{ref_period}{is_max_level})"
65
+
66
+ def __eq__(self, other: object) -> bool:
67
+ """Check equality between two ConstantTimeVector objects."""
68
+ if not isinstance(other, ConstantTimeVector):
69
+ return False
70
+ return (
71
+ self._scalar == other._scalar
72
+ and self._unit == other._unit
73
+ and self._is_max_level == other._is_max_level
74
+ and self._is_zero_one_profile == other._is_zero_one_profile
75
+ and self._reference_period == other._reference_period
76
+ )
77
+
78
+ def __hash__(self) -> int:
79
+ """Compute the hash of the ConstantTimeVector."""
80
+ return hash((self._scalar, self._unit, self._is_max_level, self._is_zero_one_profile, self._reference_period))
81
+
82
+ def get_expr_str(self) -> str:
83
+ """Simpler representation of self to show in Expr."""
84
+ if self._unit:
85
+ return f"{self._scalar} {self._unit}"
86
+
87
+ return f"{self._scalar}"
88
+
89
+ def get_vector(self, is_float32: bool) -> NDArray:
90
+ """Get the values of the TimeVector."""
91
+ dtype = np.float32 if is_float32 else np.float64
92
+ out = np.zeros(1, dtype=dtype)
93
+ out[0] = self._scalar
94
+ return out
95
+
96
+ def get_timeindex(self) -> ConstantTimeIndex:
97
+ """Get the TimeIndex of the TimeVector."""
98
+ return ConstantTimeIndex()
99
+
100
+ def is_constant(self) -> bool:
101
+ """Check if the TimeVector is constant."""
102
+ return True
103
+
104
+ def is_max_level(self) -> bool | None:
105
+ """Check if TimeVector is a level representing maximum Volume/Capacity."""
106
+ return self._is_max_level
107
+
108
+ def is_zero_one_profile(self) -> bool | None:
109
+ """Check if TimeVector is a profile with values between zero and one."""
110
+ return self._is_zero_one_profile
111
+
112
+ def get_unit(self) -> str | None:
113
+ """Get the unit of the TimeVector."""
114
+ return self._unit
115
+
116
+ def get_reference_period(self) -> ReferencePeriod | None:
117
+ """Get the reference period of the TimeVector."""
118
+ if self._reference_period is not None:
119
+ return self._reference_period
120
+ if self.is_zero_one_profile() is False:
121
+ timeindex = self.get_timeindex()
122
+ return timeindex.get_reference_period()
123
+ return None
124
+
125
+ def get_fingerprint(self) -> Fingerprint:
126
+ """Get the Fingerprint of the TimeVector."""
127
+ return self.get_fingerprint_default()
128
+
129
+ def get_loader(self) -> None:
130
+ """Interface method Not applicable for this type. Return None."""
131
+ return
@@ -0,0 +1,131 @@
1
+ import numpy as np
2
+ from numpy.typing import NDArray
3
+
4
+ from framcore.fingerprints import Fingerprint
5
+ from framcore.loaders import TimeVectorLoader
6
+ from framcore.timeindexes import ConstantTimeIndex
7
+ from framcore.timevectors import ReferencePeriod
8
+ from framcore.timevectors.TimeVector import TimeVector # NB! full import path needed for inheritance to work
9
+
10
+
11
+ class LinearTransformTimeVector(TimeVector):
12
+ """LinearTransformTimeVector represents a TimeVector as scale * timevector + shift. Immutable."""
13
+
14
+ def __init__(
15
+ self,
16
+ timevector: TimeVector,
17
+ scale: float,
18
+ shift: float,
19
+ unit: str | None,
20
+ is_max_level: bool | None = None,
21
+ is_zero_one_profile: bool | None = None,
22
+ reference_period: ReferencePeriod | None = None,
23
+ ) -> None:
24
+ """
25
+ Initialize LinearTransformTimeVector with a TimeVector, scale and shift.
26
+
27
+ May also override unit, is_max_level, is_zero_one_profile and reference_period of the original timevector.
28
+
29
+ Args:
30
+ timevector (TimeVector): TimeVector.
31
+ scale (float): Scale factor.
32
+ shift (float): Shift value.
33
+ unit (str | None): Unit of the values in the transformed vector.
34
+ is_max_level (bool | None, optional): Whether the transformed vector represents the maximum level,
35
+ average level given a reference period, or not a level at all.
36
+ Defaults to None.
37
+ is_zero_one_profile (bool | None, optional): Whether the transformed vector represents a profile with values
38
+ between 0 and 1, a profile with values averaging to 1 over a given
39
+ reference period, or is not a profile. Defaults to None.
40
+ reference_period (ReferencePeriod | None, optional): Given reference period if the transformed vector
41
+ represents average level or mean one profile. Defaults to None.
42
+
43
+ """
44
+ self._check_type(timevector, TimeVector)
45
+ self._check_type(scale, float)
46
+ self._check_type(shift, float)
47
+ self._check_type(unit, (str, type(None)))
48
+ self._check_type(is_max_level, (bool, type(None)))
49
+ self._check_type(is_zero_one_profile, (bool, type(None)))
50
+ self._check_type(reference_period, (ReferencePeriod, type(None)))
51
+ self._timevector = timevector
52
+ self._scale = scale
53
+ self._shift = shift
54
+ self._unit = unit
55
+ self._is_max_level = is_max_level
56
+ self._is_zero_one_profile = is_zero_one_profile
57
+ self._reference_period = reference_period
58
+
59
+ self._check_is_level_or_profile()
60
+
61
+ def get_vector(self, is_float32: bool) -> NDArray:
62
+ """Get the values of the TimeVector."""
63
+ vector = self._timevector.get_vector(is_float32)
64
+ if self._scale == 1.0 and self._shift == 0.0:
65
+ return vector
66
+ out = vector.copy()
67
+ if self._scale != 1.0:
68
+ np.multiply(out, self._scale, out=out)
69
+ if self._shift != 0.0:
70
+ np.add(out, self._shift, out=out)
71
+ return out
72
+
73
+ def get_fingerprint(self) -> Fingerprint:
74
+ """Get the Fingerprint of the TimeVector."""
75
+ return self.get_fingerprint_default()
76
+
77
+ def get_timeindex(self) -> ConstantTimeIndex:
78
+ """Get the TimeIndex of the TimeVector."""
79
+ return self._timevector.get_timeindex()
80
+
81
+ def is_constant(self) -> bool:
82
+ """Check if the TimeVector is constant."""
83
+ return self._timevector.is_constant()
84
+
85
+ def is_max_level(self) -> bool | None:
86
+ """Check if TimeVector is a level representing maximum Volume/Capacity."""
87
+ return self._is_max_level
88
+
89
+ def is_zero_one_profile(self) -> bool | None:
90
+ """Check if TimeVector is a profile with values between zero and one."""
91
+ return self._is_zero_one_profile
92
+
93
+ def get_unit(self) -> str | None:
94
+ """Get the unit of the TimeVector."""
95
+ return self._unit
96
+
97
+ def get_reference_period(self) -> ReferencePeriod | None:
98
+ """Get the reference period of the TimeVector."""
99
+ return self._reference_period
100
+
101
+ def get_loader(self) -> TimeVectorLoader | None:
102
+ """Call get_loader on underlying time vector."""
103
+ return self._timevector.get_loader()
104
+
105
+ def __eq__(self, other) -> bool: # noqa: ANN001
106
+ """Check if self and other are equal."""
107
+ if not isinstance(other, type(self)):
108
+ return False
109
+ return (
110
+ self._timevector == other._timevector
111
+ and self._scale == other._scale
112
+ and self._shift == other._shift
113
+ and self._unit == other._unit
114
+ and self._is_max_level == other._is_max_level
115
+ and self._is_zero_one_profile == other._is_zero_one_profile
116
+ and self._reference_period == other._reference_period
117
+ )
118
+
119
+ def __hash__(self) -> int:
120
+ """Compute the hash of the LinearTransformTimeVector."""
121
+ return hash(
122
+ (
123
+ self._timevector,
124
+ self._scale,
125
+ self._shift,
126
+ self._unit,
127
+ self._is_max_level,
128
+ self._is_zero_one_profile,
129
+ self._reference_period,
130
+ ),
131
+ )
@@ -0,0 +1,127 @@
1
+ import numpy as np
2
+ from numpy.typing import NDArray
3
+
4
+ from framcore.fingerprints import Fingerprint
5
+ from framcore.timeindexes import TimeIndex
6
+ from framcore.timevectors import ReferencePeriod
7
+ from framcore.timevectors.TimeVector import TimeVector # NB! full import path needed for inheritance to work
8
+
9
+
10
+ class ListTimeVector(TimeVector):
11
+ """TimeVector with a numpy array of values paired with a timeindex."""
12
+
13
+ def __init__(
14
+ self,
15
+ timeindex: TimeIndex,
16
+ vector: NDArray,
17
+ unit: str | None,
18
+ is_max_level: bool | None,
19
+ is_zero_one_profile: bool | None,
20
+ reference_period: ReferencePeriod | None = None,
21
+ ) -> None:
22
+ """
23
+ Initialize the ListTimeVector class.
24
+
25
+ Args:
26
+ timeindex (TimeIndex): Index of timestamps for the vector.
27
+ vector (NDArray): Array of vector values.
28
+ unit (str | None): Unit of the values in the vector.
29
+ is_max_level (bool | None): Whether the vector represents the maximum level, average level given a
30
+ reference period, or not a level at all.
31
+ is_zero_one_profile (bool | None): Whether the vector represents aprofile with values between 0 and 1, a
32
+ profile with values averaging to 1 over a given reference period, or is
33
+ not a profile.
34
+ reference_period (ReferencePeriod | None, optional): Given reference period if the vector represents average
35
+ level or mean one profile. Defaults to None.
36
+
37
+ Raises:
38
+ ValueError: When both is_max_level and is_zero_one_profile is not None. This would mean the TimeVector
39
+ represents both a level and a profile, which is not allowed.
40
+ ValueError: When the shape of the vector does not match the number of periods in the timeindex.
41
+
42
+ """
43
+ if vector.shape != (timeindex.get_num_periods(),):
44
+ msg = f"Vector shape {vector.shape} does not match number of periods {timeindex.get_num_periods()} of timeindex ({timeindex})."
45
+ raise ValueError(msg)
46
+
47
+ self._timeindex = timeindex
48
+ self._vector = vector
49
+ self._unit = unit
50
+ self._reference_period = reference_period
51
+ self._is_max_level = is_max_level
52
+ self._is_zero_one_profile = is_zero_one_profile
53
+
54
+ self._check_type(timeindex, TimeIndex)
55
+ self._check_type(vector, np.ndarray)
56
+ self._check_type(unit, (str, type(None)))
57
+ self._check_type(is_max_level, (bool, type(None)))
58
+ self._check_type(is_zero_one_profile, (bool, type(None)))
59
+ self._check_type(reference_period, (ReferencePeriod, type(None)))
60
+
61
+ self._check_is_level_or_profile()
62
+
63
+ def __eq__(self, other: object) -> None:
64
+ """Check equality between two ListTimeVector objects."""
65
+ if not isinstance(other, ListTimeVector):
66
+ return NotImplemented
67
+ return (
68
+ (self._timeindex == other._timeindex)
69
+ and np.array_equal(self._vector, other._vector)
70
+ and (self._unit == other._unit)
71
+ and (self._is_max_level == other._is_max_level)
72
+ and (self._is_zero_one_profile == other._is_zero_one_profile)
73
+ and (self._reference_period == other._reference_period)
74
+ )
75
+
76
+ def __hash__(self) -> int:
77
+ """Return hash of ListTimeVector object."""
78
+ return hash((self._timeindex, self._vector.tobytes(), self._unit, self._is_max_level, self._is_zero_one_profile, self._reference_period))
79
+
80
+ def __repr__(self) -> str:
81
+ """Return the string representation of the ListTimeVector."""
82
+ return f"ListTimeVector(timeindex={self._timeindex}, vector={self._vector}, unit={self._unit}, reference_period={self._reference_period})"
83
+
84
+ def get_vector(self, is_float32: bool) -> NDArray:
85
+ """Get the vector of the TimeVector as a numpy array."""
86
+ if is_float32:
87
+ return self._vector.astype(dtype=np.float32)
88
+ return self._vector
89
+
90
+ def get_timeindex(self) -> TimeIndex:
91
+ """Get the TimeIndex of the TimeVector."""
92
+ return self._timeindex
93
+
94
+ def is_constant(self) -> bool:
95
+ """Check if the TimeVector is constant."""
96
+ return False
97
+
98
+ def is_max_level(self) -> bool:
99
+ """Check if TimeVector is a level representing maximum Volume/Capacity."""
100
+ return self._is_max_level
101
+
102
+ def is_zero_one_profile(self) -> bool:
103
+ """Check if TimeVector is a profile with vector between zero and one."""
104
+ return self._is_zero_one_profile
105
+
106
+ def get_unit(self) -> str | None:
107
+ """Get the unit of the TimeVector."""
108
+ return self._unit
109
+
110
+ def get_reference_period(self) -> ReferencePeriod | None:
111
+ """Get the reference period of the TimeVector."""
112
+ return self._reference_period
113
+
114
+ def get_fingerprint(self) -> Fingerprint:
115
+ """
116
+ Get the fingerprint of the ListTimeVector.
117
+
118
+ Returns:
119
+ Fingerprint: The fingerprint of the ListTimeVector, excluding the reference period.
120
+
121
+ """
122
+ excludes = {"_reference_period"}
123
+ return self.get_fingerprint_default(excludes=excludes)
124
+
125
+ def get_loader(self) -> None:
126
+ """Interface method Not applicable for this type. Return None."""
127
+ return
@@ -0,0 +1,97 @@
1
+ import numpy as np
2
+ from numpy.typing import NDArray
3
+
4
+ from framcore.fingerprints import Fingerprint
5
+ from framcore.loaders import TimeVectorLoader
6
+ from framcore.timeindexes import TimeIndex
7
+ from framcore.timevectors import ReferencePeriod
8
+ from framcore.timevectors.TimeVector import TimeVector # NB! full import path needed for inheritance to work
9
+
10
+
11
+ class LoadedTimeVector(TimeVector):
12
+ """TimeVector which gets its data from a data source via a TimeVectorLoader. Subclass of TimeVector."""
13
+
14
+ def __init__(self, vector_id: str, loader: TimeVectorLoader) -> None:
15
+ """
16
+ Store vector id and loader in instance variables, get unit from loader.
17
+
18
+ Args:
19
+ vector_id (str): Unique name of this vector.
20
+ loader (TimeVectorLoader): Object connected to a data source where vector_id is associated with a time
21
+ vector. The Loader object must also implement the TimeVectorLoader API.
22
+
23
+ Raises:
24
+ ValueError: When metadata in the TimeVectorLoader for both is_max_level and is_zero_one_profile for the
25
+ given vector_id is not None. This would mean the TimeVector represents both a level and a
26
+ profile, which is not allowed.
27
+
28
+ """
29
+ self._vector_id = vector_id
30
+ self._loader = loader
31
+ self._check_type(self._vector_id, str)
32
+ self._check_type(self._loader, TimeVectorLoader)
33
+ self._is_max_level = self._loader.is_max_level(self._vector_id)
34
+ self._is_zero_one_profile = self._loader.is_zero_one_profile(self._vector_id)
35
+ self._unit = self._loader.get_unit(self._vector_id)
36
+ self._reference_period = self._loader.get_reference_period(self._vector_id)
37
+
38
+ self._check_is_level_or_profile()
39
+
40
+ def __repr__(self) -> str:
41
+ """Overwrite string representation of LoadedTimeVector objects."""
42
+ return f"{type(self).__name__}(vector_id={self._vector_id},loader={self._loader},unit={self._unit})"
43
+
44
+ def __eq__(self, other: object) -> bool:
45
+ """Check equality between two LoadedTimeVector objects."""
46
+ if not isinstance(other, LoadedTimeVector):
47
+ return NotImplemented
48
+ return (self._vector_id == other._vector_id) and (self._loader == other._loader)
49
+
50
+ def __hash__(self) -> int:
51
+ """Return hash of LoadedTimeVector object."""
52
+ return hash((self._vector_id, self._loader))
53
+
54
+ def get_vector(self, is_float32: bool) -> NDArray:
55
+ """Get the vector of the TimeVector as a numpy array."""
56
+ vector = self._loader.get_values(self._vector_id)
57
+ if is_float32:
58
+ return vector.astype(np.float32)
59
+ return vector
60
+
61
+ def get_timeindex(self) -> TimeIndex:
62
+ """
63
+ Get this time vectors index.
64
+
65
+ Returns:
66
+ TimeIndex: Object describing the index.
67
+
68
+ """
69
+ return self._loader.get_index(self._vector_id)
70
+
71
+ def is_constant(self) -> bool:
72
+ """Signify if this TimeVector is constant."""
73
+ return False
74
+
75
+ def get_unit(self) -> str:
76
+ """Get the unit of this TimeVector."""
77
+ return self._unit
78
+
79
+ def get_loader(self) -> TimeVectorLoader:
80
+ """Get the Loader this TimeVector retrieves its data from."""
81
+ return self._loader
82
+
83
+ def get_reference_period(self) -> ReferencePeriod | None:
84
+ """Get the reference period which the data of this TimeVector is from."""
85
+ return self._reference_period
86
+
87
+ def is_max_level(self) -> bool | None:
88
+ """Check if TimeVector is a level representing maximum Volume/Capacity."""
89
+ return self._loader.is_max_level(self._vector_id)
90
+
91
+ def is_zero_one_profile(self) -> bool | None:
92
+ """Check if TimeVector is a profile with values between zero and one."""
93
+ return self._loader.is_zero_one_profile(self._vector_id)
94
+
95
+ def get_fingerprint(self) -> Fingerprint:
96
+ """Get the Fingerprint of this TimeVector."""
97
+ return self._loader.get_fingerprint(self._vector_id)
@@ -0,0 +1,51 @@
1
+ from framcore import Base
2
+
3
+
4
+ class ReferencePeriod(Base):
5
+ """ReferencePeriod class represents a period of one or more years."""
6
+
7
+ def __init__(self, start_year: int, num_years: int) -> None:
8
+ """
9
+ Initialize a ReferencePeriod with the start year and number of years.
10
+
11
+ Args:
12
+ start_year (int): The first year in the reference period. Must be a positive integer.
13
+ num_years (int): The number of years in the reference period. Must be a positive non-zero integer.
14
+
15
+ """
16
+ self._check_type(start_year, int)
17
+ self._check_type(num_years, int)
18
+
19
+ if start_year < 0:
20
+ message = f"start_year must be a positive integer. Got {start_year}."
21
+ raise ValueError(message)
22
+
23
+ if num_years <= 0:
24
+ message = f"num_years must be a positive non-zero integer. Got {num_years}."
25
+ raise ValueError(message)
26
+
27
+ self._start_year = start_year
28
+ self._num_years = num_years
29
+
30
+ def get_start_year(self) -> int:
31
+ """Get the start_year from a ReferencePeriod instance."""
32
+ return self._start_year
33
+
34
+ def get_num_years(self) -> int:
35
+ """Get the number of years in the ReferencePeriod."""
36
+ return self._num_years
37
+
38
+ def __eq__(self, other) -> bool: # noqa: ANN001
39
+ """Check if self and other are equal."""
40
+ if not isinstance(other, type(self)):
41
+ return False
42
+ return self._start_year == other._start_year and self._num_years == other._num_years
43
+
44
+ def __hash__(self) -> int:
45
+ """Compute hash value.."""
46
+ return hash(
47
+ (
48
+ self._start_year,
49
+ self._num_years,
50
+ ),
51
+ )
@@ -0,0 +1,108 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
5
+
6
+ from numpy.typing import NDArray
7
+
8
+ from framcore import Base
9
+ from framcore.fingerprints import Fingerprint
10
+ from framcore.timeindexes import TimeIndex
11
+ from framcore.timevectors import ReferencePeriod
12
+
13
+ if TYPE_CHECKING:
14
+ from framcore.loaders import TimeVectorLoader
15
+
16
+
17
+ # TODO: Floating point precision
18
+ class TimeVector(Base, ABC):
19
+ """TimeVector interface class for defining timeseries data."""
20
+
21
+ def __init__(self) -> None:
22
+ """Initialize the TimeVector class."""
23
+ super().__init__()
24
+
25
+ @abstractmethod
26
+ def __eq__(self, other) -> bool: # noqa: ANN001
27
+ """Check if two TimeVectors are equal."""
28
+ pass
29
+
30
+ @abstractmethod
31
+ def __hash__(self) -> int:
32
+ """Compute hash value."""
33
+ pass
34
+
35
+ @abstractmethod
36
+ def get_vector(self, is_float32: bool) -> NDArray:
37
+ """Get the values of the TimeVector."""
38
+ pass
39
+
40
+ @abstractmethod
41
+ def get_timeindex(self) -> TimeIndex | None:
42
+ """Get the TimeIndex of the TimeVector."""
43
+ pass
44
+
45
+ @abstractmethod
46
+ def is_constant(self) -> bool:
47
+ """Check if the TimeVector is constant."""
48
+ pass
49
+
50
+ @abstractmethod
51
+ def is_max_level(self) -> bool | None:
52
+ """
53
+ Whether the TimeVector represents the maximum level, average level given a reference period, or not a level at all.
54
+
55
+ See LevelProfile for a description of Level (max or avg) and Profile (max one or mean one), and their formats.
56
+
57
+ """
58
+ pass
59
+
60
+ @abstractmethod
61
+ def is_zero_one_profile(self) -> bool | None:
62
+ """
63
+ Whether the TimeVector represents a profile with values between 0 and 1, a profile with average 1 over a given reference period, or is not a profile.
64
+
65
+ See LevelProfile for a description of Level (max or avg) and Profile (max one or mean one), and their formats.
66
+
67
+ """
68
+ pass
69
+
70
+ @abstractmethod
71
+ def get_unit(self) -> str | None:
72
+ """Get the unit of the TimeVector."""
73
+ pass
74
+
75
+ @abstractmethod
76
+ def get_fingerprint(self) -> Fingerprint:
77
+ """Get the fingerprint of the TimeVector."""
78
+ pass
79
+
80
+ @abstractmethod
81
+ def get_reference_period(self) -> ReferencePeriod | None:
82
+ """Get the reference period of the TimeVector."""
83
+ pass
84
+
85
+ @abstractmethod
86
+ def get_loader(self) -> TimeVectorLoader | None:
87
+ """
88
+ Get the TimeVectorLoader of the TimeVector if self has one.
89
+
90
+ TimeVectors can store timeseries data in Loaders that point to databases. Data is only retrieved and cached when the TimeVector is queried.
91
+ """
92
+ pass
93
+
94
+ """
95
+ Checks that the TimeVector is either a level or a profile.
96
+
97
+ Raises:
98
+ ValueError: If both is_max_level and is_zero_one_profile are None or both are not None.
99
+ """
100
+
101
+ def _check_is_level_or_profile(self) -> None:
102
+ """Ensure that the TimeVector is either a level or a profile."""
103
+ if (self.is_max_level() is not None and self.is_zero_one_profile() is not None) or (self.is_max_level() is None and self.is_zero_one_profile() is None):
104
+ message = (
105
+ f"Invalid input arguments for {self}: Must have exactly one 'non-None' value for "
106
+ "is_max_level and is_zero_one_profile. A TimeVector is either a level or a profile."
107
+ )
108
+ raise ValueError(message)
@@ -0,0 +1,17 @@
1
+ # framcore/timevectors/__init__.py
2
+
3
+ from framcore.timevectors.ReferencePeriod import ReferencePeriod
4
+ from framcore.timevectors.TimeVector import TimeVector
5
+ from framcore.timevectors.ConstantTimeVector import ConstantTimeVector
6
+ from framcore.timevectors.LinearTransformTimeVector import LinearTransformTimeVector
7
+ from framcore.timevectors.ListTimeVector import ListTimeVector
8
+ from framcore.timevectors.LoadedTimeVector import LoadedTimeVector
9
+
10
+ __all__ = [
11
+ "ConstantTimeVector",
12
+ "LinearTransformTimeVector",
13
+ "ListTimeVector",
14
+ "LoadedTimeVector",
15
+ "ReferencePeriod",
16
+ "TimeVector",
17
+ ]