dpd-stream 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.
- dpd_stream/__init__.py +56 -0
- dpd_stream/_absorb.py +288 -0
- dpd_stream/_blocks.py +26 -0
- dpd_stream/_frontend.py +447 -0
- dpd_stream/_idtests.py +181 -0
- dpd_stream/_nl.py +123 -0
- dpd_stream/_passes.py +229 -0
- dpd_stream/api.py +201 -0
- dpd_stream/estimator.py +806 -0
- dpd_stream/inference.py +292 -0
- dpd_stream/kernels/__init__.py +37 -0
- dpd_stream/kernels/cells.py +204 -0
- dpd_stream/kernels/idtests.py +165 -0
- dpd_stream/kernels/moments.py +697 -0
- dpd_stream/kernels/nl.py +224 -0
- dpd_stream/kernels/panel.py +302 -0
- dpd_stream/layout.py +360 -0
- dpd_stream/py.typed +0 -0
- dpd_stream/results.py +485 -0
- dpd_stream/simulate.py +145 -0
- dpd_stream/store.py +213 -0
- dpd_stream-0.1.0.dist-info/METADATA +155 -0
- dpd_stream-0.1.0.dist-info/RECORD +26 -0
- dpd_stream-0.1.0.dist-info/WHEEL +5 -0
- dpd_stream-0.1.0.dist-info/licenses/LICENSE.md +21 -0
- dpd_stream-0.1.0.dist-info/top_level.txt +1 -0
dpd_stream/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""dpd-stream: Arellano-Bond (difference) and Blundell-Bond (system) GMM
|
|
2
|
+
for dynamic panels, out of core. Names and conventions follow Stata's
|
|
3
|
+
xtabond2 where they overlap.
|
|
4
|
+
|
|
5
|
+
The data are read once (pass 0) into blocks of whole panels sorted by
|
|
6
|
+
time; every later pass reads the blocks, builds each panel's rows and
|
|
7
|
+
instruments in a numba kernel, and accumulates the few matrices the
|
|
8
|
+
estimator needs (j x j and j x K, j the number of instruments). Nothing
|
|
9
|
+
the size of the data is held in memory, and no instrument matrix is
|
|
10
|
+
built.
|
|
11
|
+
|
|
12
|
+
Layout
|
|
13
|
+
api.py dpd_stream()
|
|
14
|
+
_frontend.py formula and instrument parsing; Gmm, Iv
|
|
15
|
+
layout.py the instrument matrix's columns (xtabond2's order)
|
|
16
|
+
store.py pass 0: the source -> panel-sorted blocks
|
|
17
|
+
kernels/ numba: a panel's rows and instruments; the passes
|
|
18
|
+
estimator.py the fit: the passes and the estimates
|
|
19
|
+
_passes.py, _absorb.py, _idtests.py, _nl.py
|
|
20
|
+
parts of the fit (mixins of estimator.DPD): the residual
|
|
21
|
+
and Windmeijer passes; absorbed effects; identification
|
|
22
|
+
tests; Ahn-Schmidt moments
|
|
23
|
+
inference.py the algebra after the passes: generalized inverse,
|
|
24
|
+
Windmeijer, Arellano-Bond, Sargan/Hansen, MMSC
|
|
25
|
+
results.py DPDResult, DPDMulti
|
|
26
|
+
simulate.py simulate_dynamic(), truth_dynamic()
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from importlib.metadata import PackageNotFoundError
|
|
30
|
+
from importlib.metadata import version as _version
|
|
31
|
+
|
|
32
|
+
from hdfe_stream.core.multcomp import bonferroni, holm
|
|
33
|
+
from hdfe_stream.core.reporting import etable
|
|
34
|
+
from hdfe_stream.core.workspace import cleanup
|
|
35
|
+
|
|
36
|
+
from ._frontend import Gmm, Iv
|
|
37
|
+
from .api import dpd_stream
|
|
38
|
+
from .results import DPDMulti, DPDResult
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
__version__ = _version("dpd-stream")
|
|
42
|
+
except PackageNotFoundError: # pragma: no cover
|
|
43
|
+
__version__ = "0.1.0"
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"DPDMulti",
|
|
47
|
+
"DPDResult",
|
|
48
|
+
"Gmm",
|
|
49
|
+
"Iv",
|
|
50
|
+
"__version__",
|
|
51
|
+
"bonferroni",
|
|
52
|
+
"cleanup",
|
|
53
|
+
"dpd_stream",
|
|
54
|
+
"etable",
|
|
55
|
+
"holm",
|
|
56
|
+
]
|
dpd_stream/_absorb.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""The fit with absorbed period-by-group effects (`g^year`): the cells'
|
|
2
|
+
moments, their inference (Windmeijer and the Arellano-Bond sums, exactly as
|
|
3
|
+
with the dummies) and the partialling.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from . import kernels as kn
|
|
11
|
+
from ._blocks import _blocks, _chunks
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _Absorb:
|
|
15
|
+
"""Methods of `estimator.DPD`."""
|
|
16
|
+
|
|
17
|
+
def _check_absorb(self, terms):
|
|
18
|
+
"""The absorbed term must be one period-specific effect: the time
|
|
19
|
+
variable, or g^time with g constant within panels (difference GMM,
|
|
20
|
+
first differences)."""
|
|
21
|
+
tvar = self.panel[1]
|
|
22
|
+
if len(terms) != 1:
|
|
23
|
+
raise NotImplementedError(
|
|
24
|
+
"one absorbed fixed-effect term is available (e.g. '| year' or "
|
|
25
|
+
f"'| industry^year'), got {list(terms)}")
|
|
26
|
+
parts = [p.strip() for p in terms[0].split("^")]
|
|
27
|
+
if tvar not in parts or "[" in terms[0]:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"absorbed fixed effect {terms[0]!r}: difference GMM absorbs effects specific "
|
|
30
|
+
f"to a period, '{tvar}' or 'g^{tvar}' with g constant within panels (any "
|
|
31
|
+
"effect constant within a panel is removed by the differencing already)")
|
|
32
|
+
if self.pca:
|
|
33
|
+
raise NotImplementedError("absorbed fixed effects with pca are not available")
|
|
34
|
+
return [p for p in parts if p != tvar]
|
|
35
|
+
|
|
36
|
+
# ------------------------------------------------------------------ passes
|
|
37
|
+
|
|
38
|
+
def _cellscores(self, b_full, delta, Hf):
|
|
39
|
+
"""A pass for the absorbed effect's moments g2 = D'e at `b_full` and
|
|
40
|
+
`delta` (`kernels.cells.cellscore_pass`): their covariance in the
|
|
41
|
+
directions Hf (cells x nf), Hf' S22 Hf, with the clusterings and
|
|
42
|
+
factors of the moments' S; and each general cluster's Hf'D_c'e_c, by
|
|
43
|
+
clustering (unscaled)."""
|
|
44
|
+
st = self.store
|
|
45
|
+
nch = self.n_threads
|
|
46
|
+
clus = self.clusters
|
|
47
|
+
nf = Hf.shape[1]
|
|
48
|
+
Hf = np.ascontiguousarray(Hf)
|
|
49
|
+
rowmode = any(c.by_obs for c in clus)
|
|
50
|
+
general = [c for c in clus if c.col >= 0 and not c.nested]
|
|
51
|
+
nested = [c for c in clus if c.col >= 0 and c.nested]
|
|
52
|
+
off = 0
|
|
53
|
+
for c in general:
|
|
54
|
+
c.offset = off
|
|
55
|
+
off += c.n_codes
|
|
56
|
+
AccH = np.zeros((nch, max(off, 1), nf))
|
|
57
|
+
Nacc = {id(c): np.zeros((c.n_codes, nf)) for c in nested}
|
|
58
|
+
SRH = np.zeros((nch, nf, nf))
|
|
59
|
+
H = np.zeros((nf, nf))
|
|
60
|
+
|
|
61
|
+
def fac(c):
|
|
62
|
+
return c.sign * (c.n / (c.n - 1) if self.small else 1.0)
|
|
63
|
+
for blk, pstart in _blocks(st):
|
|
64
|
+
npan = len(pstart) - 1
|
|
65
|
+
CL = blk.get("cl", np.zeros((len(blk["t"]), 0), np.int64))
|
|
66
|
+
CLacc = np.full((len(blk["t"]), len(general)), -1, np.int64)
|
|
67
|
+
for q, c in enumerate(general):
|
|
68
|
+
v = CL[:, c.col]
|
|
69
|
+
CLacc[:, q] = np.where(v >= 0, v + c.offset, -1)
|
|
70
|
+
Hp = np.zeros((npan, nf))
|
|
71
|
+
kn.cellscore_pass(*self._kargs(blk, _chunks(pstart, nch), pstart), b_full,
|
|
72
|
+
blk["ag"], delta, Hf, bool(clus), rowmode, CLacc, Hp, AccH, SRH)
|
|
73
|
+
if not clus:
|
|
74
|
+
H += Hp.T @ Hp
|
|
75
|
+
for c in clus:
|
|
76
|
+
if c.is_panel:
|
|
77
|
+
H += fac(c) * (Hp.T @ Hp)
|
|
78
|
+
for c in nested:
|
|
79
|
+
codes = CL[pstart[:-1], c.col]
|
|
80
|
+
ok = codes >= 0
|
|
81
|
+
np.add.at(Nacc[id(c)], codes[ok], Hp[ok])
|
|
82
|
+
AccH = AccH.sum(0)
|
|
83
|
+
by_code = {}
|
|
84
|
+
for c in nested:
|
|
85
|
+
A = Nacc[id(c)]
|
|
86
|
+
H += fac(c) * (A.T @ A)
|
|
87
|
+
by_code[id(c)] = A
|
|
88
|
+
for c in general:
|
|
89
|
+
A = AccH[c.offset:c.offset + c.n_codes]
|
|
90
|
+
H += fac(c) * (A.T @ A)
|
|
91
|
+
by_code[id(c)] = A
|
|
92
|
+
if rowmode:
|
|
93
|
+
SRt = SRH.sum(0)
|
|
94
|
+
for c in clus:
|
|
95
|
+
if c.by_obs:
|
|
96
|
+
H += fac(c) * SRt
|
|
97
|
+
return (H + H.T) / 2, by_code
|
|
98
|
+
|
|
99
|
+
def _cells_inference(self, kind, c):
|
|
100
|
+
"""Windmeijer's correction and the Arellano-Bond tests with an
|
|
101
|
+
absorbed effect, exactly as with its dummies among the regressors
|
|
102
|
+
and instruments (where concentrating the effects out is an
|
|
103
|
+
approximation): both depend on the effects' estimates, and through
|
|
104
|
+
them on the effects' moments g2 = D'e, whose covariance S22 (cells
|
|
105
|
+
x cells) is never formed, only its products with a few directions
|
|
106
|
+
(`_cellscores`, a pass each).
|
|
107
|
+
|
|
108
|
+
The estimates are linear in the moments. With the coefficients' b^ -
|
|
109
|
+
b = G g~ (g~ = Z~'e), the effects' are delta^ - delta = -xshift (b^ -
|
|
110
|
+
b) + DDinv (g2 - SA g~), SA the effects' moments' covariance with the
|
|
111
|
+
instruments' times the instruments' weight (D'HZ~ A1 one-step, the
|
|
112
|
+
cross moments times A2 two-step). So the functional tmp_b'b +
|
|
113
|
+
tmp_d'delta of the AR tests has the influence (G'tmp_x - SA'rho)'g~
|
|
114
|
+
+ rho'g2, with tmp_x = tmp_b - xshift'tmp_d (the concentrated
|
|
115
|
+
derivative) and rho = DDinv tmp_d. Windmeijer's correction is the
|
|
116
|
+
same algebra one level down: the two-step estimates depend on the
|
|
117
|
+
one-step coefficients and effects through the weight matrix,
|
|
118
|
+
D_b (b1 - b) + D_d (delta1 - delta) = Dc (b1 - b) + R (g2 - SA1 g~)
|
|
119
|
+
with Dc the concentrated derivative (`Draw`), D_d the derivative in
|
|
120
|
+
the effects (the functionals' `Od`) and R = D_d DDinv.
|
|
121
|
+
|
|
122
|
+
`kind`: "1iid" (one-step, non-robust), "1rob" (one-step, robust), "2"
|
|
123
|
+
(two-step or iterated, uncorrected) or "2rob" (two-step with
|
|
124
|
+
Windmeijer's correction). Returns (the corrected two-step variance
|
|
125
|
+
or None, each AR test's variance less what `inference.ar_stat`
|
|
126
|
+
computes from the concentrated terms)."""
|
|
127
|
+
ab = self.absorb_info
|
|
128
|
+
bmul, DDinv = ab["bmul"], ab["DDinv"]
|
|
129
|
+
keep = c["keep"]
|
|
130
|
+
k = len(keep)
|
|
131
|
+
|
|
132
|
+
def dinv(M):
|
|
133
|
+
return bmul(DDinv, M)
|
|
134
|
+
|
|
135
|
+
def cols(a, b):
|
|
136
|
+
return np.einsum("im,im->m", a, b)
|
|
137
|
+
nar = 0
|
|
138
|
+
if c["ar"] is not None:
|
|
139
|
+
ar_sum, _, tmp, zhw, td, zhd = c["ar"]
|
|
140
|
+
nar = len(ar_sum)
|
|
141
|
+
tmpx = tmp[:, keep].T # k x m
|
|
142
|
+
zhw, zhd = zhw.T, zhd.T # L x m, cells x m
|
|
143
|
+
if kind == "1iid":
|
|
144
|
+
zhw, zhd = zhw * c["s2ar"], zhd * c["s2ar"]
|
|
145
|
+
rho = dinv(td.T) # cells x m
|
|
146
|
+
SA1 = ab["S21"] @ c["A1raw"]
|
|
147
|
+
SA = SA1 if kind[0] == "1" else c["cross"] @ c["A2"]
|
|
148
|
+
extra = np.zeros(nar)
|
|
149
|
+
if nar:
|
|
150
|
+
# the effects' part of the estimates' covariance with the tests'
|
|
151
|
+
extra -= 2 * cols(rho, zhd - SA @ zhw)
|
|
152
|
+
if kind == "1iid":
|
|
153
|
+
if nar:
|
|
154
|
+
# the effects' variance: sigma^2 times the Schur complement of
|
|
155
|
+
# D'HD in the one-step weight's inverse
|
|
156
|
+
S21r = ab["S21"].T @ rho
|
|
157
|
+
extra += c["sig2"] * (cols(rho, bmul(ab["DHD"], rho))
|
|
158
|
+
- cols(S21r, c["A1raw"] @ S21r))
|
|
159
|
+
return None, extra
|
|
160
|
+
S, Cr = c["S"], c["cross"]
|
|
161
|
+
bS, dS = c["point_S"]
|
|
162
|
+
if kind == "1rob":
|
|
163
|
+
if nar:
|
|
164
|
+
self.log("pass 3: the absorbed effects' moments")
|
|
165
|
+
H, _ = self._cellscores(bS, dS, rho)
|
|
166
|
+
a = c["G1"].T @ tmpx
|
|
167
|
+
lam = a - SA.T @ rho
|
|
168
|
+
extra += (cols(lam, S @ lam) - cols(a, S @ a) + 2 * cols(rho, Cr @ lam)
|
|
169
|
+
+ np.diag(H))
|
|
170
|
+
return None, extra
|
|
171
|
+
A2, V2 = c["A2"], c["V2"]
|
|
172
|
+
if kind == "2":
|
|
173
|
+
if nar:
|
|
174
|
+
self.log("pass 4: the absorbed effects' moments")
|
|
175
|
+
H, _ = self._cellscores(bS, dS, rho)
|
|
176
|
+
CAr = Cr.T @ rho
|
|
177
|
+
extra += np.diag(H) - cols(CAr, A2 @ CAr)
|
|
178
|
+
return None, extra
|
|
179
|
+
# Windmeijer: the derivatives in the one-step effects from the
|
|
180
|
+
# functionals P (the two-step coefficients' influence) and, for the
|
|
181
|
+
# AR tests, each test's own influence (lam2, rho)
|
|
182
|
+
G1 = c["G1"]
|
|
183
|
+
P = V2 @ c["ZXk"].T @ A2
|
|
184
|
+
Lf, Rf, beta = P, np.zeros((k, ab["ncell"])), None
|
|
185
|
+
if nar:
|
|
186
|
+
lam2 = P.T @ tmpx - A2 @ (Cr.T @ rho)
|
|
187
|
+
Lf = np.vstack([P, lam2.T])
|
|
188
|
+
Rf = np.vstack([Rf, rho.T])
|
|
189
|
+
if any(cc.col >= 0 for cc in self.clusters):
|
|
190
|
+
self.log("pass 4: the absorbed effects' moments by cluster")
|
|
191
|
+
_, bc = self._cellscores(bS, dS, rho)
|
|
192
|
+
beta = {key: np.hstack([np.zeros((v.shape[0], k)), v]) for key, v in bc.items()}
|
|
193
|
+
self.log("pass 4: Windmeijer correction")
|
|
194
|
+
Draw, Ob, Od = self._windmeijer(c["b1"], c["v"], c["info_S"], delta=c["d1"],
|
|
195
|
+
xshift=c["xs1"], funcs=(Lf, Rf, beta))
|
|
196
|
+
Dc = P @ Draw[:, keep]
|
|
197
|
+
Dd = Od[:k]
|
|
198
|
+
R = dinv(Dd.T).T
|
|
199
|
+
dxs = (c["xs1"] - c["xs2"])[:, keep]
|
|
200
|
+
Hf = [R.T]
|
|
201
|
+
if nar:
|
|
202
|
+
wx1 = Ob[k:, keep].T
|
|
203
|
+
wd = Od[k:].T
|
|
204
|
+
nu = dinv(wd)
|
|
205
|
+
Hf += [rho, nu]
|
|
206
|
+
self.log("pass 5: the absorbed effects' moments")
|
|
207
|
+
H, _ = self._cellscores(bS, dS, np.hstack(Hf))
|
|
208
|
+
# V2 + D V1r D' + D V2 + V2 D', with the effects among the
|
|
209
|
+
# parameters: D V1r D' is the variance of Dc (b1 - b) + R (g2 - SA1
|
|
210
|
+
# g~), and the effects' part of V2 enters D V2 through xshift2
|
|
211
|
+
Lam1 = Dc @ G1 - R @ SA1
|
|
212
|
+
LCR = Lam1 @ Cr.T @ R.T
|
|
213
|
+
Vc = Lam1 @ S @ Lam1.T + LCR + LCR.T + H[:k, :k]
|
|
214
|
+
E = Dc + Dd @ dxs
|
|
215
|
+
V2r = V2 + Vc + E @ V2 + V2 @ E.T
|
|
216
|
+
V2r = (V2r + V2r.T) / 2
|
|
217
|
+
if nar:
|
|
218
|
+
m = nar
|
|
219
|
+
Hrr = np.diag(H[k:k + m, k:k + m])
|
|
220
|
+
Hnn = np.diag(H[k + m:, k + m:])
|
|
221
|
+
Hnr = np.diag(H[k + m:, k:k + m])
|
|
222
|
+
CAr, CAn = Cr.T @ rho, Cr.T @ nu
|
|
223
|
+
cV2c = cols(tmpx, V2 @ tmpx) + Hrr - cols(CAr, A2 @ CAr)
|
|
224
|
+
mu = G1.T @ wx1 - SA1.T @ nu
|
|
225
|
+
wV1w = cols(mu, S @ mu) + 2 * cols(nu, Cr @ mu) + Hnn
|
|
226
|
+
wx2 = wx1 + dxs.T @ wd
|
|
227
|
+
wV2c = cols(wx2, V2 @ tmpx) + Hnr - cols(CAn, A2 @ CAr)
|
|
228
|
+
extra += cV2c + wV1w + 2 * wV2c - cols(tmpx, V2r @ tmpx)
|
|
229
|
+
return V2r, extra
|
|
230
|
+
|
|
231
|
+
def _partial(self, cells, live, ZX, ZY, S1, XX, K):
|
|
232
|
+
"""The moments net of an absorbed effect (Frisch-Waugh-Lovell for
|
|
233
|
+
GMM). With D the absorbed dummies of the transformed rows and W the
|
|
234
|
+
weights (the regressors' dummies are W D, the instruments' D):
|
|
235
|
+
instruments Z~ = Z - D Mz with Mz = (D'WD)^- D'WZ, the dummies'
|
|
236
|
+
coefficients concentrated out of the regressors and outcome by
|
|
237
|
+
Mx = (D'WD)^- D'[X y], and Z~'HZ~ = Z'HZ - Z'HD Mz - Mz'D'HZ +
|
|
238
|
+
Mz'D'HD Mz. Concentrating out the effects, which their own
|
|
239
|
+
(IV-style) moments exactly identify, leaves the same estimates of the
|
|
240
|
+
other coefficients as including the dummies. D'WD and D'HD are block
|
|
241
|
+
diagonal, one T x T block per group (diagonal and tridiagonal with
|
|
242
|
+
first differences); with forward orthogonal deviations they are
|
|
243
|
+
singular (the deviations of a full set of dummies sum to zero), and
|
|
244
|
+
their generalized inverse leaves the level effects determined up to
|
|
245
|
+
a constant per group, which the estimates do not depend on."""
|
|
246
|
+
DDx, DHD = cells["DDx"], cells["DHD"]
|
|
247
|
+
ng, T = DDx.shape[0], DDx.shape[1]
|
|
248
|
+
DDinv = np.linalg.pinv(DDx, rcond=1e-12, hermitian=True)
|
|
249
|
+
n_cells = int(np.linalg.matrix_rank(DDx, hermitian=True).sum())
|
|
250
|
+
|
|
251
|
+
def bmul(M, X):
|
|
252
|
+
return np.einsum("gst,gtc->gsc", M, X.reshape(ng, T, -1)).reshape(ng * T, -1)
|
|
253
|
+
CZw, CHZ, CX = cells["CZw"][:, live], cells["CHZ"][:, live], cells["CX"]
|
|
254
|
+
Mz = bmul(DDinv, CZw)
|
|
255
|
+
Mx = bmul(DDinv, CX)
|
|
256
|
+
DHDMz = bmul(DHD, Mz)
|
|
257
|
+
ZXt = ZX - Mz.T @ CX[:, :K]
|
|
258
|
+
ZYt = ZY - Mz.T @ CX[:, K]
|
|
259
|
+
S1t = S1 - CHZ.T @ Mz - Mz.T @ CHZ + Mz.T @ DHDMz
|
|
260
|
+
S1t = (S1t + S1t.T) / 2
|
|
261
|
+
XXt = XX - CX.T @ Mx
|
|
262
|
+
ab = dict(ncell=ng * T, DDinv=DDinv, bmul=bmul, Mz=Mz, Mx=Mx, S21=CHZ - DHDMz,
|
|
263
|
+
DHD=DHD, CZw=CZw, CX=CX, n_cells=n_cells)
|
|
264
|
+
return ab, ZXt, ZYt, S1t, XXt
|
|
265
|
+
|
|
266
|
+
def _cell_effects(self, b_full, keep, A, S21, ZXk, ZY, bk):
|
|
267
|
+
"""The effects at b in the GMM with the dummies -- their projection
|
|
268
|
+
of y - x'b, less the part their moments' covariance with the
|
|
269
|
+
others' (S21) assigns them -- and the derivative of the concentrated
|
|
270
|
+
residual with respect to b, as x - xshift."""
|
|
271
|
+
ab = self.absorb_info
|
|
272
|
+
Mx, bmul, DDinv = ab["Mx"], ab["bmul"], ab["DDinv"]
|
|
273
|
+
K = Mx.shape[1] - 1
|
|
274
|
+
q = bmul(DDinv, S21 @ (A @ (ZY - ZXk @ bk)))[:, 0]
|
|
275
|
+
delta = Mx[:, K] - Mx[:, :K] @ b_full - q
|
|
276
|
+
w = bmul(DDinv, S21 @ (A @ ZXk))
|
|
277
|
+
xshift = Mx[:, :K].copy()
|
|
278
|
+
xshift[:, keep] -= w
|
|
279
|
+
return np.ascontiguousarray(delta), np.ascontiguousarray(xshift)
|
|
280
|
+
|
|
281
|
+
def _absorb_args(self, blk, delta, xshift):
|
|
282
|
+
ab = self.absorb_info
|
|
283
|
+
if not ab:
|
|
284
|
+
z = np.zeros((1, 1))
|
|
285
|
+
return np.zeros(0, np.int64), z, np.zeros(1), z
|
|
286
|
+
return blk["ag"], ab["Mz"], delta, xshift
|
|
287
|
+
|
|
288
|
+
# ------------------------------------------------------------------ estimation
|
dpd_stream/_blocks.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Iteration over the stored blocks, and the split of a block's panels into the
|
|
2
|
+
chunks the kernels run in parallel."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _chunks(pstart, nch):
|
|
10
|
+
"""Chunk boundaries over a block's panels, balanced by rows."""
|
|
11
|
+
npan = len(pstart) - 1
|
|
12
|
+
if npan <= 0:
|
|
13
|
+
return np.zeros(1, np.int64)
|
|
14
|
+
nch = max(1, min(nch, npan))
|
|
15
|
+
targets = np.linspace(0, pstart[-1], nch + 1)
|
|
16
|
+
b = np.searchsorted(pstart, targets[1:-1])
|
|
17
|
+
out = np.unique(np.concatenate([[0], b, [npan]])).astype(np.int64)
|
|
18
|
+
return out
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _blocks(store, fields=("X", "t", "pid", "cl", "w", "ag")):
|
|
22
|
+
for blk in store.blocks.iterate(fields=list(fields)):
|
|
23
|
+
pid = blk["pid"]
|
|
24
|
+
starts = np.flatnonzero(np.diff(pid)) + 1
|
|
25
|
+
pstart = np.concatenate([[0], starts, [len(pid)]]).astype(np.int64)
|
|
26
|
+
yield blk, pstart
|