oakscriptpy 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.
- oakscriptpy/__init__.py +93 -0
- oakscriptpy/_metadata.py +118 -0
- oakscriptpy/_types.py +185 -0
- oakscriptpy/_utils.py +145 -0
- oakscriptpy/adapters/__init__.py +5 -0
- oakscriptpy/adapters/simple_input.py +63 -0
- oakscriptpy/array.py +342 -0
- oakscriptpy/box.py +151 -0
- oakscriptpy/chartpoint.py +21 -0
- oakscriptpy/color.py +134 -0
- oakscriptpy/indicator.py +82 -0
- oakscriptpy/input_.py +38 -0
- oakscriptpy/inputs.py +170 -0
- oakscriptpy/label.py +110 -0
- oakscriptpy/lib/__init__.py +5 -0
- oakscriptpy/lib/zigzag.py +158 -0
- oakscriptpy/line.py +120 -0
- oakscriptpy/linefill.py +26 -0
- oakscriptpy/math_.py +184 -0
- oakscriptpy/matrix.py +1136 -0
- oakscriptpy/plot_.py +49 -0
- oakscriptpy/polyline.py +60 -0
- oakscriptpy/runtime.py +150 -0
- oakscriptpy/runtime_types.py +52 -0
- oakscriptpy/series.py +292 -0
- oakscriptpy/str_.py +166 -0
- oakscriptpy/ta.py +1795 -0
- oakscriptpy/ta_series.py +353 -0
- oakscriptpy/time_.py +22 -0
- oakscriptpy-0.1.0.dist-info/METADATA +120 -0
- oakscriptpy-0.1.0.dist-info/RECORD +32 -0
- oakscriptpy-0.1.0.dist-info/WHEEL +4 -0
oakscriptpy/matrix.py
ADDED
|
@@ -0,0 +1,1136 @@
|
|
|
1
|
+
"""Matrix namespace — mirrors PineScript matrix.* functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import builtins as _builtins
|
|
6
|
+
import math
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from ._types import PineMatrix
|
|
10
|
+
|
|
11
|
+
builtins_max = _builtins.max
|
|
12
|
+
builtins_min = _builtins.min
|
|
13
|
+
|
|
14
|
+
EPSILON = 1e-10
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def new_matrix(rows: int = 0, columns: int = 0, initial_value: Any = None) -> PineMatrix:
|
|
18
|
+
data: list[list[Any]] = []
|
|
19
|
+
for i in range(rows):
|
|
20
|
+
row: list[Any] = []
|
|
21
|
+
for j in range(columns):
|
|
22
|
+
row.append(initial_value)
|
|
23
|
+
data.append(row)
|
|
24
|
+
return PineMatrix(rows=rows, columns=columns, data=data)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get(id: PineMatrix, row: int, column: int) -> Any:
|
|
28
|
+
if row < 0 or row >= id.rows or column < 0 or column >= id.columns:
|
|
29
|
+
raise ValueError(f"Matrix index out of bounds: [{row}, {column}] for matrix of size [{id.rows}, {id.columns}]")
|
|
30
|
+
return id.data[row][column]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def set(id: PineMatrix, row: int, column: int, value: Any) -> None:
|
|
34
|
+
if row < 0 or row >= id.rows or column < 0 or column >= id.columns:
|
|
35
|
+
raise ValueError(f"Matrix index out of bounds: [{row}, {column}] for matrix of size [{id.rows}, {id.columns}]")
|
|
36
|
+
id.data[row][column] = value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def rows(id: PineMatrix) -> int:
|
|
40
|
+
return id.rows
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def columns(id: PineMatrix) -> int:
|
|
44
|
+
return id.columns
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def elements_count(id: PineMatrix) -> int:
|
|
48
|
+
return id.rows * id.columns
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def row(id: PineMatrix, row_index: int) -> list[Any]:
|
|
52
|
+
if row_index < 0 or row_index >= id.rows:
|
|
53
|
+
raise ValueError(f"Row index out of bounds: {row_index} for matrix with {id.rows} rows")
|
|
54
|
+
return list(id.data[row_index])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def col(id: PineMatrix, column_index: int) -> list[Any]:
|
|
58
|
+
if column_index < 0 or column_index >= id.columns:
|
|
59
|
+
raise ValueError(f"Column index out of bounds: {column_index} for matrix with {id.columns} columns")
|
|
60
|
+
return [r[column_index] for r in id.data]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def copy_matrix(id: PineMatrix) -> PineMatrix:
|
|
64
|
+
new_data = [list(r) for r in id.data]
|
|
65
|
+
return PineMatrix(rows=id.rows, columns=id.columns, data=new_data)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def fill(
|
|
69
|
+
id: PineMatrix,
|
|
70
|
+
value: Any,
|
|
71
|
+
from_row: int = 0,
|
|
72
|
+
to_row: int | None = None,
|
|
73
|
+
from_column: int = 0,
|
|
74
|
+
to_column: int | None = None,
|
|
75
|
+
) -> None:
|
|
76
|
+
end_row = to_row if to_row is not None else id.rows
|
|
77
|
+
end_col = to_column if to_column is not None else id.columns
|
|
78
|
+
for i in range(from_row, end_row):
|
|
79
|
+
for j in range(from_column, end_col):
|
|
80
|
+
id.data[i][j] = value
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def is_square(id: PineMatrix) -> bool:
|
|
84
|
+
return id.rows == id.columns
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def is_zero(id: PineMatrix) -> bool:
|
|
88
|
+
for i in range(id.rows):
|
|
89
|
+
for j in range(id.columns):
|
|
90
|
+
if id.data[i][j] != 0:
|
|
91
|
+
return False
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def is_binary(id: PineMatrix) -> bool:
|
|
96
|
+
for i in range(id.rows):
|
|
97
|
+
for j in range(id.columns):
|
|
98
|
+
val = id.data[i][j]
|
|
99
|
+
if val != 0 and val != 1:
|
|
100
|
+
return False
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ==========================================
|
|
105
|
+
# Row/Column Operations
|
|
106
|
+
# ==========================================
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def add_row(id: PineMatrix, row_idx: int | None = None, array_id: list[Any] | None = None) -> None:
|
|
110
|
+
insert_index = row_idx if row_idx is not None else id.rows
|
|
111
|
+
|
|
112
|
+
if insert_index < 0 or insert_index > id.rows:
|
|
113
|
+
raise ValueError(f"Row index out of bounds: {insert_index} for matrix with {id.rows} rows")
|
|
114
|
+
|
|
115
|
+
if array_id is not None:
|
|
116
|
+
if id.columns > 0 and len(array_id) != id.columns:
|
|
117
|
+
raise ValueError(f"Array size {len(array_id)} does not match matrix columns {id.columns}")
|
|
118
|
+
new_row = list(array_id)
|
|
119
|
+
if id.rows == 0 and id.columns == 0:
|
|
120
|
+
id.columns = len(array_id)
|
|
121
|
+
else:
|
|
122
|
+
new_row = [None] * id.columns
|
|
123
|
+
|
|
124
|
+
id.data.insert(insert_index, new_row)
|
|
125
|
+
id.rows += 1
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def add_col(id: PineMatrix, column: int | None = None, array_id: list[Any] | None = None) -> None:
|
|
129
|
+
insert_index = column if column is not None else id.columns
|
|
130
|
+
|
|
131
|
+
if insert_index < 0 or insert_index > id.columns:
|
|
132
|
+
raise ValueError(f"Column index out of bounds: {insert_index} for matrix with {id.columns} columns")
|
|
133
|
+
|
|
134
|
+
if array_id is not None:
|
|
135
|
+
if id.rows > 0 and len(array_id) != id.rows:
|
|
136
|
+
raise ValueError(f"Array size {len(array_id)} does not match matrix rows {id.rows}")
|
|
137
|
+
if id.rows == 0 and id.columns == 0:
|
|
138
|
+
id.rows = len(array_id)
|
|
139
|
+
for i in range(len(array_id)):
|
|
140
|
+
id.data.append([])
|
|
141
|
+
for i in range(id.rows):
|
|
142
|
+
id.data[i].insert(insert_index, array_id[i])
|
|
143
|
+
else:
|
|
144
|
+
for i in range(id.rows):
|
|
145
|
+
id.data[i].insert(insert_index, None)
|
|
146
|
+
|
|
147
|
+
id.columns += 1
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def remove_row(id: PineMatrix, row_idx: int | None = None) -> list[Any]:
|
|
151
|
+
remove_index = row_idx if row_idx is not None else (id.rows - 1)
|
|
152
|
+
|
|
153
|
+
if remove_index < 0 or remove_index >= id.rows:
|
|
154
|
+
raise ValueError(f"Row index out of bounds: {remove_index} for matrix with {id.rows} rows")
|
|
155
|
+
|
|
156
|
+
removed_row = id.data.pop(remove_index)
|
|
157
|
+
id.rows -= 1
|
|
158
|
+
return removed_row
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def remove_col(id: PineMatrix, column: int | None = None) -> list[Any]:
|
|
162
|
+
remove_index = column if column is not None else (id.columns - 1)
|
|
163
|
+
|
|
164
|
+
if remove_index < 0 or remove_index >= id.columns:
|
|
165
|
+
raise ValueError(f"Column index out of bounds: {remove_index} for matrix with {id.columns} columns")
|
|
166
|
+
|
|
167
|
+
removed_col: list[Any] = []
|
|
168
|
+
for i in range(id.rows):
|
|
169
|
+
removed_col.append(id.data[i].pop(remove_index))
|
|
170
|
+
id.columns -= 1
|
|
171
|
+
return removed_col
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def swap_rows(id: PineMatrix, row1: int, row2: int) -> None:
|
|
175
|
+
if row1 < 0 or row1 >= id.rows:
|
|
176
|
+
raise ValueError(f"Row1 index out of bounds: {row1} for matrix with {id.rows} rows")
|
|
177
|
+
if row2 < 0 or row2 >= id.rows:
|
|
178
|
+
raise ValueError(f"Row2 index out of bounds: {row2} for matrix with {id.rows} rows")
|
|
179
|
+
id.data[row1], id.data[row2] = id.data[row2], id.data[row1]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def swap_columns(id: PineMatrix, column1: int, column2: int) -> None:
|
|
183
|
+
if column1 < 0 or column1 >= id.columns:
|
|
184
|
+
raise ValueError(f"Column1 index out of bounds: {column1} for matrix with {id.columns} columns")
|
|
185
|
+
if column2 < 0 or column2 >= id.columns:
|
|
186
|
+
raise ValueError(f"Column2 index out of bounds: {column2} for matrix with {id.columns} columns")
|
|
187
|
+
for i in range(id.rows):
|
|
188
|
+
id.data[i][column1], id.data[i][column2] = id.data[i][column2], id.data[i][column1]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ==========================================
|
|
192
|
+
# Matrix Transformations
|
|
193
|
+
# ==========================================
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def transpose(id: PineMatrix) -> PineMatrix:
|
|
197
|
+
new_data: list[list[Any]] = []
|
|
198
|
+
for j in range(id.columns):
|
|
199
|
+
new_row: list[Any] = []
|
|
200
|
+
for i in range(id.rows):
|
|
201
|
+
new_row.append(id.data[i][j])
|
|
202
|
+
new_data.append(new_row)
|
|
203
|
+
return PineMatrix(rows=id.columns, columns=id.rows, data=new_data)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def concat(id1: PineMatrix, id2: PineMatrix) -> PineMatrix:
|
|
207
|
+
if id1.columns != id2.columns:
|
|
208
|
+
raise ValueError(f"Column count mismatch: {id1.columns} vs {id2.columns}")
|
|
209
|
+
for i in range(id2.rows):
|
|
210
|
+
id1.data.append(list(id2.data[i]))
|
|
211
|
+
id1.rows += id2.rows
|
|
212
|
+
return id1
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def submatrix(
|
|
216
|
+
id: PineMatrix,
|
|
217
|
+
from_row: int = 0,
|
|
218
|
+
to_row: int | None = None,
|
|
219
|
+
from_column: int = 0,
|
|
220
|
+
to_column: int | None = None,
|
|
221
|
+
) -> PineMatrix:
|
|
222
|
+
end_row = to_row if to_row is not None else id.rows
|
|
223
|
+
end_col = to_column if to_column is not None else id.columns
|
|
224
|
+
|
|
225
|
+
if from_row < 0 or from_row > id.rows:
|
|
226
|
+
raise ValueError(f"from_row index out of bounds: {from_row}")
|
|
227
|
+
if end_row < 0 or end_row > id.rows:
|
|
228
|
+
raise ValueError(f"to_row index out of bounds: {end_row}")
|
|
229
|
+
if from_column < 0 or from_column > id.columns:
|
|
230
|
+
raise ValueError(f"from_column index out of bounds: {from_column}")
|
|
231
|
+
if end_col < 0 or end_col > id.columns:
|
|
232
|
+
raise ValueError(f"to_column index out of bounds: {end_col}")
|
|
233
|
+
|
|
234
|
+
new_rows = end_row - from_row
|
|
235
|
+
new_cols = end_col - from_column
|
|
236
|
+
new_data: list[list[Any]] = []
|
|
237
|
+
|
|
238
|
+
for i in range(from_row, end_row):
|
|
239
|
+
new_row: list[Any] = []
|
|
240
|
+
for j in range(from_column, end_col):
|
|
241
|
+
new_row.append(id.data[i][j])
|
|
242
|
+
new_data.append(new_row)
|
|
243
|
+
|
|
244
|
+
return PineMatrix(rows=new_rows, columns=new_cols, data=new_data)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def reshape(id: PineMatrix, rows_count: int, columns_count: int) -> None:
|
|
248
|
+
total_elements = id.rows * id.columns
|
|
249
|
+
new_total_elements = rows_count * columns_count
|
|
250
|
+
|
|
251
|
+
if total_elements != new_total_elements:
|
|
252
|
+
raise ValueError(
|
|
253
|
+
f"Cannot reshape {id.rows}x{id.columns} ({total_elements} elements) "
|
|
254
|
+
f"to {rows_count}x{columns_count} ({new_total_elements} elements)"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
flat: list[Any] = []
|
|
258
|
+
for i in range(id.rows):
|
|
259
|
+
for j in range(id.columns):
|
|
260
|
+
flat.append(id.data[i][j])
|
|
261
|
+
|
|
262
|
+
new_data: list[list[Any]] = []
|
|
263
|
+
index = 0
|
|
264
|
+
for i in range(rows_count):
|
|
265
|
+
new_row: list[Any] = []
|
|
266
|
+
for j in range(columns_count):
|
|
267
|
+
new_row.append(flat[index])
|
|
268
|
+
index += 1
|
|
269
|
+
new_data.append(new_row)
|
|
270
|
+
|
|
271
|
+
id.rows = rows_count
|
|
272
|
+
id.columns = columns_count
|
|
273
|
+
id.data = new_data
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def reverse(id: PineMatrix) -> None:
|
|
277
|
+
id.data.reverse()
|
|
278
|
+
for i in range(id.rows):
|
|
279
|
+
id.data[i].reverse()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def sort(id: PineMatrix, column: int = 0, order: str = "ascending") -> None:
|
|
283
|
+
if column < 0 or column >= id.columns:
|
|
284
|
+
raise ValueError(f"Column index out of bounds: {column} for matrix with {id.columns} columns")
|
|
285
|
+
|
|
286
|
+
def sort_key(a_row: list[Any]) -> Any:
|
|
287
|
+
val = a_row[column]
|
|
288
|
+
if isinstance(val, (int, float)):
|
|
289
|
+
return val
|
|
290
|
+
return val
|
|
291
|
+
|
|
292
|
+
reverse_flag = order == "descending"
|
|
293
|
+
id.data.sort(key=sort_key, reverse=reverse_flag)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# ==========================================
|
|
297
|
+
# Element-wise Arithmetic
|
|
298
|
+
# ==========================================
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def sum(id1: PineMatrix, id2: PineMatrix | float | int) -> PineMatrix:
|
|
302
|
+
new_data: list[list[float]] = []
|
|
303
|
+
|
|
304
|
+
if isinstance(id2, (int, float)):
|
|
305
|
+
for i in range(id1.rows):
|
|
306
|
+
new_row: list[float] = []
|
|
307
|
+
for j in range(id1.columns):
|
|
308
|
+
new_row.append(id1.data[i][j] + id2)
|
|
309
|
+
new_data.append(new_row)
|
|
310
|
+
return PineMatrix(rows=id1.rows, columns=id1.columns, data=new_data)
|
|
311
|
+
else:
|
|
312
|
+
if id1.rows != id2.rows or id1.columns != id2.columns:
|
|
313
|
+
raise ValueError(
|
|
314
|
+
f"Matrix dimensions must match: {id1.rows}x{id1.columns} vs {id2.rows}x{id2.columns}"
|
|
315
|
+
)
|
|
316
|
+
for i in range(id1.rows):
|
|
317
|
+
new_row = []
|
|
318
|
+
for j in range(id1.columns):
|
|
319
|
+
new_row.append(id1.data[i][j] + id2.data[i][j])
|
|
320
|
+
new_data.append(new_row)
|
|
321
|
+
return PineMatrix(rows=id1.rows, columns=id1.columns, data=new_data)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def diff(id1: PineMatrix, id2: PineMatrix | float | int) -> PineMatrix:
|
|
325
|
+
new_data: list[list[float]] = []
|
|
326
|
+
|
|
327
|
+
if isinstance(id2, (int, float)):
|
|
328
|
+
for i in range(id1.rows):
|
|
329
|
+
new_row: list[float] = []
|
|
330
|
+
for j in range(id1.columns):
|
|
331
|
+
new_row.append(id1.data[i][j] - id2)
|
|
332
|
+
new_data.append(new_row)
|
|
333
|
+
return PineMatrix(rows=id1.rows, columns=id1.columns, data=new_data)
|
|
334
|
+
else:
|
|
335
|
+
if id1.rows != id2.rows or id1.columns != id2.columns:
|
|
336
|
+
raise ValueError(
|
|
337
|
+
f"Matrix dimensions must match: {id1.rows}x{id1.columns} vs {id2.rows}x{id2.columns}"
|
|
338
|
+
)
|
|
339
|
+
for i in range(id1.rows):
|
|
340
|
+
new_row = []
|
|
341
|
+
for j in range(id1.columns):
|
|
342
|
+
new_row.append(id1.data[i][j] - id2.data[i][j])
|
|
343
|
+
new_data.append(new_row)
|
|
344
|
+
return PineMatrix(rows=id1.rows, columns=id1.columns, data=new_data)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# ==========================================
|
|
348
|
+
# Statistical Functions
|
|
349
|
+
# ==========================================
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _is_valid_number(val: Any) -> bool:
|
|
353
|
+
if val is None:
|
|
354
|
+
return False
|
|
355
|
+
if isinstance(val, float) and math.isnan(val):
|
|
356
|
+
return False
|
|
357
|
+
return isinstance(val, (int, float))
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def avg(id: PineMatrix) -> float:
|
|
361
|
+
if id.rows == 0 or id.columns == 0:
|
|
362
|
+
return float("nan")
|
|
363
|
+
|
|
364
|
+
total = 0.0
|
|
365
|
+
count = 0
|
|
366
|
+
for i in range(id.rows):
|
|
367
|
+
for j in range(id.columns):
|
|
368
|
+
val = id.data[i][j]
|
|
369
|
+
if _is_valid_number(val):
|
|
370
|
+
total += val
|
|
371
|
+
count += 1
|
|
372
|
+
|
|
373
|
+
return float("nan") if count == 0 else total / count
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def min(id: PineMatrix) -> float:
|
|
377
|
+
if id.rows == 0 or id.columns == 0:
|
|
378
|
+
return float("nan")
|
|
379
|
+
|
|
380
|
+
min_val = float("inf")
|
|
381
|
+
for i in range(id.rows):
|
|
382
|
+
for j in range(id.columns):
|
|
383
|
+
val = id.data[i][j]
|
|
384
|
+
if _is_valid_number(val) and val < min_val:
|
|
385
|
+
min_val = val
|
|
386
|
+
|
|
387
|
+
return float("nan") if min_val == float("inf") else min_val
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def max(id: PineMatrix) -> float:
|
|
391
|
+
if id.rows == 0 or id.columns == 0:
|
|
392
|
+
return float("nan")
|
|
393
|
+
|
|
394
|
+
max_val = float("-inf")
|
|
395
|
+
for i in range(id.rows):
|
|
396
|
+
for j in range(id.columns):
|
|
397
|
+
val = id.data[i][j]
|
|
398
|
+
if _is_valid_number(val) and val > max_val:
|
|
399
|
+
max_val = val
|
|
400
|
+
|
|
401
|
+
return float("nan") if max_val == float("-inf") else max_val
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def median(id: PineMatrix) -> float:
|
|
405
|
+
if id.rows == 0 or id.columns == 0:
|
|
406
|
+
return float("nan")
|
|
407
|
+
|
|
408
|
+
values: list[float] = []
|
|
409
|
+
for i in range(id.rows):
|
|
410
|
+
for j in range(id.columns):
|
|
411
|
+
val = id.data[i][j]
|
|
412
|
+
if _is_valid_number(val):
|
|
413
|
+
values.append(val)
|
|
414
|
+
|
|
415
|
+
if len(values) == 0:
|
|
416
|
+
return float("nan")
|
|
417
|
+
|
|
418
|
+
values.sort()
|
|
419
|
+
mid = len(values) // 2
|
|
420
|
+
|
|
421
|
+
if len(values) % 2 == 0:
|
|
422
|
+
return (values[mid - 1] + values[mid]) / 2
|
|
423
|
+
else:
|
|
424
|
+
return values[mid]
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def mode(id: PineMatrix) -> float:
|
|
428
|
+
if id.rows == 0 or id.columns == 0:
|
|
429
|
+
return float("nan")
|
|
430
|
+
|
|
431
|
+
frequency: dict[float, int] = {}
|
|
432
|
+
max_freq = 0
|
|
433
|
+
mode_value = float("nan")
|
|
434
|
+
|
|
435
|
+
for i in range(id.rows):
|
|
436
|
+
for j in range(id.columns):
|
|
437
|
+
val = id.data[i][j]
|
|
438
|
+
if _is_valid_number(val):
|
|
439
|
+
freq = frequency.get(val, 0) + 1
|
|
440
|
+
frequency[val] = freq
|
|
441
|
+
if freq > max_freq or (freq == max_freq and (math.isnan(mode_value) or val < mode_value)):
|
|
442
|
+
max_freq = freq
|
|
443
|
+
mode_value = val
|
|
444
|
+
|
|
445
|
+
return mode_value
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def trace(id: PineMatrix) -> float:
|
|
449
|
+
if not is_square(id):
|
|
450
|
+
raise ValueError(f"Matrix must be square for trace calculation: {id.rows}x{id.columns}")
|
|
451
|
+
|
|
452
|
+
if id.rows == 0:
|
|
453
|
+
return 0.0
|
|
454
|
+
|
|
455
|
+
total = 0.0
|
|
456
|
+
for i in range(id.rows):
|
|
457
|
+
val = id.data[i][i]
|
|
458
|
+
if _is_valid_number(val):
|
|
459
|
+
total += val
|
|
460
|
+
|
|
461
|
+
return total
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
# ==========================================
|
|
465
|
+
# Boolean Checks
|
|
466
|
+
# ==========================================
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def is_diagonal(id: PineMatrix) -> bool:
|
|
470
|
+
if not is_square(id):
|
|
471
|
+
return False
|
|
472
|
+
|
|
473
|
+
for i in range(id.rows):
|
|
474
|
+
for j in range(id.columns):
|
|
475
|
+
if i != j and id.data[i][j] != 0:
|
|
476
|
+
return False
|
|
477
|
+
|
|
478
|
+
return True
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def is_identity(id: PineMatrix) -> bool:
|
|
482
|
+
if not is_square(id):
|
|
483
|
+
return False
|
|
484
|
+
|
|
485
|
+
for i in range(id.rows):
|
|
486
|
+
for j in range(id.columns):
|
|
487
|
+
val = id.data[i][j]
|
|
488
|
+
if i == j:
|
|
489
|
+
if val != 1:
|
|
490
|
+
return False
|
|
491
|
+
else:
|
|
492
|
+
if val != 0:
|
|
493
|
+
return False
|
|
494
|
+
|
|
495
|
+
return True
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def is_symmetric(id: PineMatrix) -> bool:
|
|
499
|
+
if not is_square(id):
|
|
500
|
+
return False
|
|
501
|
+
|
|
502
|
+
for i in range(id.rows):
|
|
503
|
+
for j in range(i + 1, id.columns):
|
|
504
|
+
if id.data[i][j] != id.data[j][i]:
|
|
505
|
+
return False
|
|
506
|
+
|
|
507
|
+
return True
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def is_antisymmetric(id: PineMatrix) -> bool:
|
|
511
|
+
if not is_square(id):
|
|
512
|
+
return False
|
|
513
|
+
|
|
514
|
+
for i in range(id.rows):
|
|
515
|
+
if id.data[i][i] != 0:
|
|
516
|
+
return False
|
|
517
|
+
for j in range(i + 1, id.columns):
|
|
518
|
+
upper_val = id.data[i][j]
|
|
519
|
+
lower_val = id.data[j][i]
|
|
520
|
+
if upper_val != -lower_val:
|
|
521
|
+
return False
|
|
522
|
+
|
|
523
|
+
return True
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def is_triangular(id: PineMatrix) -> bool:
|
|
527
|
+
if not is_square(id):
|
|
528
|
+
return False
|
|
529
|
+
|
|
530
|
+
is_upper = True
|
|
531
|
+
for i in range(1, id.rows):
|
|
532
|
+
if not is_upper:
|
|
533
|
+
break
|
|
534
|
+
for j in range(0, i):
|
|
535
|
+
if id.data[i][j] != 0:
|
|
536
|
+
is_upper = False
|
|
537
|
+
break
|
|
538
|
+
|
|
539
|
+
if is_upper:
|
|
540
|
+
return True
|
|
541
|
+
|
|
542
|
+
is_lower = True
|
|
543
|
+
for i in range(0, id.rows - 1):
|
|
544
|
+
if not is_lower:
|
|
545
|
+
break
|
|
546
|
+
for j in range(i + 1, id.columns):
|
|
547
|
+
if id.data[i][j] != 0:
|
|
548
|
+
is_lower = False
|
|
549
|
+
break
|
|
550
|
+
|
|
551
|
+
return is_lower
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def is_antidiagonal(id: PineMatrix) -> bool:
|
|
555
|
+
if not is_square(id):
|
|
556
|
+
return False
|
|
557
|
+
|
|
558
|
+
n = id.rows
|
|
559
|
+
for i in range(n):
|
|
560
|
+
for j in range(n):
|
|
561
|
+
is_anti_diag = (i + j == n - 1)
|
|
562
|
+
if not is_anti_diag and id.data[i][j] != 0:
|
|
563
|
+
return False
|
|
564
|
+
|
|
565
|
+
return True
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def is_stochastic(id: PineMatrix) -> bool:
|
|
569
|
+
if id.rows == 0 or id.columns == 0:
|
|
570
|
+
return False
|
|
571
|
+
|
|
572
|
+
tolerance = 1e-10
|
|
573
|
+
|
|
574
|
+
for i in range(id.rows):
|
|
575
|
+
row_sum = 0.0
|
|
576
|
+
for j in range(id.columns):
|
|
577
|
+
val = id.data[i][j]
|
|
578
|
+
if val < 0:
|
|
579
|
+
return False
|
|
580
|
+
row_sum += val
|
|
581
|
+
if abs(row_sum - 1) > tolerance:
|
|
582
|
+
return False
|
|
583
|
+
|
|
584
|
+
return True
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
# ==========================================
|
|
588
|
+
# Linear Algebra Functions
|
|
589
|
+
# ==========================================
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def _create_identity(n: int) -> PineMatrix:
|
|
593
|
+
m = new_matrix(n, n, 0.0)
|
|
594
|
+
for i in range(n):
|
|
595
|
+
m.data[i][i] = 1.0
|
|
596
|
+
return m
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def mult(id1: PineMatrix, id2: PineMatrix | float | int | list[float]) -> PineMatrix | list[float]:
|
|
600
|
+
if isinstance(id2, (int, float)):
|
|
601
|
+
new_data: list[list[float]] = []
|
|
602
|
+
for i in range(id1.rows):
|
|
603
|
+
new_row: list[float] = []
|
|
604
|
+
for j in range(id1.columns):
|
|
605
|
+
new_row.append(id1.data[i][j] * id2)
|
|
606
|
+
new_data.append(new_row)
|
|
607
|
+
return PineMatrix(rows=id1.rows, columns=id1.columns, data=new_data)
|
|
608
|
+
|
|
609
|
+
if isinstance(id2, list):
|
|
610
|
+
vec = id2
|
|
611
|
+
if len(vec) != id1.columns:
|
|
612
|
+
raise ValueError(f"Vector length {len(vec)} must equal matrix columns {id1.columns}")
|
|
613
|
+
result: list[float] = []
|
|
614
|
+
for i in range(id1.rows):
|
|
615
|
+
s = 0.0
|
|
616
|
+
for j in range(id1.columns):
|
|
617
|
+
s += id1.data[i][j] * vec[j]
|
|
618
|
+
result.append(s)
|
|
619
|
+
return result
|
|
620
|
+
|
|
621
|
+
m2 = id2
|
|
622
|
+
if id1.columns != m2.rows:
|
|
623
|
+
raise ValueError(
|
|
624
|
+
f"Matrix multiplication dimension mismatch: {id1.rows}x{id1.columns} x {m2.rows}x{m2.columns}. "
|
|
625
|
+
f"First matrix columns ({id1.columns}) must equal second matrix rows ({m2.rows})"
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
new_rows = id1.rows
|
|
629
|
+
new_cols = m2.columns
|
|
630
|
+
new_data_m: list[list[float]] = []
|
|
631
|
+
|
|
632
|
+
for i in range(new_rows):
|
|
633
|
+
new_row_m: list[float] = []
|
|
634
|
+
for j in range(new_cols):
|
|
635
|
+
s = 0.0
|
|
636
|
+
for k in range(id1.columns):
|
|
637
|
+
s += id1.data[i][k] * m2.data[k][j]
|
|
638
|
+
new_row_m.append(s)
|
|
639
|
+
new_data_m.append(new_row_m)
|
|
640
|
+
|
|
641
|
+
return PineMatrix(rows=new_rows, columns=new_cols, data=new_data_m)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def pow(id: PineMatrix, power: int) -> PineMatrix:
|
|
645
|
+
if not is_square(id):
|
|
646
|
+
raise ValueError(f"Matrix must be square for power calculation: {id.rows}x{id.columns}")
|
|
647
|
+
|
|
648
|
+
n = id.rows
|
|
649
|
+
|
|
650
|
+
if power == 0:
|
|
651
|
+
return _create_identity(n)
|
|
652
|
+
|
|
653
|
+
if power < 0:
|
|
654
|
+
inverse = inv(id)
|
|
655
|
+
if inverse is None:
|
|
656
|
+
raise ValueError("Cannot compute negative power of singular matrix")
|
|
657
|
+
base = inverse
|
|
658
|
+
p = -power
|
|
659
|
+
else:
|
|
660
|
+
base = copy_matrix(id)
|
|
661
|
+
p = power
|
|
662
|
+
|
|
663
|
+
result = _create_identity(n)
|
|
664
|
+
|
|
665
|
+
while p > 0:
|
|
666
|
+
if p % 2 == 1:
|
|
667
|
+
result = mult(result, base) # type: ignore[assignment]
|
|
668
|
+
base = mult(base, base) # type: ignore[assignment]
|
|
669
|
+
p = p // 2
|
|
670
|
+
|
|
671
|
+
return result # type: ignore[return-value]
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def det(id: PineMatrix) -> float:
|
|
675
|
+
if not is_square(id):
|
|
676
|
+
raise ValueError(f"Matrix must be square for determinant calculation: {id.rows}x{id.columns}")
|
|
677
|
+
|
|
678
|
+
n = id.rows
|
|
679
|
+
|
|
680
|
+
if n == 0:
|
|
681
|
+
return 1.0
|
|
682
|
+
|
|
683
|
+
if n == 1:
|
|
684
|
+
return float(id.data[0][0])
|
|
685
|
+
|
|
686
|
+
if n == 2:
|
|
687
|
+
return float(id.data[0][0] * id.data[1][1] - id.data[0][1] * id.data[1][0])
|
|
688
|
+
|
|
689
|
+
a: list[list[float]] = [list(r) for r in id.data]
|
|
690
|
+
determinant = 1.0
|
|
691
|
+
swap_count = 0
|
|
692
|
+
|
|
693
|
+
for col_idx in range(n):
|
|
694
|
+
max_row = col_idx
|
|
695
|
+
max_val = abs(a[col_idx][col_idx])
|
|
696
|
+
for row_idx in range(col_idx + 1, n):
|
|
697
|
+
abs_val = abs(a[row_idx][col_idx])
|
|
698
|
+
if abs_val > max_val:
|
|
699
|
+
max_val = abs_val
|
|
700
|
+
max_row = row_idx
|
|
701
|
+
|
|
702
|
+
if max_row != col_idx:
|
|
703
|
+
a[col_idx], a[max_row] = a[max_row], a[col_idx]
|
|
704
|
+
swap_count += 1
|
|
705
|
+
|
|
706
|
+
pivot = a[col_idx][col_idx]
|
|
707
|
+
if abs(pivot) < EPSILON:
|
|
708
|
+
return 0.0
|
|
709
|
+
|
|
710
|
+
determinant *= pivot
|
|
711
|
+
|
|
712
|
+
for row_idx in range(col_idx + 1, n):
|
|
713
|
+
factor = a[row_idx][col_idx] / pivot
|
|
714
|
+
for j in range(col_idx, n):
|
|
715
|
+
a[row_idx][j] = a[row_idx][j] - factor * a[col_idx][j]
|
|
716
|
+
|
|
717
|
+
if swap_count % 2 == 1:
|
|
718
|
+
determinant = -determinant
|
|
719
|
+
|
|
720
|
+
return determinant
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def inv(id: PineMatrix) -> PineMatrix | None:
|
|
724
|
+
if not is_square(id):
|
|
725
|
+
raise ValueError(f"Matrix must be square for inverse calculation: {id.rows}x{id.columns}")
|
|
726
|
+
|
|
727
|
+
n = id.rows
|
|
728
|
+
|
|
729
|
+
if n == 0:
|
|
730
|
+
return new_matrix(0, 0, 0.0)
|
|
731
|
+
|
|
732
|
+
aug: list[list[float]] = []
|
|
733
|
+
for i in range(n):
|
|
734
|
+
row_data: list[float] = []
|
|
735
|
+
for j in range(n):
|
|
736
|
+
row_data.append(float(id.data[i][j]))
|
|
737
|
+
for j in range(n):
|
|
738
|
+
row_data.append(1.0 if i == j else 0.0)
|
|
739
|
+
aug.append(row_data)
|
|
740
|
+
|
|
741
|
+
for col_idx in range(n):
|
|
742
|
+
max_row = col_idx
|
|
743
|
+
max_val = abs(aug[col_idx][col_idx])
|
|
744
|
+
for row_idx in range(col_idx + 1, n):
|
|
745
|
+
abs_val = abs(aug[row_idx][col_idx])
|
|
746
|
+
if abs_val > max_val:
|
|
747
|
+
max_val = abs_val
|
|
748
|
+
max_row = row_idx
|
|
749
|
+
|
|
750
|
+
if max_row != col_idx:
|
|
751
|
+
aug[col_idx], aug[max_row] = aug[max_row], aug[col_idx]
|
|
752
|
+
|
|
753
|
+
pivot = aug[col_idx][col_idx]
|
|
754
|
+
if abs(pivot) < EPSILON:
|
|
755
|
+
return None
|
|
756
|
+
|
|
757
|
+
for j in range(2 * n):
|
|
758
|
+
aug[col_idx][j] = aug[col_idx][j] / pivot
|
|
759
|
+
|
|
760
|
+
for row_idx in range(n):
|
|
761
|
+
if row_idx != col_idx:
|
|
762
|
+
factor = aug[row_idx][col_idx]
|
|
763
|
+
for j in range(2 * n):
|
|
764
|
+
aug[row_idx][j] = aug[row_idx][j] - factor * aug[col_idx][j]
|
|
765
|
+
|
|
766
|
+
inverse_data: list[list[float]] = []
|
|
767
|
+
for i in range(n):
|
|
768
|
+
row_data = []
|
|
769
|
+
for j in range(n):
|
|
770
|
+
row_data.append(aug[i][n + j])
|
|
771
|
+
inverse_data.append(row_data)
|
|
772
|
+
|
|
773
|
+
return PineMatrix(rows=n, columns=n, data=inverse_data)
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def pinv(id: PineMatrix) -> PineMatrix:
|
|
777
|
+
m = id.rows
|
|
778
|
+
n = id.columns
|
|
779
|
+
|
|
780
|
+
if m == 0 or n == 0:
|
|
781
|
+
return new_matrix(n, m, 0.0)
|
|
782
|
+
|
|
783
|
+
if m == n:
|
|
784
|
+
inverse = inv(id)
|
|
785
|
+
if inverse is not None:
|
|
786
|
+
return inverse
|
|
787
|
+
|
|
788
|
+
at = transpose(id)
|
|
789
|
+
|
|
790
|
+
if m >= n:
|
|
791
|
+
ata = mult(at, id)
|
|
792
|
+
ata_inv = inv(ata) # type: ignore[arg-type]
|
|
793
|
+
if ata_inv is not None:
|
|
794
|
+
return mult(ata_inv, at) # type: ignore[return-value]
|
|
795
|
+
|
|
796
|
+
aat = mult(id, at)
|
|
797
|
+
aat_inv = inv(aat) # type: ignore[arg-type]
|
|
798
|
+
if aat_inv is not None:
|
|
799
|
+
return mult(at, aat_inv) # type: ignore[return-value]
|
|
800
|
+
|
|
801
|
+
lam = 1e-10
|
|
802
|
+
ata = mult(at, id)
|
|
803
|
+
for i in range(ata.rows): # type: ignore[union-attr]
|
|
804
|
+
ata.data[i][i] = ata.data[i][i] + lam # type: ignore[union-attr]
|
|
805
|
+
|
|
806
|
+
ata_inv = inv(ata) # type: ignore[arg-type]
|
|
807
|
+
if ata_inv is not None:
|
|
808
|
+
return mult(ata_inv, at) # type: ignore[return-value]
|
|
809
|
+
|
|
810
|
+
return new_matrix(n, m, 0.0)
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def rank(id: PineMatrix) -> int:
|
|
814
|
+
if id.rows == 0 or id.columns == 0:
|
|
815
|
+
return 0
|
|
816
|
+
|
|
817
|
+
a: list[list[float]] = [list(r) for r in id.data]
|
|
818
|
+
m = id.rows
|
|
819
|
+
n = id.columns
|
|
820
|
+
|
|
821
|
+
r = 0
|
|
822
|
+
|
|
823
|
+
for col_idx in range(n):
|
|
824
|
+
if r >= m:
|
|
825
|
+
break
|
|
826
|
+
|
|
827
|
+
max_row = r
|
|
828
|
+
max_val = abs(a[r][col_idx])
|
|
829
|
+
for row_idx in range(r + 1, m):
|
|
830
|
+
abs_val = abs(a[row_idx][col_idx])
|
|
831
|
+
if abs_val > max_val:
|
|
832
|
+
max_val = abs_val
|
|
833
|
+
max_row = row_idx
|
|
834
|
+
|
|
835
|
+
if max_val < EPSILON:
|
|
836
|
+
continue
|
|
837
|
+
|
|
838
|
+
if max_row != r:
|
|
839
|
+
a[r], a[max_row] = a[max_row], a[r]
|
|
840
|
+
|
|
841
|
+
pivot = a[r][col_idx]
|
|
842
|
+
for row_idx in range(r + 1, m):
|
|
843
|
+
factor = a[row_idx][col_idx] / pivot
|
|
844
|
+
for j in range(col_idx, n):
|
|
845
|
+
a[row_idx][j] = a[row_idx][j] - factor * a[r][j]
|
|
846
|
+
|
|
847
|
+
r += 1
|
|
848
|
+
|
|
849
|
+
return r
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def eigenvalues(id: PineMatrix) -> list[float]:
|
|
853
|
+
if not is_square(id):
|
|
854
|
+
raise ValueError(f"Matrix must be square for eigenvalue calculation: {id.rows}x{id.columns}")
|
|
855
|
+
|
|
856
|
+
n = id.rows
|
|
857
|
+
|
|
858
|
+
if n == 0:
|
|
859
|
+
return []
|
|
860
|
+
|
|
861
|
+
if n == 1:
|
|
862
|
+
return [float(id.data[0][0])]
|
|
863
|
+
|
|
864
|
+
if n == 2:
|
|
865
|
+
a = id.data[0][0]
|
|
866
|
+
b = id.data[0][1]
|
|
867
|
+
c = id.data[1][0]
|
|
868
|
+
d = id.data[1][1]
|
|
869
|
+
|
|
870
|
+
tr = a + d
|
|
871
|
+
determinant = a * d - b * c
|
|
872
|
+
discriminant = tr * tr - 4 * determinant
|
|
873
|
+
|
|
874
|
+
if discriminant >= 0:
|
|
875
|
+
sqrt_disc = math.sqrt(discriminant)
|
|
876
|
+
return [(tr + sqrt_disc) / 2, (tr - sqrt_disc) / 2]
|
|
877
|
+
else:
|
|
878
|
+
return [tr / 2, tr / 2]
|
|
879
|
+
|
|
880
|
+
h = _to_hessenberg(id)
|
|
881
|
+
result = _qr_algorithm(h, 100)
|
|
882
|
+
return result
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
def _to_hessenberg(m: PineMatrix) -> PineMatrix:
|
|
886
|
+
n = m.rows
|
|
887
|
+
h: list[list[float]] = [list(r) for r in m.data]
|
|
888
|
+
|
|
889
|
+
for k in range(n - 2):
|
|
890
|
+
max_val = 0.0
|
|
891
|
+
for i in range(k + 1, n):
|
|
892
|
+
max_val = builtins_max(max_val, abs(h[i][k]))
|
|
893
|
+
|
|
894
|
+
if max_val < EPSILON:
|
|
895
|
+
continue
|
|
896
|
+
|
|
897
|
+
sigma = 0.0
|
|
898
|
+
for i in range(k + 1, n):
|
|
899
|
+
sigma += h[i][k] * h[i][k]
|
|
900
|
+
sigma = math.sqrt(sigma)
|
|
901
|
+
|
|
902
|
+
if h[k + 1][k] < 0:
|
|
903
|
+
sigma = -sigma
|
|
904
|
+
|
|
905
|
+
u: list[float] = [0.0] * n
|
|
906
|
+
u[k + 1] = h[k + 1][k] + sigma
|
|
907
|
+
for i in range(k + 2, n):
|
|
908
|
+
u[i] = h[i][k]
|
|
909
|
+
|
|
910
|
+
u_tu = 0.0
|
|
911
|
+
for i in range(k + 1, n):
|
|
912
|
+
u_tu += u[i] * u[i]
|
|
913
|
+
|
|
914
|
+
if u_tu < EPSILON:
|
|
915
|
+
continue
|
|
916
|
+
|
|
917
|
+
for j in range(k, n):
|
|
918
|
+
dot = 0.0
|
|
919
|
+
for i in range(k + 1, n):
|
|
920
|
+
dot += u[i] * h[i][j]
|
|
921
|
+
factor = 2 * dot / u_tu
|
|
922
|
+
for i in range(k + 1, n):
|
|
923
|
+
h[i][j] = h[i][j] - factor * u[i]
|
|
924
|
+
|
|
925
|
+
for i in range(n):
|
|
926
|
+
dot = 0.0
|
|
927
|
+
for j in range(k + 1, n):
|
|
928
|
+
dot += h[i][j] * u[j]
|
|
929
|
+
factor = 2 * dot / u_tu
|
|
930
|
+
for j in range(k + 1, n):
|
|
931
|
+
h[i][j] = h[i][j] - factor * u[j]
|
|
932
|
+
|
|
933
|
+
return PineMatrix(rows=n, columns=n, data=h)
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def _qr_algorithm(h: PineMatrix, max_iter: int) -> list[float]:
|
|
937
|
+
n = h.rows
|
|
938
|
+
a: list[list[float]] = [list(r) for r in h.data]
|
|
939
|
+
eigenvals: list[float] = []
|
|
940
|
+
|
|
941
|
+
remaining = n
|
|
942
|
+
|
|
943
|
+
for _iter in range(max_iter):
|
|
944
|
+
if remaining <= 1:
|
|
945
|
+
break
|
|
946
|
+
|
|
947
|
+
converged = False
|
|
948
|
+
for i in range(remaining - 1, 0, -1):
|
|
949
|
+
if abs(a[i][i - 1]) < EPSILON * (abs(a[i - 1][i - 1]) + abs(a[i][i])):
|
|
950
|
+
a[i][i - 1] = 0.0
|
|
951
|
+
if i == remaining - 1:
|
|
952
|
+
eigenvals.append(a[remaining - 1][remaining - 1])
|
|
953
|
+
remaining -= 1
|
|
954
|
+
converged = True
|
|
955
|
+
break
|
|
956
|
+
|
|
957
|
+
if converged:
|
|
958
|
+
continue
|
|
959
|
+
if remaining <= 1:
|
|
960
|
+
break
|
|
961
|
+
|
|
962
|
+
d = (a[remaining - 2][remaining - 2] - a[remaining - 1][remaining - 1]) / 2
|
|
963
|
+
sign = 1 if d >= 0 else -1
|
|
964
|
+
sub_diag_sq = a[remaining - 1][remaining - 2] * a[remaining - 1][remaining - 2]
|
|
965
|
+
mu = a[remaining - 1][remaining - 1] - sign * sub_diag_sq / (abs(d) + math.sqrt(d * d + sub_diag_sq))
|
|
966
|
+
|
|
967
|
+
x = a[0][0] - mu
|
|
968
|
+
z = a[1][0]
|
|
969
|
+
|
|
970
|
+
for k in range(remaining - 1):
|
|
971
|
+
r = math.sqrt(x * x + z * z)
|
|
972
|
+
if r < EPSILON:
|
|
973
|
+
r = EPSILON
|
|
974
|
+
c = x / r
|
|
975
|
+
s = z / r
|
|
976
|
+
|
|
977
|
+
for j in range(builtins_max(0, k - 1), remaining):
|
|
978
|
+
temp = c * a[k][j] + s * a[k + 1][j]
|
|
979
|
+
a[k + 1][j] = -s * a[k][j] + c * a[k + 1][j]
|
|
980
|
+
a[k][j] = temp
|
|
981
|
+
|
|
982
|
+
for i in range(builtins_min(k + 3, remaining)):
|
|
983
|
+
temp = c * a[i][k] + s * a[i][k + 1]
|
|
984
|
+
a[i][k + 1] = -s * a[i][k] + c * a[i][k + 1]
|
|
985
|
+
a[i][k] = temp
|
|
986
|
+
|
|
987
|
+
if k < remaining - 2:
|
|
988
|
+
x = a[k + 1][k]
|
|
989
|
+
z = a[k + 2][k]
|
|
990
|
+
|
|
991
|
+
for i in range(remaining):
|
|
992
|
+
eigenvals.append(a[i][i])
|
|
993
|
+
|
|
994
|
+
eigenvals.sort(key=lambda v: abs(v), reverse=True)
|
|
995
|
+
return eigenvals
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def eigenvectors(id: PineMatrix) -> PineMatrix:
|
|
999
|
+
if not is_square(id):
|
|
1000
|
+
raise ValueError(f"Matrix must be square for eigenvector calculation: {id.rows}x{id.columns}")
|
|
1001
|
+
|
|
1002
|
+
n = id.rows
|
|
1003
|
+
|
|
1004
|
+
if n == 0:
|
|
1005
|
+
return new_matrix(0, 0, 0.0)
|
|
1006
|
+
|
|
1007
|
+
evals = eigenvalues(id)
|
|
1008
|
+
|
|
1009
|
+
vectors: list[list[float]] = []
|
|
1010
|
+
for i in range(n):
|
|
1011
|
+
lam = evals[i]
|
|
1012
|
+
vec = _inverse_iteration(id, lam)
|
|
1013
|
+
vectors.append(vec)
|
|
1014
|
+
|
|
1015
|
+
result_data: list[list[float]] = []
|
|
1016
|
+
for i in range(n):
|
|
1017
|
+
row_data: list[float] = []
|
|
1018
|
+
for j in range(n):
|
|
1019
|
+
row_data.append(vectors[j][i])
|
|
1020
|
+
result_data.append(row_data)
|
|
1021
|
+
|
|
1022
|
+
return PineMatrix(rows=n, columns=n, data=result_data)
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
def _inverse_iteration(m: PineMatrix, lam: float) -> list[float]:
|
|
1026
|
+
n = m.rows
|
|
1027
|
+
|
|
1028
|
+
shifted: list[list[float]] = []
|
|
1029
|
+
for i in range(n):
|
|
1030
|
+
row_data: list[float] = []
|
|
1031
|
+
for j in range(n):
|
|
1032
|
+
val = m.data[i][j]
|
|
1033
|
+
if i == j:
|
|
1034
|
+
val = val - lam
|
|
1035
|
+
row_data.append(float(val))
|
|
1036
|
+
shifted.append(row_data)
|
|
1037
|
+
|
|
1038
|
+
for i in range(n):
|
|
1039
|
+
if abs(shifted[i][i]) < EPSILON:
|
|
1040
|
+
shifted[i][i] = EPSILON
|
|
1041
|
+
|
|
1042
|
+
v: list[float] = [1.0] * n
|
|
1043
|
+
|
|
1044
|
+
for _iter in range(50):
|
|
1045
|
+
w = _solve_linear_system(shifted, v)
|
|
1046
|
+
|
|
1047
|
+
norm = 0.0
|
|
1048
|
+
for i in range(n):
|
|
1049
|
+
norm += w[i] * w[i]
|
|
1050
|
+
norm = math.sqrt(norm)
|
|
1051
|
+
|
|
1052
|
+
if norm < EPSILON:
|
|
1053
|
+
return v
|
|
1054
|
+
|
|
1055
|
+
new_v: list[float] = []
|
|
1056
|
+
for i in range(n):
|
|
1057
|
+
new_v.append(w[i] / norm)
|
|
1058
|
+
|
|
1059
|
+
diff_val = 0.0
|
|
1060
|
+
for i in range(n):
|
|
1061
|
+
diff_val += abs(abs(new_v[i]) - abs(v[i]))
|
|
1062
|
+
|
|
1063
|
+
v = new_v
|
|
1064
|
+
|
|
1065
|
+
if diff_val < EPSILON:
|
|
1066
|
+
break
|
|
1067
|
+
|
|
1068
|
+
return v
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def _solve_linear_system(a: list[list[float]], b: list[float]) -> list[float]:
|
|
1072
|
+
n = len(a)
|
|
1073
|
+
|
|
1074
|
+
aug: list[list[float]] = []
|
|
1075
|
+
for i in range(n):
|
|
1076
|
+
aug.append(list(a[i]) + [b[i]])
|
|
1077
|
+
|
|
1078
|
+
for col_idx in range(n):
|
|
1079
|
+
max_row = col_idx
|
|
1080
|
+
max_val = abs(aug[col_idx][col_idx])
|
|
1081
|
+
for row_idx in range(col_idx + 1, n):
|
|
1082
|
+
abs_val = abs(aug[row_idx][col_idx])
|
|
1083
|
+
if abs_val > max_val:
|
|
1084
|
+
max_val = abs_val
|
|
1085
|
+
max_row = row_idx
|
|
1086
|
+
|
|
1087
|
+
if max_row != col_idx:
|
|
1088
|
+
aug[col_idx], aug[max_row] = aug[max_row], aug[col_idx]
|
|
1089
|
+
|
|
1090
|
+
pivot = aug[col_idx][col_idx]
|
|
1091
|
+
if abs(pivot) < EPSILON:
|
|
1092
|
+
continue
|
|
1093
|
+
|
|
1094
|
+
for row_idx in range(col_idx + 1, n):
|
|
1095
|
+
factor = aug[row_idx][col_idx] / pivot
|
|
1096
|
+
for j in range(col_idx, n + 1):
|
|
1097
|
+
aug[row_idx][j] = aug[row_idx][j] - factor * aug[col_idx][j]
|
|
1098
|
+
|
|
1099
|
+
x: list[float] = [0.0] * n
|
|
1100
|
+
for i in range(n - 1, -1, -1):
|
|
1101
|
+
s = aug[i][n]
|
|
1102
|
+
for j in range(i + 1, n):
|
|
1103
|
+
s -= aug[i][j] * x[j]
|
|
1104
|
+
diag = aug[i][i]
|
|
1105
|
+
x[i] = 0.0 if abs(diag) < EPSILON else s / diag
|
|
1106
|
+
|
|
1107
|
+
return x
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
def kron(id1: PineMatrix, id2: PineMatrix) -> PineMatrix:
|
|
1111
|
+
m1 = id1.rows
|
|
1112
|
+
n1 = id1.columns
|
|
1113
|
+
m2 = id2.rows
|
|
1114
|
+
n2 = id2.columns
|
|
1115
|
+
|
|
1116
|
+
result_rows = m1 * m2
|
|
1117
|
+
result_cols = n1 * n2
|
|
1118
|
+
data: list[list[float]] = []
|
|
1119
|
+
|
|
1120
|
+
for i in range(result_rows):
|
|
1121
|
+
data.append([0.0] * result_cols)
|
|
1122
|
+
|
|
1123
|
+
for i1 in range(m1):
|
|
1124
|
+
for j1 in range(n1):
|
|
1125
|
+
a = id1.data[i1][j1]
|
|
1126
|
+
for i2 in range(m2):
|
|
1127
|
+
for j2 in range(n2):
|
|
1128
|
+
r = i1 * m2 + i2
|
|
1129
|
+
c = j1 * n2 + j2
|
|
1130
|
+
data[r][c] = a * id2.data[i2][j2]
|
|
1131
|
+
|
|
1132
|
+
return PineMatrix(rows=result_rows, columns=result_cols, data=data)
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
def newtype(rows_count: int = 0, columns_count: int = 0, initial_value: Any = None) -> PineMatrix:
|
|
1136
|
+
return new_matrix(rows_count, columns_count, initial_value)
|