regdiff 3.6.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.
- regdiff/__init__.py +1 -0
- regdiff/regdiff.py +240 -0
- regdiff-3.6.1.dist-info/METADATA +7 -0
- regdiff-3.6.1.dist-info/RECORD +6 -0
- regdiff-3.6.1.dist-info/WHEEL +4 -0
- regdiff-3.6.1.dist-info/entry_points.txt +3 -0
regdiff/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .regdiff import finite_diff, matrix_anti_diff, matrix_diff, reg_diff # noqa: F401
|
regdiff/regdiff.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import numpy.linalg as npl
|
|
3
|
+
import scipy as sp
|
|
4
|
+
|
|
5
|
+
# NOTE - all matrix/vector operations are called using np.matvel/np.subtract, etc. Only scalars should multiply ndarrays.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def finite_diff(x):
|
|
9
|
+
"""take the finite difference of the input vector. Performs no validation.
|
|
10
|
+
:param np.ndarray(x): data to take the finite difference of -- 1-dimensional array of length n
|
|
11
|
+
|
|
12
|
+
:return: diff(x) -- 1 dimensional array of length n-1
|
|
13
|
+
"""
|
|
14
|
+
return np.subtract(x[1:], x[:-1])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def matrix_diff(n, dx=1.0):
|
|
18
|
+
"""return the matrix representations of the first-order central finite first difference, first-order central second
|
|
19
|
+
finite difference, and simple finite difference for a specified size and delta x increment.
|
|
20
|
+
:param n: the size of the requested matrix
|
|
21
|
+
:param dx: the time step
|
|
22
|
+
|
|
23
|
+
:return: diff -- first order central finite first difference matrix of size n with free boundary conditions
|
|
24
|
+
diff2 -- first order central finite second difference matrix of size n with free boundary conditions
|
|
25
|
+
d -- simple finite differnceing matrix (nxn-1)
|
|
26
|
+
"""
|
|
27
|
+
# first central difference
|
|
28
|
+
mul_step = 1 / (2 * dx)
|
|
29
|
+
c = mul_step * np.ones(n - 1)
|
|
30
|
+
diff = sp.sparse.diags_array([-c, c], offsets=[-1, 1]).toarray()
|
|
31
|
+
diff[0, 0:3] = mul_step * np.asarray([-3, 4, -1])
|
|
32
|
+
diff[-1, -3:] = mul_step * np.asarray([1, -4, 3])
|
|
33
|
+
|
|
34
|
+
# second central difference
|
|
35
|
+
mul_step = 1 / (dx**2)
|
|
36
|
+
c = mul_step * np.ones(n)
|
|
37
|
+
diff2 = sp.sparse.diags_array(
|
|
38
|
+
[c[0:-1], -2 * c, c[0:-1]], offsets=[-1, 0, 1]
|
|
39
|
+
).toarray()
|
|
40
|
+
diff2[0, 0:4] = mul_step * np.asarray([2, -5, 4, -1])
|
|
41
|
+
diff2[-1, -4:] = mul_step * np.asarray([-1, 4, -5, 2])
|
|
42
|
+
|
|
43
|
+
# first order differencing matrix
|
|
44
|
+
mul_step = 1 / dx
|
|
45
|
+
c = mul_step * np.ones(n)
|
|
46
|
+
d = sp.sparse.diags_array([-c, c], offsets=[0, 1], shape=[n - 1, n]).toarray()
|
|
47
|
+
|
|
48
|
+
return diff, diff2, d
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def matrix_anti_diff(n, dx=1.0):
|
|
52
|
+
"""return the matrix representation of the antidifferentiation operator for an input of size n
|
|
53
|
+
:param n: the size of the requested matrix
|
|
54
|
+
:param dx: the time step
|
|
55
|
+
|
|
56
|
+
:return: a -- cumulative trapezoidal integration matrix that when multiplied by a vector yields the cumulative integral (without the first index) (nxn-1)
|
|
57
|
+
a2 -- cumulative trapezoidal double integration matrix that when multiplied by a vector yields the cumulative double integral (without the first index) (nxn-1)
|
|
58
|
+
"""
|
|
59
|
+
c = 0.5 * dx * np.ones(n)
|
|
60
|
+
a = np.add(dx * np.tri(n, k=-1), np.diag(c))
|
|
61
|
+
a[:, 0] = 0.5 * dx
|
|
62
|
+
a[0, 0] = 0
|
|
63
|
+
|
|
64
|
+
a2 = np.matmul(a, a)
|
|
65
|
+
|
|
66
|
+
a = a[1:, :]
|
|
67
|
+
a2 = a2[1:, :]
|
|
68
|
+
|
|
69
|
+
return a, a2
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def reg_diff(
|
|
73
|
+
raw_data,
|
|
74
|
+
alpha,
|
|
75
|
+
alpha2=None,
|
|
76
|
+
dx=1,
|
|
77
|
+
u0=None,
|
|
78
|
+
min_iter=5,
|
|
79
|
+
max_iter=100000,
|
|
80
|
+
delta_cost_tol=1e-6,
|
|
81
|
+
delta_norm_tol=1e-6,
|
|
82
|
+
ep=1e-8,
|
|
83
|
+
diag=False,
|
|
84
|
+
):
|
|
85
|
+
"""differentation function that takes a vector of position data and returns the first and second derivatives, evaluated using TV-regularized differentiation.
|
|
86
|
+
:param data: the input data
|
|
87
|
+
:param alpha: the regularization coefficient for the first derivative
|
|
88
|
+
:param alpha2: the regularization coeffiient for the second derivative. If left blank only the first derivative will be calculated.
|
|
89
|
+
:param dx: the time step for the data
|
|
90
|
+
:param u0: an initial guess for the derivative(s) as an ndarray or list of ndarrays
|
|
91
|
+
:param min_iter: minimal number of iterations to calculate before returning. Default value 5.
|
|
92
|
+
:param max_iter: maximum number of iterations to calculate before returning. Default value is 1000000.
|
|
93
|
+
:param delta_cost_tol: relative delta cost to reach iteratively before returning (applies to both derivatives). Default value is 1e-6.
|
|
94
|
+
:param delta_norm_tol: relative delta norm of step to reach iteratively before returning (applies to both derivatives). Default value is 1e-6.
|
|
95
|
+
:param ep: epsilon to use to smooth the absolute value function. Devault value is 1e-8
|
|
96
|
+
:param diag: if True, prints diagnostic information during execution. Default False.
|
|
97
|
+
|
|
98
|
+
:return: u -- the first derivative
|
|
99
|
+
v -- the second derivative
|
|
100
|
+
"""
|
|
101
|
+
data = np.asarray(raw_data, copy=True)
|
|
102
|
+
n = len(data)
|
|
103
|
+
data_mean = data.mean()
|
|
104
|
+
data_st_dev = data.std()
|
|
105
|
+
data = (data - data_mean) / data_st_dev
|
|
106
|
+
|
|
107
|
+
if alpha2:
|
|
108
|
+
second_deriv = True
|
|
109
|
+
else:
|
|
110
|
+
second_deriv = False
|
|
111
|
+
|
|
112
|
+
diff, diff2, d = matrix_diff(n)
|
|
113
|
+
d_tp = np.transpose(d)
|
|
114
|
+
|
|
115
|
+
a, a2 = matrix_anti_diff(n)
|
|
116
|
+
a_tp = np.transpose(a)
|
|
117
|
+
a_tp_a = np.matmul(a_tp, a)
|
|
118
|
+
a_tp_a2 = np.matmul(a_tp, a2)
|
|
119
|
+
|
|
120
|
+
# bunch of code for handling an input u0
|
|
121
|
+
u = None
|
|
122
|
+
v = None
|
|
123
|
+
if u0 is not None:
|
|
124
|
+
if isinstance(u0, list):
|
|
125
|
+
if len(u0) == n:
|
|
126
|
+
u = np.asarray(u0, copy=True) * dx / data_st_dev
|
|
127
|
+
elif len(u0) == 2 * n:
|
|
128
|
+
u = np.asarray(u0[:n], copy=True) * dx / data_st_dev
|
|
129
|
+
v = np.asarray(u0[n:], copy=True) * (dx**2) / data_st_dev
|
|
130
|
+
elif len(u0) == 2 and len(u0[0]) == n and len(u0[1]) == n:
|
|
131
|
+
u = np.asarray(u0[0], copy=True) * dx / data_st_dev
|
|
132
|
+
v = np.asarray(u0[1], copy=True) * (dx**2) / data_st_dev
|
|
133
|
+
elif isinstance(u0, np.ndarray):
|
|
134
|
+
if u0.shape[0] == 2 and u0.shape[1] == n:
|
|
135
|
+
u = np.asarray(u0[0, :], copy=True) * dx / data_st_dev
|
|
136
|
+
v = np.asarray(u0[1, :], copy=True) * (dx**2) / data_st_dev
|
|
137
|
+
elif u0.shape[1] == 2 and u0.shape[0] == n:
|
|
138
|
+
u = np.asarray(u0[:, 0], copy=True) * dx / data_st_dev
|
|
139
|
+
v = np.asarray(u0[:, 1], copy=True) * (dx**2) / data_st_dev
|
|
140
|
+
if not isinstance(u, np.ndarray):
|
|
141
|
+
u = np.matvec(diff, data)
|
|
142
|
+
if not isinstance(v, np.ndarray) and second_deriv:
|
|
143
|
+
v = np.matvec(diff2, data)
|
|
144
|
+
|
|
145
|
+
# start calculating u
|
|
146
|
+
ofst_1 = data[0]
|
|
147
|
+
data_comp = data[1:] - ofst_1
|
|
148
|
+
a_tp_b = np.matvec(a_tp, data_comp)
|
|
149
|
+
if diag:
|
|
150
|
+
u_cost = [
|
|
151
|
+
npl.norm(np.subtract(sp.integrate.cumulative_trapezoid(u), data_comp))
|
|
152
|
+
+ alpha * npl.norm(finite_diff(u), ord=1)
|
|
153
|
+
]
|
|
154
|
+
prev_cost = None
|
|
155
|
+
for iter in range(max_iter):
|
|
156
|
+
c = np.reciprocal(np.sqrt(np.power(finite_diff(u), 2) + ep))
|
|
157
|
+
En = np.diag(c)
|
|
158
|
+
Ln = np.matmul(np.matmul(d_tp, En), d)
|
|
159
|
+
gn = np.add(np.subtract(np.matvec(a_tp_a, u), a_tp_b), alpha * np.matvec(Ln, u))
|
|
160
|
+
Hn = np.add(a_tp_a, alpha * Ln)
|
|
161
|
+
s = npl.solve(Hn, gn)
|
|
162
|
+
u = np.subtract(u, s)
|
|
163
|
+
cost_n = npl.norm(
|
|
164
|
+
np.subtract(sp.integrate.cumulative_trapezoid(u), data_comp)
|
|
165
|
+
) + alpha * npl.norm(finite_diff(u), ord=1)
|
|
166
|
+
if diag:
|
|
167
|
+
print(
|
|
168
|
+
f"u, iteration {iter + 1}: cost: {cost_n}, relative change: {npl.norm(s) / npl.norm(u)}"
|
|
169
|
+
)
|
|
170
|
+
u_cost.append(cost_n)
|
|
171
|
+
if iter >= min_iter and npl.norm(s) / npl.norm(u) < delta_norm_tol:
|
|
172
|
+
if diag:
|
|
173
|
+
print("u, delta norm tolerance reached")
|
|
174
|
+
break
|
|
175
|
+
if (
|
|
176
|
+
prev_cost
|
|
177
|
+
and iter >= min_iter
|
|
178
|
+
and (prev_cost - cost_n) / prev_cost < delta_cost_tol
|
|
179
|
+
):
|
|
180
|
+
if diag:
|
|
181
|
+
print("u, relative cost tolerance reached")
|
|
182
|
+
break
|
|
183
|
+
prev_cost = cost_n
|
|
184
|
+
|
|
185
|
+
if second_deriv:
|
|
186
|
+
ofst_2 = u[0]
|
|
187
|
+
data_comp = np.subtract(data[1:] - ofst_1, ofst_2 * np.arange(1, n))
|
|
188
|
+
a_tp_b = np.matvec(a_tp, data_comp)
|
|
189
|
+
if diag:
|
|
190
|
+
v_cost = [
|
|
191
|
+
npl.norm(
|
|
192
|
+
np.subtract(
|
|
193
|
+
sp.integrate.cumulative_trapezoid(
|
|
194
|
+
sp.integrate.cumulative_trapezoid(v, initial=0)
|
|
195
|
+
),
|
|
196
|
+
data_comp,
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
+ alpha2 * npl.norm(finite_diff(v), ord=1)
|
|
200
|
+
]
|
|
201
|
+
prev_cost = None
|
|
202
|
+
for iter in range(max_iter):
|
|
203
|
+
c = np.reciprocal(np.sqrt(np.power(finite_diff(v), 2) + ep))
|
|
204
|
+
En = np.diag(c)
|
|
205
|
+
Ln = np.matmul(np.matmul(d_tp, En), d)
|
|
206
|
+
gn = np.add(
|
|
207
|
+
np.subtract(np.matvec(a_tp_a2, v), a_tp_b), alpha2 * np.matvec(Ln, v)
|
|
208
|
+
)
|
|
209
|
+
Hn = np.add(a_tp_a2, alpha2 * Ln)
|
|
210
|
+
s = npl.solve(Hn, gn)
|
|
211
|
+
v = np.subtract(v, s)
|
|
212
|
+
cost_n = npl.norm(
|
|
213
|
+
np.subtract(
|
|
214
|
+
sp.integrate.cumulative_trapezoid(
|
|
215
|
+
sp.integrate.cumulative_trapezoid(v, initial=0)
|
|
216
|
+
),
|
|
217
|
+
data_comp,
|
|
218
|
+
)
|
|
219
|
+
) + alpha2 * npl.norm(finite_diff(v), ord=1)
|
|
220
|
+
if diag:
|
|
221
|
+
print(
|
|
222
|
+
f"v, iteration {iter + 1}: cost: {cost_n}, relative change: {npl.norm(s) / npl.norm(v)}"
|
|
223
|
+
)
|
|
224
|
+
v_cost.append(cost_n)
|
|
225
|
+
if iter >= min_iter and npl.norm(s) / npl.norm(v) < delta_norm_tol:
|
|
226
|
+
if diag:
|
|
227
|
+
print("v, delta norm tolerance reached")
|
|
228
|
+
break
|
|
229
|
+
if (
|
|
230
|
+
prev_cost
|
|
231
|
+
and iter >= min_iter
|
|
232
|
+
and (prev_cost - cost_n) / prev_cost < delta_cost_tol
|
|
233
|
+
):
|
|
234
|
+
if diag:
|
|
235
|
+
print("v, relative cost tolerance reached")
|
|
236
|
+
break
|
|
237
|
+
prev_cost = cost_n
|
|
238
|
+
v = v * data_st_dev / (dx**2)
|
|
239
|
+
u = u * data_st_dev / dx
|
|
240
|
+
return u, v
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
regdiff/__init__.py,sha256=_RqYKdJ6fYXBMBVcO8SuCV8uR5sWF1SIf4rtuuIRo5w,88
|
|
2
|
+
regdiff/regdiff.py,sha256=KvOXaP2lSY_YwLGdBGtQMbWZ34_X2NGWz4QM6nwTnXo,9647
|
|
3
|
+
regdiff-3.6.1.dist-info/WHEEL,sha256=_d8F1e7SqtoW6CDj4Gi8lFC26a_7I17R7zPLCKTp4Fg,81
|
|
4
|
+
regdiff-3.6.1.dist-info/entry_points.txt,sha256=wQIoF7cZJ4p4m9kOMpU8WwSZioY6AfiPSTBt3ZFwwR8,42
|
|
5
|
+
regdiff-3.6.1.dist-info/METADATA,sha256=apkvwLLp0qfIKVRGZ_PjOpdgm371ohPYWRK3cuFfK2I,202
|
|
6
|
+
regdiff-3.6.1.dist-info/RECORD,,
|