forked from AMICI-dev/AMICI
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconserved_quantities_rref.py
More file actions
99 lines (82 loc) · 2.7 KB
/
Copy pathconserved_quantities_rref.py
File metadata and controls
99 lines (82 loc) · 2.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""Find conserved quantities deterministically"""
import numpy as np
__all__ = ["nullspace_by_rref", "rref"]
def rref(
mat: np.ndarray, round_ndigits: bool | int | None = None
) -> np.ndarray:
"""
Bring matrix ``mat`` to reduced row echelon form
see https://en.wikipedia.org/wiki/Row_echelon_form
:param mat: Numpy float matrix to operate on (will be copied)
:param round_ndigits: Number of digits to round intermediary results to,
or ``False`` to disable rounding completely.
Helps to avoid numerical artifacts.
:returns: ``mat`` in rref form.
"""
# Rounding function
if round_ndigits is False:
# no-op
def _round(mat):
return mat
else:
if round_ndigits is None:
# drop the least significant digit (more or less)
round_ndigits = -int(np.ceil(np.log10(np.spacing(1))))
def _round(mat):
mat = np.round(mat, round_ndigits)
mat[np.abs(mat) <= 10 ** (-round_ndigits)] = 0
return mat
# create a copy that will be modified
mat = mat.copy()
lead = 0
n_rows, n_columns = mat.shape
for r in range(n_rows):
if n_columns <= lead:
return mat
i = r
while mat[i, lead] == 0:
i += 1
if n_rows == i:
i = r
lead += 1
if n_columns == lead:
return mat
if i != r:
# Swap rows
mat[[i, r]] = mat[[r, i]]
# Divide row
mat[r] /= mat[r, lead]
for i in range(n_rows):
if i != r:
# Subtract multiple
mat[i] -= mat[i, lead] * mat[r]
mat = _round(mat)
lead += 1
return mat
def pivots(mat: np.ndarray) -> list[int]:
"""Get indices of pivot columns in ``mat``, assumed to be in reduced row
echelon form"""
pivot_cols = []
last_pivot_col = -1
for i in range(mat.shape[0]):
for j in range(last_pivot_col + 1, mat.shape[1]):
if mat[i, j] != 0:
pivot_cols.append(j)
last_pivot_col = j
break
return pivot_cols
def nullspace_by_rref(mat: np.ndarray) -> np.ndarray:
"""Compute basis of the nullspace of ``mat`` based on the reduced row
echelon form"""
rref_mat = rref(mat)
pivot_cols = pivots(rref_mat)
rows, cols = mat.shape
basis = []
for i in range(cols):
if i in pivot_cols:
continue
vec = [1.0 if i == j else 0.0 for j in range(cols)]
for pivot_row, pivot_col in enumerate(pivot_cols):
vec[pivot_col] -= rref_mat[pivot_row][i]
basis.append(vec)
return np.array(basis)