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.
- fram_core-0.1.0.dist-info/METADATA +42 -0
- fram_core-0.1.0.dist-info/RECORD +100 -0
- {fram_core-0.0.0.dist-info → fram_core-0.1.0.dist-info}/WHEEL +1 -2
- fram_core-0.1.0.dist-info/licenses/LICENSE.md +8 -0
- framcore/Base.py +161 -0
- framcore/Model.py +90 -0
- framcore/__init__.py +10 -0
- framcore/aggregators/Aggregator.py +172 -0
- framcore/aggregators/HydroAggregator.py +849 -0
- framcore/aggregators/NodeAggregator.py +530 -0
- framcore/aggregators/WindSolarAggregator.py +315 -0
- framcore/aggregators/__init__.py +13 -0
- framcore/aggregators/_utils.py +184 -0
- framcore/attributes/Arrow.py +307 -0
- framcore/attributes/ElasticDemand.py +90 -0
- framcore/attributes/ReservoirCurve.py +23 -0
- framcore/attributes/SoftBound.py +16 -0
- framcore/attributes/StartUpCost.py +65 -0
- framcore/attributes/Storage.py +158 -0
- framcore/attributes/TargetBound.py +16 -0
- framcore/attributes/__init__.py +63 -0
- framcore/attributes/hydro/HydroBypass.py +49 -0
- framcore/attributes/hydro/HydroGenerator.py +100 -0
- framcore/attributes/hydro/HydroPump.py +178 -0
- framcore/attributes/hydro/HydroReservoir.py +27 -0
- framcore/attributes/hydro/__init__.py +13 -0
- framcore/attributes/level_profile_attributes.py +911 -0
- framcore/components/Component.py +136 -0
- framcore/components/Demand.py +144 -0
- framcore/components/Flow.py +189 -0
- framcore/components/HydroModule.py +371 -0
- framcore/components/Node.py +99 -0
- framcore/components/Thermal.py +208 -0
- framcore/components/Transmission.py +198 -0
- framcore/components/_PowerPlant.py +81 -0
- framcore/components/__init__.py +22 -0
- framcore/components/wind_solar.py +82 -0
- framcore/curves/Curve.py +44 -0
- framcore/curves/LoadedCurve.py +146 -0
- framcore/curves/__init__.py +9 -0
- framcore/events/__init__.py +21 -0
- framcore/events/events.py +51 -0
- framcore/expressions/Expr.py +591 -0
- framcore/expressions/__init__.py +30 -0
- framcore/expressions/_get_constant_from_expr.py +477 -0
- framcore/expressions/_utils.py +73 -0
- framcore/expressions/queries.py +416 -0
- framcore/expressions/units.py +227 -0
- framcore/fingerprints/__init__.py +11 -0
- framcore/fingerprints/fingerprint.py +292 -0
- framcore/juliamodels/JuliaModel.py +171 -0
- framcore/juliamodels/__init__.py +7 -0
- framcore/loaders/__init__.py +10 -0
- framcore/loaders/loaders.py +405 -0
- framcore/metadata/Div.py +73 -0
- framcore/metadata/ExprMeta.py +56 -0
- framcore/metadata/LevelExprMeta.py +32 -0
- framcore/metadata/Member.py +55 -0
- framcore/metadata/Meta.py +44 -0
- framcore/metadata/__init__.py +15 -0
- framcore/populators/Populator.py +108 -0
- framcore/populators/__init__.py +7 -0
- framcore/querydbs/CacheDB.py +50 -0
- framcore/querydbs/ModelDB.py +34 -0
- framcore/querydbs/QueryDB.py +45 -0
- framcore/querydbs/__init__.py +11 -0
- framcore/solvers/Solver.py +63 -0
- framcore/solvers/SolverConfig.py +272 -0
- framcore/solvers/__init__.py +9 -0
- framcore/timeindexes/AverageYearRange.py +27 -0
- framcore/timeindexes/ConstantTimeIndex.py +22 -0
- framcore/timeindexes/DailyIndex.py +33 -0
- framcore/timeindexes/FixedFrequencyTimeIndex.py +814 -0
- framcore/timeindexes/HourlyIndex.py +33 -0
- framcore/timeindexes/IsoCalendarDay.py +33 -0
- framcore/timeindexes/ListTimeIndex.py +277 -0
- framcore/timeindexes/ModelYear.py +23 -0
- framcore/timeindexes/ModelYears.py +27 -0
- framcore/timeindexes/OneYearProfileTimeIndex.py +29 -0
- framcore/timeindexes/ProfileTimeIndex.py +43 -0
- framcore/timeindexes/SinglePeriodTimeIndex.py +37 -0
- framcore/timeindexes/TimeIndex.py +103 -0
- framcore/timeindexes/WeeklyIndex.py +33 -0
- framcore/timeindexes/__init__.py +36 -0
- framcore/timeindexes/_time_vector_operations.py +689 -0
- framcore/timevectors/ConstantTimeVector.py +131 -0
- framcore/timevectors/LinearTransformTimeVector.py +131 -0
- framcore/timevectors/ListTimeVector.py +127 -0
- framcore/timevectors/LoadedTimeVector.py +97 -0
- framcore/timevectors/ReferencePeriod.py +51 -0
- framcore/timevectors/TimeVector.py +108 -0
- framcore/timevectors/__init__.py +17 -0
- framcore/utils/__init__.py +35 -0
- framcore/utils/get_regional_volumes.py +387 -0
- framcore/utils/get_supported_components.py +60 -0
- framcore/utils/global_energy_equivalent.py +63 -0
- framcore/utils/isolate_subnodes.py +172 -0
- framcore/utils/loaders.py +97 -0
- framcore/utils/node_flow_utils.py +236 -0
- framcore/utils/storage_subsystems.py +106 -0
- fram_core-0.0.0.dist-info/METADATA +0 -5
- fram_core-0.0.0.dist-info/RECORD +0 -4
- 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
|
+
]
|