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,292 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import pickle
5
+ from enum import Enum
6
+
7
+
8
+ class FingerprintRef:
9
+ """Refers to another fingerprint."""
10
+
11
+ def __init__(self, key: str) -> None:
12
+ """
13
+ Initialize a FingerprintRef with the given key.
14
+
15
+ Args:
16
+ key (str): The key referencing another fingerprint.
17
+
18
+ """
19
+ self._key = key
20
+
21
+ def get_key(self) -> str:
22
+ """
23
+ Return the key referencing another fingerprint.
24
+
25
+ Returns:
26
+ str: The key referencing another fingerprint.
27
+
28
+ """
29
+ return self._key
30
+
31
+
32
+ class FingerprintDiffType(Enum):
33
+ """Type of difference between two fingerprints."""
34
+
35
+ NEW = "new"
36
+ MODIFIED = "modified"
37
+ DELETED = "deleted"
38
+
39
+
40
+ class FingerprintDiff:
41
+ """Differences between two fingerprints."""
42
+
43
+ def __init__(self) -> None:
44
+ """Initialize an empty FingerprintDiff."""
45
+ self._diffs: dict[str, tuple] = {}
46
+
47
+ def add_diff(
48
+ self,
49
+ key: str,
50
+ diff_type: FingerprintDiffType,
51
+ obj: object,
52
+ ) -> None:
53
+ """
54
+ Add a difference entry for a fingerprint.
55
+
56
+ Args:
57
+ key (str): The key identifying the fingerprint part.
58
+ diff_type (FingerprintDiffType): The type of difference (NEW, MODIFIED, DELETED).
59
+ obj: The object associated with the difference.
60
+
61
+ """
62
+ from framcore.components.Component import Component
63
+ from framcore.curves.Curve import Curve
64
+ from framcore.timevectors.TimeVector import TimeVector
65
+
66
+ # Trenger vi denne sjekken, siden vi filtrerer ut alt som ikke er Fingerprint før vi kjører add_diff()?
67
+ if isinstance(obj, TimeVector | Curve | Component):
68
+ if key in self._diffs:
69
+ message = f"duplicate entry: {key} ({obj})"
70
+ print(message)
71
+
72
+ self._diffs[key] = (obj, diff_type)
73
+
74
+ def get_diffs(self) -> dict[str, tuple]:
75
+ """
76
+ Return the dictionary of differences.
77
+
78
+ Returns:
79
+ dict[str, tuple]: The differences stored in the FingerprintDiff.
80
+
81
+ """
82
+ return self._diffs
83
+
84
+ def is_changed(self) -> bool:
85
+ """Return True if there are any differences."""
86
+ return bool(self._diffs)
87
+
88
+ def update(self, other: FingerprintDiff) -> None:
89
+ """
90
+ Update this FingerprintDiff with differences from another FingerprintDiff.
91
+
92
+ Args:
93
+ other (FingerprintDiff): Another FingerprintDiff whose differences will be added.
94
+
95
+ """
96
+ self._diffs.update(other.get_diffs())
97
+
98
+
99
+ class Fingerprint:
100
+ """Fingerprint of various data structures."""
101
+
102
+ def __init__(self, source: object = None) -> None:
103
+ """
104
+ Initialize a Fingerprint instance.
105
+
106
+ Args:
107
+ source (object, optional): The source object to fingerprint. Defaults to None.
108
+
109
+ """
110
+ self._nested = {}
111
+ self._hash = None
112
+ self._source = source
113
+
114
+ def add(self, key: str, value: object) -> None:
115
+ """
116
+ Add a value to the fingerprint under the specified key.
117
+
118
+ Args:
119
+ key (str): The key to associate with the value.
120
+ value: The value to add, which can be a Fingerprint, FingerprintRef, or other supported types.
121
+
122
+ Returns:
123
+ None
124
+
125
+ """
126
+ assert key not in self._nested
127
+
128
+ if isinstance(value, Fingerprint | FingerprintRef):
129
+ self._nested[key] = value
130
+ elif hasattr(value, "get_fingerprint"):
131
+ self.add(key, value.get_fingerprint())
132
+ elif isinstance(value, list | tuple | set):
133
+ self.add(key, self._fingerprint_from_list(value))
134
+ elif isinstance(value, dict):
135
+ self.add(key, self._fingerprint_from_dict(value))
136
+ else:
137
+ self._nested[key] = _custom_hash(value)
138
+
139
+ self._hash = None
140
+
141
+ def _fingerprint_from_list(self, items: list | tuple | set) -> Fingerprint:
142
+ fingerprint = Fingerprint()
143
+ for index, value in enumerate(items):
144
+ fingerprint.add(f"{index}", value)
145
+ return fingerprint
146
+
147
+ def _fingerprint_from_dict(self, a_dict: dict) -> Fingerprint:
148
+ fingerprint = Fingerprint()
149
+ for key, value in a_dict.items():
150
+ fingerprint.add(f"{key}", value)
151
+ return fingerprint
152
+
153
+ def add_ref(self, prop: str, ref_key: str) -> None:
154
+ """
155
+ Add a FingerprintRef to the fingerprint under the specified property key.
156
+
157
+ Args:
158
+ prop (str): The property key to associate with the reference.
159
+ ref_key (str): The key referencing another fingerprint.
160
+
161
+ Returns:
162
+ None
163
+
164
+ """
165
+ self.add(prop, FingerprintRef(ref_key))
166
+
167
+ def get_parts(self) -> dict:
168
+ """
169
+ Return the dictionary of parts contained in the fingerprint.
170
+
171
+ Returns:
172
+ dict: A dictionary mapping keys to their associated fingerprint parts.
173
+
174
+ """
175
+ return {k: v for k, v in self._nested.items()}
176
+
177
+ def update_ref(self, ref_key: str, fingerprint: Fingerprint) -> None:
178
+ """
179
+ Update the reference at the given key with a new Fingerprint.
180
+
181
+ Args:
182
+ ref_key (str): The key referencing the FingerprintRef to update.
183
+ fingerprint (Fingerprint): The new Fingerprint to set at the reference.
184
+
185
+ Returns:
186
+ None
187
+
188
+ """
189
+ assert ref_key in self._nested
190
+ assert isinstance(self._nested[ref_key], FingerprintRef)
191
+
192
+ self._nested[ref_key] = fingerprint
193
+ self._hash = None
194
+
195
+ def get_hash(self) -> str:
196
+ """
197
+ Return the hash value of the fingerprint.
198
+
199
+ Returns:
200
+ str: The computed hash value representing the fingerprint.
201
+
202
+ """
203
+ self._resolve_total_hash()
204
+ return self._hash
205
+
206
+ def _contains_refs(self) -> bool:
207
+ return any(isinstance(v, FingerprintRef) for v in self._nested.values())
208
+
209
+ def _contains_key(self, key: str) -> bool:
210
+ return key in self._nested
211
+
212
+ def _resolve_total_hash(self) -> None:
213
+ parts = []
214
+ for k, v in self._nested.items():
215
+ if isinstance(v, Fingerprint):
216
+ parts.append((k, v.get_hash()))
217
+ elif isinstance(v, FingerprintRef):
218
+ parts.append((k, f"#ref:{v.get_key()}"))
219
+ else:
220
+ parts.append((k, v))
221
+
222
+ self._hash = _custom_hash(sorted(parts))
223
+
224
+ def diff(self, other: Fingerprint | None) -> FingerprintDiff:
225
+ """Return differences between this and other fingerprint."""
226
+ diff = FingerprintDiff()
227
+
228
+ if other is None:
229
+ for parent_key, parent_value in self.get_parts().items():
230
+ if isinstance(parent_value, Fingerprint):
231
+ diff.add_diff(parent_key, FingerprintDiffType.NEW, parent_value._source) # noqa: SLF001
232
+ diff.update(parent_value.diff(None))
233
+ return diff
234
+
235
+ if self.get_hash() == other.get_hash():
236
+ return diff
237
+
238
+ self_parts: dict[str, Fingerprint] = {
239
+ key: value for key, value in self.get_parts().items() if isinstance(value, Fingerprint)
240
+ }
241
+ other_parts: dict[str, Fingerprint] = {
242
+ key: value for key, value in other.get_parts().items() if isinstance(value, Fingerprint)
243
+ }
244
+
245
+ # Check for new or modified keys
246
+ for key, value in self_parts.items():
247
+ if key not in other_parts:
248
+ diff.add_diff(key, FingerprintDiffType.NEW, value._source) # noqa: SLF001
249
+ diff.update(value.diff(None))
250
+ elif value.get_hash() != other_parts[key].get_hash():
251
+ diff.add_diff(key, FingerprintDiffType.MODIFIED, value._source) # noqa: SLF001
252
+ diff.update(value.diff(other_parts[key]))
253
+
254
+ # Check for deleted keys
255
+ for key in other_parts.keys() - self_parts.keys():
256
+ other_value = other_parts[key]
257
+ diff.add_diff(key, FingerprintDiffType.DELETED, other_value._source) # noqa: SLF001
258
+ source = self # TODO: Is this correct?
259
+ diff.update(Fingerprint(source).diff(other_value))
260
+
261
+ return diff
262
+
263
+ def __eq__(self, other: Fingerprint) -> bool:
264
+ """
265
+ Determine if two Fingerprint instances are equal based on their hash values.
266
+
267
+ Args:
268
+ other (Fingerprint): The other Fingerprint instance to compare.
269
+
270
+ Returns:
271
+ bool: True if the hash values are equal, False otherwise.
272
+
273
+ """
274
+ return self.get_hash() == other.get_hash()
275
+
276
+
277
+ def _custom_hash(value: object) -> str:
278
+ """Return hash of value represented as str."""
279
+ if isinstance(value, int | bool | float | None):
280
+ return str(value)
281
+
282
+ if isinstance(value, str):
283
+ return hashlib.sha1(value.encode()).hexdigest()
284
+
285
+ if isinstance(value, list | tuple | set):
286
+ return _custom_hash(str(sorted([_custom_hash(x) for x in value])))
287
+
288
+ if isinstance(value, dict):
289
+ return _custom_hash([(_custom_hash(k), (_custom_hash(v))) for k, v in value.items()])
290
+
291
+ sha1_hash = hashlib.sha1(pickle.dumps(value))
292
+ return sha1_hash.hexdigest()
@@ -0,0 +1,171 @@
1
+ """Manage Julia environment and usage of juliacall for Solvers implemented in the Julia language."""
2
+
3
+ import importlib
4
+ import os
5
+ import sys
6
+ from pathlib import Path
7
+ from urllib.parse import urlparse
8
+
9
+ from framcore import Base
10
+
11
+ os.environ["JULIA_SSL_CA_ROOTS_PATH"] = ""
12
+ os.environ["SSL_CERT_FILE"] = ""
13
+
14
+
15
+ def _is_url(url_string: str) -> bool:
16
+ """
17
+ Check if a string is a valid url.
18
+
19
+ Args:
20
+ url_string (str): Strong to be validated.
21
+
22
+ Returns:
23
+ bool: True if valid as a url, False if invalid.
24
+
25
+ """
26
+ try:
27
+ result = urlparse(url_string)
28
+ return all([result.scheme, result.netloc])
29
+ except ValueError:
30
+ return False
31
+
32
+
33
+ class JuliaModel(Base):
34
+ """Class for running julia code with juliacall."""
35
+
36
+ ENV_NAME: str = "julia_env" # Used to let each model define their own project/environment to avoid overwriting.
37
+ _jl = None
38
+
39
+ def __init__(
40
+ self,
41
+ env_path: Path | str | None = None,
42
+ depot_path: Path | str | None = None,
43
+ julia_path: Path | str | None = None,
44
+ dependencies: list[str | tuple[str, str | None]] | None = None,
45
+ skip_install_dependencies: bool = False,
46
+ force_julia_install: bool = True,
47
+ ) -> None:
48
+ """
49
+ Initialize management of Julia model, environment and dependencies.
50
+
51
+ The three parameters env_path, depot_path and julia_path sets environment variables for locations of your Julia
52
+ environment, packages and language.
53
+
54
+ - If user has not specified locations, the default is to use the current python/conda environment.
55
+ - If a system installation of Python is used, the default is set to the current user location.
56
+
57
+ Args:
58
+ env_path (Path | str | None, optional): Path to location of Julia environment. If it doesnt exist it will be
59
+ created. Defaults to None.
60
+ depot_path (Path | str | None, optional): Path to location where JuliaCall shoult install package
61
+ dependencies. Defaults to None.
62
+ julia_path (Path | str | None, optional): Path to Julia language location. Will be installed here if it
63
+ doesnt exist. Defaults to None.
64
+ dependencies (list[str] | None, optional): List of dependencies of the model. The strings in the list can be
65
+ either urls or Julia package names.. Defaults to None.
66
+ skip_install_dependencies (bool, optional): Skip installation of dependencies. Defaults to False.
67
+ force_julia_install (bool): Force new Julia install.
68
+
69
+ """
70
+ self._check_type(env_path, (Path, str, type(None)))
71
+ self._check_type(depot_path, (Path, str, type(None)))
72
+ self._check_type(julia_path, (Path, str, type(None)))
73
+ self._check_type(dependencies, (list, str, type(None)))
74
+ self._check_type(skip_install_dependencies, bool)
75
+
76
+ self._env_path = env_path
77
+ self._depot_path = depot_path
78
+ self._julia_path = julia_path
79
+ self._dependencies = dependencies if dependencies else []
80
+ self._skip_install_dependencies = skip_install_dependencies
81
+ self._force_julia_install = force_julia_install
82
+
83
+ self._jlpkg = None
84
+ self._initialize_julia()
85
+
86
+ def _initialize_julia(self) -> None:
87
+ """Initialize Julia language, package depot, and environment with JuliaCall."""
88
+ if self._jl is not None:
89
+ return
90
+
91
+ # figure out what kind of environment we are in
92
+ prefix = sys.prefix if sys.prefix != sys.base_prefix else os.getenv("CONDA_PREFIX")
93
+ # we have python system installation
94
+ project = Path("~/.julia").expanduser() if prefix is None else prefix
95
+
96
+ self._env_path = str(Path(project) / "julia_envs" / self.ENV_NAME) if not self._env_path else str(self._env_path)
97
+ self._depot_path = str(Path(project) / "julia_pkgs") if not self._depot_path else str(self._depot_path)
98
+
99
+ os.environ["PYTHON_JULIAPKG_PROJECT"] = self._env_path
100
+ os.environ["JULIA_DEPOT_PATH"] = self._depot_path
101
+ if self._julia_path: # If Julia path is not set, let JuliaCall handle defaults.
102
+ os.environ["PYTHON_JULIAPKG_EXE"] = str(self._julia_path)
103
+
104
+ if self._force_julia_install:
105
+ path = os.environ.get("PATH", "")
106
+ cleaned = os.pathsep.join(p for p in path.split(os.pathsep) if "julia" not in p.lower())
107
+ os.environ["PATH"] = cleaned
108
+
109
+ juliacall = importlib.import_module("juliacall")
110
+ JuliaModel._jl = juliacall.Main
111
+ self._jlpkg = juliacall.Pkg
112
+
113
+ self._jlpkg.activate(str(self._env_path))
114
+
115
+ if not self._skip_install_dependencies:
116
+ self._install_dependencies()
117
+
118
+ # Print sysimage Julia
119
+ try:
120
+ path_sysimage = self._jl.seval("unsafe_string(Base.JLOptions().image_file)")
121
+ message = f"path_sysimage: {path_sysimage}"
122
+ self.send_debug_event(message)
123
+ except Exception:
124
+ pass
125
+
126
+ def _install_dependencies(self) -> None:
127
+ """Install dependencies."""
128
+ # if (Path(self._env_path) / Path("Manifest.toml")).exists():
129
+ # print("Manifest found, assuming environment is already initialized.")
130
+ # return
131
+
132
+ url_tuples = [p for p in self._dependencies if isinstance(p, tuple) and _is_url(p[0])]
133
+ urls = [p for p in self._dependencies if isinstance(p, str) and _is_url(p)]
134
+ dev_paths = [p for p in self._dependencies if isinstance(p, str) and Path(p).exists()]
135
+ pkg_names = [p for p in self._dependencies if isinstance(p, str) and not _is_url(p) and not Path(p).exists()]
136
+
137
+ unknowns = [p for p in self._dependencies if not (p in url_tuples or p in urls or p in pkg_names or p in dev_paths)]
138
+
139
+ if unknowns:
140
+ messages = []
141
+ for p in unknowns:
142
+ messages.append(
143
+ (
144
+ f"Unsupported julia package definition: '{p}' of type '{type(p)}' is not supported. "
145
+ "Must be defined as either str or tuple[str, str | None]"
146
+ ),
147
+ )
148
+ message = "\n".join(messages)
149
+ raise ValueError(message)
150
+
151
+ self._jl.seval("using Pkg")
152
+
153
+ pkg_spec_vector = self._jl.seval("x = Pkg.PackageSpec[]")
154
+
155
+ for url, rev in url_tuples:
156
+ self._jl.seval(f'push!(x, Pkg.PackageSpec(url="{url}", rev="{rev}"))')
157
+
158
+ for url in urls:
159
+ self._jl.seval(f'push!(x, Pkg.PackageSpec(url="{url}))"')
160
+
161
+ for pkg_name in pkg_names:
162
+ self._jl.seval(f'push!(x, Pkg.PackageSpec(name="{pkg_name}"))')
163
+
164
+ self._jlpkg.add(pkg_spec_vector)
165
+
166
+ for dev_path in dev_paths:
167
+ self._jl.seval(f'Pkg.develop(path="{dev_path}")')
168
+
169
+ def _run(self, julia_code: str) -> None:
170
+ """Run a string of julia code wich is supposed to start running the Julia Model in the given environment."""
171
+ self._jl.seval(julia_code)
@@ -0,0 +1,7 @@
1
+ # framcore/juliamodels/__init__.py
2
+
3
+ from framcore.juliamodels.JuliaModel import JuliaModel
4
+
5
+ __all__ = [
6
+ "JuliaModel",
7
+ ]
@@ -0,0 +1,10 @@
1
+ # framcore/loaders/__init__.py
2
+
3
+ from framcore.loaders.loaders import CurveLoader, FileLoader, Loader, TimeVectorLoader
4
+
5
+ __all__ = [
6
+ "CurveLoader",
7
+ "FileLoader",
8
+ "Loader",
9
+ "TimeVectorLoader",
10
+ ]