cuwave 0.2.0__tar.gz → 0.3.0__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.
Files changed (38) hide show
  1. {cuwave-0.2.0 → cuwave-0.3.0}/PKG-INFO +1 -1
  2. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/kernels/common.cuh +16 -0
  3. cuwave-0.3.0/cuwave/kernels/scalar_sensitivity.cu +302 -0
  4. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/optimization.py +9 -7
  5. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/scalar.py +47 -2
  6. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/sensitivity.py +52 -13
  7. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/wave.py +31 -0
  8. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave.egg-info/PKG-INFO +1 -1
  9. {cuwave-0.2.0 → cuwave-0.3.0}/pyproject.toml +1 -1
  10. cuwave-0.2.0/cuwave/kernels/scalar_sensitivity.cu +0 -140
  11. {cuwave-0.2.0 → cuwave-0.3.0}/LICENSE +0 -0
  12. {cuwave-0.2.0 → cuwave-0.3.0}/README.md +0 -0
  13. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/__init__.py +0 -0
  14. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/anisotropic.py +0 -0
  15. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/boundary.py +0 -0
  16. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/elastic.py +0 -0
  17. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/evals.py +0 -0
  18. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/geometry.py +0 -0
  19. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/kernels/__init__.py +0 -0
  20. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/kernels/anisotropic.cu +0 -0
  21. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/kernels/anisotropic_sensitivity.cu +0 -0
  22. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/kernels/elastic.cu +0 -0
  23. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/kernels/elastic_sensitivity.cu +0 -0
  24. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/kernels/maxwell.cu +0 -0
  25. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/kernels/maxwell_sensitivity.cu +0 -0
  26. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/kernels/scalar.cu +0 -0
  27. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/maxwell.py +0 -0
  28. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/nn.py +0 -0
  29. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/postprocessing.py +0 -0
  30. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/regularization.py +0 -0
  31. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/signals.py +0 -0
  32. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/stencils.py +0 -0
  33. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave/utils.py +0 -0
  34. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave.egg-info/SOURCES.txt +0 -0
  35. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave.egg-info/dependency_links.txt +0 -0
  36. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave.egg-info/requires.txt +0 -0
  37. {cuwave-0.2.0 → cuwave-0.3.0}/cuwave.egg-info/top_level.txt +0 -0
  38. {cuwave-0.2.0 → cuwave-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuwave
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: GPU finite-difference wave solver with differentiable adjoints
5
5
  Author-email: Leon Herrmann <herrmann.leon@pm.me>
6
6
  License-Expression: MIT
@@ -69,6 +69,22 @@ excitation_kernel(real_t *__restrict__ u, const real_t *__restrict__ source,
69
69
  }
70
70
  }
71
71
 
72
+ // ------------------------------------------------------------------------------------
73
+ __global__ void adjoint_excitation_kernel(
74
+ real_t *__restrict__ l2, const real_t *__restrict__ signal,
75
+ const int offset, const int *__restrict__ lin_index, const int num_sensors,
76
+ const real_t *__restrict__ weight, real_t *__restrict__ g_mass,
77
+ const real_t *__restrict__ u1, const real_t mf) {
78
+ const int idx = blockIdx.x * blockDim.x + threadIdx.x;
79
+ if (idx < num_sensors) {
80
+ const int n = lin_index[idx];
81
+ const real_t load = weight[idx] * signal[offset + idx];
82
+ atomicAdd(&l2[n], load);
83
+ // the injected load is part of the second time difference of lambda
84
+ atomicAdd(&g_mass[n], -mf * u1[n] * load);
85
+ }
86
+ }
87
+
72
88
  // ------------------------------------------------------------------------------------
