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,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)
|