regdiff 3.6.1__tar.gz

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-3.6.1/PKG-INFO ADDED
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.3
2
+ Name: regdiff
3
+ Version: 3.6.1
4
+ Summary: Regularized differentiation of time-series as numpy ndarrays
5
+ Requires-Dist: numpy>=2.5.4
6
+ Requires-Dist: scipy>=1.18.1
7
+ Requires-Python: >=3.14
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "regdiff"
3
+ version = "3.6.1"
4
+ description = "Regularized differentiation of time-series as numpy ndarrays"
5
+ requires-python = ">=3.14"
6
+ dependencies = [
7
+ "numpy>=2.5.4",
8
+ "scipy>=1.18.1",
9
+ ]
10
+
11
+ [project.scripts]
12
+ regdiff = "regdiff:main"
13
+
14
+ [build-system]
15
+ requires = ["uv_build>=0.12.15,<0.13.0"]
16
+ build-backend = "uv_build"
17
+
18
+ [dependency-groups]
19
+ dev = ["pytest>=9.1.1"]
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "regdiff"
3
+ version = "3.6.1"
4
+ description = "Regularized differentiation of time-series as numpy ndarrays"
5
+ requires-python = ">=3.14"
6
+ dependencies = [
7
+ "numpy>=2.5.4",
8
+ "scipy>=1.18.1",
9
+ ]
10
+
11
+ [project.scripts]
12
+ regdiff = "regdiff:main"
13
+
14
+ [build-system]
15
+ requires = ["uv_build>=0.12.15,<0.13.0"]
16
+ build-backend = "uv_build"
17
+
18
+ [dependency-groups]
19
+ dev = [
20
+ "pytest>=9.1.1",
21
+ ]
@@ -0,0 +1 @@
1
+ from .regdiff import finite_diff, matrix_anti_diff, matrix_diff, reg_diff # noqa: F401
@@ -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