fameio 3.3.0__py3-none-any.whl → 3.4.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.
- fameio/input/scenario/contract.py +55 -10
- fameio/time.py +41 -0
- {fameio-3.3.0.dist-info → fameio-3.4.0.dist-info}/METADATA +32 -18
- {fameio-3.3.0.dist-info → fameio-3.4.0.dist-info}/RECORD +10 -10
- {fameio-3.3.0.dist-info → fameio-3.4.0.dist-info}/LICENSE.txt +0 -0
- {fameio-3.3.0.dist-info → fameio-3.4.0.dist-info}/LICENSES/Apache-2.0.txt +0 -0
- {fameio-3.3.0.dist-info → fameio-3.4.0.dist-info}/LICENSES/CC-BY-4.0.txt +0 -0
- {fameio-3.3.0.dist-info → fameio-3.4.0.dist-info}/LICENSES/CC0-1.0.txt +0 -0
- {fameio-3.3.0.dist-info → fameio-3.4.0.dist-info}/WHEEL +0 -0
- {fameio-3.3.0.dist-info → fameio-3.4.0.dist-info}/entry_points.txt +0 -0
@@ -10,7 +10,7 @@ from fameio.input.metadata import Metadata
|
|
10
10
|
from fameio.input.scenario.attribute import Attribute
|
11
11
|
from fameio.logs import log, log_error
|
12
12
|
from fameio.time import FameTime, ConversionError
|
13
|
-
from fameio.tools import
|
13
|
+
from fameio.tools import keys_to_lower
|
14
14
|
|
15
15
|
|
16
16
|
class Contract(Metadata):
|
@@ -24,6 +24,7 @@ class Contract(Metadata):
|
|
24
24
|
KEY_PRODUCT: Final[str] = "ProductName".lower()
|
25
25
|
KEY_FIRST_DELIVERY: Final[str] = "FirstDeliveryTime".lower()
|
26
26
|
KEY_INTERVAL: Final[str] = "DeliveryIntervalInSteps".lower()
|
27
|
+
KEY_EVERY: Final[str] = "Every".lower()
|
27
28
|
KEY_EXPIRE: Final[str] = "ExpirationTime".lower()
|
28
29
|
KEY_ATTRIBUTES: Final[str] = "Attributes".lower()
|
29
30
|
|
@@ -33,7 +34,8 @@ class Contract(Metadata):
|
|
33
34
|
"or N-to-N sender-to-receiver numbers. Found M-to-N pairing in Contract with "
|
34
35
|
"Senders: {} and Receivers: {}."
|
35
36
|
)
|
36
|
-
|
37
|
+
_ERR_XOR_KEYS = "Contract expects exactly one of the keys '{}' or '{}'. Found either both or none."
|
38
|
+
_ERR_INTERVAL_INVALID = "Contract delivery interval must be a positive integer but was: {}"
|
37
39
|
_ERR_SENDER_IS_RECEIVER = "Contract sender and receiver have the same id: {}"
|
38
40
|
_ERR_DOUBLE_ATTRIBUTE = "Cannot add attribute '{}' to contract because it already exists."
|
39
41
|
_ERR_TIME_CONVERSION = "Contract item '{}' is an ill-formatted time: '{}'"
|
@@ -73,7 +75,7 @@ class Contract(Metadata):
|
|
73
75
|
if sender_id == receiver_id:
|
74
76
|
log().warning(self._ERR_SENDER_IS_RECEIVER.format(sender_id))
|
75
77
|
if delivery_interval <= 0:
|
76
|
-
raise log_error(self.ContractError(self.
|
78
|
+
raise log_error(self.ContractError(self._ERR_INTERVAL_INVALID.format(delivery_interval)))
|
77
79
|
self._sender_id = sender_id
|
78
80
|
self._receiver_id = receiver_id
|
79
81
|
self._product_name = product_name
|
@@ -164,7 +166,7 @@ class Contract(Metadata):
|
|
164
166
|
product_name = Contract._get_or_raise(definitions, Contract.KEY_PRODUCT, Contract._ERR_MISSING_KEY)
|
165
167
|
|
166
168
|
first_delivery_time = Contract._get_time(definitions, Contract.KEY_FIRST_DELIVERY)
|
167
|
-
delivery_interval = Contract.
|
169
|
+
delivery_interval = Contract._get_interval(definitions)
|
168
170
|
expiration_time = Contract._get_time(definitions, Contract.KEY_EXPIRE, mandatory=False)
|
169
171
|
|
170
172
|
contract = cls(sender_id, receiver_id, product_name, delivery_interval, first_delivery_time, expiration_time)
|
@@ -224,6 +226,43 @@ class Contract(Metadata):
|
|
224
226
|
raise log_error(Contract.ContractError(Contract._ERR_MISSING_KEY.format(key)))
|
225
227
|
return None
|
226
228
|
|
229
|
+
@staticmethod
|
230
|
+
def _get_interval(definitions: dict) -> int:
|
231
|
+
"""Extract delivery interval from Contract definition, or raise an error if not present or ill formatted.
|
232
|
+
|
233
|
+
Args:
|
234
|
+
definitions: to extract the delivery interval from
|
235
|
+
|
236
|
+
Returns:
|
237
|
+
the delivery interval in fame time steps
|
238
|
+
|
239
|
+
Raises:
|
240
|
+
ContractError: if delivery interval is not defined or invalid, logged with level "ERROR"
|
241
|
+
"""
|
242
|
+
has_interval = Contract.KEY_INTERVAL in definitions
|
243
|
+
has_every = Contract.KEY_EVERY in definitions
|
244
|
+
|
245
|
+
if has_interval and not has_every:
|
246
|
+
value = definitions[Contract.KEY_INTERVAL]
|
247
|
+
if isinstance(value, int):
|
248
|
+
return value
|
249
|
+
raise log_error(Contract.ContractError(Contract._ERR_INTERVAL_INVALID.format(value)))
|
250
|
+
if has_every and not has_interval:
|
251
|
+
value = definitions[Contract.KEY_EVERY]
|
252
|
+
if isinstance(value, int):
|
253
|
+
return value
|
254
|
+
if isinstance(value, str):
|
255
|
+
try:
|
256
|
+
return FameTime.convert_text_to_time_span(value)
|
257
|
+
except ConversionError as e:
|
258
|
+
raise log_error(
|
259
|
+
Contract.ContractError(Contract._ERR_TIME_CONVERSION.format(Contract.KEY_EVERY, value))
|
260
|
+
) from e
|
261
|
+
raise log_error(Contract.ContractError(Contract._ERR_TIME_CONVERSION.format(Contract.KEY_EVERY, value)))
|
262
|
+
raise log_error(
|
263
|
+
Contract.ContractError(Contract._ERR_XOR_KEYS.format(Contract.KEY_INTERVAL, Contract.KEY_EVERY))
|
264
|
+
)
|
265
|
+
|
227
266
|
def _init_attributes_from_dict(self, attributes: dict[str, Any]) -> None:
|
228
267
|
"""Resets Contract `attributes` from dict.
|
229
268
|
|
@@ -280,15 +319,14 @@ class Contract(Metadata):
|
|
280
319
|
Contract.KEY_EXPIRE,
|
281
320
|
Contract.KEY_METADATA,
|
282
321
|
Contract.KEY_ATTRIBUTES,
|
322
|
+
Contract.KEY_EVERY,
|
283
323
|
]:
|
284
324
|
if key in multi_definition:
|
285
325
|
base_data[key] = multi_definition[key]
|
286
|
-
|
287
|
-
|
288
|
-
)
|
289
|
-
receivers =
|
290
|
-
Contract._get_or_raise(multi_definition, Contract.KEY_RECEIVER, Contract._ERR_MISSING_KEY)
|
291
|
-
)
|
326
|
+
sender_value = Contract._get_or_raise(multi_definition, Contract.KEY_SENDER, Contract._ERR_MISSING_KEY)
|
327
|
+
senders = Contract._unpack_list(sender_value)
|
328
|
+
receiver_value = Contract._get_or_raise(multi_definition, Contract.KEY_RECEIVER, Contract._ERR_MISSING_KEY)
|
329
|
+
receivers = Contract._unpack_list(receiver_value)
|
292
330
|
if len(senders) > 1 and len(receivers) == 1:
|
293
331
|
for index, sender in enumerate(senders):
|
294
332
|
contracts.append(Contract._copy_contract(sender, receivers[0], base_data))
|
@@ -302,6 +340,13 @@ class Contract(Metadata):
|
|
302
340
|
raise log_error(Contract.ContractError(Contract._ERR_MULTI_CONTRACT_CORRUPT.format(senders, receivers)))
|
303
341
|
return contracts
|
304
342
|
|
343
|
+
@staticmethod
|
344
|
+
def _unpack_list(obj: Any | list) -> list[Any]:
|
345
|
+
"""Returns the given value as a flat list - unpacks potential nested list(s)"""
|
346
|
+
if isinstance(obj, list):
|
347
|
+
return [item for element in obj for item in Contract._unpack_list(element)]
|
348
|
+
return [obj]
|
349
|
+
|
305
350
|
@staticmethod
|
306
351
|
def _copy_contract(sender: int, receiver: int, base_data: dict) -> dict:
|
307
352
|
"""Returns a new contract definition dictionary, with given `sender` and `receiver` and copied `base_data`."""
|
fameio/time.py
CHANGED
@@ -17,6 +17,7 @@ START_IN_REAL_TIME = "2000-01-01_00:00:00"
|
|
17
17
|
DATE_FORMAT = "%Y-%m-%d_%H:%M:%S"
|
18
18
|
DATE_REGEX = re.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}:[0-9]{2}:[0-9]{2}")
|
19
19
|
FAME_FIRST_DATETIME = dt.datetime.strptime(START_IN_REAL_TIME, DATE_FORMAT)
|
20
|
+
TIME_SPAN_REGEX = re.compile(r"^[0-9]+\s*[A-z]+$")
|
20
21
|
|
21
22
|
|
22
23
|
class ConversionError(InputError, OutputError):
|
@@ -71,6 +72,8 @@ class FameTime:
|
|
71
72
|
_INVALID_TOO_LARGE = "Cannot convert time stamp string '{}' - last day of leap year is Dec 30th!"
|
72
73
|
_NO_TIMESTAMP = "Time value expected, but '{}' is neither a time stamp string nor an integer."
|
73
74
|
_INVALID_DATE_FORMAT = "Received invalid date format '{}'."
|
75
|
+
_INVALID_SPAN_FORMAT = "Time span must be provided in the format '<positive integer> <TimeUnit>' but was: '{}'"
|
76
|
+
_INVALID_TIME_UNIT = f"Time span unit '{{}}' unknown, must be one of: {[u.name for u in TimeUnit]}"
|
74
77
|
|
75
78
|
@staticmethod
|
76
79
|
def convert_datetime_to_fame_time_step(datetime_string: str) -> int:
|
@@ -160,6 +163,32 @@ class FameTime:
|
|
160
163
|
return steps * value
|
161
164
|
raise log_error(ConversionError(FameTime._TIME_UNIT_UNKNOWN.format(unit)))
|
162
165
|
|
166
|
+
@staticmethod
|
167
|
+
def convert_text_to_time_span(string: str) -> int:
|
168
|
+
"""Converts given string in form of "<positive integer> <TimeUnit>" to a time span in FAME time steps.
|
169
|
+
|
170
|
+
Args:
|
171
|
+
string: to convert to a time span from
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
FAME time steps equivalent of `value x unit`
|
175
|
+
|
176
|
+
Raises:
|
177
|
+
ConversionError: if an unknown time unit or an unsupported format is used, logged with level "ERROR"
|
178
|
+
"""
|
179
|
+
string = string.strip()
|
180
|
+
if TIME_SPAN_REGEX.fullmatch(string) is None:
|
181
|
+
raise log_error(ConversionError(FameTime._INVALID_SPAN_FORMAT.format(string)))
|
182
|
+
multiple, unit_name = string.split()
|
183
|
+
unit_name = unit_name.upper()
|
184
|
+
if not unit_name.endswith("S"):
|
185
|
+
unit_name += "S"
|
186
|
+
try:
|
187
|
+
unit = TimeUnit[unit_name]
|
188
|
+
except KeyError as e:
|
189
|
+
raise log_error(ConversionError(FameTime._INVALID_TIME_UNIT.format(unit_name))) from e
|
190
|
+
return FameTime.convert_time_span_to_fame_time_steps(int(multiple), unit)
|
191
|
+
|
163
192
|
@staticmethod
|
164
193
|
def is_datetime(string: Any) -> bool:
|
165
194
|
"""Returns `True` if given `string` matches Datetime string format and can be converted to FAME time step."""
|
@@ -208,3 +237,15 @@ class FameTime:
|
|
208
237
|
return int(value)
|
209
238
|
except ValueError as e:
|
210
239
|
raise log_error(ConversionError(FameTime._NO_TIMESTAMP.format(value))) from e
|
240
|
+
|
241
|
+
@staticmethod
|
242
|
+
def get_first_fame_time_step_of_year(year: int) -> int:
|
243
|
+
"""Returns FAME time in integer format of first time step of given year.
|
244
|
+
|
245
|
+
Args:
|
246
|
+
year: to get the first time step for
|
247
|
+
|
248
|
+
Returns:
|
249
|
+
first time step in the requested year in integer format
|
250
|
+
"""
|
251
|
+
return (year - FAME_FIRST_DATETIME.year) * Constants.STEPS_PER_YEAR
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: fameio
|
3
|
-
Version: 3.
|
3
|
+
Version: 3.4.0
|
4
4
|
Summary: Tools for input preparation and output digestion of FAME models
|
5
5
|
License: Apache-2.0
|
6
6
|
Keywords: FAME,fameio,agent-based modelling,energy systems
|
@@ -507,7 +507,7 @@ Contracts:
|
|
507
507
|
ReceiverId: 2
|
508
508
|
ProductName: ProductOfAgent_1
|
509
509
|
FirstDeliveryTime: -25
|
510
|
-
|
510
|
+
Every: 3600
|
511
511
|
Metadata:
|
512
512
|
Some: "additional information can go here"
|
513
513
|
|
@@ -515,7 +515,7 @@ Contracts:
|
|
515
515
|
ReceiverId: 1
|
516
516
|
ProductName: ProductOfAgent_2
|
517
517
|
FirstDeliveryTime: -22
|
518
|
-
|
518
|
+
Every: 1 hour
|
519
519
|
Attributes:
|
520
520
|
ProductAppendix: value
|
521
521
|
TimeOffset: 42
|
@@ -527,7 +527,8 @@ Contract Parameters:
|
|
527
527
|
* `ReceiverId` unique ID of agent receiving the product
|
528
528
|
* `ProductName` name of the product to be sent
|
529
529
|
* `FirstDeliveryTime` first time of delivery in the format "seconds after the January 1st 2000, 00:00:00"
|
530
|
-
* `
|
530
|
+
* `Every` delay time in between deliveries; either an integer value in seconds, or a qualified time span in the format "<integer> <TimeUnit>(s)", where TimeUnit is one of "second", "minute", "hour", "day", "week", "month", "year" - with an options "s" at the end; mind that week, month, and year refer to fixed-length intervals of 168, 730, and 8760 hours.
|
531
|
+
* `DeliveryIntervalInSteps` deprecated; delay time in between deliveries in seconds (use instead of `Every`)
|
531
532
|
* `Metadata` can be assigned to add further helpful information about a Contract
|
532
533
|
* `Attributes` can be set to include additional information as `int`, `float`, `enum`, or `dict` data types
|
533
534
|
|
@@ -541,59 +542,72 @@ example:
|
|
541
542
|
```yaml
|
542
543
|
Contracts:
|
543
544
|
# effectively 3 similar contracts (0 -> 11), (0 -> 12), (0 -> 13)
|
544
|
-
# with otherwise identical ProductName, FirstDeliveryTime
|
545
|
+
# with otherwise identical ProductName, FirstDeliveryTime, and Every
|
545
546
|
- SenderId: 0
|
546
547
|
ReceiverId: [ 11, 12, 13 ]
|
547
548
|
ProductName: MyOtherProduct
|
548
549
|
FirstDeliveryTime: 100
|
549
|
-
|
550
|
+
Every: 1 hour
|
550
551
|
|
551
552
|
# effectively 3 similar contracts (1 -> 10), (2 -> 10), (3 -> 10)
|
552
|
-
# with otherwise identical ProductName, FirstDeliveryTime
|
553
|
+
# with otherwise identical ProductName, FirstDeliveryTime, and Every
|
553
554
|
- SenderId: [ 1, 2, 3 ]
|
554
555
|
ReceiverId: 10
|
555
556
|
ProductName: MyProduct
|
556
557
|
FirstDeliveryTime: 100
|
557
|
-
|
558
|
+
Every: 1 hour
|
558
559
|
|
559
560
|
# effectively 3 similar contracts (1 -> 11), (2 -> 12), (3 -> 13)
|
560
|
-
# with otherwise identical ProductName, FirstDeliveryTime
|
561
|
+
# with otherwise identical ProductName, FirstDeliveryTime, and Every
|
561
562
|
- SenderId: [ 1, 2, 3 ]
|
562
563
|
ReceiverId: [ 11, 12, 13 ]
|
563
564
|
ProductName: MyThirdProduct
|
564
565
|
FirstDeliveryTime: 100
|
565
|
-
|
566
|
+
Every: 1 hour
|
566
567
|
```
|
567
568
|
|
568
|
-
|
569
|
-
The following example is equivalent to the previous one
|
570
|
-
agents e.g. (4;14):
|
569
|
+
When combined with YAML anchors, the complexity of extensive contract chains can be reduced.
|
570
|
+
The following example is equivalent to the previous one, enabling contracts to be quickly extended to a new group of agents:
|
571
571
|
|
572
572
|
```yaml
|
573
573
|
Groups:
|
574
|
-
- &agentList1: [ 1,2,3 ]
|
575
|
-
- &agentList2: [ 11,12,13 ]
|
574
|
+
- &agentList1: [ 1, 2, 3 ]
|
575
|
+
- &agentList2: [ 11, 12, 13 ]
|
576
576
|
|
577
577
|
Contracts:
|
578
578
|
- SenderId: 0
|
579
579
|
ReceiverId: *agentList2
|
580
580
|
ProductName: MyOtherProduct
|
581
581
|
FirstDeliveryTime: 100
|
582
|
-
|
582
|
+
Every: 1 hour
|
583
583
|
|
584
584
|
- SenderId: *agentList1
|
585
585
|
ReceiverId: 10
|
586
586
|
ProductName: MyProduct
|
587
587
|
FirstDeliveryTime: 100
|
588
|
-
|
588
|
+
Every: 1 hour
|
589
589
|
|
590
590
|
- SenderId: *agentList1
|
591
591
|
ReceiverId: *agentList2
|
592
592
|
ProductName: MyThirdProduct
|
593
593
|
FirstDeliveryTime: 100
|
594
|
-
|
594
|
+
Every: 1 hour
|
595
595
|
```
|
596
596
|
|
597
|
+
Lists can be nested in senders and receivers as follows:
|
598
|
+
|
599
|
+
```
|
600
|
+
Groups:
|
601
|
+
- SenderId: 42
|
602
|
+
ReceiverId: [1, [2, 3], [4, [5]]]
|
603
|
+
ProductName: AnImportantProduct
|
604
|
+
FirstDeliveryTime: 101
|
605
|
+
Every: 30 minutes
|
606
|
+
```
|
607
|
+
|
608
|
+
This feature should not be overused, as nested lists can become messy and difficult to read.
|
609
|
+
Special care should be taken when using it with an N-to-N mapping, as it will be difficult to check whether senders and receivers are matched correctly.
|
610
|
+
|
597
611
|
#### StringSets
|
598
612
|
|
599
613
|
This optional section defines values of type `string_set`.
|
@@ -14,7 +14,7 @@ fameio/input/resolver.py,sha256=NakBjnCCWRMz-8gTC_Ggx-2tXq-u6OPjfBOua0Rd2nA,1902
|
|
14
14
|
fameio/input/scenario/__init__.py,sha256=Pb8O9rVOTwEo48WIgiq1kBnpovpc4D_syC6EjTFGHew,404
|
15
15
|
fameio/input/scenario/agent.py,sha256=n0H8nHwQFfAeTwdJceJpi9VV1muYjJu_PjmzC_vrP84,5526
|
16
16
|
fameio/input/scenario/attribute.py,sha256=pp9cquxfBUKNFwV3bTDBkDXv1k8ThBiAL6Eh-ag4kQk,11386
|
17
|
-
fameio/input/scenario/contract.py,sha256=
|
17
|
+
fameio/input/scenario/contract.py,sha256=fv4XRMMF8X9Q1LMNJsWcPfQx-EY40gDBWsEMJ7rsaBQ,15324
|
18
18
|
fameio/input/scenario/exception.py,sha256=o64hd7FQrkTF6Ze075Cbt4TM3OkcyJFVSi4qexLuMoU,1939
|
19
19
|
fameio/input/scenario/fameiofactory.py,sha256=HgLHVQGKsTPEFy8K1ILB7F_lJtHoMhu89inOgDWYP5k,2800
|
20
20
|
fameio/input/scenario/generalproperties.py,sha256=C3ND-PLb1FrCRheIBIyxXHKsZVMW8GZVHUidyInqrOw,3749
|
@@ -48,13 +48,13 @@ fameio/scripts/make_config.py.license,sha256=EXKiZn-aoR7nO3auGMNGk9upHbobPLHAIBY
|
|
48
48
|
fameio/scripts/reformat.py,sha256=jYJsl0UkXtZyn2GyA-QVAARilkHa_ZBWa5CGNIGNDuo,2850
|
49
49
|
fameio/scripts/reformat.py.license,sha256=EXKiZn-aoR7nO3auGMNGk9upHbobPLHAIBYUO0S6LUg,107
|
50
50
|
fameio/series.py,sha256=ipjDsDmgVlVzaYvXwXcWM8fpCy5w8hTNWTi4hPmvOuM,13654
|
51
|
-
fameio/time.py,sha256=
|
51
|
+
fameio/time.py,sha256=S8TagfGA7B44JPkj8YfC3ftkxODNTC6ZXELNQyHC1VA,10201
|
52
52
|
fameio/tools.py,sha256=metmgKuZ0lubmTIPY3w_ertDxSLQtHIa6OlpcapyIk4,2478
|
53
|
-
fameio-3.
|
54
|
-
fameio-3.
|
55
|
-
fameio-3.
|
56
|
-
fameio-3.
|
57
|
-
fameio-3.
|
58
|
-
fameio-3.
|
59
|
-
fameio-3.
|
60
|
-
fameio-3.
|
53
|
+
fameio-3.4.0.dist-info/entry_points.txt,sha256=IUbTceB_CLFOHulubEf9jgiCFsV2TchlzCssmjbiOKI,176
|
54
|
+
fameio-3.4.0.dist-info/LICENSE.txt,sha256=eGHBZnhr9CWjE95SWjRfmhtK1lvVn5X4Fpf3KrrAZDg,10391
|
55
|
+
fameio-3.4.0.dist-info/LICENSES/Apache-2.0.txt,sha256=eGHBZnhr9CWjE95SWjRfmhtK1lvVn5X4Fpf3KrrAZDg,10391
|
56
|
+
fameio-3.4.0.dist-info/LICENSES/CC-BY-4.0.txt,sha256=y9WvMYKGt0ZW8UXf9QkZB8wj1tjJrQngKR7CSXeSukE,19051
|
57
|
+
fameio-3.4.0.dist-info/LICENSES/CC0-1.0.txt,sha256=9Ofzc7m5lpUDN-jUGkopOcLZC3cl6brz1QhKInF60yg,7169
|
58
|
+
fameio-3.4.0.dist-info/METADATA,sha256=qRURN8lvabRE9CmzKRuwsoi_1rKyv71TGkkdLJUAydE,42374
|
59
|
+
fameio-3.4.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
60
|
+
fameio-3.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|