73
89
  __global__ void get_signal_kernel(const real_t *__restrict__ u,
74
90
  real_t *__restrict__ um, const int offset,
@@ -0,0 +1,302 @@
1
+ // Prepended by wave.compile_kernels: stencils.preamble, then common.cuh.
2
+ // Compile-time configuration this file responds to:
3
+ // NDIM = 1 | 2 | 3
4
+ // USE_DAMPING
5
+
6
+ // ------------------------------- interior guard macros
7
+ #if NDIM == 1
8
+ #define INTERIOR_OR_RETURN \
9
+ const int a0 = blockIdx.x * blockDim.x + threadIdx.x; \
10
+ if (!(a0 > 0 && a0 < N0 - 1)) \
11
+ return; \
12
+ const int idx = a0
13
+ #define AXIS_RADII const int r0 = CLOSURE(a0, N0)
14
+ #define AXIS_OFFSETS const int o0 = 1
15
+ #elif NDIM == 2
16
+ #define INTERIOR_OR_RETURN \
17
+ const int a1 = blockIdx.x * blockDim.x + threadIdx.x; \
18
+ const int a0 = blockIdx.y * blockDim.y + threadIdx.y; \
19
+ if (!(a0 > 0 && a0 < N0 - 1 && a1 > 0 && a1 < N1 - 1)) \
20
+ return; \
21
+ const int idx = a0 * s0 + a1
22
+ #define AXIS_RADII const int r0 = CLOSURE(a0, N0), r1 = CLOSURE(a1, N1)
23
+ #define AXIS_OFFSETS const int o0 = s0, o1 = 1
24
+ #elif NDIM == 3
25
+ #define INTERIOR_OR_RETURN \
26
+ const int a2 = blockIdx.x * blockDim.x + threadIdx.x; \
27
+ const int a1 = blockIdx.y * blockDim.y + threadIdx.y; \
28
+ const int a0 = blockIdx.z * blockDim.z + threadIdx.z; \
29
+ if (!(a0 > 0 && a0 < N0 - 1 && a1 > 0 && a1 < N1 - 1 && a2 > 0 && \
30
+ a2 < N2 - 1)) \
31
+ return; \
32
+ const int idx = a0 * s0 + a1 * s1 + a2
33
+ #define AXIS_RADII \
34
+ const int r0 = CLOSURE(a0, N0), r1 = CLOSURE(a1, N1), r2 = CLOSURE(a2, N2)
35
+ #define AXIS_OFFSETS const int o0 = s0, o1 = s1, o2 = 1
36
+ #endif
37
+
38
+ // ---------------------------------- gradient helpers
39
+ __device__ __forceinline__ real_t stiffness_gradient_axis(
40
+ const real_t *__restrict__ u1, const real_t *__restrict__ l1,
41
+ const real_t *__restrict__ stiff, const int idx, const int s,
42
+ const real_t uc, const real_t lc, const real_t sc, const real_t factor,
43
+ const int r) {
44
+ const real_t sp = stiff[idx + s]; // plus of sc
45
+ const real_t sm = stiff[idx - s]; // minus of sc
46
+ const real_t dgp = sp * sp / ((sc + sp) * (sc + sp)); // d(harmonic mean)/dsc
47
+ const real_t dgm = sm * sm / ((sc + sm) * (sc + sm)); // d(harmonic mean)/dsc
48
+ real_t Dp = OP_W(r, 1) * (u1[idx + s] - uc); // inner grad
49
+ real_t Dm = OP_W(r, 1) * (uc - u1[idx - s]); // inner grad
50
+ #pragma unroll
51
+ for (int k = 2; k <= STENCIL_RADIUS; ++k)
52
+ if (k <= r) {
53
+ Dp += OP_W(r, k) * (u1[idx + k * s] - u1[idx - (k - 1) * s]);
54
+ Dm += OP_W(r, k) * (u1[idx + (k - 1) * s] - u1[idx - k * s]);
55
+ }
56
+ return factor * (dgp * Dp * (l1[idx + s] - lc) +
57
+ dgm * Dm * (lc - l1[idx - s])); // both cells of the node
58
+ }
59
+
60
+ // the adjoint step's flux divergence and the stiffness gradient of one axis,
61
+ // sharing the cell stiffnesses
62
+ __device__ __forceinline__ void adjoint_gradient_axis(
63
+ const real_t *__restrict__ u1, const real_t *__restrict__ l1,
64
+ const real_t *__restrict__ stiff, const int idx, const int s,
65
+ const real_t uc, const real_t lc, const real_t sc, const real_t factor,
66
+ const int r, real_t &div_l, real_t &g) {
67
+ const real_t sp = stiff[idx + s]; // plus of sc
68
+ const real_t sm = stiff[idx - s]; // minus of sc
69
+ const real_t gp = sc * sp / (sc + sp); // harmonic mean
70
+ const real_t gm = sc * sm / (sc + sm); // harmonic mean
71
+ const real_t dgp = sp * sp / ((sc + sp) * (sc + sp)); // d(harmonic mean)/dsc
72
+ const real_t dgm = sm * sm / ((sc + sm) * (sc + sm)); // d(harmonic mean)/dsc
73
+ const real_t lp = l1[idx + s];
74
+ const real_t lm = l1[idx - s];
75
+ real_t Lp = OP_W(r, 1) * (lp - lc); // inner grad of lambda
76
+ real_t Lm = OP_W(r, 1) * (lc - lm); // inner grad of lambda
77
+ real_t Dp = OP_W(r, 1) * (u1[idx + s] - uc); // inner grad of u
78
+ real_t Dm = OP_W(r, 1) * (uc - u1[idx - s]); // inner grad of u
79
+ #pragma unroll
80
+ for (int k = 2; k <= STENCIL_RADIUS; ++k)
81
+ if (k <= r) {
82
+ Lp += OP_W(r, k) * (l1[idx + k * s] - l1[idx - (k - 1) * s]);
83
+ Lm += OP_W(r, k) * (l1[idx + (k - 1) * s] - l1[idx - k * s]);
84
+ Dp += OP_W(r, k) * (u1[idx + k * s] - u1[idx - (k - 1) * s]);
85
+ Dm += OP_W(r, k) * (u1[idx + (k - 1) * s] - u1[idx - k * s]);
86
+ }
87
+ div_l += factor * (Lp * gp - Lm * gm); // the step's flux divergence
88
+ g += factor * (dgp * Dp * (lp - lc) + dgm * Dm * (lc - lm));
89
+ }
90
+
91
+ // byte-identical to scalar.cu: each file is its own compilation unit
92
+ __device__ __forceinline__ real_t flux_divergence_axis(
93
+ const real_t *__restrict__ u1, const real_t *__restrict__ stiff,
94
+ const int idx, const int s, const real_t uc, const real_t sc,
95
+ const real_t factor, const int r) {
96
+ const real_t sp = stiff[idx + s]; // plus of sc
97
+ const real_t sm = stiff[idx - s]; // minus of sc
98
+ const real_t gp = sc * sp / (sc + sp); // harmonic mean
99
+ const real_t gm = sc * sm / (sc + sm); // harmonic mean
100
+ real_t Dp = OP_W(r, 1) * (u1[idx + s] - uc); // initialization: inner grad
101
+ real_t Dm = OP_W(r, 1) * (uc - u1[idx - s]); // initialization: inner grad
102
+ #pragma unroll
103
+ for (int k = 2; k <= STENCIL_RADIUS; ++k)
104
+ if (k <= r) {
105
+ Dp +=
106
+ OP_W(r, k) * (u1[idx + k * s] - u1[idx - (k - 1) * s]); // inner grad
107
+ Dm +=
108
+ OP_W(r, k) * (u1[idx + (k - 1) * s] - u1[idx - k * s]); // inner grad
109
+ }
110
+ return factor * (Dp * gp - Dm * gm); // outer grad (incl. inner grad)
111
+ }
112
+
113
+ // -------------------------------------- kernels
114
+ extern "C" {
115
+
116
+ // ------------------------------------------------------------------------------------
117
+ __global__ void
118
+ gradient_kernel(real_t *__restrict__ g_mass, real_t *__restrict__ g_stiff,
119
+ const real_t *__restrict__ u0, const real_t *__restrict__ u1,
120
+ const real_t *__restrict__ u2, const real_t *__restrict__ l1,
121
+ const real_t *__restrict__ stiff, const real_t inv_dt2,
122
+ const real_t F0, const int N0
123
+ #if NDIM >= 2
124
+ ,
125
+ const real_t F1, const int N1, const int s0
126
+ #endif
127
+ #if NDIM >= 3
128
+ ,
129
+ const real_t F2, const int N2, const int s1
130
+ #endif
131
+ ) {
132
+ INTERIOR_OR_RETURN;
133
+ AXIS_RADII;
134
+
135
+ const real_t uc = u1[idx];
136
+ const real_t lc = l1[idx];
137
+
138
+ // dJ/dmass: no neighbour and no material load
139
+ g_mass[idx] -= inv_dt2 * lc * (u2[idx] - 2.f * uc + u0[idx]);
140
+
141
+ // dJ/dstiff: scalar.cu's harmonic cell mean differentiated in place
142
+ const real_t sc = stiff[idx];
143
+ #if NDIM == 1
144
+ const real_t g =
145
+ stiffness_gradient_axis(u1, l1, stiff, idx, 1, uc, lc, sc, F0, r0);
146
+ #elif NDIM == 2
147
+ const real_t g =
148
+ stiffness_gradient_axis(u1, l1, stiff, idx, s0, uc, lc, sc, F0, r0) +
149
+ stiffness_gradient_axis(u1, l1, stiff, idx, 1, uc, lc, sc, F1, r1);
150
+ #elif NDIM == 3
151
+ const real_t g =
152
+ stiffness_gradient_axis(u1, l1, stiff, idx, s0, uc, lc, sc, F0, r0) +
153
+ stiffness_gradient_axis(u1, l1, stiff, idx, s1, uc, lc, sc, F1, r1) +
154
+ stiffness_gradient_axis(u1, l1, stiff, idx, 1, uc, lc, sc, F2, r2);
155
+ #endif
156
+ g_stiff[idx] -= g;
157
+ }
158
+
159
+ // ------------------------------------------------------------------------------------
160
+ __global__ void frechet_kernel(real_t *__restrict__ acc_mass,
161
+ real_t *__restrict__ acc_stiff,
162
+ const real_t *__restrict__ u0,
163
+ const real_t *__restrict__ u1,
164
+ const real_t *__restrict__ u2, const real_t ft,
165
+ const real_t f0, const int N0
166
+ #if NDIM >= 2
167
+ ,
168
+ const real_t f1, const int N1, const int s0
169
+ #endif
170
+ #if NDIM >= 3
171
+ ,
172
+ const real_t f2, const int N2, const int s1
173
+ #endif
174
+ ) {
175
+ INTERIOR_OR_RETURN;
176
+ AXIS_OFFSETS;
177
+
178
+ const real_t dudt = u2[idx] - u0[idx];
179
+ acc_mass[idx] += ft * dudt * dudt;
180
+
181
+ const real_t g0 = u1[idx + o0] - u1[idx - o0];
182
+ real_t sum = f0 * g0 * g0;
183
+ #if NDIM >= 2
184
+ const real_t g1 = u1[idx + o1] - u1[idx - o1];
185
+ sum += f1 * g1 * g1;
186
+ #endif
187
+ #if NDIM >= 3
188
+ const real_t g2 = u1[idx + o2] - u1[idx - o2];
189
+ sum += f2 * g2 * g2;
190
+ #endif
191
+ acc_stiff[idx] += sum;
192
+ }
193
+
194
+ // ------------------------------------------------------------------------------------
195
+ __global__ void adjoint_gradient_kernel(
196
+ real_t *__restrict__ l0, const real_t *__restrict__ l1,
197
+ real_t *__restrict__ g_mass, real_t *__restrict__ g_stiff,
198
+ const real_t *__restrict__ u1, const real_t *__restrict__ stiff,
199
+ const real_t *__restrict__ minv, const int derive_inertia,
200
+ #ifdef USE_DAMPING
201
+ const real_t *__restrict__ damping, const real_t dt,
202
+ #endif
203
+ const real_t mf, const real_t f0, const int N0
204
+ #if NDIM >= 2
205
+ ,
206
+ const real_t f1, const int N1, const int s0
207
+ #endif
208
+ #if NDIM >= 3
209
+ ,
210
+ const real_t f2, const int N2, const int s1
211
+ #endif
212
+ ) {
213
+ INTERIOR_OR_RETURN;
214
+ AXIS_RADII;
215
+
216
+ const real_t uc = u1[idx];
217
+ const real_t lc = l1[idx];
218
+ const real_t sc = stiff[idx];
219
+ real_t div_l = 0, g = 0;
220
+ #if NDIM == 1
221
+ adjoint_gradient_axis(u1, l1, stiff, idx, 1, uc, lc, sc, f0, r0, div_l, g);
222
+ #elif NDIM == 2
223
+ adjoint_gradient_axis(u1, l1, stiff, idx, s0, uc, lc, sc, f0, r0, div_l, g);
224
+ adjoint_gradient_axis(u1, l1, stiff, idx, 1, uc, lc, sc, f1, r1, div_l, g);
225
+ #elif NDIM == 3
226
+ adjoint_gradient_axis(u1, l1, stiff, idx, s0, uc, lc, sc, f0, r0, div_l, g);
227
+ adjoint_gradient_axis(u1, l1, stiff, idx, s1, uc, lc, sc, f1, r1, div_l, g);
228
+ adjoint_gradient_axis(u1, l1, stiff, idx, 1, uc, lc, sc, f2, r2, div_l, g);
229
+ #endif
230
+
231
+ const real_t mi = derive_inertia ? 1.f / sc : minv[idx];
232
+ const real_t lo = l0[idx];
233
+ #ifdef USE_DAMPING
234
+ const real_t beta = 0.5f * mi * damping[idx] * dt;
235
+ const real_t ln = (2.f * lc - lo * (1.f - beta) + mi * div_l) / (1.f + beta);
236
+ #else
237
+ const real_t ln = -lo + 2.f * lc + mi * div_l;
238
+ #endif
239
+ l0[idx] = ln;
240
+ // dJ/dmass by parts in time: u against the second difference of lambda
241
+ g_mass[idx] -= mf * uc * (ln - 2.f * lc + lo);
242
+ g_stiff[idx] -= mf * g;
243
+ }
244
+
245
+ // ------------------------------------------------------------------------------------
246
+ __global__ void superposed_kernel(
247
+ const real_t *__restrict__ u0, const real_t *__restrict__ u1,
248
+ real_t *__restrict__ u2, real_t *__restrict__ acc_mass,
249
+ real_t *__restrict__ acc_stiff, const real_t *__restrict__ stiff,
250
+ const real_t *__restrict__ minv, const int derive_inertia, const real_t ft,
251
+ const real_t fs, const real_t f0, const int N0
252
+ #if NDIM >= 2
253
+ ,
254
+ const real_t f1, const int N1, const int s0
255
+ #endif
256
+ #if NDIM >= 3
257
+ ,
258
+ const real_t f2, const int N2, const int s1
259
+ #endif
260
+ ) {
261
+ INTERIOR_OR_RETURN;
262
+ AXIS_RADII;
263
+ AXIS_OFFSETS;
264
+
265
+ const real_t uc = u1[idx];
266
+ const real_t sc = stiff[idx];
267
+ #if NDIM == 1
268
+ const real_t laplacian =
269
+ flux_divergence_axis(u1, stiff, idx, 1, uc, sc, f0, r0);
270
+ #elif NDIM == 2
271
+ const real_t laplacian =
272
+ flux_divergence_axis(u1, stiff, idx, s0, uc, sc, f0, r0) +
273
+ flux_divergence_axis(u1, stiff, idx, 1, uc, sc, f1, r1);
274
+ #elif NDIM == 3
275
+ const real_t laplacian =
276
+ flux_divergence_axis(u1, stiff, idx, s0, uc, sc, f0, r0) +
277
+ flux_divergence_axis(u1, stiff, idx, s1, uc, sc, f1, r1) +
278
+ flux_divergence_axis(u1, stiff, idx, 1, uc, sc, f2, r2);
279
+ #endif
280
+
281
+ // the mass density of the triplet before, whose oldest slot u2 still holds
282
+ const real_t dudt = uc - u2[idx];
283
+ acc_mass[idx] += ft * dudt * dudt;
284
+
285
+ // the stiffness density of this triplet, its middle slot being u1
286
+ const real_t g0 = u1[idx + o0] - u1[idx - o0];
287
+ real_t sum = f0 * g0 * g0;
288
+ #if NDIM >= 2
289
+ const real_t g1 = u1[idx + o1] - u1[idx - o1];
290
+ sum += f1 * g1 * g1;
291
+ #endif
292
+ #if NDIM >= 3
293
+ const real_t g2 = u1[idx + o2] - u1[idx - o2];
294
+ sum += f2 * g2 * g2;
295
+ #endif
296
+ acc_stiff[idx] += fs * sum;
297
+
298
+ const real_t mi = derive_inertia ? 1.f / sc : minv[idx];
299
+ u2[idx] = -u0[idx] + 2.f * uc + mi * laplacian;
300
+ }
301
+
302
+ } // extern "C"
@@ -68,24 +68,26 @@ class Lbfgs:
68
68
  project: applied to every trial, a constraint on the design variables.
69
69
 
70
70
  Returns:
71
- (design, cost, alpha, trials) of the accepted trial, which is the last one
72
- attempted even when `alpha` bottomed out at `min_alpha`.
71
+ (design, cost, alpha, trials) of the accepted trial. A search that reaches
72
+ `min_alpha` without a decrease returns `x` and `cost` unchanged and drops
73
+ the secant pairs, so the next call restarts from the scaled gradient.
73
74
  """
74
75
  step = self.step(x, grad) - x
75
76
  if not self.pairs:
76
77
  step = step * (self.first_step / float(abs(step).max()))
77
- slope = float(grad.ravel() @ step.ravel())
78
78
 
79
79
  alpha, trials = 1.0, 0
80
80
  while True:
81
81
  trial = project(x + alpha * step)
82
82
  trial_cost = f(trial)
83
83
  trials += 1
84
- if (
85
- trial_cost <= cost + self.armijo * alpha * slope
86
- or alpha <= self.min_alpha
87
- ):
84
+ # projected displacement: a clipped trial moves less than alpha * step
85
+ slope = float(grad.ravel() @ (trial - x).ravel())
86
+ if trial_cost <= cost + self.armijo * slope:
88
87
  return trial, trial_cost, alpha, trials
88
+ if alpha <= self.min_alpha:
89
+ self.pairs.clear()
90
+ return x, cost, alpha, trials
89
91
  alpha *= self.shrink
90
92
 
91
93
  def step(self, x: cpt.NDArray, grad: cpt.NDArray) -> cpt.NDArray:
@@ -67,6 +67,7 @@ class PressureWave(Simulation):
67
67
  return args
68
68
 
69
69
  gradient_names = ("mass", "stiff") # the fields the adjoint differentiates
70
+ fuses_adjoint = True # one kernel steps the adjoint and accumulates both gradients
70
71
 
71
72
  def gradient_fields(self, mat: dict) -> dict[str, cpt.NDArray]:
72
73
  """Zeroed accumulators the adjoint kernels add into, one per material field."""
@@ -103,15 +104,59 @@ class PressureWave(Simulation):
103
104
 
104
105
  return gradient_step
105
106
 
107
+ def define_adjoint_gradient(
108
+ self, kernels: cp.RawModule, mat: dict, grads: dict
109
+ ) -> Callable:
110
+ """Closure stepping the adjoint `l0` over `l1`, accumulating both gradients of `l1`."""
111
+ adjoint_gradient_kernel = kernels.get_function("adjoint_gradient_kernel")
112
+ grid, block = grid_block(self)
113
+ args = [None, None, grads["mass"], grads["stiff"], None]
114
+ args += [*self.step_kernel_args(mat), self.dtype(self.adjoint_mass_factor())]
115
+ args += axis_geometry(self, self.step_factors())
116
+
117
+ def adjoint_gradient_step(l0, l1, u1):
118
+ args[0], args[1], args[4] = l0, l1, u1
119
+ adjoint_gradient_kernel(grid, block, args)
120
+ return l0
121
+
122
+ return adjoint_gradient_step
123
+
124
+ def define_superposed(
125
+ self, kernels: cp.RawModule, mat: dict, accs: dict, sign: float
126
+ ) -> Callable:
127
+ """Closure stepping (u0, u1, u2), adding `sign` times the stiffness density of this triplet and the mass density of the one before."""
128
+ superposed_kernel = kernels.get_function("superposed_kernel")
129
+ grid, block = grid_block(self)
130
+ factors = self.step_factors()
131
+ # both per-axis factors go as 1 / dx**2, so their ratio is one number
132
+ ratios = [-sign / (2.0 * d) ** 2 / float(f) for d, f in zip(self.dx, factors)]
133
+ if not np.allclose(ratios, ratios[0]):
134
+ raise ValueError(
135
+ f"step factors {factors} are not proportional to 1 / dx**2"
136
+ )
137
+ args = [None, None, None, accs["mass"], accs["stiff"]]
138
+ args += [*self.step_kernel_args(mat), self.dtype(sign / (2.0 * self.dt) ** 2)]
139
+ args += [self.dtype(ratios[0]), *axis_geometry(self, factors)]
140
+ massless = list(args)
141
+ massless[8] = self.dtype(0.0)
142
+
143
+ def superposed_step(u0, u1, u2, mass=True):
144
+ launch = args if mass else massless
145
+ launch[0], launch[1], launch[2] = u0, u1, u2
146
+ superposed_kernel(grid, block, launch)
147
+ return u2
148
+
149
+ return superposed_step
150
+
106
151
  def define_frechet(
107
- self, kernels: cp.RawModule, accs: dict, sign: float
152
+ self, kernels: cp.RawModule, accs: dict, sign: float, stiffness: bool = True
108
153
  ) -> Callable:
109
154
  """Closure accumulating both Frechet densities of one field triplet, times `sign`."""
110
155
  # sign is fixed per pass, so it is folded into the factors, not recomputed
111
156
  frechet_kernel = kernels.get_function("frechet_kernel")
112
157
  grid, block = grid_block(self)
113
158
  # the stiffness density enters negated, so the epilogue scales both alike
114
- factors = [self.dtype(-sign / (2.0 * d) ** 2) for d in self.dx]
159
+ factors = [self.dtype(-sign * stiffness / (2.0 * d) ** 2) for d in self.dx]
115
160
  args = [accs["mass"], accs["stiff"], None, None, None]
116
161
  args += [self.dtype(sign / (2.0 * self.dt) ** 2), *axis_geometry(self, factors)]
117
162
 
@@ -25,6 +25,7 @@ from .wave import (
25
25
  Source,
26
26
  Window,
27
27
  compile_kernels,
28
+ define_adjoint_excitation,
28
29
  define_excitation,
29
30
  define_get_signal,
30
31
  define_set_signal,
@@ -218,7 +219,6 @@ def sensitivity(
218
219
  bc_step = define_boundary(sim, kernels)
219
220
  excitation_step = define_excitation(sim, source.position, kernels, mat)
220
221
  grads = sim.gradient_fields(mat)
221
- gradient_step = sim.define_gradient(sens_kernels, mat, grads)
222
222
 
223
223
  # ------------------------------------ forward pass -----------------------------------
224
224
  # stepped straight into the history, so the leading zeros are u^-2 / u^-1
@@ -238,10 +238,24 @@ def sensitivity(
238
238
 
239
239
  # --------------------------------- adjoint excitation --------------------------------
240
240
  signal = adjoint_signal(sim, dphi, sensors)
241
- adjoint_excitation = define_excitation(sim, sensors, kernels, mat)
242
241
 
243
242
  # ----------------------------------- backward pass -----------------------------------
244
243
  P = cp.zeros((2, *sim.field_shape), dtype=sim.dtype)
244
+ if sim.fuses_adjoint:
245
+ adjoint_step = sim.define_adjoint_gradient(sens_kernels, mat, grads)
246
+ adjoint_excitation = define_adjoint_excitation(sim, sensors, kernels, mat, grads)
247
+
248
+ # reversed, so row m is the middle slot both gradients of lambda^(n + 1) read
249
+ def backward(m, signal, history):
250
+ p = adjoint_step(P[m % 2], P[1 - m % 2], history[m]) # lambda^n
251
+ adjoint_excitation(p, signal, history[m], m)
252
+ bc_step(p)
253
+
254
+ march(sim.N, backward, [Window(signal), Window(V[::-1])])
255
+ return cost, sim.finalize_gradients(grads, sens_kernels), um, {}
256
+
257
+ gradient_step = sim.define_gradient(sens_kernels, mat, grads)
258
+ adjoint_excitation = define_excitation(sim, sensors, kernels, mat)
245
259
 
246
260
  # reversed, so row m + 2 - i is u^(n + i) for n = N - 1 - m
247
261
  def backward(m, signal, history):
@@ -418,13 +432,18 @@ def superposition_sensitivity(
418
432
  kernels = compile_kernels(sim)
419
433
  sens_kernels = compile_kernels(sim, sim.sensitivity_path)
420
434
 
421
- fd_step = sim.define_step(kernels, mat)
422
435
  bc_step = define_boundary(sim, kernels)
423
436
  excitation_step = define_excitation(sim, source.position, kernels, mat)
424
437
  get_signal = define_get_signal(sim, sensors, kernels)
425
438
  accs = sim.gradient_fields(mat)
426
- subtract_step = sim.define_frechet(sens_kernels, accs, -1.0)
427
- add_step = sim.define_frechet(sens_kernels, accs, 1.0)
439
+ fused = sim.fuses_adjoint
440
+ if fused:
441
+ subtract_step = sim.define_superposed(sens_kernels, mat, accs, -1.0)
442
+ add_step = sim.define_superposed(sens_kernels, mat, accs, 1.0)
443
+ else:
444
+ fd_step = sim.define_step(kernels, mat)
445
+ subtract_step = sim.define_frechet(sens_kernels, accs, -1.0)
446
+ add_step = sim.define_frechet(sens_kernels, accs, 1.0)
428
447
 
429
448
  U = cp.zeros((3, *sim.field_shape), dtype=sim.dtype)
430
449
  um = cp.zeros((sim.N, sensors.shape[1]), dtype=sim.dtype)
@@ -433,13 +452,21 @@ def superposition_sensitivity(
433
452
  # records the traces and subtracts the forward diagonal B(u, u)
434
453
  def forward(t, signal, um):
435
454
  u0, u1, u2 = U[t % 3], U[(t + 1) % 3], U[(t + 2) % 3]
436
- fd_step(u0, u1, u2)
455
+ if fused:
456
+ subtract_step(u0, u1, u2) # the mass density one triplet late
457
+ else:
458
+ fd_step(u0, u1, u2)
437
459
  excitation_step(u2, signal, t)
438
460
  bc_step(u2)
439
461
  get_signal(u2, um, t)
440
- subtract_step(u0, u1, u2)
462
+ if not fused:
463
+ subtract_step(u0, u1, u2)
441
464
 
442
465
  march(sim.N, forward, [Window(source.signal), Window(um, store=True)])
466
+ if fused:
467
+ t = sim.N - 1
468
+ last = (U[t % 3], U[(t + 1) % 3], U[(t + 2) % 3])
469
+ sim.define_frechet(sens_kernels, accs, -1.0, stiffness=False)(*last)
443
470
 
444
471
  cost, dphi = objective(um)
445
472
 
@@ -461,14 +488,26 @@ def superposition_sensitivity(
461
488
 
462
489
  # ----------------------------------- backward pass -----------------------------------
463
490
  # starting from u^(N - 1) over u^(N - 2), so the one array carries u + k lambda
464
- def backward(t, signal):
491
+ def backward(t, signal, row, mass=True):
465
492
  u0, u1, u2 = (U[(sim.N + i - t) % 3] for i in (1, 0, 2))
466
- fd_step(u0, u1, u2)
467
- backward_excitation(u2, signal, t)
493
+ if fused:
494
+ add_step(u0, u1, u2, mass)
495
+ else:
496
+ fd_step(u0, u1, u2)
497
+ backward_excitation(u2, signal, row)
468
498
  bc_step(u2)
469
- add_step(u0, u1, u2)
470
-
471
- march(sim.N, backward, [Window(backward_signal)])
499
+ if not fused:
500
+ add_step(u0, u1, u2)
501
+
502
+ def shifted(t, signal):
503
+ backward(t + 1, signal, t)
504
+
505
+ # outside the graph, as the first fused step's triplet before is the forward's last
506
+ backward(0, backward_signal, 0, mass=False)
507
+ march(sim.N - 1, shifted, [Window(backward_signal[1:])])
508
+ if fused:
509
+ last = (U[(1 + i) % 3] for i in (1, 0, 2))
510
+ sim.define_frechet(sens_kernels, accs, 1.0, stiffness=False)(*last)
472
511
 
473
512
  after = _accumulated(accs)
474
513
  cancellation = before / after if after > 0.0 else float("inf")
@@ -271,6 +271,7 @@ class Simulation:
271
271
  dirichlet: cpt.NDArray | None = None # nodal mask held at zero, its cells left open
272
272
 
273
273
  accepts_domain = False # whether the step kernel honours -DUSE_DOMAIN
274
+ fuses_adjoint = False # whether the gradients ride in the adjoint step kernels
274
275
 
275
276
  @property
276
277
  def compile_flags(self) -> tuple[str, ...]:
@@ -331,6 +332,10 @@ class Simulation:
331
332
  f"index; coarsen the grid"
332
333
  )
333
334
 
335
+ def adjoint_mass_factor(self) -> float:
336
+ """Scale of the inertia gradient density: `1 / dt**2`, the residual's own."""
337
+ return 1.0 / self.dt**2
338
+
334
339
  def define_step(self, kernels: cp.RawModule, mat: dict) -> Callable:
335
340
  """Closure launching the finite-difference step kernel over (u0, u1, u2)."""
336
341
  fd_kernel = kernels.get_function("fd_kernel")
@@ -595,6 +600,32 @@ def define_excitation(
595
600
  return _define_transfer(sim, "excitation_kernel", position, kernels, weight)
596
601
 
597
602
 
603
+ def define_adjoint_excitation(
604
+ sim: Simulation,
605
+ sensors: cpt.NDArray[cp.int32],
606
+ kernels: cp.RawModule,
607
+ mat: dict,
608
+ grads: dict,
609
+ ) -> Callable:
610
+ """Closure adding row `t_index` of the adjoint signal into `l2`, and its share of the inertia gradient against `u1`."""
611
+ lin_index = flatten_indices(sim, sensors)
612
+ kernel = kernels.get_function("adjoint_excitation_kernel")
613
+ threads = 256
614
+ num = sensors.shape[1]
615
+ blocks = ((num + threads - 1) // threads,)
616
+ args = [None, None, np.int32(0), lin_index, np.int32(num)]
617
+ args += [sim.excitation_weights(mat, lin_index), grads["mass"], None]
618
+ args.append(sim.dtype(sim.adjoint_mass_factor()))
619
+
620
+ def adjoint_excitation_step(l2, signal, u1, t_index):
621
+ args[0], args[1], args[7] = l2, signal, u1
622
+ args[2] = np.int32(t_index * num)
623
+ kernel(blocks, (threads,), args)
624
+ return l2
625
+
626
+ return adjoint_excitation_step
627
+
628
+
598
629
  def define_get_signal(
599
630
  sim: Simulation, sensors: cpt.NDArray[cp.int32], kernels: cp.RawModule
600
631
  ) -> Callable:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cuwave
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: GPU finite-difference wave solver with differentiable adjoints
5
5
  Author-email: Leon Herrmann <herrmann.leon@pm.me>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cuwave"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "GPU finite-difference wave solver with differentiable adjoints"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,140 +0,0 @@
1
- // Prepended by wave.compile_kernels: stencils.preamble, then common.cuh.
2
- // Compile-time configuration this file responds to:
3
- // NDIM = 1 | 2 | 3
4
-
5
- // ------------------------------- interior guard macros
6
- #if NDIM == 1
7
- #define INTERIOR_OR_RETURN \
8
- const int a0 = blockIdx.x * blockDim.x + threadIdx.x; \
9
- if (!(a0 > 0 && a0 < N0 - 1)) \
10
- return; \
11
- const int idx = a0
12
- #define AXIS_RADII const int r0 = CLOSURE(a0, N0)
13
- #define AXIS_OFFSETS const int o0 = 1
14
- #elif NDIM == 2
15
- #define INTERIOR_OR_RETURN \
16
- const int a1 = blockIdx.x * blockDim.x + threadIdx.x; \
17
- const int a0 = blockIdx.y * blockDim.y + threadIdx.y; \
18
- if (!(a0 > 0 && a0 < N0 - 1 && a1 > 0 && a1 < N1 - 1)) \
19
- return; \
20
- const int idx = a0 * s0 + a1
21
- #define AXIS_RADII const int r0 = CLOSURE(a0, N0), r1 = CLOSURE(a1, N1)
22
- #define AXIS_OFFSETS const int o0 = s0, o1 = 1
23
- #elif NDIM == 3
24
- #define INTERIOR_OR_RETURN \
25
- const int a2 = blockIdx.x * blockDim.x + threadIdx.x; \
26
- const int a1 = blockIdx.y * blockDim.y + threadIdx.y; \
27
- const int a0 = blockIdx.z * blockDim.z + threadIdx.z; \
28
- if (!(a0 > 0 && a0 < N0 - 1 && a1 > 0 && a1 < N1 - 1 && a2 > 0 && \
29
- a2 < N2 - 1)) \
30
- return; \
31
- const int idx = a0 * s0 + a1 * s1 + a2
32
- #define AXIS_RADII \
33
- const int r0 = CLOSURE(a0, N0), r1 = CLOSURE(a1, N1), r2 = CLOSURE(a2, N2)
34
- #define AXIS_OFFSETS const int o0 = s0, o1 = s1, o2 = 1
35
- #endif
36
-
37
- // ---------------------------------- gradient helpers
38
- __device__ __forceinline__ real_t stiffness_gradient_axis(
39
- const real_t *__restrict__ u1, const real_t *__restrict__ l1,
40
- const real_t *__restrict__ stiff, const int idx, const int s,
41
- const real_t uc, const real_t lc, const real_t sc, const real_t factor,
42
- const int r) {
43
- const real_t sp = stiff[idx + s]; // plus of sc
44
- const real_t sm = stiff[idx - s]; // minus of sc
45
- const real_t dgp = sp * sp / ((sc + sp) * (sc + sp)); // d(harmonic mean)/dsc
46
- const real_t dgm = sm * sm / ((sc + sm) * (sc + sm)); // d(harmonic mean)/dsc
47
- real_t Dp = OP_W(r, 1) * (u1[idx + s] - uc); // inner grad
48
- real_t Dm = OP_W(r, 1) * (uc - u1[idx - s]); // inner grad
49
- #pragma unroll
50
- for (int k = 2; k <= STENCIL_RADIUS; ++k)
51
- if (k <= r) {
52
- Dp += OP_W(r, k) * (u1[idx + k * s] - u1[idx - (k - 1) * s]);
53
- Dm += OP_W(r, k) * (u1[idx + (k - 1) * s] - u1[idx - k * s]);
54
- }
55
- return factor * (dgp * Dp * (l1[idx + s] - lc) +
56
- dgm * Dm * (lc - l1[idx - s])); // both cells of the node
57
- }
58
-
59
- // -------------------------------------- kernels
60
- extern "C" {
61
-
62
- // ------------------------------------------------------------------------------------
63
- __global__ void
64
- gradient_kernel(real_t *__restrict__ g_mass, real_t *__restrict__ g_stiff,
65
- const real_t *__restrict__ u0, const real_t *__restrict__ u1,
66
- const real_t *__restrict__ u2, const real_t *__restrict__ l1,
67
- const real_t *__restrict__ stiff, const real_t inv_dt2,
68
- const real_t F0, const int N0
69
- #if NDIM >= 2
70
- ,
71
- const real_t F1, const int N1, const int s0
72
- #endif
73
- #if NDIM >= 3
74
- ,
75
- const real_t F2, const int N2, const int s1
76
- #endif
77
- ) {
78
- INTERIOR_OR_RETURN;
79
- AXIS_RADII;
80
-
81
- const real_t uc = u1[idx];
82
- const real_t lc = l1[idx];
83
-
84
- // dJ/dmass: no neighbour and no material load
85
- g_mass[idx] -= inv_dt2 * lc * (u2[idx] - 2.f * uc + u0[idx]);
86
-
87
- // dJ/dstiff: scalar.cu's harmonic cell mean differentiated in place
88
- const real_t sc = stiff[idx];
89
- #if NDIM == 1
90
- const real_t g =
91
- stiffness_gradient_axis(u1, l1, stiff, idx, 1, uc, lc, sc, F0, r0);
92
- #elif NDIM == 2
93
- const real_t g =
94
- stiffness_gradient_axis(u1, l1, stiff, idx, s0, uc, lc, sc, F0, r0) +
95
- stiffness_gradient_axis(u1, l1, stiff, idx, 1, uc, lc, sc, F1, r1);
96
- #elif NDIM == 3
97
- const real_t g =
98
- stiffness_gradient_axis(u1, l1, stiff, idx, s0, uc, lc, sc, F0, r0) +
99
- stiffness_gradient_axis(u1, l1, stiff, idx, s1, uc, lc, sc, F1, r1) +
100
- stiffness_gradient_axis(u1, l1, stiff, idx, 1, uc, lc, sc, F2, r2);
101
- #endif
102
- g_stiff[idx] -= g;
103
- }
104
-
105
- // ------------------------------------------------------------------------------------
106
- __global__ void frechet_kernel(real_t *__restrict__ acc_mass,
107
- real_t *__restrict__ acc_stiff,
108
- const real_t *__restrict__ u0,
109
- const real_t *__restrict__ u1,
110
- const real_t *__restrict__ u2, const real_t ft,
111
- const real_t f0, const int N0
112
- #if NDIM >= 2
113
- ,
114
- const real_t f1, const int N1, const int s0
115
- #endif
116
- #if NDIM >= 3
117
- ,
118
- const real_t f2, const int N2, const int s1
119
- #endif
120
- ) {
121
- INTERIOR_OR_RETURN;
122
- AXIS_OFFSETS;
123
-
124
- const real_t dudt = u2[idx] - u0[idx];
125
- acc_mass[idx] += ft * dudt * dudt;
126
-
127
- const real_t g0 = u1[idx + o0] - u1[idx - o0];
128
- real_t sum = f0 * g0 * g0;
129
- #if NDIM >= 2
130
- const real_t g1 = u1[idx + o1] - u1[idx - o1];
131
- sum += f1 * g1 * g1;
132
- #endif
133
- #if NDIM >= 3
134
- const real_t g2 = u1[idx + o2] - u1[idx - o2];
135
- sum += f2 * g2 * g2;
136
- #endif
137
- acc_stiff[idx] += sum;
138
- }
139
-
140
- } // extern "C"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes