pydae 0.56.5__py3-none-any.whl → 0.57.1__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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "V_1": 1.0,
3
+ "theta_1": 0.0,
4
+ "V_2": 1.0,
5
+ "theta_2": 0.0,
6
+ "omega_coi": 1.0
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "V_1": 1.0,
3
+ "theta_1": 0.0,
4
+ "V_2": 1.0,
5
+ "theta_2": 0.0,
6
+ "omega_coi": 1.0
7
+ }
pydae/build_cffi.py CHANGED
@@ -1083,7 +1083,7 @@ class builder():
1083
1083
  ffi.compile()
1084
1084
  print(f'Compilation time: {time.time()-t_0:0.2f} s')
1085
1085
 
1086
- def build(self):
1086
+ def build(self, API=False):
1087
1087
 
1088
1088
  sys = self.sys
1089
1089
  name = self.name
pydae/utils/dates.py ADDED
@@ -0,0 +1,285 @@
1
+ import datetime
2
+ import pytz
3
+ from typing import Optional,List,Tuple
4
+
5
+ def get_absolute_hour_of_year_with_dst(month: int, day: int, day_hour: int, year: int, timezone_name: str) -> int:
6
+ """
7
+ Calculates the absolute hour of the year (0-indexed) for a given date and hour,
8
+ correctly handling Daylight Saving Time (DST) shifts.
9
+
10
+ Args:
11
+ month: The month (1-12).
12
+ day: The day of the month (1-31).
13
+ day_hour: The hour of the day (0-23).
14
+ year: The year for the calculation.
15
+ timezone_name: The IANA time zone string (e.g., 'Europe/Madrid', 'America/New_York').
16
+
17
+ Returns:
18
+ The absolute hour of the year (0-indexed).
19
+ """
20
+ try:
21
+ # 1. Define the time zone
22
+ tz = pytz.timezone(timezone_name)
23
+
24
+ # 2. Create the start-of-year time (Jan 1st, 00:00), making it time zone-aware
25
+ start_of_year = tz.localize(datetime(year, 1, 1, 0, 0, 0))
26
+
27
+ # 3. Create the target time, making it time zone-aware
28
+ # Note: localize handles ambiguous times (the hour repeated when clocks fall back)
29
+ # by defaulting to the first occurrence (is_dst=False), which is generally safe.
30
+ target_naive = datetime.datetime(year, month, day, day_hour)
31
+ target_time = tz.localize(target_naive)
32
+
33
+ # 4. Calculate the difference (timedelta)
34
+ time_difference = target_time - start_of_year
35
+
36
+ # 5. Convert the difference to total hours
37
+ # This conversion correctly uses the total elapsed seconds, accounting for
38
+ # the 23-hour or 25-hour days caused by DST shifts.
39
+ total_hours = int(time_difference.total_seconds() / 3600)
40
+
41
+ return total_hours
42
+
43
+ except pytz.UnknownTimeZoneError:
44
+ print(f"Error: Unknown time zone '{timezone_name}'.")
45
+ return -1
46
+ except ValueError as e:
47
+ print(f"Error: Invalid date/time input. {e}")
48
+ return -1
49
+
50
+
51
+
52
+
53
+ def datetime_to_hour_of_year(date_input: datetime.datetime) -> Optional[int]:
54
+ """
55
+ Calculates the absolute hour of the year (0-indexed) for a given date.
56
+ It uses the timezone already attached to the input object for the calculation.
57
+
58
+ Args:
59
+ date_input: A timezone-aware datetime object (tzinfo MUST be set).
60
+
61
+ Returns:
62
+ The absolute hour of the year (0-indexed) as an integer, or None on error.
63
+ """
64
+
65
+ # 🚨 Input Validation: Check if the input is timezone-aware
66
+ target_tz = date_input.tzinfo
67
+ if target_tz is None or target_tz.utcoffset(date_input) is None:
68
+ print("Error: Invalid date/time input. Input 'date_input' MUST be a timezone-aware datetime object.")
69
+ return None
70
+
71
+ try:
72
+ # 1. The target timezone is extracted directly from the input object (target_tz)
73
+
74
+ # 2. Create the start-of-year time (Jan 1st, 00:00) in the input's timezone
75
+ start_of_year_naive = datetime.datetime(date_input.year, 1, 1, 0, 0, 0)
76
+
77
+ # We must use localize() here since start_of_year_naive is, by definition, naive.
78
+ start_of_year_aware = target_tz.localize(start_of_year_naive)
79
+
80
+ # 3. The target time is already aware (date_input), so no conversion is needed.
81
+ target_time_aware = date_input
82
+
83
+ # 4. Calculate the difference (timedelta)
84
+ # This calculation is safe because both datetimes are aware and use the same tzinfo.
85
+ time_difference = target_time_aware - start_of_year_aware
86
+
87
+ # 5. Convert the difference to total hours
88
+ total_hours = int(time_difference.total_seconds() / 3600)
89
+
90
+ return total_hours
91
+
92
+ except pytz.exceptions.UnknownTimeZoneError:
93
+ # This catch is mostly for safety, though the tzinfo should be valid if set by pytz initially.
94
+ print(f"Error: Timezone attached to the input is not recognized.")
95
+ return None
96
+ except Exception as e:
97
+ print(f"An unexpected error occurred: {e}")
98
+ return None
99
+
100
+
101
+ def parse_datetime(date_string: str, timezone_name: str) -> Optional[datetime.datetime]:
102
+ """
103
+ Attempts to parse a date string using a predefined list of common formats.
104
+
105
+ Args:
106
+ date_string: The raw string input from the user (e.g., '2025-11-22 10:30').
107
+ timezone_name: The timezone to apply (e.g., 'Europe/Madrid').
108
+
109
+ Returns:
110
+ A timezone-aware datetime object if successful, otherwise None.
111
+ """
112
+
113
+ # 1. Define the list of formats to try (from most specific/common to least)
114
+ common_formats = [
115
+ # Full date and time
116
+ "%Y-%m-%d %H:%M:%S", # 2025-11-22 10:30:00
117
+ "%Y-%m-%d %H:%M", # 2025-11-22 10:30
118
+ "%d/%m/%Y %H:%M:%S", # 22/11/2025 10:30:00
119
+ "%d/%m/%Y %H:%M", # 22/11/2025 10:30
120
+
121
+ # Date only (assuming midnight)
122
+ "%Y-%m-%d", # 2025-11-22
123
+ "%d/%m/%Y", # 22/11/2025
124
+ "%m/%d/%Y", # 11/22/2025 (Common in US)
125
+ ]
126
+
127
+ # --- Timezone Setup ---
128
+ try:
129
+ # Get the timezone object using pytz
130
+ tz = pytz.timezone(timezone_name)
131
+ except pytz.exceptions.UnknownTimeZoneError:
132
+ print(f"Error: Timezone '{timezone_name}' is not recognized.")
133
+ return None
134
+
135
+ # --- Iteration and Parsing ---
136
+ for fmt in common_formats:
137
+ try:
138
+ # 1. Try to parse the string with the current format
139
+ naive_dt = datetime.datetime.strptime(date_string, fmt)
140
+
141
+ # 2. **Safety Net**: Ensure the object is naive before localization.
142
+ # (Though strptime is expected to return a naive object, this is safe practice)
143
+ naive_dt = naive_dt.replace(tzinfo=None)
144
+
145
+ # 3. Localize the naive datetime object (make it timezone-aware)
146
+ localized_dt = tz.localize(naive_dt)
147
+
148
+ # 4. Success! Return the localized object immediately
149
+ print(f"✅ Successfully parsed '{date_string}' using format: '{fmt}'")
150
+ return localized_dt
151
+
152
+ except ValueError:
153
+ # If the parsing fails for this format, simply continue to the next one
154
+ continue
155
+
156
+ # If the loop finishes without returning, no format worked
157
+ print(f"❌ Could not parse '{date_string}' with any known format.")
158
+ return None
159
+
160
+
161
+ def list_full_hours_between(start_dt: datetime, end_dt: datetime) -> List[datetime]:
162
+ """
163
+ Lists all full hours (on the hour mark) between a start and end datetime.
164
+ The list includes the hour of the start_dt, but excludes the hour of the end_dt.
165
+
166
+ Args:
167
+ start_dt: The starting datetime object (must be timezone-aware).
168
+ end_dt: The ending datetime object (must be timezone-aware).
169
+
170
+ Returns:
171
+ A list of timezone-aware datetime objects, all set to the start of the hour.
172
+
173
+ Raises:
174
+ ValueError: If datetimes are not timezone-aware or start_dt is after end_dt.
175
+ """
176
+
177
+ # --- Input Validation ---
178
+ if start_dt.tzinfo is None or start_dt.tzinfo.utcoffset(start_dt) is None:
179
+ raise ValueError("Start datetime must be timezone-aware (tzinfo is required).")
180
+ if end_dt.tzinfo is None or end_dt.tzinfo.utcoffset(end_dt) is None:
181
+ raise ValueError("End datetime must be timezone-aware (tzinfo is required).")
182
+ if start_dt >= end_dt:
183
+ return []
184
+
185
+ # --- 1. Find the first full hour mark to start the iteration ---
186
+ # Set the minute and second to zero for the start_dt's hour
187
+ current_dt = start_dt.replace(minute=0, second=0, microsecond=0)
188
+
189
+ # If the start_dt itself was not exactly on the hour, we must include it
190
+ # as the first item in the list, otherwise, the next iteration would skip it.
191
+ if start_dt.minute > 0 or start_dt.second > 0:
192
+ pass # current_dt now represents the start of the hour containing start_dt
193
+
194
+ hours_list = []
195
+
196
+ # --- 2. Iterate hour by hour until the end_dt is reached ---
197
+ one_hour = datetime.timedelta(hours=1)
198
+
199
+ while current_dt < end_dt:
200
+ # Append the current full hour to the list
201
+ hours_list.append(current_dt)
202
+
203
+ # Move to the next hour
204
+ current_dt += one_hour
205
+
206
+ return hours_list
207
+
208
+ def absolute_hours_list(date_ini,date_end,time_zone='Europe/Paris'):
209
+
210
+ result_ini = parse_datetime(date_ini, time_zone)
211
+ result_end = parse_datetime(date_end, time_zone)
212
+
213
+ hours_list = []
214
+ for item in list_full_hours_between(result_ini,result_end):
215
+ hours_list += [datetime_to_hour_of_year(item)]
216
+
217
+ return hours_list
218
+
219
+
220
+
221
+ def hour_index_to_date_components(absolute_hour_index: int, timezone_name: str) -> Optional[Tuple[int, int]]:
222
+ """
223
+ Converts an absolute hour of the year index (0-indexed) to the corresponding
224
+ day of the year and hour of the day, respecting DST rules.
225
+
226
+ Args:
227
+ absolute_hour_index: The 0-indexed hour of the year (e.g., 8759 is the last hour).
228
+ timezone_name: The IANA time zone string (e.g., 'Europe/Madrid').
229
+
230
+ Returns:
231
+ A tuple (day_of_year, hour_of_day) as integers, or None on error.
232
+ """
233
+
234
+ current_year = datetime.datetime.now().year # Use the current year for the calculation
235
+
236
+ try:
237
+ # 1. Define the time zone
238
+ tz = pytz.timezone(timezone_name)
239
+
240
+ # 2. Create the start-of-year time (Jan 1st, 00:00)
241
+ # This is the reference point for hour index 0.
242
+ start_of_year_naive = datetime.datetime(current_year, 1, 1, 0, 0, 0)
243
+
244
+ # Localize the start of the year to make it aware
245
+ start_of_year_aware = tz.localize(start_of_year_naive)
246
+
247
+ # 3. Create the time difference
248
+ # Multiplying the hour index by 3600 gives the exact number of seconds
249
+ # that have elapsed since the start_of_year_aware.
250
+ time_difference = datetime.timedelta(hours=absolute_hour_index)
251
+
252
+ # 4. Calculate the target time
253
+ # This addition operation correctly handles DST shifts. If 25 hours have
254
+ # passed since Jan 1st, and one hour was lost due to DST, the target time
255
+ # will only be advanced by 24 clock hours.
256
+ target_time_aware = start_of_year_aware + time_difference
257
+
258
+ # 5. Extract the required components
259
+ # %j gives the 1-indexed day of the year (1-366).
260
+ day_of_year = int(target_time_aware.strftime('%j'))
261
+ hour_of_day = target_time_aware.hour # 0-23
262
+
263
+ return (day_of_year, hour_of_day)
264
+
265
+ except pytz.exceptions.UnknownTimeZoneError:
266
+ print(f"Error: Timezone '{timezone_name}' is not recognized.")
267
+ return None
268
+ except Exception as e:
269
+ print(f"An unexpected error occurred: {e}")
270
+ return None
271
+
272
+
273
+
274
+ if __name__ == '__main__':
275
+
276
+ result_1 = parse_datetime('31/12/2024 23:00', 'Europe/Paris')
277
+ print(datetime_to_hour_of_year(result_1))
278
+
279
+ result_ini = parse_datetime( '1/12/2024 23:00', 'Europe/Paris')
280
+ result_end = parse_datetime('31/12/2024 23:00', 'Europe/Paris')
281
+
282
+ print(list_full_hours_between(result_ini,result_end))
283
+
284
+ print(absolute_hours_list('1/1/2024 00:00','31/1/2024 23:00'))
285
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydae
3
- Version: 0.56.5
3
+ Version: 0.57.1
4
4
  Summary: =====
5
5
  Keywords: engineering
6
6
  Author-email: Juan Manuel Mauricio <jmmauricio6@gmail.com>
@@ -1,9 +1,9 @@
1
1
  pydae/.gitignore,sha256=c1jUQV9WXX3PWrlOzHiKvUWFomLJUrfYogbU7nYwAwc,288
2
- pydae/__init__.py,sha256=A32mD4kI6nx5itPtqfG-zWIa5VO-UGCHxGW6qDcnU_M,106
2
+ pydae/__init__.py,sha256=AooVv7ncEnuMOCQXzdgZBl_YmdIa5ILy7R4W22vxoNw,106
3
3
  pydae/b_pu.py,sha256=FdRRVieJLQDYPwYNWhbXJ0SXvf4jtPdbvIZTKWi16I0,6775
4
4
  pydae/bpu.py,sha256=FdRRVieJLQDYPwYNWhbXJ0SXvf4jtPdbvIZTKWi16I0,6775
5
5
  pydae/build.py,sha256=ECfc_doyK9PhrjEAlWJGLfirQmpT24XuYNxUgtrXI74,46151
6
- pydae/build_cffi.py,sha256=o-l7xV5wH4sg0PuWq8Qll53phuXpRB2H04BDkYRuSzk,43879
6
+ pydae/build_cffi.py,sha256=jzsAIT_D3bWQHR6mpJQ4O99F2yR9iPtA6OdKfwKxxgs,43890
7
7
  pydae/build_cffi_mp.py,sha256=gJSQ9Ff_g09zqBvDQQBfMA4DTU6U0mtrV3ac4MLkP5A,44276
8
8
  pydae/build_test_cffi.c,sha256=5DtNrpLj8_dWHKrVaffSEIitG1PYFO37In__abX9jDQ,106204
9
9
  pydae/build_v2.py,sha256=ctx4Q6KxZr7LyDkbMPlnXRWESV-4CAZn50l-l8sAnr8,67240
@@ -47,7 +47,8 @@ pydae/transformers.py,sha256=tVei7P88EFJWx-68NjscVL8eYFJYECpQtRgS6BH_3B0,17289
47
47
  pydae/xy_0.json,sha256=uX-p5beHXjMDIkSTd5egfdq2UOq4n37oHpB8XJbpUts,240
48
48
  pydae/.ipynb_checkpoints/models-checkpoint.py,sha256=N8-m1Dk6HdmgpELHhEudVGup3ZO_b_DrJls7nanY4O8,58246
49
49
  pydae/bmapu/__init__.py,sha256=r7mgVYduCfuZ_pqaNXp_Y6Y-L3wcOKT4dzBLPsr8oak,105
50
- pydae/bmapu/bmapu_builder.py,sha256=ekIjvqEDGG7Nr1Zlnd7NFlER_i0LsKcRFOY-yE8FzuI,27488
50
+ pydae/bmapu/bmapu_builder.py,sha256=xuLXpO5OpfXAxMXjYz3bWUf_U6epWpWXbFqpoNNYlrY,26807
51
+ pydae/bmapu/bmapu_builder_line_exp.py,sha256=QljQTMT24enZPCvoQyxtN4uSzhmgWQWkdR3qnKYDxDE,21974
51
52
  pydae/bmapu/avrs/__init__.py,sha256=JhpeBVii27qLqxJqimWLBYwjPgXrz3hYq7PQQf2ssGs,99
52
53
  pydae/bmapu/avrs/avr_antiw.py,sha256=WtezvBDcr82VEqEmbSMdfKS_jdeI-ZcBan5FWqTURd4,15589
53
54
  pydae/bmapu/avrs/avrs.py,sha256=sWz29mcLMPcK7N0NiCtkF7MszAkv-RNAvo8Vst81m2A,1104
@@ -89,7 +90,14 @@ pydae/bmapu/govs/temp_xy_0.json,sha256=s1NHn0VvC_-yKfF5ZWR3c45AbF-2wyHxQHfEtavNp
89
90
  pydae/bmapu/govs/tgov1.py,sha256=FBpgHh9n__9Z4PdVolU6dTx-qpi887Km89939k3qNVU,2799
90
91
  pydae/bmapu/govs/xy_0.json,sha256=s1NHn0VvC_-yKfF5ZWR3c45AbF-2wyHxQHfEtavNpYQ,325
91
92
  pydae/bmapu/lines/__init__.py,sha256=oqQul2vN-ptslMdLyvGd7_KLLYgCV5beQvOICuavTRA,189
92
- pydae/bmapu/lines/lines.py,sha256=y0pjz63NIgwdDbmS58k9VV69FHcujeHzlZtBqXaTrXI,2331
93
+ pydae/bmapu/lines/lib_dtr.py,sha256=cen93cIFMQDWV92tt2GTKVwS2dEfg8IGmzwD49lgDkE,15675
94
+ pydae/bmapu/lines/lines.py,sha256=HJldB_qECtnOZD2k_KCqaEtjbYveHKqLkg7aT8z9CNM,11430
95
+ pydae/bmapu/lines/temp.py,sha256=NX7WzyKRAMqUFNLY6briw5DHwiz871iAvqYYUfVuNzU,61118
96
+ pydae/bmapu/lines/temp_ini_cffi.c,sha256=fkVX3FKuznEXZy7RcpzaNLKVb0e1MhkAflq0zcUSFIM,45210
97
+ pydae/bmapu/lines/temp_run_cffi.c,sha256=IwLffjIAYi-sfkTfB-tFfOBuNkQb6rHfpZGIT-SA_9U,45210
98
+ pydae/bmapu/lines/temp_trap_cffi.c,sha256=8fYuXK1AtdWQcQkU4T2M2tP9BMeycnd4Gz1GQLh-jjI,35014
99
+ pydae/bmapu/lines/temp_xy_0.json,sha256=CPFm4uAYlOUHuONVUyrJCc-ecch9fHk-I6HXmKH7k8A,102
100
+ pydae/bmapu/lines/xy_0.json,sha256=CPFm4uAYlOUHuONVUyrJCc-ecch9fHk-I6HXmKH7k8A,102
93
101
  pydae/bmapu/loads/__init__.py,sha256=rh5unTQUN14OzJ7B98byS7szyklhxQyipKDZdxN9pno,77
94
102
  pydae/bmapu/loads/load_zip.py,sha256=Y8DOXsDCTdznWH0mycZ0Zd6PG8f3HJo0ssfCnleMeVc,4734
95
103
  pydae/bmapu/loads/loads.py,sha256=7xDg4c5JjKWJKkMT4qWEGIoiU_eDu1GQ4q5Fky9bcls,1085
@@ -727,14 +735,15 @@ pydae/urisi/vsgs/vsg_lpf_ib.hjson,sha256=xpuo5fPCdO5Ze0X-KDCsa_eRpde5f5KDDFoDkWI
727
735
  pydae/urisi/vsgs/vsgs.py,sha256=v17sLm7EaaAyBVJk9-8DnLtxPDtXmkyizFlGhgqyFEE,644
728
736
  pydae/urisi/vsgs/xy_0.json,sha256=Q6qWPIqEP-w5Qu2twd3S4PX4zMggpNpG2W62e7ry70g,851
729
737
  pydae/utils/__init__.py,sha256=kwp76qeU2mnwrCexDSF15id9ih12Q0VRL3l-_c6omcQ,184
738
+ pydae/utils/dates.py,sha256=wWVhgprZ6Iu8garC8BASKrPWDtXtZY1_ss3iDMLE2mA,11349
730
739
  pydae/utils/ss_num2sym.py,sha256=ZA-x01y1mEWk1HdlpNv9rNspFdH0RBlTq_JRDHmjlpU,3209
731
740
  pydae/utils/svg2pdf.py,sha256=6tNORhlu_cMPDp264Kg3X_eBORfT9Q1cqAcWaWgDs7k,1861
732
741
  pydae/utils/tools.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
733
742
  pydae/utils/types_converter.py,sha256=3eAWwELgG1d6iFX8sn8iGPIp6NBaX4I-eyUriRDOFyE,7929
734
743
  pydae/utils/utils.py,sha256=AI0wZr4PJ3Td3vjfAW06FJdgcK01TpUAukhG37gZP5g,1079
735
744
  pydae/utils/emtp/readPL4.py,sha256=R59KhXTmFrqtdv-9nv5tkdHoTs9uoofGqEfBCF4fGEY,3088
736
- pydae-0.56.5.dist-info/licenses/COPYING,sha256=QWnWoslbTjQmtlFSRoGF3wxJEJuTJdlEMZ7lHtbGAeQ,1139
737
- pydae-0.56.5.dist-info/licenses/LICENSE,sha256=8hQe1oM4ySdliF3R-NEvR6HqrcGDKvsLFJC3gA1sNjY,1108
738
- pydae-0.56.5.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
739
- pydae-0.56.5.dist-info/METADATA,sha256=5BgEFFF3DBXyuF7qDDIwdfFpQo0WSZrkDY1bbYMJjQU,1090
740
- pydae-0.56.5.dist-info/RECORD,,
745
+ pydae-0.57.1.dist-info/licenses/COPYING,sha256=QWnWoslbTjQmtlFSRoGF3wxJEJuTJdlEMZ7lHtbGAeQ,1139
746
+ pydae-0.57.1.dist-info/licenses/LICENSE,sha256=8hQe1oM4ySdliF3R-NEvR6HqrcGDKvsLFJC3gA1sNjY,1108
747
+ pydae-0.57.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
748
+ pydae-0.57.1.dist-info/METADATA,sha256=JFOPnyIcoXWAmiFg-hoAZwyQ_QrkH-hKD45FgPG9vrs,1090
749
+ pydae-0.57.1.dist-info/RECORD,,
File without changes